mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 00:21:32 +00:00
81eaadcae8
After commitddde3c18b7
("vt: More locking checks") kdb / kgdb has become useless because my console is filled with spews of: WARNING: CPU: 0 PID: 0 at .../drivers/tty/vt/vt.c:3846 con_is_visible+0x50/0x74 CPU: 0 PID: 0 Comm: swapper/0 Not tainted 5.3.0-rc1+ #48 Hardware name: Rockchip (Device Tree) Backtrace: [<c020ce9c>] (dump_backtrace) from [<c020d188>] (show_stack+0x20/0x24) [<c020d168>] (show_stack) from [<c0a8fc14>] (dump_stack+0xb0/0xd0) [<c0a8fb64>] (dump_stack) from [<c0232c58>] (__warn+0xec/0x11c) [<c0232b6c>] (__warn) from [<c0232dc4>] (warn_slowpath_null+0x4c/0x58) [<c0232d78>] (warn_slowpath_null) from [<c06338a0>] (con_is_visible+0x50/0x74) [<c0633850>] (con_is_visible) from [<c0634078>] (con_scroll+0x108/0x1ac) [<c0633f70>] (con_scroll) from [<c0634160>] (lf+0x44/0x88) [<c063411c>] (lf) from [<c06363ec>] (vt_console_print+0x1a4/0x2bc) [<c0636248>] (vt_console_print) from [<c02f628c>] (vkdb_printf+0x420/0x8a4) [<c02f5e6c>] (vkdb_printf) from [<c02f6754>] (kdb_printf+0x44/0x60) [<c02f6714>] (kdb_printf) from [<c02fa6f4>] (kdb_main_loop+0xf4/0x6e0) [<c02fa600>] (kdb_main_loop) from [<c02fd5f0>] (kdb_stub+0x268/0x398) [<c02fd388>] (kdb_stub) from [<c02f3ba0>] (kgdb_cpu_enter+0x1f8/0x674) [<c02f39a8>] (kgdb_cpu_enter) from [<c02f4330>] (kgdb_handle_exception+0x1c4/0x1fc) [<c02f416c>] (kgdb_handle_exception) from [<c0210fe0>] (kgdb_compiled_brk_fn+0x30/0x3c) [<c0210fb0>] (kgdb_compiled_brk_fn) from [<c020d7ac>] (do_undefinstr+0x180/0x1a0) [<c020d62c>] (do_undefinstr) from [<c0201b44>] (__und_svc_finish+0x0/0x3c) ... [<c02f3224>] (kgdb_breakpoint) from [<c02f3310>] (sysrq_handle_dbg+0x58/0x6c) [<c02f32b8>] (sysrq_handle_dbg) from [<c062abf0>] (__handle_sysrq+0xac/0x154) Let's disable this warning when we're in kgdb to avoid the spew. The whole system is stopped when we're in kgdb so we can't exactly wait for someone else to drop the lock. Presumably the best we can do is to disable the warning and hope for the best. Fixes:ddde3c18b7
("vt: More locking checks") Cc: Daniel Vetter <daniel.vetter@intel.com> Signed-off-by: Douglas Anderson <dianders@chromium.org> Link: https://lore.kernel.org/r/20190725183551.169208-1-dianders@chromium.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
350 lines
7.4 KiB
C
350 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Based on the same principle as kgdboe using the NETPOLL api, this
|
|
* driver uses a console polling api to implement a gdb serial inteface
|
|
* which is multiplexed on a console port.
|
|
*
|
|
* Maintainer: Jason Wessel <jason.wessel@windriver.com>
|
|
*
|
|
* 2007-2008 (c) Jason Wessel - Wind River Systems, Inc.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/kgdb.h>
|
|
#include <linux/kdb.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/console.h>
|
|
#include <linux/vt_kern.h>
|
|
#include <linux/input.h>
|
|
#include <linux/module.h>
|
|
|
|
#define MAX_CONFIG_LEN 40
|
|
|
|
static struct kgdb_io kgdboc_io_ops;
|
|
|
|
/* -1 = init not run yet, 0 = unconfigured, 1 = configured. */
|
|
static int configured = -1;
|
|
|
|
static char config[MAX_CONFIG_LEN];
|
|
static struct kparam_string kps = {
|
|
.string = config,
|
|
.maxlen = MAX_CONFIG_LEN,
|
|
};
|
|
|
|
static int kgdboc_use_kms; /* 1 if we use kernel mode switching */
|
|
static struct tty_driver *kgdb_tty_driver;
|
|
static int kgdb_tty_line;
|
|
|
|
#ifdef CONFIG_KDB_KEYBOARD
|
|
static int kgdboc_reset_connect(struct input_handler *handler,
|
|
struct input_dev *dev,
|
|
const struct input_device_id *id)
|
|
{
|
|
input_reset_device(dev);
|
|
|
|
/* Return an error - we do not want to bind, just to reset */
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void kgdboc_reset_disconnect(struct input_handle *handle)
|
|
{
|
|
/* We do not expect anyone to actually bind to us */
|
|
BUG();
|
|
}
|
|
|
|
static const struct input_device_id kgdboc_reset_ids[] = {
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
|
|
.evbit = { BIT_MASK(EV_KEY) },
|
|
},
|
|
{ }
|
|
};
|
|
|
|
static struct input_handler kgdboc_reset_handler = {
|
|
.connect = kgdboc_reset_connect,
|
|
.disconnect = kgdboc_reset_disconnect,
|
|
.name = "kgdboc_reset",
|
|
.id_table = kgdboc_reset_ids,
|
|
};
|
|
|
|
static DEFINE_MUTEX(kgdboc_reset_mutex);
|
|
|
|
static void kgdboc_restore_input_helper(struct work_struct *dummy)
|
|
{
|
|
/*
|
|
* We need to take a mutex to prevent several instances of
|
|
* this work running on different CPUs so they don't try
|
|
* to register again already registered handler.
|
|
*/
|
|
mutex_lock(&kgdboc_reset_mutex);
|
|
|
|
if (input_register_handler(&kgdboc_reset_handler) == 0)
|
|
input_unregister_handler(&kgdboc_reset_handler);
|
|
|
|
mutex_unlock(&kgdboc_reset_mutex);
|
|
}
|
|
|
|
static DECLARE_WORK(kgdboc_restore_input_work, kgdboc_restore_input_helper);
|
|
|
|
static void kgdboc_restore_input(void)
|
|
{
|
|
if (likely(system_state == SYSTEM_RUNNING))
|
|
schedule_work(&kgdboc_restore_input_work);
|
|
}
|
|
|
|
static int kgdboc_register_kbd(char **cptr)
|
|
{
|
|
if (strncmp(*cptr, "kbd", 3) == 0 ||
|
|
strncmp(*cptr, "kdb", 3) == 0) {
|
|
if (kdb_poll_idx < KDB_POLL_FUNC_MAX) {
|
|
kdb_poll_funcs[kdb_poll_idx] = kdb_get_kbd_char;
|
|
kdb_poll_idx++;
|
|
if (cptr[0][3] == ',')
|
|
*cptr += 4;
|
|
else
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void kgdboc_unregister_kbd(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < kdb_poll_idx; i++) {
|
|
if (kdb_poll_funcs[i] == kdb_get_kbd_char) {
|
|
kdb_poll_idx--;
|
|
kdb_poll_funcs[i] = kdb_poll_funcs[kdb_poll_idx];
|
|
kdb_poll_funcs[kdb_poll_idx] = NULL;
|
|
i--;
|
|
}
|
|
}
|
|
flush_work(&kgdboc_restore_input_work);
|
|
}
|
|
#else /* ! CONFIG_KDB_KEYBOARD */
|
|
#define kgdboc_register_kbd(x) 0
|
|
#define kgdboc_unregister_kbd()
|
|
#define kgdboc_restore_input()
|
|
#endif /* ! CONFIG_KDB_KEYBOARD */
|
|
|
|
static void cleanup_kgdboc(void)
|
|
{
|
|
if (kgdb_unregister_nmi_console())
|
|
return;
|
|
kgdboc_unregister_kbd();
|
|
if (configured == 1)
|
|
kgdb_unregister_io_module(&kgdboc_io_ops);
|
|
}
|
|
|
|
static int configure_kgdboc(void)
|
|
{
|
|
struct tty_driver *p;
|
|
int tty_line = 0;
|
|
int err = -ENODEV;
|
|
char *cptr = config;
|
|
struct console *cons;
|
|
|
|
if (!strlen(config) || isspace(config[0])) {
|
|
err = 0;
|
|
goto noconfig;
|
|
}
|
|
|
|
kgdboc_io_ops.is_console = 0;
|
|
kgdb_tty_driver = NULL;
|
|
|
|
kgdboc_use_kms = 0;
|
|
if (strncmp(cptr, "kms,", 4) == 0) {
|
|
cptr += 4;
|
|
kgdboc_use_kms = 1;
|
|
}
|
|
|
|
if (kgdboc_register_kbd(&cptr))
|
|
goto do_register;
|
|
|
|
p = tty_find_polling_driver(cptr, &tty_line);
|
|
if (!p)
|
|
goto noconfig;
|
|
|
|
cons = console_drivers;
|
|
while (cons) {
|
|
int idx;
|
|
if (cons->device && cons->device(cons, &idx) == p &&
|
|
idx == tty_line) {
|
|
kgdboc_io_ops.is_console = 1;
|
|
break;
|
|
}
|
|
cons = cons->next;
|
|
}
|
|
|
|
kgdb_tty_driver = p;
|
|
kgdb_tty_line = tty_line;
|
|
|
|
do_register:
|
|
err = kgdb_register_io_module(&kgdboc_io_ops);
|
|
if (err)
|
|
goto noconfig;
|
|
|
|
err = kgdb_register_nmi_console();
|
|
if (err)
|
|
goto nmi_con_failed;
|
|
|
|
configured = 1;
|
|
|
|
return 0;
|
|
|
|
nmi_con_failed:
|
|
kgdb_unregister_io_module(&kgdboc_io_ops);
|
|
noconfig:
|
|
kgdboc_unregister_kbd();
|
|
config[0] = 0;
|
|
configured = 0;
|
|
cleanup_kgdboc();
|
|
|
|
return err;
|
|
}
|
|
|
|
static int __init init_kgdboc(void)
|
|
{
|
|
/* Already configured? */
|
|
if (configured == 1)
|
|
return 0;
|
|
|
|
return configure_kgdboc();
|
|
}
|
|
|
|
static int kgdboc_get_char(void)
|
|
{
|
|
if (!kgdb_tty_driver)
|
|
return -1;
|
|
return kgdb_tty_driver->ops->poll_get_char(kgdb_tty_driver,
|
|
kgdb_tty_line);
|
|
}
|
|
|
|
static void kgdboc_put_char(u8 chr)
|
|
{
|
|
if (!kgdb_tty_driver)
|
|
return;
|
|
kgdb_tty_driver->ops->poll_put_char(kgdb_tty_driver,
|
|
kgdb_tty_line, chr);
|
|
}
|
|
|
|
static int param_set_kgdboc_var(const char *kmessage,
|
|
const struct kernel_param *kp)
|
|
{
|
|
size_t len = strlen(kmessage);
|
|
|
|
if (len >= MAX_CONFIG_LEN) {
|
|
pr_err("config string too long\n");
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/* Only copy in the string if the init function has not run yet */
|
|
if (configured < 0) {
|
|
strcpy(config, kmessage);
|
|
return 0;
|
|
}
|
|
|
|
if (kgdb_connected) {
|
|
pr_err("Cannot reconfigure while KGDB is connected.\n");
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
strcpy(config, kmessage);
|
|
/* Chop out \n char as a result of echo */
|
|
if (len && config[len - 1] == '\n')
|
|
config[len - 1] = '\0';
|
|
|
|
if (configured == 1)
|
|
cleanup_kgdboc();
|
|
|
|
/* Go and configure with the new params. */
|
|
return configure_kgdboc();
|
|
}
|
|
|
|
static int dbg_restore_graphics;
|
|
|
|
static void kgdboc_pre_exp_handler(void)
|
|
{
|
|
if (!dbg_restore_graphics && kgdboc_use_kms) {
|
|
dbg_restore_graphics = 1;
|
|
con_debug_enter(vc_cons[fg_console].d);
|
|
}
|
|
/* Increment the module count when the debugger is active */
|
|
if (!kgdb_connected)
|
|
try_module_get(THIS_MODULE);
|
|
|
|
atomic_inc(&ignore_console_lock_warning);
|
|
}
|
|
|
|
static void kgdboc_post_exp_handler(void)
|
|
{
|
|
atomic_dec(&ignore_console_lock_warning);
|
|
|
|
/* decrement the module count when the debugger detaches */
|
|
if (!kgdb_connected)
|
|
module_put(THIS_MODULE);
|
|
if (kgdboc_use_kms && dbg_restore_graphics) {
|
|
dbg_restore_graphics = 0;
|
|
con_debug_leave();
|
|
}
|
|
kgdboc_restore_input();
|
|
}
|
|
|
|
static struct kgdb_io kgdboc_io_ops = {
|
|
.name = "kgdboc",
|
|
.read_char = kgdboc_get_char,
|
|
.write_char = kgdboc_put_char,
|
|
.pre_exception = kgdboc_pre_exp_handler,
|
|
.post_exception = kgdboc_post_exp_handler,
|
|
};
|
|
|
|
#ifdef CONFIG_KGDB_SERIAL_CONSOLE
|
|
static int kgdboc_option_setup(char *opt)
|
|
{
|
|
if (!opt) {
|
|
pr_err("config string not provided\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strlen(opt) >= MAX_CONFIG_LEN) {
|
|
pr_err("config string too long\n");
|
|
return -ENOSPC;
|
|
}
|
|
strcpy(config, opt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
__setup("kgdboc=", kgdboc_option_setup);
|
|
|
|
|
|
/* This is only available if kgdboc is a built in for early debugging */
|
|
static int __init kgdboc_early_init(char *opt)
|
|
{
|
|
/* save the first character of the config string because the
|
|
* init routine can destroy it.
|
|
*/
|
|
char save_ch;
|
|
|
|
kgdboc_option_setup(opt);
|
|
save_ch = config[0];
|
|
init_kgdboc();
|
|
config[0] = save_ch;
|
|
return 0;
|
|
}
|
|
|
|
early_param("ekgdboc", kgdboc_early_init);
|
|
#endif /* CONFIG_KGDB_SERIAL_CONSOLE */
|
|
|
|
module_init(init_kgdboc);
|
|
module_exit(cleanup_kgdboc);
|
|
module_param_call(kgdboc, param_set_kgdboc_var, param_get_string, &kps, 0644);
|
|
MODULE_PARM_DESC(kgdboc, "<serial_device>[,baud]");
|
|
MODULE_DESCRIPTION("KGDB Console TTY Driver");
|
|
MODULE_LICENSE("GPL");
|