patch-2.4.10 linux/kernel/printk.c
Next file: linux/kernel/ptrace.c
Previous file: linux/kernel/panic.c
Back to the patch index
Back to the overall index
- Lines: 632
- Date:
Mon Sep 17 13:16:30 2001
- Orig file:
v2.4.9/linux/kernel/printk.c
- Orig date:
Tue Feb 13 13:15:05 2001
diff -u --recursive --new-file v2.4.9/linux/kernel/printk.c linux/kernel/printk.c
@@ -12,6 +12,8 @@
* Modified for sysctl support, 1/8/97, Chris Horn.
* Fixed SMP synchronization, 08/08/99, Manfred Spraul
* manfreds@colorfullife.com
+ * Rewrote bits to get rid of console_lock
+ * 01Mar01 Andrew Morton <andrewm@uow.edu.au>
*/
#include <linux/mm.h>
@@ -20,14 +22,14 @@
#include <linux/smp_lock.h>
#include <linux/console.h>
#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h> /* For in_interrupt() */
#include <asm/uaccess.h>
-#define LOG_BUF_LEN (16384)
+#define LOG_BUF_LEN (16384) /* This must be a power of two */
#define LOG_BUF_MASK (LOG_BUF_LEN-1)
-static char buf[1024];
-
/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
@@ -35,7 +37,6 @@
#define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
-unsigned long log_size;
DECLARE_WAIT_QUEUE_HEAD(log_wait);
/* Keep together for sysctl support */
@@ -44,15 +45,41 @@
int minimum_console_loglevel = MINIMUM_CONSOLE_LOGLEVEL;
int default_console_loglevel = DEFAULT_CONSOLE_LOGLEVEL;
-spinlock_t console_lock = SPIN_LOCK_UNLOCKED;
+int oops_in_progress;
+/*
+ * console_sem protects the console_drivers list, and also
+ * provides serialisation for access to the entire console
+ * driver system.
+ */
+static DECLARE_MUTEX(console_sem);
struct console *console_drivers;
+
+/*
+ * 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 spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED;
+
static char log_buf[LOG_BUF_LEN];
-static unsigned long log_start;
-static unsigned long logged_chars;
+#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK])
+
+/*
+ * The indices into log_buf are not constrained to LOG_BUF_LEN - they
+ * must be masked before subscripting
+ */
+static unsigned long log_start; /* Index into log_buf: next char to be read by syslog() */
+static unsigned long con_start; /* Index into log_buf: next char to be sent to consoles */
+static unsigned long log_end; /* Index into log_buf: most-recently-written-char + 1 */
+static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */
+
struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
static int preferred_console = -1;
+/* Flag: console code may call schedule() */
+static int console_may_schedule;
+
/*
* Setup a list of consoles. Called from init/main.c
*/
@@ -120,6 +147,7 @@
* 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
*/
int do_syslog(int type, char * buf, int len)
{
@@ -143,22 +171,21 @@
error = verify_area(VERIFY_WRITE,buf,len);
if (error)
goto out;
- error = wait_event_interruptible(log_wait, log_size);
+ error = wait_event_interruptible(log_wait, (log_start - log_end));
if (error)
goto out;
i = 0;
- spin_lock_irq(&console_lock);
- while (log_size && i < len) {
- c = log_buf[log_start & LOG_BUF_MASK];
+ spin_lock_irq(&logbuf_lock);
+ while ((log_start != log_end) && i < len) {
+ c = LOG_BUF(log_start);
log_start++;
- log_size--;
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
__put_user(c,buf);
buf++;
i++;
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
}
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
error = i;
break;
case 4: /* Read/clear last kernel messages */
@@ -177,12 +204,12 @@
count = len;
if (count > LOG_BUF_LEN)
count = LOG_BUF_LEN;
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
if (count > logged_chars)
count = logged_chars;
if (do_clear)
logged_chars = 0;
- limit = log_start + log_size;
+ limit = log_end;
/*
* __put_user() could sleep, and while we sleep
* printk() could overwrite the messages
@@ -191,14 +218,14 @@
*/
for(i=0;i < count;i++) {
j = limit-1-i;
- if (j+LOG_BUF_LEN < log_start+log_size)
+ if (j+LOG_BUF_LEN < log_end)
break;
- c = log_buf[ j & LOG_BUF_MASK ];
- spin_unlock_irq(&console_lock);
+ c = LOG_BUF(j);
+ spin_unlock_irq(&logbuf_lock);
__put_user(c,&buf[count-1-i]);
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
}
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
error = i;
if(i != count) {
int offset = count-error;
@@ -211,31 +238,36 @@
break;
case 5: /* Clear ring buffer */
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
logged_chars = 0;
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
break;
case 6: /* Disable logging to console */
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
console_loglevel = minimum_console_loglevel;
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
break;
case 7: /* Enable logging to console */
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
console_loglevel = default_console_loglevel;
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
break;
- case 8:
+ case 8: /* Set level of messages printed to console */
error = -EINVAL;
if (len < 1 || len > 8)
goto out;
if (len < minimum_console_loglevel)
len = minimum_console_loglevel;
- spin_lock_irq(&console_lock);
+ spin_lock_irq(&logbuf_lock);
console_loglevel = len;
- spin_unlock_irq(&console_lock);
+ spin_unlock_irq(&logbuf_lock);
error = 0;
break;
+ case 9: /* Number of chars in the log buffer */
+ spin_lock_irq(&logbuf_lock);
+ error = log_end - log_start;
+ spin_unlock_irq(&logbuf_lock);
+ break;
default:
error = -EINVAL;
break;
@@ -251,98 +283,250 @@
return do_syslog(type, buf, len);
}
-asmlinkage int printk(const char *fmt, ...)
+/*
+ * Call the console drivers on a range of log_buf
+ */
+static void __call_console_drivers(unsigned long start, unsigned long end)
{
- va_list args;
- int i;
- char *msg, *p, *buf_end;
- int line_feed;
- static signed char msg_level = -1;
- long flags;
+ struct console *con;
- spin_lock_irqsave(&console_lock, flags);
- va_start(args, fmt);
- i = vsprintf(buf + 3, fmt, args); /* hopefully i < sizeof(buf)-4 */
- buf_end = buf + 3 + i;
- va_end(args);
- for (p = buf + 3; p < buf_end; p++) {
- msg = p;
- if (msg_level < 0) {
- if (
- p[0] != '<' ||
- p[1] < '0' ||
- p[1] > '7' ||
- p[2] != '>'
- ) {
- p -= 3;
- p[0] = '<';
- p[1] = default_message_loglevel + '0';
- p[2] = '>';
- } else
- msg += 3;
- msg_level = p[1] - '0';
+ for (con = console_drivers; con; con = con->next) {
+ if ((con->flags & CON_ENABLED) && con->write)
+ con->write(con, &LOG_BUF(start), end - start);
+ }
+}
+
+/*
+ * Write out chars from start to end - 1 inclusive
+ */
+static void _call_console_drivers(unsigned long start, unsigned long end, int msg_log_level)
+{
+ if (msg_log_level < console_loglevel && console_drivers && start != end) {
+ if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
+ /* wrapped write */
+ __call_console_drivers(start & LOG_BUF_MASK, LOG_BUF_LEN);
+ __call_console_drivers(0, end & LOG_BUF_MASK);
+ } else {
+ __call_console_drivers(start, end);
}
- line_feed = 0;
- for (; p < buf_end; p++) {
- log_buf[(log_start+log_size) & LOG_BUF_MASK] = *p;
- if (log_size < LOG_BUF_LEN)
- log_size++;
- else
- log_start++;
-
- logged_chars++;
- if (*p == '\n') {
- line_feed = 1;
+ }
+}
+
+/*
+ * Call the console drivers, asking them to write out
+ * log_buf[start] to log_buf[end - 1].
+ * The console_sem must be held.
+ */
+static void call_console_drivers(unsigned long start, unsigned long end)
+{
+ unsigned long cur_index, start_print;
+ static int msg_level = -1;
+
+ if (((long)(start - end)) > 0)
+ BUG();
+
+ cur_index = start;
+ 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';
+ cur_index += 3;
+ start_print = cur_index;
+ }
+ while (cur_index != end) {
+ char c = LOG_BUF(cur_index);
+ cur_index++;
+
+ if (c == '\n') {
+ if (msg_level < 0) {
+ /*
+ * printk() has already given us loglevel tags in
+ * the buffer. This code is here in case the
+ * log buffer has wrapped right round and scribbled
+ * on those tags
+ */
+ msg_level = default_message_loglevel;
+ }
+ _call_console_drivers(start_print, cur_index, msg_level);
+ msg_level = -1;
+ start_print = cur_index;
break;
}
}
- if (msg_level < console_loglevel && console_drivers) {
- struct console *c = console_drivers;
- while(c) {
- if ((c->flags & CON_ENABLED) && c->write)
- c->write(c, msg, p - msg + line_feed);
- c = c->next;
+ }
+ _call_console_drivers(start_print, end, msg_level);
+}
+
+static void emit_log_char(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++;
+}
+
+/*
+ * This is printk. It can be called from any context. We want it to work.
+ *
+ * We try to grab the console_sem. If we succeed, it's easy - we log the output and
+ * call the console drivers. If we fail to get the semaphore we place the output
+ * into the log buffer and return. The current holder of the console_sem will
+ * notice the new output in release_console_sem() and will send it to the
+ * consoles before releasing the semaphore.
+ *
+ * One effect of this deferred printing is that code which calls printk() and
+ * then changes console_loglevel may break. This is because console_loglevel
+ * is inspected when the actual printing occurs.
+ */
+asmlinkage int printk(const char *fmt, ...)
+{
+ va_list args;
+ unsigned long flags;
+ int printed_len;
+ char *p;
+ static char printk_buf[1024];
+ static int log_level_unknown = 1;
+
+ if (oops_in_progress) {
+ /* If a crash is occurring, make sure we can't deadlock */
+ spin_lock_init(&logbuf_lock);
+ /* And make sure that we print immediately */
+ init_MUTEX(&console_sem);
+ }
+
+ /* This stops the holder of console_sem just where we want him */
+ spin_lock_irqsave(&logbuf_lock, flags);
+
+ /* Emit the output into the temporary buffer */
+ va_start(args, fmt);
+ printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
+ va_end(args);
+
+ /*
+ * 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++) {
+ if (log_level_unknown) {
+ if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') {
+ emit_log_char('<');
+ emit_log_char(default_message_loglevel + '0');
+ emit_log_char('>');
}
+ log_level_unknown = 0;
}
- if (line_feed)
- msg_level = -1;
+ emit_log_char(*p);
+ if (*p == '\n')
+ log_level_unknown = 1;
+ }
+
+ if (!down_trylock(&console_sem)) {
+ /*
+ * We own the drivers. We can drop the spinlock and let
+ * release_console_sem() print the text
+ */
+ spin_unlock_irqrestore(&logbuf_lock, flags);
+ console_may_schedule = 0;
+ release_console_sem();
+ } else {
+ /*
+ * Someone else owns the drivers. We drop the spinlock, which
+ * allows the semaphore holder to proceed and to call the
+ * console drivers with the output which we just produced.
+ */
+ spin_unlock_irqrestore(&logbuf_lock, flags);
}
- spin_unlock_irqrestore(&console_lock, flags);
- wake_up_interruptible(&log_wait);
- return i;
+ return printed_len;
}
+EXPORT_SYMBOL(printk);
-void console_print(const char *s)
+/**
+ * acquire_console_sem - lock the console system for exclusive use.
+ *
+ * Acquires a semaphore which guarantees that the caller has
+ * exclusive access to the console system and the console_drivers list.
+ *
+ * Can sleep, returns nothing.
+ */
+void acquire_console_sem(void)
+{
+ if (in_interrupt())
+ BUG();
+ down(&console_sem);
+ console_may_schedule = 1;
+}
+EXPORT_SYMBOL(acquire_console_sem);
+
+/**
+ * release_console_sem - unlock the console system
+ *
+ * Releases the semaphore which the caller holds on the console system
+ * and the console driver list.
+ *
+ * While the semaphore was held, console output may have been buffered
+ * by printk(). If this is the case, release_console_sem() emits
+ * the output prior to releasing the semaphore.
+ *
+ * If there is output waiting for klogd, we wake it up.
+ *
+ * release_console_sem() may be called from any context.
+ */
+void release_console_sem(void)
{
- struct console *c;
unsigned long flags;
- int len = strlen(s);
+ unsigned long _con_start, _log_end;
+ unsigned long must_wake_klogd = 0;
- spin_lock_irqsave(&console_lock, flags);
- c = console_drivers;
- while(c) {
- if ((c->flags & CON_ENABLED) && c->write)
- c->write(c, s, len);
- c = c->next;
+ for ( ; ; ) {
+ spin_lock_irqsave(&logbuf_lock, flags);
+ must_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_irqrestore(&logbuf_lock, flags);
+ call_console_drivers(_con_start, _log_end);
}
- spin_unlock_irqrestore(&console_lock, flags);
+ console_may_schedule = 0;
+ up(&console_sem);
+ spin_unlock_irqrestore(&logbuf_lock, flags);
+ if (must_wake_klogd && !oops_in_progress)
+ wake_up_interruptible(&log_wait);
}
-void unblank_console(void)
+/** console_conditional_schedule - yield the CPU if required
+ *
+ * If the console code is currently allowed to sleep, and
+ * if this CPU should yield the CPU to another task, do
+ * so here.
+ *
+ * Must be called within acquire_console_sem().
+ */
+void console_conditional_schedule(void)
{
- struct console *c;
- unsigned long flags;
-
- spin_lock_irqsave(&console_lock, flags);
- c = console_drivers;
- while(c) {
- if ((c->flags & CON_ENABLED) && c->unblank)
- c->unblank();
- c = c->next;
+ if (console_may_schedule && current->need_resched) {
+ set_current_state(TASK_RUNNING);
+ schedule();
}
- spin_unlock_irqrestore(&console_lock, flags);
}
+void console_print(const char *s)
+{
+ printk(KERN_EMERG "%s", s);
+}
+EXPORT_SYMBOL(console_print);
+
/*
* The console driver calls this routine during kernel initialization
* to register the console printing procedure with printk() and to
@@ -351,11 +535,7 @@
*/
void register_console(struct console * console)
{
- int i, j,len;
- int p;
- char buf[16];
- signed char msg_level = -1;
- char *q;
+ int i;
unsigned long flags;
/*
@@ -402,7 +582,7 @@
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
- spin_lock_irqsave(&console_lock, flags);
+ acquire_console_sem();
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
@@ -410,57 +590,28 @@
console->next = console_drivers->next;
console_drivers->next = console;
}
- if ((console->flags & CON_PRINTBUFFER) == 0)
- goto done;
- /*
- * Print out buffered log messages.
- */
- p = log_start & LOG_BUF_MASK;
-
- for (i=0,j=0; i < log_size; i++) {
- buf[j++] = log_buf[p];
- p = (p+1) & LOG_BUF_MASK;
- if (buf[j-1] != '\n' && i < log_size - 1 && j < sizeof(buf)-1)
- continue;
- buf[j] = 0;
- q = buf;
- len = j;
- if (msg_level < 0) {
- if(buf[0] == '<' &&
- buf[1] >= '0' &&
- buf[1] <= '7' &&
- buf[2] == '>') {
- msg_level = buf[1] - '0';
- q = buf + 3;
- len -= 3;
- } else
- {
- msg_level = default_message_loglevel;
- }
- }
- if (msg_level < console_loglevel)
- console->write(console, q, len);
- if (buf[j-1] == '\n')
- msg_level = -1;
- j = 0;
+ if (console->flags & CON_PRINTBUFFER) {
+ /*
+ * release_cosole_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);
}
-done:
- spin_unlock_irqrestore(&console_lock, flags);
+ release_console_sem();
}
-
+EXPORT_SYMBOL(register_console);
int unregister_console(struct console * console)
{
struct console *a,*b;
- unsigned long flags;
int res = 1;
- spin_lock_irqsave(&console_lock, flags);
+ acquire_console_sem();
if (console_drivers == console) {
console_drivers=console->next;
res = 0;
- } else
- {
+ } else {
for (a=console_drivers->next, b=console_drivers ;
a; b=a, a=b->next) {
if (a == console) {
@@ -479,13 +630,15 @@
preferred_console = -1;
- spin_unlock_irqrestore(&console_lock, flags);
+ release_console_sem();
return res;
}
+EXPORT_SYMBOL(unregister_console);
-/*
- * Write a message to a certain tty, not just the console. This is used for
- * messages that need to be redirected to a specific tty.
+/**
+ * tty_write_message - write a message to a certain tty, not just the console.
+ *
+ * This is used for messages that need to be redirected to a specific tty.
* We don't put it into the syslog queue right now maybe in the future if
* really needed.
*/
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)