summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/tty/serial/kgdboc.c136
-rw-r--r--include/linux/kgdb.h4
-rw-r--r--kernel/debug/debug_core.c22
3 files changed, 158 insertions, 4 deletions
diff --git a/drivers/tty/serial/kgdboc.c b/drivers/tty/serial/kgdboc.c
index a260ecd13e8f..34b5e91dd245 100644
--- a/drivers/tty/serial/kgdboc.c
+++ b/drivers/tty/serial/kgdboc.c
@@ -21,6 +21,7 @@
#include <linux/input.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/serial_core.h>
#define MAX_CONFIG_LEN 40
@@ -42,6 +43,10 @@ static int kgdb_tty_line;
static struct platform_device *kgdboc_pdev;
+static struct kgdb_io kgdboc_earlycon_io_ops;
+static struct console *earlycon;
+static int (*earlycon_orig_exit)(struct console *con);
+
#ifdef CONFIG_KDB_KEYBOARD
static int kgdboc_reset_connect(struct input_handler *handler,
struct input_dev *dev,
@@ -137,6 +142,9 @@ static void kgdboc_unregister_kbd(void)
static void cleanup_kgdboc(void)
{
+ if (earlycon)
+ kgdb_unregister_io_module(&kgdboc_earlycon_io_ops);
+
if (configured != 1)
return;
@@ -409,6 +417,134 @@ static int __init kgdboc_early_init(char *opt)
early_param("ekgdboc", kgdboc_early_init);
+static int kgdboc_earlycon_get_char(void)
+{
+ char c;
+
+ if (!earlycon->read(earlycon, &c, 1))
+ return NO_POLL_CHAR;
+
+ return c;
+}
+
+static void kgdboc_earlycon_put_char(u8 chr)
+{
+ earlycon->write(earlycon, &chr, 1);
+}
+
+static void kgdboc_earlycon_pre_exp_handler(void)
+{
+ struct console *con;
+ static bool already_warned;
+
+ if (already_warned)
+ return;
+
+ /*
+ * When the first normal console comes up the kernel will take all
+ * the boot consoles out of the list. Really, we should stop using
+ * the boot console when it does that but until a TTY is registered
+ * we have no other choice so we keep using it. Since not all
+ * serial drivers might be OK with this, print a warning once per
+ * boot if we detect this case.
+ */
+ for_each_console(con)
+ if (con == earlycon)
+ return;
+
+ already_warned = true;
+ pr_warn("kgdboc_earlycon is still using bootconsole\n");
+}
+
+static int kgdboc_earlycon_deferred_exit(struct console *con)
+{
+ /*
+ * If we get here it means the boot console is going away but we
+ * don't yet have a suitable replacement. Don't pass through to
+ * the original exit routine. We'll call it later in our deinit()
+ * function. For now, restore the original exit() function pointer
+ * as a sentinal that we've hit this point.
+ */
+ con->exit = earlycon_orig_exit;
+
+ return 0;
+}
+
+static void kgdboc_earlycon_deinit(void)
+{
+ if (!earlycon)
+ return;
+
+ if (earlycon->exit == kgdboc_earlycon_deferred_exit)
+ /*
+ * kgdboc_earlycon is exiting but original boot console exit
+ * was never called (AKA kgdboc_earlycon_deferred_exit()
+ * didn't ever run). Undo our trap.
+ */
+ earlycon->exit = earlycon_orig_exit;
+ else if (earlycon->exit)
+ /*
+ * We skipped calling the exit() routine so we could try to
+ * keep using the boot console even after it went away. We're
+ * finally done so call the function now.
+ */
+ earlycon->exit(earlycon);
+
+ earlycon = NULL;
+}
+
+static struct kgdb_io kgdboc_earlycon_io_ops = {
+ .name = "kgdboc_earlycon",
+ .read_char = kgdboc_earlycon_get_char,
+ .write_char = kgdboc_earlycon_put_char,
+ .pre_exception = kgdboc_earlycon_pre_exp_handler,
+ .deinit = kgdboc_earlycon_deinit,
+ .is_console = true,
+};
+
+static int __init kgdboc_earlycon_init(char *opt)
+{
+ struct console *con;
+
+ kdb_init(KDB_INIT_EARLY);
+
+ /*
+ * Look for a matching console, or if the name was left blank just
+ * pick the first one we find.
+ */
+ console_lock();
+ for_each_console(con) {
+ if (con->write && con->read &&
+ (con->flags & (CON_BOOT | CON_ENABLED)) &&
+ (!opt || !opt[0] || strcmp(con->name, opt) == 0))
+ break;
+ }
+
+ if (!con) {
+ pr_info("Couldn't find kgdb earlycon\n");
+ goto unlock;
+ }
+
+ earlycon = con;
+ pr_info("Going to register kgdb with earlycon '%s'\n", con->name);
+ if (kgdb_register_io_module(&kgdboc_earlycon_io_ops) != 0) {
+ earlycon = NULL;
+ pr_info("Failed to register kgdb with earlycon\n");
+ } else {
+ /* Trap exit so we can keep earlycon longer if needed. */
+ earlycon_orig_exit = con->exit;
+ con->exit = kgdboc_earlycon_deferred_exit;
+ }
+
+unlock:
+ console_unlock();
+
+ /* Non-zero means malformed option so we always return zero */
+ return 0;
+}
+
+early_param("kgdboc_earlycon", kgdboc_earlycon_init);
+
module_init(init_kgdboc);
module_exit(exit_kgdboc);
module_param_call(kgdboc, param_set_kgdboc_var, param_get_string, &kps, 0644);
diff --git a/include/linux/kgdb.h b/include/linux/kgdb.h
index c2caee08e418..c62d76478adc 100644
--- a/include/linux/kgdb.h
+++ b/include/linux/kgdb.h
@@ -269,6 +269,9 @@ struct kgdb_arch {
* @write_char: Pointer to a function that will write one char.
* @flush: Pointer to a function that will flush any pending writes.
* @init: Pointer to a function that will initialize the device.
+ * @deinit: Pointer to a function that will deinit the device. Implies that
+ * this I/O driver is temporary and expects to be replaced. Called when
+ * an I/O driver is replaced or explicitly unregistered.
* @pre_exception: Pointer to a function that will do any prep work for
* the I/O driver.
* @post_exception: Pointer to a function that will do any cleanup work
@@ -282,6 +285,7 @@ struct kgdb_io {
void (*write_char) (u8);
void (*flush) (void);
int (*init) (void);
+ void (*deinit) (void);
void (*pre_exception) (void);
void (*post_exception) (void);
int is_console;
diff --git a/kernel/debug/debug_core.c b/kernel/debug/debug_core.c
index e2d67b163fb6..4d59aa907fdc 100644
--- a/kernel/debug/debug_core.c
+++ b/kernel/debug/debug_core.c
@@ -1073,15 +1073,23 @@ EXPORT_SYMBOL_GPL(kgdb_schedule_breakpoint);
*/
int kgdb_register_io_module(struct kgdb_io *new_dbg_io_ops)
{
+ struct kgdb_io *old_dbg_io_ops;
int err;
spin_lock(&kgdb_registration_lock);
- if (dbg_io_ops) {
- spin_unlock(&kgdb_registration_lock);
+ old_dbg_io_ops = dbg_io_ops;
+ if (old_dbg_io_ops) {
+ if (!old_dbg_io_ops->deinit) {
+ spin_unlock(&kgdb_registration_lock);
- pr_err("Another I/O driver is already registered with KGDB\n");
- return -EBUSY;
+ pr_err("KGDB I/O driver %s can't replace %s.\n",
+ new_dbg_io_ops->name, old_dbg_io_ops->name);
+ return -EBUSY;
+ }
+ pr_info("Replacing I/O driver %s with %s\n",
+ old_dbg_io_ops->name, new_dbg_io_ops->name);
+ old_dbg_io_ops->deinit();
}
if (new_dbg_io_ops->init) {
@@ -1096,6 +1104,9 @@ int kgdb_register_io_module(struct kgdb_io *new_dbg_io_ops)
spin_unlock(&kgdb_registration_lock);
+ if (old_dbg_io_ops)
+ return 0;
+
pr_info("Registered I/O driver %s\n", new_dbg_io_ops->name);
/* Arm KGDB now. */
@@ -1132,6 +1143,9 @@ void kgdb_unregister_io_module(struct kgdb_io *old_dbg_io_ops)
spin_unlock(&kgdb_registration_lock);
+ if (old_dbg_io_ops->deinit)
+ old_dbg_io_ops->deinit();
+
pr_info("Unregistered I/O driver %s, debugger disabled\n",
old_dbg_io_ops->name);
}