patch-2.2.13 linux/kernel/printk.c

Next file: linux/kernel/sched.c
Previous file: linux/kernel/ksyms.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.12/linux/kernel/printk.c linux/kernel/printk.c
@@ -10,6 +10,8 @@
  * elsewhere, in preparation for a serial line console (someday).
  * Ted Ts'o, 2/11/93.
  * Modified for sysctl support, 1/8/97, Chris Horn.
+ * Fixed SMP synchronization, 08/08/99, Manfred Spraul 
+ *     manfreds@colorfullife.com
  */
 
 #include <linux/mm.h>
@@ -21,6 +23,7 @@
 #include <asm/uaccess.h>
 
 #define LOG_BUF_LEN	(16384)
+#define LOG_BUF_MASK	(LOG_BUF_LEN-1)
 
 static char buf[1024];
 
@@ -40,6 +43,8 @@
 int minimum_console_loglevel = MINIMUM_CONSOLE_LOGLEVEL;
 int default_console_loglevel = DEFAULT_CONSOLE_LOGLEVEL;
 
+spinlock_t console_lock = SPIN_LOCK_UNLOCKED;
+
 struct console *console_drivers = NULL;
 static char log_buf[LOG_BUF_LEN];
 static unsigned long log_start = 0;
@@ -115,12 +120,11 @@
  */
 int do_syslog(int type, char * buf, int len)
 {
-	unsigned long i, j, count, flags;
+	unsigned long i, j, limit, count;
 	int do_clear = 0;
 	char c;
 	int error = -EPERM;
 
-	lock_kernel();
 	error = 0;
 	switch (type) {
 	case 0:		/* Close log */
@@ -141,18 +145,18 @@
 		if (error)
 			goto out;
 		i = 0;
+		spin_lock_irq(&console_lock);
 		while (log_size && i < len) {
-			c = *((char *) log_buf+log_start);
+			c = log_buf[log_start & LOG_BUF_MASK];
 			log_start++;
 			log_size--;
-			log_start &= LOG_BUF_LEN-1;
-			sti();
+			spin_unlock_irq(&console_lock);
 			__put_user(c,buf);
 			buf++;
 			i++;
-			cli();
+			spin_lock_irq(&console_lock);
 		}
-		sti();
+		spin_unlock_irq(&console_lock);
 		error = i;
 		break;
 	case 4:		/* Read/clear last kernel messages */
@@ -168,35 +172,56 @@
 		error = verify_area(VERIFY_WRITE,buf,len);
 		if (error)
 			goto out;
-		/*
-		 * The logged_chars, log_start, and log_size values may
-		 * change from an interrupt, so we disable interrupts.
-		 */
-		__save_flags(flags);
-		__cli();
+		spin_lock_irq(&console_lock);
 		count = len;
 		if (count > LOG_BUF_LEN)
 			count = LOG_BUF_LEN;
 		if (count > logged_chars)
 			count = logged_chars;
-		j = log_start + log_size - count;
-		__restore_flags(flags);
-		for (i = 0; i < count; i++) {
-			c = *((char *) log_buf+(j++ & (LOG_BUF_LEN-1)));
-			__put_user(c, buf++);
-		}
 		if (do_clear)
 			logged_chars = 0;
+		limit = log_start + log_size;
+		/*
+		 * __put_user() could sleep, and while we sleep
+		 * printk() could overwrite the messages 
+		 * we try to copy to user space. Therefore
+		 * the messages are copied in reverse. <manfreds>
+		 */
+		for(i=0;i < count;i++) {
+			j = limit-1-i;
+			if (j+LOG_BUF_LEN < log_start+log_size)
+				break;
+			c = log_buf[ j  & LOG_BUF_MASK ];
+			spin_unlock_irq(&console_lock);
+			__put_user(c,&buf[count-1-i]);
+			spin_lock_irq(&console_lock);
+		}
+		spin_unlock_irq(&console_lock);
 		error = i;
+		if(i != count) {
+			int offset = count-error;
+			/* buffer overflow during copy, correct user buffer. */
+			for(i=0;i<error;i++) {
+				__get_user(c,&buf[i+offset]);
+				__put_user(c,&buf[i]);
+			}
+		}
+
 		break;
 	case 5:		/* Clear ring buffer */
+		spin_lock_irq(&console_lock);
 		logged_chars = 0;
+		spin_unlock_irq(&console_lock);
 		break;
 	case 6:		/* Disable logging to console */
+		spin_lock_irq(&console_lock);
 		console_loglevel = minimum_console_loglevel;
+		spin_unlock_irq(&console_lock);
 		break;
 	case 7:		/* Enable logging to console */
+		spin_lock_irq(&console_lock);
 		console_loglevel = default_console_loglevel;
+		spin_unlock_irq(&console_lock);
 		break;
 	case 8:
 		error = -EINVAL;
@@ -204,7 +229,9 @@
 			goto out;
 		if (len < minimum_console_loglevel)
 			len = minimum_console_loglevel;
+		spin_lock_irq(&console_lock);
 		console_loglevel = len;
+		spin_unlock_irq(&console_lock);
 		error = 0;
 		break;
 	default:
@@ -212,7 +239,6 @@
 		break;
 	}
 out:
-	unlock_kernel();
 	return error;
 }
 
