Commit 6116dea8 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'kgdb-5.8-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/danielt/linux

Pull kgdb fixes from Daniel Thompson:
 "The main change here is a fix for a number of unsafe interactions
  between kdb and the console system. The fixes are specific to kdb
  (pure kgdb debugging does not use the console system at all). On
  systems with an NMI then kdb, if it is enabled, must get messages to
  the user despite potentially running from some "difficult" calling
  contexts. These fixes avoid using the console system where we have
  been provided an alternative (safer) way to interact with the user
  and, if using the console system in unavoidable, use oops_in_progress
  for deadlock avoidance. These fixes also ensure kdb honours the
  console enable flag.

  Also included is a fix that wraps kgdb trap handling in an RCU read
  lock to avoids triggering diagnostic warnings. This is a wide lock
  scope but this is OK because kgdb is a stop-the-world debugger. When
  we stop the world we put all the CPUs into holding pens and this
  inhibits RCU update anyway"

* tag 'kgdb-5.8-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/danielt/linux:
  kgdb: Avoid suspicious RCU usage warning
  kdb: Switch to use safer dbg_io_ops over console APIs
  kdb: Make kdb_printf() console handling more robust
  kdb: Check status of console prior to invoking handlers
  kdb: Re-factor kdb_printf() message write code
parents 21d2f685 440ab9e1
...@@ -50,7 +50,7 @@ static int kgdb_nmi_console_setup(struct console *co, char *options) ...@@ -50,7 +50,7 @@ static int kgdb_nmi_console_setup(struct console *co, char *options)
* I/O utilities that messages sent to the console will automatically * I/O utilities that messages sent to the console will automatically
* be displayed on the dbg_io. * be displayed on the dbg_io.
*/ */
dbg_io_ops->is_console = true; dbg_io_ops->cons = co;
return 0; return 0;
} }
......
...@@ -45,7 +45,6 @@ static struct platform_device *kgdboc_pdev; ...@@ -45,7 +45,6 @@ static struct platform_device *kgdboc_pdev;
#if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) #if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE)
static struct kgdb_io kgdboc_earlycon_io_ops; static struct kgdb_io kgdboc_earlycon_io_ops;
static struct console *earlycon;
static int (*earlycon_orig_exit)(struct console *con); static int (*earlycon_orig_exit)(struct console *con);
#endif /* IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */ #endif /* IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */
...@@ -145,7 +144,7 @@ static void kgdboc_unregister_kbd(void) ...@@ -145,7 +144,7 @@ static void kgdboc_unregister_kbd(void)
#if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) #if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE)
static void cleanup_earlycon(void) static void cleanup_earlycon(void)
{ {
if (earlycon) if (kgdboc_earlycon_io_ops.cons)
kgdb_unregister_io_module(&kgdboc_earlycon_io_ops); kgdb_unregister_io_module(&kgdboc_earlycon_io_ops);
} }
#else /* !IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */ #else /* !IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */
...@@ -178,7 +177,7 @@ static int configure_kgdboc(void) ...@@ -178,7 +177,7 @@ static int configure_kgdboc(void)
goto noconfig; goto noconfig;
} }
kgdboc_io_ops.is_console = 0; kgdboc_io_ops.cons = NULL;
kgdb_tty_driver = NULL; kgdb_tty_driver = NULL;
kgdboc_use_kms = 0; kgdboc_use_kms = 0;
...@@ -198,7 +197,7 @@ static int configure_kgdboc(void) ...@@ -198,7 +197,7 @@ static int configure_kgdboc(void)
int idx; int idx;
if (cons->device && cons->device(cons, &idx) == p && if (cons->device && cons->device(cons, &idx) == p &&
idx == tty_line) { idx == tty_line) {
kgdboc_io_ops.is_console = 1; kgdboc_io_ops.cons = cons;
break; break;
} }
} }
...@@ -433,7 +432,8 @@ static int kgdboc_earlycon_get_char(void) ...@@ -433,7 +432,8 @@ static int kgdboc_earlycon_get_char(void)
{ {
char c; char c;
if (!earlycon->read(earlycon, &c, 1)) if (!kgdboc_earlycon_io_ops.cons->read(kgdboc_earlycon_io_ops.cons,
&c, 1))
return NO_POLL_CHAR; return NO_POLL_CHAR;
return c; return c;
...@@ -441,7 +441,8 @@ static int kgdboc_earlycon_get_char(void) ...@@ -441,7 +441,8 @@ static int kgdboc_earlycon_get_char(void)
static void kgdboc_earlycon_put_char(u8 chr) static void kgdboc_earlycon_put_char(u8 chr)
{ {
earlycon->write(earlycon, &chr, 1); kgdboc_earlycon_io_ops.cons->write(kgdboc_earlycon_io_ops.cons, &chr,
1);
} }
static void kgdboc_earlycon_pre_exp_handler(void) static void kgdboc_earlycon_pre_exp_handler(void)
...@@ -461,7 +462,7 @@ static void kgdboc_earlycon_pre_exp_handler(void) ...@@ -461,7 +462,7 @@ static void kgdboc_earlycon_pre_exp_handler(void)
* boot if we detect this case. * boot if we detect this case.
*/ */
for_each_console(con) for_each_console(con)
if (con == earlycon) if (con == kgdboc_earlycon_io_ops.cons)
return; return;
already_warned = true; already_warned = true;
...@@ -484,25 +485,25 @@ static int kgdboc_earlycon_deferred_exit(struct console *con) ...@@ -484,25 +485,25 @@ static int kgdboc_earlycon_deferred_exit(struct console *con)
static void kgdboc_earlycon_deinit(void) static void kgdboc_earlycon_deinit(void)
{ {
if (!earlycon) if (!kgdboc_earlycon_io_ops.cons)
return; return;
if (earlycon->exit == kgdboc_earlycon_deferred_exit) if (kgdboc_earlycon_io_ops.cons->exit == kgdboc_earlycon_deferred_exit)
/* /*
* kgdboc_earlycon is exiting but original boot console exit * kgdboc_earlycon is exiting but original boot console exit
* was never called (AKA kgdboc_earlycon_deferred_exit() * was never called (AKA kgdboc_earlycon_deferred_exit()
* didn't ever run). Undo our trap. * didn't ever run). Undo our trap.
*/ */
earlycon->exit = earlycon_orig_exit; kgdboc_earlycon_io_ops.cons->exit = earlycon_orig_exit;
else if (earlycon->exit) else if (kgdboc_earlycon_io_ops.cons->exit)
/* /*
* We skipped calling the exit() routine so we could try to * We skipped calling the exit() routine so we could try to
* keep using the boot console even after it went away. We're * keep using the boot console even after it went away. We're
* finally done so call the function now. * finally done so call the function now.
*/ */
earlycon->exit(earlycon); kgdboc_earlycon_io_ops.cons->exit(kgdboc_earlycon_io_ops.cons);
earlycon = NULL; kgdboc_earlycon_io_ops.cons = NULL;
} }
static struct kgdb_io kgdboc_earlycon_io_ops = { static struct kgdb_io kgdboc_earlycon_io_ops = {
...@@ -511,7 +512,6 @@ static struct kgdb_io kgdboc_earlycon_io_ops = { ...@@ -511,7 +512,6 @@ static struct kgdb_io kgdboc_earlycon_io_ops = {
.write_char = kgdboc_earlycon_put_char, .write_char = kgdboc_earlycon_put_char,
.pre_exception = kgdboc_earlycon_pre_exp_handler, .pre_exception = kgdboc_earlycon_pre_exp_handler,
.deinit = kgdboc_earlycon_deinit, .deinit = kgdboc_earlycon_deinit,
.is_console = true,
}; };
#define MAX_CONSOLE_NAME_LEN (sizeof((struct console *) 0)->name) #define MAX_CONSOLE_NAME_LEN (sizeof((struct console *) 0)->name)
...@@ -557,10 +557,10 @@ static int __init kgdboc_earlycon_init(char *opt) ...@@ -557,10 +557,10 @@ static int __init kgdboc_earlycon_init(char *opt)
goto unlock; goto unlock;
} }
earlycon = con; kgdboc_earlycon_io_ops.cons = con;
pr_info("Going to register kgdb with earlycon '%s'\n", con->name); pr_info("Going to register kgdb with earlycon '%s'\n", con->name);
if (kgdb_register_io_module(&kgdboc_earlycon_io_ops) != 0) { if (kgdb_register_io_module(&kgdboc_earlycon_io_ops) != 0) {
earlycon = NULL; kgdboc_earlycon_io_ops.cons = NULL;
pr_info("Failed to register kgdb with earlycon\n"); pr_info("Failed to register kgdb with earlycon\n");
} else { } else {
/* Trap exit so we can keep earlycon longer if needed. */ /* Trap exit so we can keep earlycon longer if needed. */
......
...@@ -1058,7 +1058,8 @@ static int __init kgdbdbgp_parse_config(char *str) ...@@ -1058,7 +1058,8 @@ static int __init kgdbdbgp_parse_config(char *str)
kgdbdbgp_wait_time = simple_strtoul(ptr, &ptr, 10); kgdbdbgp_wait_time = simple_strtoul(ptr, &ptr, 10);
} }
kgdb_register_io_module(&kgdbdbgp_io_ops); kgdb_register_io_module(&kgdbdbgp_io_ops);
kgdbdbgp_io_ops.is_console = early_dbgp_console.index != -1; if (early_dbgp_console.index != -1)
kgdbdbgp_io_ops.cons = &early_dbgp_console;
return 0; return 0;
} }
......
...@@ -276,8 +276,7 @@ struct kgdb_arch { ...@@ -276,8 +276,7 @@ struct kgdb_arch {
* the I/O driver. * the I/O driver.
* @post_exception: Pointer to a function that will do any cleanup work * @post_exception: Pointer to a function that will do any cleanup work
* for the I/O driver. * for the I/O driver.
* @is_console: 1 if the end device is a console 0 if the I/O device is * @cons: valid if the I/O device is a console; else NULL.
* not a console
*/ */
struct kgdb_io { struct kgdb_io {
const char *name; const char *name;
...@@ -288,7 +287,7 @@ struct kgdb_io { ...@@ -288,7 +287,7 @@ struct kgdb_io {
void (*deinit) (void); void (*deinit) (void);
void (*pre_exception) (void); void (*pre_exception) (void);
void (*post_exception) (void); void (*post_exception) (void);
int is_console; struct console *cons;
}; };
extern const struct kgdb_arch arch_kgdb_ops; extern const struct kgdb_arch arch_kgdb_ops;
......
...@@ -587,6 +587,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs, ...@@ -587,6 +587,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
arch_kgdb_ops.disable_hw_break(regs); arch_kgdb_ops.disable_hw_break(regs);
acquirelock: acquirelock:
rcu_read_lock();
/* /*
* Interrupts will be restored by the 'trap return' code, except when * Interrupts will be restored by the 'trap return' code, except when
* single stepping. * single stepping.
...@@ -646,6 +647,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs, ...@@ -646,6 +647,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
atomic_dec(&slaves_in_kgdb); atomic_dec(&slaves_in_kgdb);
dbg_touch_watchdogs(); dbg_touch_watchdogs();
local_irq_restore(flags); local_irq_restore(flags);
rcu_read_unlock();
return 0; return 0;
} }
cpu_relax(); cpu_relax();
...@@ -664,6 +666,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs, ...@@ -664,6 +666,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
raw_spin_unlock(&dbg_master_lock); raw_spin_unlock(&dbg_master_lock);
dbg_touch_watchdogs(); dbg_touch_watchdogs();
local_irq_restore(flags); local_irq_restore(flags);
rcu_read_unlock();
goto acquirelock; goto acquirelock;
} }
...@@ -787,6 +790,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs, ...@@ -787,6 +790,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
raw_spin_unlock(&dbg_master_lock); raw_spin_unlock(&dbg_master_lock);
dbg_touch_watchdogs(); dbg_touch_watchdogs();
local_irq_restore(flags); local_irq_restore(flags);
rcu_read_unlock();
return kgdb_info[cpu].ret_state; return kgdb_info[cpu].ret_state;
} }
......
...@@ -542,6 +542,44 @@ static int kdb_search_string(char *searched, char *searchfor) ...@@ -542,6 +542,44 @@ static int kdb_search_string(char *searched, char *searchfor)
return 0; return 0;
} }
static void kdb_msg_write(const char *msg, int msg_len)
{
struct console *c;
if (msg_len == 0)
return;
if (dbg_io_ops) {
const char *cp = msg;
int len = msg_len;
while (len--) {
dbg_io_ops->write_char(*cp);
cp++;
}
}
for_each_console(c) {
if (!(c->flags & CON_ENABLED))
continue;
if (c == dbg_io_ops->cons)
continue;
/*
* Set oops_in_progress to encourage the console drivers to
* disregard their internal spin locks: in the current calling
* context the risk of deadlock is a bigger problem than risks
* due to re-entering the console driver. We operate directly on
* oops_in_progress rather than using bust_spinlocks() because
* the calls bust_spinlocks() makes on exit are not appropriate
* for this calling context.
*/
++oops_in_progress;
c->write(c, msg, msg_len);
--oops_in_progress;
touch_nmi_watchdog();
}
}
int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap) int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
{ {
int diag; int diag;
...@@ -553,7 +591,6 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap) ...@@ -553,7 +591,6 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
int this_cpu, old_cpu; int this_cpu, old_cpu;
char *cp, *cp2, *cphold = NULL, replaced_byte = ' '; char *cp, *cp2, *cphold = NULL, replaced_byte = ' ';
char *moreprompt = "more> "; char *moreprompt = "more> ";
struct console *c;
unsigned long uninitialized_var(flags); unsigned long uninitialized_var(flags);
/* Serialize kdb_printf if multiple cpus try to write at once. /* Serialize kdb_printf if multiple cpus try to write at once.
...@@ -687,22 +724,11 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap) ...@@ -687,22 +724,11 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
*/ */
retlen = strlen(kdb_buffer); retlen = strlen(kdb_buffer);
cp = (char *) printk_skip_headers(kdb_buffer); cp = (char *) printk_skip_headers(kdb_buffer);
if (!dbg_kdb_mode && kgdb_connected) { if (!dbg_kdb_mode && kgdb_connected)
gdbstub_msg_write(cp, retlen - (cp - kdb_buffer)); gdbstub_msg_write(cp, retlen - (cp - kdb_buffer));
} else { else
if (dbg_io_ops && !dbg_io_ops->is_console) { kdb_msg_write(cp, retlen - (cp - kdb_buffer));
len = retlen - (cp - kdb_buffer);
cp2 = cp;
while (len--) {
dbg_io_ops->write_char(*cp2);
cp2++;
}
}
for_each_console(c) {
c->write(c, cp, retlen - (cp - kdb_buffer));
touch_nmi_watchdog();
}
}
if (logging) { if (logging) {
saved_loglevel = console_loglevel; saved_loglevel = console_loglevel;
console_loglevel = CONSOLE_LOGLEVEL_SILENT; console_loglevel = CONSOLE_LOGLEVEL_SILENT;
...@@ -751,19 +777,7 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap) ...@@ -751,19 +777,7 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
moreprompt = "more> "; moreprompt = "more> ";
kdb_input_flush(); kdb_input_flush();
kdb_msg_write(moreprompt, strlen(moreprompt));
if (dbg_io_ops && !dbg_io_ops->is_console) {
len = strlen(moreprompt);
cp = moreprompt;
while (len--) {
dbg_io_ops->write_char(*cp);
cp++;
}
}
for_each_console(c) {
c->write(c, moreprompt, strlen(moreprompt));
touch_nmi_watchdog();
}
if (logging) if (logging)
printk("%s", moreprompt); printk("%s", moreprompt);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment