Skip to content

Commit

Permalink
console: add write_atomic interface
Browse files Browse the repository at this point in the history
Add a write_atomic() callback to the console. This is an optional
function for console drivers. The function must be atomic (including
NMI safe) for writing to the console.

Console drivers must still implement the write() callback. The
write_atomic() callback will only be used in special situations,
such as when the kernel panics.

Creating an NMI safe write_atomic() that must synchronize with
write() requires a careful implementation of the console driver. To
aid with the implementation, a set of console_atomic_*() functions
are provided:

    void console_atomic_lock(unsigned long flags);
    void console_atomic_unlock(unsigned long flags);

These functions synchronize using the printk cpulock and disable
hardware interrupts.

kgdb makes use of its own cpulock (@dbg_master_lock, @kgdb_active)
during cpu roundup. This will conflict with the printk cpulock.
Therefore, a CPU must ensure that it is not holding the printk
cpulock when calling kgdb_cpu_enter(). If it is, it must allow its
printk context to complete first.

A new helper function kgdb_roundup_delay() is introduced for kgdb
to determine if it is holding the printk cpulock. If so, a flag is
set so that when the printk cpulock is released, kgdb will be
re-triggered for that CPU.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
  • Loading branch information
jogness authored and Sebastian Andrzej Siewior committed Sep 13, 2021
1 parent 8ea42ce commit 27293f7
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 23 deletions.
1 change: 1 addition & 0 deletions arch/powerpc/include/asm/smp.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct smp_ops_t {

extern int smp_send_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us);
extern int smp_send_safe_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us);
extern void smp_send_debugger_break_cpu(unsigned int cpu);
extern void smp_send_debugger_break(void);
extern void start_secondary_resume(void);
extern void smp_generic_give_timebase(void);
Expand Down
10 changes: 9 additions & 1 deletion arch/powerpc/kernel/kgdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,19 @@ int kgdb_skipexception(int exception, struct pt_regs *regs)

static int kgdb_debugger_ipi(struct pt_regs *regs)
{
kgdb_nmicallback(raw_smp_processor_id(), regs);
int cpu = raw_smp_processor_id();

if (!kgdb_roundup_delay(cpu))
kgdb_nmicallback(cpu, regs);
return 0;
}

#ifdef CONFIG_SMP
void kgdb_roundup_cpu(unsigned int cpu)
{
smp_send_debugger_break_cpu(cpu);
}

void kgdb_roundup_cpus(void)
{
smp_send_debugger_break();
Expand Down
5 changes: 5 additions & 0 deletions arch/powerpc/kernel/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,11 @@ static void debugger_ipi_callback(struct pt_regs *regs)
debugger_ipi(regs);
}

void smp_send_debugger_break_cpu(unsigned int cpu)
{
smp_send_nmi_ipi(cpu, debugger_ipi_callback, 1000000);
}

