X-Git-Url: http://ftp.safe.ca/?p=safe%2Fjmp%2Flinux-2.6;a=blobdiff_plain;f=kernel%2Fprintk.c;h=7f60d8a41cf77eac76606abe3fff981c3e3e3e43;hp=e3602d0755b0dd99c9fb47887fd67d371cdd5424;hb=7f95d48056f1569a9e97e78e6f1557c7172bf6ac;hpb=403f307576396f3362fbb65af190885b6036c72c diff --git a/kernel/printk.c b/kernel/printk.c index e3602d0..7f60d8a 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -32,18 +32,26 @@ #include #include #include +#include +#include +#include +#include #include /* + * for_each_console() allows you to iterate on each console + */ +#define for_each_console(con) \ + for (con = console_drivers; con != NULL; con = con->next) + +/* * Architectures can override it: */ void asmlinkage __attribute__((weak)) early_printk(const char *fmt, ...) { } -#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) - /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ @@ -86,23 +94,19 @@ EXPORT_SYMBOL_GPL(console_drivers); */ static int console_locked, console_suspended; -/* - * logbuf_lock protects log_buf, log_start, log_end, con_start and logged_chars - * It is also used in interesting ways to provide interlocking in - * release_console_sem(). - */ -static DEFINE_SPINLOCK(logbuf_lock); - -#define LOG_BUF_MASK (log_buf_len-1) -#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK]) +#define LOG_BUF_MASK(ns) ((ns)->buf_len-1) +#define LOG_BUF(ns, idx) ((ns)->buf[(idx) & LOG_BUF_MASK(ns)]) /* - * The indices into log_buf are not constrained to log_buf_len - they - * must be masked before subscripting + * To access container syslog ring buffer */ -static unsigned log_start; /* Index into log_buf: next char to be read by syslog() */ -static unsigned con_start; /* Index into log_buf: next char to be sent to consoles */ -static unsigned log_end; /* Index into log_buf: most-recently-written-char + 1 */ +#define sys_log_lock (syslog_ns->logbuf_lock) +#define sys_log_start (syslog_ns->log_start) +#define sys_log_end (syslog_ns->log_end) +#define sys_log_con_start (syslog_ns->con_start) +#define sys_log_buf_len (syslog_ns->buf_len) +#define sys_log_logged_chars (syslog_ns->logged_chars) +#define sys_log_buf (syslog_ns->buf) /* * Array of consoles built from command line options (console=) @@ -130,47 +134,36 @@ static int console_may_schedule; #ifdef CONFIG_PRINTK -static char __log_buf[__LOG_BUF_LEN]; -static char *log_buf = __log_buf; -static int log_buf_len = __LOG_BUF_LEN; -static unsigned logged_chars; /* Number of chars produced since last read+clear operation */ +static int saved_console_loglevel = -1; + +#ifdef CONFIG_KEXEC +/* + * This appends the listed symbols to /proc/vmcoreinfo + * + * /proc/vmcoreinfo is used by various utiilties, like crash and makedumpfile to + * obtain access to symbols that are otherwise very difficult to locate. These + * symbols are specifically used so that utilities can access and extract the + * dmesg log from a vmcore file after a crash. + */ +void log_buf_kexec_setup(void) +{ + struct syslog_ns *syslog_ns = current_syslog_ns(); + + VMCOREINFO_SYMBOL(sys_log_buf); + VMCOREINFO_SYMBOL(sys_log_end); + VMCOREINFO_SYMBOL(sys_log_buf_len); + VMCOREINFO_SYMBOL(sys_log_logged_chars); +} +#endif static int __init log_buf_len_setup(char *str) { unsigned size = memparse(str, &str); - unsigned long flags; - if (size) + if (size) { size = roundup_pow_of_two(size); - if (size > log_buf_len) { - unsigned start, dest_idx, offset; - char *new_log_buf; - - new_log_buf = alloc_bootmem(size); - if (!new_log_buf) { - printk(KERN_WARNING "log_buf_len: allocation failed\n"); - goto out; - } - - spin_lock_irqsave(&logbuf_lock, flags); - log_buf_len = size; - log_buf = new_log_buf; - - offset = start = min(con_start, log_start); - dest_idx = 0; - while (start != log_end) { - log_buf[dest_idx] = __log_buf[start & (__LOG_BUF_LEN - 1)]; - start++; - dest_idx++; - } - log_start -= offset; - con_start -= offset; - log_end -= offset; - spin_unlock_irqrestore(&logbuf_lock, flags); - - printk(KERN_NOTICE "log_buf_len: %d\n", log_buf_len); + resize_syslog_ns(&init_syslog_ns, size); } -out: return 1; } @@ -179,12 +172,11 @@ __setup("log_buf_len=", log_buf_len_setup); #ifdef CONFIG_BOOT_PRINTK_DELAY static unsigned int boot_delay; /* msecs delay after each printk during bootup */ -static unsigned long long printk_delay_msec; /* per msec, based on boot_delay */ +static unsigned long long loops_per_msec; /* based on boot_delay */ static int __init boot_delay_setup(char *str) { unsigned long lpj; - unsigned long long loops_per_msec; lpj = preset_lpj ? preset_lpj : 1000000; /* some guess */ loops_per_msec = (unsigned long long)lpj / 1000 * HZ; @@ -193,10 +185,9 @@ static int __init boot_delay_setup(char *str) if (boot_delay > 10 * 1000) boot_delay = 0; - printk_delay_msec = loops_per_msec; - printk(KERN_DEBUG "boot_delay: %u, preset_lpj: %ld, lpj: %lu, " - "HZ: %d, printk_delay_msec: %llu\n", - boot_delay, preset_lpj, lpj, HZ, printk_delay_msec); + pr_debug("boot_delay: %u, preset_lpj: %ld, lpj: %lu, " + "HZ: %d, loops_per_msec: %llu\n", + boot_delay, preset_lpj, lpj, HZ, loops_per_msec); return 1; } __setup("boot_delay=", boot_delay_setup); @@ -209,7 +200,7 @@ static void boot_delay_msec(void) if (boot_delay == 0 || system_state != SYSTEM_BOOTING) return; - k = (unsigned long long)printk_delay_msec * boot_delay; + k = (unsigned long long)loops_per_msec * boot_delay; timeout = jiffies + msecs_to_jiffies(boot_delay); while (k) { @@ -231,38 +222,24 @@ static inline void boot_delay_msec(void) } #endif -/* - * Commands to do_syslog: - * - * 0 -- Close the log. Currently a NOP. - * 1 -- Open the log. Currently a NOP. - * 2 -- Read from the log. - * 3 -- Read all messages remaining in the ring buffer. - * 4 -- Read and clear all messages remaining in the ring buffer - * 5 -- Clear ring buffer. - * 6 -- Disable printk's to console - * 7 -- Enable printk's to console - * 8 -- Set level of messages printed to console - * 9 -- Return number of unread characters in the log buffer - * 10 -- Return size of the log buffer - */ -int do_syslog(int type, char __user *buf, int len) +int do_syslog(int type, char __user *buf, int len, bool from_file) { unsigned i, j, limit, count; int do_clear = 0; char c; int error = 0; + struct syslog_ns *syslog_ns = current_syslog_ns(); - error = security_syslog(type); + error = security_syslog(type, from_file); if (error) return error; switch (type) { - case 0: /* Close log */ + case SYSLOG_ACTION_CLOSE: /* Close log */ break; - case 1: /* Open log */ + case SYSLOG_ACTION_OPEN: /* Open log */ break; - case 2: /* Read from log */ + case SYSLOG_ACTION_READ: /* Read from log */ error = -EINVAL; if (!buf || len < 0) goto out; @@ -274,29 +251,31 @@ int do_syslog(int type, char __user *buf, int len) goto out; } error = wait_event_interruptible(log_wait, - (log_start - log_end)); + (sys_log_start - sys_log_end)); if (error) goto out; i = 0; - spin_lock_irq(&logbuf_lock); - while (!error && (log_start != log_end) && i < len) { - c = LOG_BUF(log_start); - log_start++; - spin_unlock_irq(&logbuf_lock); + spin_lock_irq(&sys_log_lock); + while (!error && (sys_log_start != sys_log_end) && i < len) { + c = LOG_BUF(syslog_ns, sys_log_start); + sys_log_start++; + spin_unlock_irq(&sys_log_lock); error = __put_user(c,buf); buf++; i++; cond_resched(); - spin_lock_irq(&logbuf_lock); + spin_lock_irq(&sys_log_lock); } - spin_unlock_irq(&logbuf_lock); + spin_unlock_irq(&sys_log_lock); if (!error) error = i; break; - case 4: /* Read/clear last kernel messages */ + /* Read/clear last kernel messages */ + case SYSLOG_ACTION_READ_CLEAR: do_clear = 1; /* FALL THRU */ - case 3: /* Read last kernel messages */ + /* Read last kernel messages */ + case SYSLOG_ACTION_READ_ALL: error = -EINVAL; if (!buf || len < 0) goto out; @@ -308,14 +287,14 @@ int do_syslog(int type, char __user *buf, int len) goto out; } count = len; - if (count > log_buf_len) - count = log_buf_len; - spin_lock_irq(&logbuf_lock); - if (count > logged_chars) - count = logged_chars; + if (count > sys_log_buf_len) + count = sys_log_buf_len; + spin_lock_irq(&sys_log_lock); + if (count > sys_log_logged_chars) + count = sys_log_logged_chars; if (do_clear) - logged_chars = 0; - limit = log_end; + sys_log_logged_chars = 0; + limit = sys_log_end; /* * __put_user() could sleep, and while we sleep * printk() could overwrite the messages @@ -324,15 +303,15 @@ int do_syslog(int type, char __user *buf, int len) */ for (i = 0; i < count && !error; i++) { j = limit-1-i; - if (j + log_buf_len < log_end) + if (j + sys_log_buf_len < sys_log_end) break; - c = LOG_BUF(j); - spin_unlock_irq(&logbuf_lock); + c = LOG_BUF(syslog_ns, j); + spin_unlock_irq(&sys_log_lock); error = __put_user(c,&buf[count-1-i]); cond_resched(); - spin_lock_irq(&logbuf_lock); + spin_lock_irq(&sys_log_lock); } - spin_unlock_irq(&logbuf_lock); + spin_unlock_irq(&sys_log_lock); if (error) break; error = i; @@ -349,29 +328,42 @@ int do_syslog(int type, char __user *buf, int len) } } break; - case 5: /* Clear ring buffer */ - logged_chars = 0; + /* Clear ring buffer */ + case SYSLOG_ACTION_CLEAR: + sys_log_logged_chars = 0; break; - case 6: /* Disable logging to console */ + /* Disable logging to console */ + case SYSLOG_ACTION_CONSOLE_OFF: + if (saved_console_loglevel == -1) + saved_console_loglevel = console_loglevel; console_loglevel = minimum_console_loglevel; break; - case 7: /* Enable logging to console */ - console_loglevel = default_console_loglevel; + /* Enable logging to console */ + case SYSLOG_ACTION_CONSOLE_ON: + if (saved_console_loglevel != -1) { + console_loglevel = saved_console_loglevel; + saved_console_loglevel = -1; + } break; - case 8: /* Set level of messages printed to console */ + /* Set level of messages printed to console */ + case SYSLOG_ACTION_CONSOLE_LEVEL: error = -EINVAL; if (len < 1 || len > 8) goto out; if (len < minimum_console_loglevel) len = minimum_console_loglevel; console_loglevel = len; + /* Implicitly re-enable logging to console */ + saved_console_loglevel = -1; error = 0; break; - case 9: /* Number of chars in the log buffer */ - error = log_end - log_start; + /* Number of chars in the log buffer */ + case SYSLOG_ACTION_SIZE_UNREAD: + error = sys_log_end - sys_log_start; break; - case 10: /* Size of the log buffer */ - error = log_buf_len; + /* Size of the log buffer */ + case SYSLOG_ACTION_SIZE_BUFFER: + error = sys_log_buf_len; break; default: error = -EINVAL; @@ -383,21 +375,23 @@ out: SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len) { - return do_syslog(type, buf, len); + return do_syslog(type, buf, len, SYSLOG_FROM_CALL); } /* * Call the console drivers on a range of log_buf */ -static void __call_console_drivers(unsigned start, unsigned end) +static void __call_console_drivers(struct syslog_ns *syslog_ns, + unsigned start, unsigned end) { struct console *con; - for (con = console_drivers; con; con = con->next) { + for_each_console(con) { if ((con->flags & CON_ENABLED) && con->write && (cpu_online(smp_processor_id()) || (con->flags & CON_ANYTIME))) - con->write(con, &LOG_BUF(start), end - start); + con->write(con, &LOG_BUF(syslog_ns, start), + end - start); } } @@ -416,18 +410,21 @@ early_param("ignore_loglevel", ignore_loglevel_setup); /* * Write out chars from start to end - 1 inclusive */ -static void _call_console_drivers(unsigned start, - unsigned end, int msg_log_level) +static void _call_console_drivers(struct syslog_ns *syslog_ns, unsigned start, + unsigned end, int msg_log_level) { if ((msg_log_level < console_loglevel || ignore_loglevel) && console_drivers && start != end) { - if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) { + if ((start & LOG_BUF_MASK(syslog_ns)) > + (end & LOG_BUF_MASK(syslog_ns))) { /* wrapped write */ - __call_console_drivers(start & LOG_BUF_MASK, - log_buf_len); - __call_console_drivers(0, end & LOG_BUF_MASK); + __call_console_drivers(syslog_ns, + start & LOG_BUF_MASK(syslog_ns), + sys_log_buf_len); + __call_console_drivers(syslog_ns, 0, + end & LOG_BUF_MASK(syslog_ns)); } else { - __call_console_drivers(start, end); + __call_console_drivers(syslog_ns, start, end); } } } @@ -437,7 +434,8 @@ static void _call_console_drivers(unsigned start, * log_buf[start] to log_buf[end - 1]. * The console_sem must be held. */ -static void call_console_drivers(unsigned start, unsigned end) +static void call_console_drivers(struct syslog_ns *syslog_ns, + unsigned start, unsigned end) { unsigned cur_index, start_print; static int msg_level = -1; @@ -448,16 +446,16 @@ static void call_console_drivers(unsigned start, unsigned end) start_print = start; while (cur_index != end) { if (msg_level < 0 && ((end - cur_index) > 2) && - LOG_BUF(cur_index + 0) == '<' && - LOG_BUF(cur_index + 1) >= '0' && - LOG_BUF(cur_index + 1) <= '7' && - LOG_BUF(cur_index + 2) == '>') { - msg_level = LOG_BUF(cur_index + 1) - '0'; + LOG_BUF(syslog_ns, cur_index + 0) == '<' && + LOG_BUF(syslog_ns, cur_index + 1) >= '0' && + LOG_BUF(syslog_ns, cur_index + 1) <= '7' && + LOG_BUF(syslog_ns, cur_index + 2) == '>') { + msg_level = LOG_BUF(syslog_ns, cur_index + 1) - '0'; cur_index += 3; start_print = cur_index; } while (cur_index != end) { - char c = LOG_BUF(cur_index); + char c = LOG_BUF(syslog_ns, cur_index); cur_index++; if (c == '\n') { @@ -470,26 +468,27 @@ static void call_console_drivers(unsigned start, unsigned end) */ msg_level = default_message_loglevel; } - _call_console_drivers(start_print, cur_index, msg_level); + _call_console_drivers(syslog_ns, + start_print, cur_index, msg_level); msg_level = -1; start_print = cur_index; break; } } } - _call_console_drivers(start_print, end, msg_level); + _call_console_drivers(syslog_ns, start_print, end, msg_level); } -static void emit_log_char(char c) +static void emit_log_char(struct syslog_ns *syslog_ns, char c) { - LOG_BUF(log_end) = c; - log_end++; - if (log_end - log_start > log_buf_len) - log_start = log_end - log_buf_len; - if (log_end - con_start > log_buf_len) - con_start = log_end - log_buf_len; - if (logged_chars < log_buf_len) - logged_chars++; + LOG_BUF(syslog_ns, sys_log_end) = c; + sys_log_end++; + if (sys_log_end - sys_log_start > sys_log_buf_len) + sys_log_start = sys_log_end - sys_log_buf_len; + if (sys_log_end - sys_log_con_start > sys_log_buf_len) + sys_log_con_start = sys_log_end - sys_log_buf_len; + if (sys_log_logged_chars < sys_log_buf_len) + sys_log_logged_chars++; } /* @@ -497,7 +496,7 @@ static void emit_log_char(char c) * every 10 seconds, to leave time for slow consoles to print a * full oops. */ -static void zap_locks(void) +static void zap_locks(struct syslog_ns *syslog_ns) { static unsigned long oops_timestamp; @@ -508,7 +507,7 @@ static void zap_locks(void) oops_timestamp = jiffies; /* If a crash is occurring, make sure we can't deadlock */ - spin_lock_init(&logbuf_lock); + spin_lock_init(&sys_log_lock); /* And make sure that we print immediately */ init_MUTEX(&console_sem); } @@ -525,7 +524,7 @@ static int have_callable_console(void) { struct console *con; - for (con = console_drivers; con; con = con->next) + for_each_console(con) if (con->flags & CON_ANYTIME) return 1; @@ -592,7 +591,8 @@ static inline int can_use_console(unsigned int cpu) * interrupts disabled. It should return with 'lockbuf_lock' * released but interrupts still disabled. */ -static int acquire_console_semaphore_for_printk(unsigned int cpu) +static int acquire_console_semaphore_for_printk( + struct syslog_ns *syslog_ns, unsigned int cpu) { int retval = 0; @@ -612,7 +612,7 @@ static int acquire_console_semaphore_for_printk(unsigned int cpu) } } printk_cpu = UINT_MAX; - spin_unlock(&logbuf_lock); + spin_unlock(&sys_log_lock); return retval; } static const char recursion_bug_msg [] = @@ -621,15 +621,31 @@ static int recursion_bug; static int new_text_line = 1; static char printk_buf[1024]; +int printk_delay_msec __read_mostly; + +static inline void printk_delay(void) +{ + if (unlikely(printk_delay_msec)) { + int m = printk_delay_msec; + + while (m--) { + mdelay(1); + touch_nmi_watchdog(); + } + } +} + asmlinkage int vprintk(const char *fmt, va_list args) { int printed_len = 0; int current_log_level = default_message_loglevel; + struct syslog_ns *syslog_ns = current_syslog_ns(); unsigned long flags; int this_cpu; char *p; boot_delay_msec(); + printk_delay(); preempt_disable(); /* This stops the holder of console_sem just where we want him */ @@ -651,11 +667,11 @@ asmlinkage int vprintk(const char *fmt, va_list args) recursion_bug = 1; goto out_restore_irqs; } - zap_locks(); + zap_locks(syslog_ns); } lockdep_off(); - spin_lock(&logbuf_lock); + spin_lock(&sys_log_lock); printk_cpu = this_cpu; if (recursion_bug) { @@ -668,24 +684,39 @@ asmlinkage int vprintk(const char *fmt, va_list args) sizeof(printk_buf) - printed_len, fmt, args); + p = printk_buf; + + /* Do we have a loglevel in the string? */ + if (p[0] == '<') { + unsigned char c = p[1]; + if (c && p[2] == '>') { + switch (c) { + case '0' ... '7': /* loglevel */ + current_log_level = c - '0'; + /* Fallthrough - make sure we're on a new line */ + case 'd': /* KERN_DEFAULT */ + if (!new_text_line) { + emit_log_char(syslog_ns, '\n'); + new_text_line = 1; + } + /* Fallthrough - skip the loglevel */ + case 'c': /* KERN_CONT */ + p += 3; + break; + } + } + } + /* * Copy the output into log_buf. If the caller didn't provide * appropriate log level tags, we insert them here */ - for (p = printk_buf; *p; p++) { + for ( ; *p; p++) { if (new_text_line) { - /* If a token, set current_log_level and skip over */ - if (p[0] == '<' && p[1] >= '0' && p[1] <= '7' && - p[2] == '>') { - current_log_level = p[1] - '0'; - p += 3; - printed_len -= 3; - } - /* Always output the token */ - emit_log_char('<'); - emit_log_char(current_log_level + '0'); - emit_log_char('>'); + emit_log_char(syslog_ns, '<'); + emit_log_char(syslog_ns, current_log_level + '0'); + emit_log_char(syslog_ns, '>'); printed_len += 3; new_text_line = 0; @@ -698,12 +729,13 @@ asmlinkage int vprintk(const char *fmt, va_list args) t = cpu_clock(printk_cpu); nanosec_rem = do_div(t, 1000000000); - tlen = sprintf(tbuf, "[%5lu.%06lu] ", + tlen = sprintf(tbuf, "ns_id='%d' %5lu.%06lu] ", + syslog_ns->handle, (unsigned long) t, nanosec_rem / 1000); for (tp = tbuf; tp < tbuf + tlen; tp++) - emit_log_char(*tp); + emit_log_char(syslog_ns, *tp); printed_len += tlen; } @@ -711,7 +743,7 @@ asmlinkage int vprintk(const char *fmt, va_list args) break; } - emit_log_char(*p); + emit_log_char(syslog_ns, *p); if (*p == '\n') new_text_line = 1; } @@ -726,7 +758,7 @@ asmlinkage int vprintk(const char *fmt, va_list args) * will release 'logbuf_lock' regardless of whether it * actually gets the semaphore or not. */ - if (acquire_console_semaphore_for_printk(this_cpu)) + if (acquire_console_semaphore_for_printk(syslog_ns, this_cpu)) release_console_sem(); lockdep_on(); @@ -739,12 +771,6 @@ out_restore_irqs: EXPORT_SYMBOL(printk); EXPORT_SYMBOL(vprintk); -#else - -static void call_console_drivers(unsigned start, unsigned end) -{ -} - #endif static int __add_preferred_console(char *name, int idx, char *options, @@ -977,36 +1003,40 @@ void wake_up_klogd(void) */ void release_console_sem(void) { - unsigned long flags; - unsigned _con_start, _log_end; - unsigned wake_klogd = 0; - if (console_suspended) { up(&console_sem); return; } console_may_schedule = 0; - - for ( ; ; ) { - spin_lock_irqsave(&logbuf_lock, flags); - wake_klogd |= log_start - log_end; - if (con_start == log_end) - break; /* Nothing to print */ - _con_start = con_start; - _log_end = log_end; - con_start = log_end; /* Flush */ - spin_unlock(&logbuf_lock); - stop_critical_timings(); /* don't trace print latency */ - call_console_drivers(_con_start, _log_end); - start_critical_timings(); - local_irq_restore(flags); +#ifdef CONFIG_PRINTK + { + unsigned long flags; + unsigned _con_start, _log_end; + unsigned wake_klogd = 0; + struct syslog_ns *syslog_ns = current_syslog_ns(); + + for ( ; ; ) { + spin_lock_irqsave(&sys_log_lock, flags); + wake_klogd |= sys_log_start - sys_log_end; + if (sys_log_con_start == sys_log_end) + break; /* Nothing to print */ + _con_start = sys_log_con_start; + _log_end = sys_log_end; + sys_log_con_start = sys_log_end; /* Flush */ + spin_unlock(&sys_log_lock); + stop_critical_timings();/* don't trace print latency */ + call_console_drivers(syslog_ns, _con_start, _log_end); + start_critical_timings(); + local_irq_restore(flags); + } + spin_unlock_irqrestore(&sys_log_lock, flags); + if (wake_klogd) + wake_up_klogd(); } +#endif console_locked = 0; up(&console_sem); - spin_unlock_irqrestore(&logbuf_lock, flags); - if (wake_klogd) - wake_up_klogd(); } EXPORT_SYMBOL(release_console_sem); @@ -1026,12 +1056,6 @@ void __sched console_conditional_schedule(void) } EXPORT_SYMBOL(console_conditional_schedule); -void console_print(const char *s) -{ - printk(KERN_EMERG "%s", s); -} -EXPORT_SYMBOL(console_print); - void console_unblank(void) { struct console *c; @@ -1048,7 +1072,7 @@ void console_unblank(void) console_locked = 1; console_may_schedule = 0; - for (c = console_drivers; c != NULL; c = c->next) + for_each_console(c) if ((c->flags & CON_ENABLED) && c->unblank) c->unblank(); release_console_sem(); @@ -1063,7 +1087,7 @@ struct tty_driver *console_device(int *index) struct tty_driver *driver = NULL; acquire_console_sem(); - for (c = console_drivers; c != NULL; c = c->next) { + for_each_console(c) { if (!c->device) continue; driver = c->device(c, index); @@ -1100,25 +1124,48 @@ EXPORT_SYMBOL(console_start); * to register the console printing procedure with printk() and to * print any messages that were printed by the kernel before the * console driver was initialized. + * + * This can happen pretty early during the boot process (because of + * early_printk) - sometimes before setup_arch() completes - be careful + * of what kernel features are used - they may not be initialised yet. + * + * There are two types of consoles - bootconsoles (early_printk) and + * "real" consoles (everything which is not a bootconsole) which are + * handled differently. + * - Any number of bootconsoles can be registered at any time. + * - As soon as a "real" console is registered, all bootconsoles + * will be unregistered automatically. + * - Once a "real" console is registered, any attempt to register a + * bootconsoles will be rejected */ -void register_console(struct console *console) +void register_console(struct console *newcon) { int i; - unsigned long flags; - struct console *bootconsole = NULL; + struct console *bcon = NULL; - if (console_drivers) { - if (console->flags & CON_BOOT) - return; - if (console_drivers->flags & CON_BOOT) - bootconsole = console_drivers; + /* + * before we register a new CON_BOOT console, make sure we don't + * already have a valid console + */ + if (console_drivers && newcon->flags & CON_BOOT) { + /* find the last or real console */ + for_each_console(bcon) { + if (!(bcon->flags & CON_BOOT)) { + printk(KERN_INFO "Too late to register bootconsole %s%d\n", + newcon->name, newcon->index); + return; + } + } } - if (preferred_console < 0 || bootconsole || !console_drivers) + if (console_drivers && console_drivers->flags & CON_BOOT) + bcon = console_drivers; + + if (preferred_console < 0 || bcon || !console_drivers) preferred_console = selected_console; - if (console->early_setup) - console->early_setup(); + if (newcon->early_setup) + newcon->early_setup(); /* * See if we want to use this console driver. If we @@ -1126,13 +1173,13 @@ void register_console(struct console *console) * that registers here. */ if (preferred_console < 0) { - if (console->index < 0) - console->index = 0; - if (console->setup == NULL || - console->setup(console, NULL) == 0) { - console->flags |= CON_ENABLED; - if (console->device) { - console->flags |= CON_CONSDEV; + if (newcon->index < 0) + newcon->index = 0; + if (newcon->setup == NULL || + newcon->setup(newcon, NULL) == 0) { + newcon->flags |= CON_ENABLED; + if (newcon->device) { + newcon->flags |= CON_CONSDEV; preferred_console = 0; } } @@ -1144,73 +1191,99 @@ void register_console(struct console *console) */ for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) { - if (strcmp(console_cmdline[i].name, console->name) != 0) + if (strcmp(console_cmdline[i].name, newcon->name) != 0) continue; - if (console->index >= 0 && - console->index != console_cmdline[i].index) + if (newcon->index >= 0 && + newcon->index != console_cmdline[i].index) continue; - if (console->index < 0) - console->index = console_cmdline[i].index; + if (newcon->index < 0) + newcon->index = console_cmdline[i].index; #ifdef CONFIG_A11Y_BRAILLE_CONSOLE if (console_cmdline[i].brl_options) { - console->flags |= CON_BRL; - braille_register_console(console, + newcon->flags |= CON_BRL; + braille_register_console(newcon, console_cmdline[i].index, console_cmdline[i].options, console_cmdline[i].brl_options); return; } #endif - if (console->setup && - console->setup(console, console_cmdline[i].options) != 0) + if (newcon->setup && + newcon->setup(newcon, console_cmdline[i].options) != 0) break; - console->flags |= CON_ENABLED; - console->index = console_cmdline[i].index; + newcon->flags |= CON_ENABLED; + newcon->index = console_cmdline[i].index; if (i == selected_console) { - console->flags |= CON_CONSDEV; + newcon->flags |= CON_CONSDEV; preferred_console = selected_console; } break; } - if (!(console->flags & CON_ENABLED)) + if (!(newcon->flags & CON_ENABLED)) return; - if (bootconsole && (console->flags & CON_CONSDEV)) { - printk(KERN_INFO "console handover: boot [%s%d] -> real [%s%d]\n", - bootconsole->name, bootconsole->index, - console->name, console->index); - unregister_console(bootconsole); - console->flags &= ~CON_PRINTBUFFER; - } else { - printk(KERN_INFO "console [%s%d] enabled\n", - console->name, console->index); - } + /* + * If we have a bootconsole, and are switching to a real console, + * don't print everything out again, since when the boot console, and + * the real console are the same physical device, it's annoying to + * see the beginning boot messages twice + */ + if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) + newcon->flags &= ~CON_PRINTBUFFER; /* * Put this console in the list - keep the * preferred driver at the head of the list. */ acquire_console_sem(); - if ((console->flags & CON_CONSDEV) || console_drivers == NULL) { - console->next = console_drivers; - console_drivers = console; - if (console->next) - console->next->flags &= ~CON_CONSDEV; + if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) { + newcon->next = console_drivers; + console_drivers = newcon; + if (newcon->next) + newcon->next->flags &= ~CON_CONSDEV; } else { - console->next = console_drivers->next; - console_drivers->next = console; + newcon->next = console_drivers->next; + console_drivers->next = newcon; } - if (console->flags & CON_PRINTBUFFER) { +#ifdef CONFIG_PRINTK + if (newcon->flags & CON_PRINTBUFFER) { + unsigned long flags; /* * release_console_sem() will print out the buffered messages * for us. */ - spin_lock_irqsave(&logbuf_lock, flags); - con_start = log_start; - spin_unlock_irqrestore(&logbuf_lock, flags); + + struct syslog_ns *syslog_ns = current_syslog_ns(); + + spin_lock_irqsave(&sys_log_lock, flags); + sys_log_con_start = sys_log_start; + spin_unlock_irqrestore(&sys_log_lock, flags); } +#endif release_console_sem(); + + /* + * By unregistering the bootconsoles after we enable the real console + * we get the "console xxx enabled" message on all the consoles - + * boot consoles, real consoles, etc - this is to ensure that end + * users know there might be something in the kernel's log buffer that + * went to the bootconsole (that they do not see on the real console) + */ + if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) { + /* we need to iterate through twice, to make sure we print + * everything out, before we unregister the console(s) + */ + printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n", + newcon->name, newcon->index); + for_each_console(bcon) + if (bcon->flags & CON_BOOT) + unregister_console(bcon); + } else { + printk(KERN_INFO "%sconsole [%s%d] enabled\n", + (newcon->flags & CON_BOOT) ? "boot" : "" , + newcon->name, newcon->index); + } } EXPORT_SYMBOL(register_console); @@ -1253,11 +1326,13 @@ EXPORT_SYMBOL(unregister_console); static int __init disable_boot_consoles(void) { - if (console_drivers != NULL) { - if (console_drivers->flags & CON_BOOT) { + struct console *con; + + for_each_console(con) { + if (con->flags & CON_BOOT) { printk(KERN_INFO "turn off boot console %s%d\n", - console_drivers->name, console_drivers->index); - return unregister_console(console_drivers); + con->name, con->index); + unregister_console(con); } } return 0; @@ -1274,11 +1349,11 @@ late_initcall(disable_boot_consoles); */ DEFINE_RATELIMIT_STATE(printk_ratelimit_state, 5 * HZ, 10); -int printk_ratelimit(void) +int __printk_ratelimit(const char *func) { - return __ratelimit(&printk_ratelimit_state); + return ___ratelimit(&printk_ratelimit_state, func); } -EXPORT_SYMBOL(printk_ratelimit); +EXPORT_SYMBOL(__printk_ratelimit); /** * printk_timed_ratelimit - caller-controlled printk ratelimiting @@ -1292,11 +1367,135 @@ EXPORT_SYMBOL(printk_ratelimit); bool printk_timed_ratelimit(unsigned long *caller_jiffies, unsigned int interval_msecs) { - if (*caller_jiffies == 0 || time_after(jiffies, *caller_jiffies)) { - *caller_jiffies = jiffies + msecs_to_jiffies(interval_msecs); + if (*caller_jiffies == 0 + || !time_in_range(jiffies, *caller_jiffies, + *caller_jiffies + + msecs_to_jiffies(interval_msecs))) { + *caller_jiffies = jiffies; return true; } return false; } EXPORT_SYMBOL(printk_timed_ratelimit); + +static DEFINE_SPINLOCK(dump_list_lock); +static LIST_HEAD(dump_list); + +/** + * kmsg_dump_register - register a kernel log dumper. + * @dumper: pointer to the kmsg_dumper structure + * + * Adds a kernel log dumper to the system. The dump callback in the + * structure will be called when the kernel oopses or panics and must be + * set. Returns zero on success and %-EINVAL or %-EBUSY otherwise. + */ +int kmsg_dump_register(struct kmsg_dumper *dumper) +{ + unsigned long flags; + int err = -EBUSY; + + /* The dump callback needs to be set */ + if (!dumper->dump) + return -EINVAL; + + spin_lock_irqsave(&dump_list_lock, flags); + /* Don't allow registering multiple times */ + if (!dumper->registered) { + dumper->registered = 1; + list_add_tail(&dumper->list, &dump_list); + err = 0; + } + spin_unlock_irqrestore(&dump_list_lock, flags); + + return err; +} +EXPORT_SYMBOL_GPL(kmsg_dump_register); + +/** + * kmsg_dump_unregister - unregister a kmsg dumper. + * @dumper: pointer to the kmsg_dumper structure + * + * Removes a dump device from the system. Returns zero on success and + * %-EINVAL otherwise. + */ +int kmsg_dump_unregister(struct kmsg_dumper *dumper) +{ + unsigned long flags; + int err = -EINVAL; + + spin_lock_irqsave(&dump_list_lock, flags); + if (dumper->registered) { + dumper->registered = 0; + list_del(&dumper->list); + err = 0; + } + spin_unlock_irqrestore(&dump_list_lock, flags); + + return err; +} +EXPORT_SYMBOL_GPL(kmsg_dump_unregister); + +static const char const *kmsg_reasons[] = { + [KMSG_DUMP_OOPS] = "oops", + [KMSG_DUMP_PANIC] = "panic", + [KMSG_DUMP_KEXEC] = "kexec", +}; + +static const char *kmsg_to_str(enum kmsg_dump_reason reason) +{ + if (reason >= ARRAY_SIZE(kmsg_reasons) || reason < 0) + return "unknown"; + + return kmsg_reasons[reason]; +} + +/** + * kmsg_dump - dump kernel log to kernel message dumpers. + * @reason: the reason (oops, panic etc) for dumping + * + * Iterate through each of the dump devices and call the oops/panic + * callbacks with the log buffer. + */ +void kmsg_dump(enum kmsg_dump_reason reason) +{ + unsigned long end; + unsigned chars; + struct kmsg_dumper *dumper; + const char *s1, *s2; + unsigned long l1, l2; + unsigned long flags; + struct syslog_ns *syslog_ns = current_syslog_ns(); + + /* Theoretically, the log could move on after we do this, but + there's not a lot we can do about that. The new messages + will overwrite the start of what we dump. */ + spin_lock_irqsave(&sys_log_lock, flags); + end = sys_log_end & LOG_BUF_MASK(syslog_ns); + chars = sys_log_logged_chars; + spin_unlock_irqrestore(&sys_log_lock, flags); + + if (sys_log_logged_chars > end) { + s1 = sys_log_buf + sys_log_buf_len - sys_log_logged_chars + end; + l1 = sys_log_logged_chars - end; + + s2 = sys_log_buf; + l2 = end; + } else { + s1 = ""; + l1 = 0; + + s2 = sys_log_buf + end - sys_log_logged_chars; + l2 = sys_log_logged_chars; + } + + if (!spin_trylock_irqsave(&dump_list_lock, flags)) { + printk(KERN_ERR "dump_kmsg: dump list lock is held during %s, skipping dump\n", + kmsg_to_str(reason)); + return; + } + list_for_each_entry(dumper, &dump_list, list) + dumper->dump(dumper, reason, s1, l1, s2, l2); + spin_unlock_irqrestore(&dump_list_lock, flags); +} #endif +