@@ -223,9 +249,6 @@
 	return do_syslog(type, buf, len);
 }
 
-
-spinlock_t console_lock;
-
 asmlinkage int printk(const char *fmt, ...)
 {
 	va_list args;
@@ -259,13 +282,12 @@
 		}
 		line_feed = 0;
 		for (; p < buf_end; p++) {
-			log_buf[(log_start+log_size) & (LOG_BUF_LEN-1)] = *p;
+			log_buf[(log_start+log_size) & LOG_BUF_MASK] = *p;
 			if (log_size < LOG_BUF_LEN)
 				log_size++;
-			else {
+			else
 				log_start++;
-				log_start &= LOG_BUF_LEN-1;
-			}
+
 			logged_chars++;
 			if (*p == '\n') {
 				line_feed = 1;
@@ -290,24 +312,33 @@
 
 void console_print(const char *s)
 {
-	struct console *c = console_drivers;
+	struct console *c;
+	unsigned long flags;
 	int len = strlen(s);
 
+	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;
 	}
+	spin_unlock_irqrestore(&console_lock,flags);
 }
 
 void unblank_console(void)
 {
-	struct console *c = console_drivers;
+	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;
 	}
+	spin_unlock_irqrestore(&console_lock,flags);
 }
 
 /*
@@ -318,11 +349,12 @@
  */
 void register_console(struct console * console)
 {
-	int	i,j,len;
-	int	p = log_start;
+	int     i, j,len;
+	int	p;
 	char	buf[16];
 	signed char msg_level = -1;
 	char	*q;
+	unsigned long flags;
 
 	/*
 	 *	See if we want to use this console driver. If we
@@ -368,6 +400,7 @@
 	 *	Put this console in the list - keep the
 	 *	preferred driver at the head of the list.
 	 */
+	spin_lock_irqsave(&console_lock,flags);
 	if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
 		console->next = console_drivers;
 		console_drivers = console;
@@ -375,23 +408,33 @@
 		console->next = console_drivers->next;
 		console_drivers->next = console;
 	}
-	if ((console->flags & CON_PRINTBUFFER) == 0) return;
-
+	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 &= LOG_BUF_LEN-1;
+		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) {
-			msg_level = buf[1] - '0';
-			q = buf + 3;
-			len -= 3;
+			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);
@@ -399,26 +442,35 @@
 			msg_level = -1;
 		j = 0;
 	}
+done:
+	spin_unlock_irqrestore(&console_lock,flags);
 }
 
 
 int unregister_console(struct console * console)
 {
         struct console *a,*b;
-	
+	unsigned long flags;
+	int res = 1;
+
+	spin_lock_irqsave(&console_lock,flags);
 	if (console_drivers == console) {
 		console_drivers=console->next;
-		return (0);
-	}
-	for (a=console_drivers->next, b=console_drivers ;
-	     a; b=a, a=b->next) {
-		if (a == console) {
-			b->next = a->next;
-			return 0;
-		}  
+		res = 0;
+	} else
+	{
+		for (a=console_drivers->next, b=console_drivers ;
+		     a; b=a, a=b->next) {
+			if (a == console) {
+				b->next = a->next;
+				res = 0;
+				break;
+			}  
+		}
 	}
 	
-	return (1);
+	spin_unlock_irqrestore(&console_lock,flags);
+	return res;
 }
 	
 /*

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)