void smp_send_debugger_break(void)
{
smp_send_nmi_ipi(NMI_IPI_ALL_OTHERS, debugger_ipi_callback, 1000000);
Expand Down
9 changes: 6 additions & 3 deletions arch/x86/kernel/kgdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,9 +502,12 @@ static int kgdb_nmi_handler(unsigned int cmd, struct pt_regs *regs)
if (atomic_read(&kgdb_active) != -1) {
/* KGDB CPU roundup */
cpu = raw_smp_processor_id();
kgdb_nmicallback(cpu, regs);
set_bit(cpu, was_in_debug_nmi);
touch_nmi_watchdog();

if (!kgdb_roundup_delay(cpu)) {
kgdb_nmicallback(cpu, regs);
set_bit(cpu, was_in_debug_nmi);
touch_nmi_watchdog();
}

return NMI_HANDLED;
}
Expand Down
1 change: 1 addition & 0 deletions include/linux/console.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ static inline int con_debug_leave(void)
struct console {
char name[16];
void (*write)(struct console *, const char *, unsigned);
void (*write_atomic)(struct console *co, const char *s, unsigned int count);
int (*read)(struct console *, char *, unsigned);
struct tty_driver *(*device)(struct console *, int *);
void (*unblank)(void);
Expand Down
3 changes: 3 additions & 0 deletions include/linux/kgdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ extern void kgdb_call_nmi_hook(void *ignored);
*/
extern void kgdb_roundup_cpus(void);

extern void kgdb_roundup_cpu(unsigned int cpu);

/**
* kgdb_arch_set_pc - Generic call back to the program counter
* @regs: Current &struct pt_regs.
Expand Down Expand Up @@ -365,5 +367,6 @@ extern void kgdb_free_init_mem(void);
#define dbg_late_init()
static inline void kgdb_panic(const char *msg) {}
static inline void kgdb_free_init_mem(void) { }
static inline void kgdb_roundup_cpu(unsigned int cpu) {}
#endif /* ! CONFIG_KGDB */
#endif /* _KGDB_H_ */
23 changes: 23 additions & 0 deletions include/linux/printk.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,18 @@ static inline void dump_stack(void)
extern int __printk_cpu_trylock(void);
extern void __printk_wait_on_cpu_lock(void);
extern void __printk_cpu_unlock(void);
extern bool kgdb_roundup_delay(unsigned int cpu);

#else

#define __printk_cpu_trylock() 1
#define __printk_wait_on_cpu_lock()
#define __printk_cpu_unlock()

static inline bool kgdb_roundup_delay(unsigned int cpu)
{
return false;
}
#endif /* CONFIG_SMP */

/**
Expand Down Expand Up @@ -315,6 +323,21 @@ extern void __printk_cpu_unlock(void);
local_irq_restore(flags); \
} while (0)

/*
* Used to synchronize atomic consoles.
*
* The same as raw_printk_cpu_lock_irqsave() except that hardware interrupts
* are _not_ restored while spinning.
*/
#define console_atomic_lock(flags) \
do { \
local_irq_save(flags); \
while (!__printk_cpu_trylock()) \
cpu_relax(); \
} while (0)

#define console_atomic_unlock raw_printk_cpu_unlock_irqrestore

extern int kptr_restrict;

/**
Expand Down
45 changes: 26 additions & 19 deletions kernel/debug/debug_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,35 +238,42 @@ NOKPROBE_SYMBOL(kgdb_call_nmi_hook);
static DEFINE_PER_CPU(call_single_data_t, kgdb_roundup_csd) =
CSD_INIT(kgdb_call_nmi_hook, NULL);

void __weak kgdb_roundup_cpus(void)
void __weak kgdb_roundup_cpu(unsigned int cpu)
{
call_single_data_t *csd;
int ret;

csd = &per_cpu(kgdb_roundup_csd, cpu);

/*
* If it didn't round up last time, don't try again
* since smp_call_function_single_async() will block.
*
* If rounding_up is false then we know that the
* previous call must have at least started and that
* means smp_call_function_single_async() won't block.
*/
if (kgdb_info[cpu].rounding_up)
return;
kgdb_info[cpu].rounding_up = true;

ret = smp_call_function_single_async(cpu, csd);
if (ret)
kgdb_info[cpu].rounding_up = false;
}
NOKPROBE_SYMBOL(kgdb_roundup_cpu);

void __weak kgdb_roundup_cpus(void)
{
int this_cpu = raw_smp_processor_id();
int cpu;
int ret;

for_each_online_cpu(cpu) {
/* No need to roundup ourselves */
if (cpu == this_cpu)
continue;

csd = &per_cpu(kgdb_roundup_csd, cpu);

/*
* If it didn't round up last time, don't try again
* since smp_call_function_single_async() will block.
*
* If rounding_up is false then we know that the
* previous call must have at least started and that
* means smp_call_function_single_async() won't block.
*/
if (kgdb_info[cpu].rounding_up)
continue;
kgdb_info[cpu].rounding_up = true;

ret = smp_call_function_single_async(cpu, csd);
if (ret)
kgdb_info[cpu].rounding_up = false;
kgdb_roundup_cpu(cpu);
}
}
NOKPROBE_SYMBOL(kgdb_roundup_cpus);
Expand Down
26 changes: 26 additions & 0 deletions kernel/printk/printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <linux/irq_work.h>
#include <linux/ctype.h>
#include <linux/uio.h>
#include <linux/kgdb.h>
#include <linux/sched/clock.h>
#include <linux/sched/debug.h>
#include <linux/sched/task_stack.h>
Expand Down Expand Up @@ -3582,6 +3583,7 @@ EXPORT_SYMBOL_GPL(kmsg_dump_rewind);
#ifdef CONFIG_SMP
static atomic_t printk_cpulock_owner = ATOMIC_INIT(-1);
static atomic_t printk_cpulock_nested = ATOMIC_INIT(0);
static unsigned int kgdb_cpu = -1;

/**
* __printk_wait_on_cpu_lock() - Busy wait until the printk cpu-reentrant
Expand Down Expand Up @@ -3661,6 +3663,9 @@ EXPORT_SYMBOL(__printk_cpu_trylock);
*/
void __printk_cpu_unlock(void)
{
bool trigger_kgdb = false;
unsigned int cpu;

if (atomic_read(&printk_cpulock_nested)) {
atomic_dec(&printk_cpulock_nested);
return;
Expand All @@ -3671,6 +3676,12 @@ void __printk_cpu_unlock(void)
* LMM(__printk_cpu_unlock:A)
*/

cpu = smp_processor_id();
if (kgdb_cpu == cpu) {
trigger_kgdb = true;
kgdb_cpu = -1;
}

/*
* Guarantee loads and stores from this CPU when it was the
* lock owner are visible to the next lock owner. This pairs
Expand All @@ -3691,6 +3702,21 @@ void __printk_cpu_unlock(void)
*/
atomic_set_release(&printk_cpulock_owner,
-1); /* LMM(__printk_cpu_unlock:B) */

if (trigger_kgdb) {
pr_warn("re-triggering kgdb roundup for CPU#%d\n", cpu);
kgdb_roundup_cpu(cpu);
}
}
EXPORT_SYMBOL(__printk_cpu_unlock);

bool kgdb_roundup_delay(unsigned int cpu)
{
if (cpu != atomic_read(&printk_cpulock_owner))
return false;

kgdb_cpu = cpu;
return true;
}
EXPORT_SYMBOL(kgdb_roundup_delay);
#endif /* CONFIG_SMP */

0 comments on commit 27293f7

Please sign in to comment.