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

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)