Support atomic printks printk() was atomic at the granularity of a line - most of the time. It wasn't for kernel faults, which leads to inscrutable bug reports. At the time, I avoided the lock to avoid deadlocking. Instead, we can use a recursive lock, which I'm not normally a fan of. Additionally, other callers can use print_lock() to atomically print multiple lines. The classic example is panic() and warn(). panic() has a few issues of its own, so we have print_unlock_force(). Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/include/stdio.h b/kern/include/stdio.h index ce0f048..fd9cbaf 100644 --- a/kern/include/stdio.h +++ b/kern/include/stdio.h
@@ -35,6 +35,9 @@ void vprintfmt(void (*putch)(int, void**), void **putdat, const char *fmt, va_list); // lib/printf.c +void print_lock(void); +void print_unlock(void); +void print_unlock_force(void); int ( cprintf)(const char *fmt, ...); int vcprintf(const char *fmt, va_list);
diff --git a/kern/src/init.c b/kern/src/init.c index 28b8e06..60c64a7 100644 --- a/kern/src/init.c +++ b/kern/src/init.c
@@ -229,11 +229,12 @@ #endif /* Multiple cores can panic concurrently. We could also panic recursively, - * which would deadlock. We also only want to automatically backtrace the first + * which could deadlock. We also only want to automatically backtrace the first * time through, since BTs are often the source of panics. Finally, we want to * know when the other panicking cores are done (or likely to be done) before - * entering the monitor. */ -static spinlock_t panic_lock = SPINLOCK_INITIALIZER_IRQSAVE; + * entering the monitor. + * + * We'll use the print_lock(), which is recursive, to protect panic_printing. */ static bool panic_printing; static DEFINE_PERCPU(int, panic_depth); @@ -244,17 +245,11 @@ void _panic(struct hw_trapframe *hw_tf, const char *file, int line, const char *fmt, ...) { + struct per_cpu_info *pcpui = &per_cpu_info[core_id_early()]; va_list ap; - struct per_cpu_info *pcpui; - /* We're panicing, possibly in a place that can't handle the lock checker */ - pcpui = &per_cpu_info[core_id_early()]; - pcpui->__lock_checking_enabled--; - - if (!PERCPU_VAR(panic_depth)) { - spin_lock_irqsave(&panic_lock); - panic_printing = true; - } + print_lock(); + panic_printing = true; PERCPU_VAR(panic_depth)++; va_start(ap, fmt); @@ -278,11 +273,11 @@ } printk("\n"); - /* If we're here, we panicked and currently hold the lock. We might have - * panicked recursively. We must unlock unconditionally, since the initial - * panic (which grabbed the lock) will never run again. */ + /* If we're here, we panicked and currently hold the print_lock. We might + * have panicked recursively. We must unlock unconditionally, since the + * initial panic (which grabbed the lock) will never run again. */ panic_printing = false; - spin_unlock_irqsave(&panic_lock); + print_unlock_force(); /* And we have to clear the depth, so that we lock again next time in. * Otherwise, we'd be unlocking without locking (which is another panic). */ PERCPU_VAR(panic_depth) = 0; @@ -293,34 +288,39 @@ udelay(500000); cmb(); } while (panic_printing); - monitor(NULL); - if (pcpui->cur_proc) { - printk("panic killing proc %d\n", pcpui->cur_proc->pid); - proc_destroy(pcpui->cur_proc); - } /* Yikes! We're claiming to be not in IRQ/trap ctx and not holding any * locks. Obviously we could be wrong, and could easily deadlock. We could * be in an IRQ handler, an unhandled kernel fault, or just a 'normal' panic * in a syscall - any of which can involve unrestore invariants. */ pcpui->__ctx_depth = 0; pcpui->lock_depth = 0; + /* And keep this off, for good measure. */ + pcpui->__lock_checking_enabled--; + + monitor(NULL); + + if (pcpui->cur_proc) { + printk("panic killing proc %d\n", pcpui->cur_proc->pid); + proc_destroy(pcpui->cur_proc); + } if (pcpui->cur_kthread) kth_panic_sysc(pcpui->cur_kthread); smp_idle(); } -/* like panic, but don't */ void _warn(const char *file, int line, const char *fmt,...) { va_list ap; + print_lock(); va_start(ap, fmt); printk("kernel warning at %s:%d, from core %d: ", file, line, core_id_early()); vcprintf(fmt, ap); cprintf("\n"); va_end(ap); + print_unlock(); } static void run_links(linker_func_t *linkstart, linker_func_t *linkend)
diff --git a/kern/src/printf.c b/kern/src/printf.c index 6e54a04..7b415d9 100644 --- a/kern/src/printf.c +++ b/kern/src/printf.c
@@ -11,7 +11,42 @@ #include <kprof.h> #include <init.h> -spinlock_t output_lock = SPINLOCK_INITIALIZER_IRQSAVE; +/* Recursive lock. Would like to avoid spreading these in the kernel. */ +static spinlock_t output_lock = SPINLOCK_INITIALIZER_IRQSAVE; +static int output_lock_holder = -1; /* core_id. */ +static int output_lock_count; + +void print_lock(void) +{ + if (output_lock_holder == core_id_early()) { + output_lock_count++; + return; + } + pcpui_var(core_id_early(), __lock_checking_enabled)--; + spin_lock_irqsave(&output_lock); + output_lock_holder = core_id_early(); + output_lock_count = 1; +} + +void print_unlock(void) +{ + output_lock_count--; + if (output_lock_count) + return; + output_lock_holder = -1; + spin_unlock_irqsave(&output_lock); + pcpui_var(core_id_early(), __lock_checking_enabled)++; +} + +/* Regardless of where we are, unlock. This is dangerous, and only used when + * you know you will never unwind your stack, such as for a panic. */ +void print_unlock_force(void) +{ + output_lock_holder = -1; + output_lock_count = 0; + spin_unlock_irqsave(&output_lock); + pcpui_var(core_id_early(), __lock_checking_enabled)++; +} void putch(int ch, int **cnt) { @@ -43,50 +78,24 @@ int vcprintf(const char *fmt, va_list ap) { - struct per_cpu_info *pcpui; int cnt = 0; int *cntp = &cnt; volatile int i; - int8_t irq_state = 0; va_list args; + print_lock(); + va_copy(args, ap); trace_vprintk(fmt, args); va_end(args); - /* this ktrap depth stuff is in case the kernel faults in a printfmt call. - * we disable the locking if we're in a fault handler so that we don't - * deadlock. */ - if (booting) - pcpui = &per_cpu_info[0]; - else - pcpui = &per_cpu_info[core_id()]; - /* lock all output. this will catch any printfs at line granularity. when - * tracing, we short-circuit the main lock call, so as not to clobber the - * results as we print. */ - if (!ktrap_depth(pcpui)) { - #ifdef CONFIG_TRACE_LOCKS - disable_irqsave(&irq_state); - __spin_lock(&output_lock); - #else - spin_lock_irqsave(&output_lock); - #endif - } - // do the buffered printf vprintfmt((void*)buffered_putch, (void*)&cntp, fmt, ap); // write out remaining chars in the buffer buffered_putch(-1,&cntp); - if (!ktrap_depth(pcpui)) { - #ifdef CONFIG_TRACE_LOCKS - __spin_unlock(&output_lock); - enable_irqsave(&irq_state); - #else - spin_unlock_irqsave(&output_lock); - #endif - } + print_unlock(); return cnt; }