/*
       *  linux/kernel/printk.c
       *
       *  Copyright (C) 1991, 1992  Linus Torvalds
       *
       * Modified to make sys_syslog() more flexible: added commands to
       * return the last 4k of kernel messages, regardless of whether
       * they've been read or not.  Added option to suppress kernel printk's
       * to the console.  Added hook for sending the console messages
       * 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>
      #include <linux/tty_driver.h>
      #include <linux/smp_lock.h>
      #include <linux/console.h>
      #include <linux/init.h>
      
      #include <asm/uaccess.h>
      
      #define LOG_BUF_LEN	(16384)
      #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 */
      
      /* We show everything that is MORE important than this.. */
      #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 */
      int console_loglevel = DEFAULT_CONSOLE_LOGLEVEL;
      int default_message_loglevel = DEFAULT_MESSAGE_LOGLEVEL;
      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;
      static char log_buf[LOG_BUF_LEN];
      static unsigned long log_start;
      static unsigned long logged_chars;
      struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
      static int preferred_console = -1;
      
      /*
       *	Setup a list of consoles. Called from init/main.c
       */
  58  static int __init console_setup(char *str)
      {
      	struct console_cmdline *c;
      	char name[sizeof(c->name)];
      	char *s, *options;
      	int i, idx;
      
      	/*
      	 *	Decode str into name, index, options.
      	 */
  68  	if (str[0] >= '0' && str[0] <= '9') {
      		strcpy(name, "ttyS");
      		strncpy(name + 4, str, sizeof(name) - 5);
  71  	} else
      		strncpy(name, str, sizeof(name) - 1);
      	name[sizeof(name) - 1] = 0;
  74  	if ((options = strchr(str, ',')) != NULL)
      		*(options++) = 0;
      #ifdef __sparc__
      	if (!strcmp(str, "ttya"))
      		strcpy(name, "ttyS0");
      	if (!strcmp(str, "ttyb"))
      		strcpy(name, "ttyS1");
      #endif
  82  	for(s = name; *s; s++)
  83  		if (*s >= '0' && *s <= '9')
  84  			break;
      	idx = simple_strtoul(s, NULL, 10);
      	*s = 0;
      
      	/*
      	 *	See if this tty is not yet registered, and
      	 *	if we have a slot free.
      	 */
  92  	for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
      		if (strcmp(console_cmdline[i].name, name) == 0 &&
  94  			  console_cmdline[i].index == idx) {
      				preferred_console = i;
  96  				return 1;
      		}
  98  	if (i == MAX_CMDLINECONSOLES)
  99  		return 1;
      	preferred_console = i;
      	c = &console_cmdline[i];
      	memcpy(c->name, name, sizeof(c->name));
      	c->options = options;
      	c->index = idx;
 105  	return 1;
      }
      
      __setup("console=", console_setup);
      
      /*
       * 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
       */
 123  int do_syslog(int type, char * buf, int len)
      {
      	unsigned long i, j, limit, count;
      	int do_clear = 0;
      	char c;
      	int error = -EPERM;
      
      	error = 0;
 131  	switch (type) {
 132  	case 0:		/* Close log */
 133  		break;
 134  	case 1:		/* Open log */
 135  		break;
 136  	case 2:		/* Read from log */
      		error = -EINVAL;
 138  		if (!buf || len < 0)
 139  			goto out;
      		error = 0;
 141  		if (!len)
 142  			goto out;
      		error = verify_area(VERIFY_WRITE,buf,len);
 144  		if (error)
 145  			goto out;
      		error = wait_event_interruptible(log_wait, log_size);
 147  		if (error)
 148  			goto out;
      		i = 0;
 150  		spin_lock_irq(&console_lock);
 151  		while (log_size && i < len) {
      			c = log_buf[log_start & LOG_BUF_MASK];
      			log_start++;
      			log_size--;
 155  			spin_unlock_irq(&console_lock);
      			__put_user(c,buf);
      			buf++;
      			i++;
 159  			spin_lock_irq(&console_lock);
      		}
 161  		spin_unlock_irq(&console_lock);
      		error = i;
 163  		break;
 164  	case 4:		/* Read/clear last kernel messages */
      		do_clear = 1; 
      		/* FALL THRU */
 167  	case 3:		/* Read last kernel messages */
      		error = -EINVAL;
 169  		if (!buf || len < 0)
 170  			goto out;
      		error = 0;
 172  		if (!len)
 173  			goto out;
      		error = verify_area(VERIFY_WRITE,buf,len);
 175  		if (error)
 176  			goto out;
      		count = len;
 178  		if (count > LOG_BUF_LEN)
      			count = LOG_BUF_LEN;
 180  		spin_lock_irq(&console_lock);
 181  		if (count > logged_chars)
      			count = logged_chars;
 183  		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>
      		 */
 192  		for(i=0;i < count;i++) {
      			j = limit-1-i;
 194  			if (j+LOG_BUF_LEN < log_start+log_size)
 195  				break;
      			c = log_buf[ j  & LOG_BUF_MASK ];
 197  			spin_unlock_irq(&console_lock);
      			__put_user(c,&buf[count-1-i]);
 199  			spin_lock_irq(&console_lock);
      		}
 201  		spin_unlock_irq(&console_lock);
      		error = i;
 203  		if(i != count) {
      			int offset = count-error;
      			/* buffer overflow during copy, correct user buffer. */
 206  			for(i=0;i<error;i++) {
      				__get_user(c,&buf[i+offset]);
      				__put_user(c,&buf[i]);
      			}
      		}
      
 212  		break;
 213  	case 5:		/* Clear ring buffer */
 214  		spin_lock_irq(&console_lock);
      		logged_chars = 0;
 216  		spin_unlock_irq(&console_lock);
 217  		break;
 218  	case 6:		/* Disable logging to console */
 219  		spin_lock_irq(&console_lock);
      		console_loglevel = minimum_console_loglevel;
 221  		spin_unlock_irq(&console_lock);
 222  		break;
 223  	case 7:		/* Enable logging to console */
 224  		spin_lock_irq(&console_lock);
      		console_loglevel = default_console_loglevel;
 226  		spin_unlock_irq(&console_lock);
 227  		break;
 228  	case 8:
      		error = -EINVAL;
 230  		if (len < 1 || len > 8)
 231  			goto out;
 232  		if (len < minimum_console_loglevel)
      			len = minimum_console_loglevel;
 234  		spin_lock_irq(&console_lock);
      		console_loglevel = len;
 236  		spin_unlock_irq(&console_lock);
      		error = 0;
 238  		break;
 239  	default:
      		error = -EINVAL;
 241  		break;
      	}
      out:
 244  	return error;
      }
      
 247  asmlinkage long sys_syslog(int type, char * buf, int len)
      {
 249  	if ((type != 3) && !capable(CAP_SYS_ADMIN))
 250  		return -EPERM;
 251  	return do_syslog(type, buf, len);
      }
      
 254  asmlinkage int printk(const char *fmt, ...)
      {
      	va_list args;
      	int i;
      	char *msg, *p, *buf_end;
      	int line_feed;
      	static signed char msg_level = -1;
      	long flags;
      
 263  	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);
 268  	for (p = buf + 3; p < buf_end; p++) {
      		msg = p;
 270  		if (msg_level < 0) {
      			if (
      				p[0] != '<' ||
      				p[1] < '0' || 
      				p[1] > '7' ||
 275  				p[2] != '>'
 276  			) {
      				p -= 3;
      				p[0] = '<';
      				p[1] = default_message_loglevel + '0';
      				p[2] = '>';
 281  			} else
      				msg += 3;
      			msg_level = p[1] - '0';
      		}
      		line_feed = 0;
 286  		for (; p < buf_end; p++) {
      			log_buf[(log_start+log_size) & LOG_BUF_MASK] = *p;
 288  			if (log_size < LOG_BUF_LEN)
      				log_size++;
 290  			else
      				log_start++;
      
      			logged_chars++;
 294  			if (*p == '\n') {
      				line_feed = 1;
 296  				break;
      			}
      		}
 299  		if (msg_level < console_loglevel && console_drivers) {
      			struct console *c = console_drivers;
 301  			while(c) {
 302  				if ((c->flags & CON_ENABLED) && c->write)
      					c->write(c, msg, p - msg + line_feed);
      				c = c->next;
      			}
      		}
 307  		if (line_feed)
      			msg_level = -1;
      	}
 310  	spin_unlock_irqrestore(&console_lock, flags);
      	wake_up_interruptible(&log_wait);
 312  	return i;
      }
      
 315  void console_print(const char *s)
      {
      	struct console *c;
      	unsigned long flags;
      	int len = strlen(s);
      
 321  	spin_lock_irqsave(&console_lock, flags);
      	c = console_drivers;
 323  	while(c) {
 324  		if ((c->flags & CON_ENABLED) && c->write)
      			c->write(c, s, len);
      		c = c->next;
      	}
 328  	spin_unlock_irqrestore(&console_lock, flags);
      }
      
 331  void unblank_console(void)
      {
      	struct console *c;
      	unsigned long flags;
      	
 336  	spin_lock_irqsave(&console_lock, flags);
      	c = console_drivers;
 338  	while(c) {
 339  		if ((c->flags & CON_ENABLED) && c->unblank)
      			c->unblank();
      		c = c->next;
      	}
 343  	spin_unlock_irqrestore(&console_lock, flags);
      }
      
      /*
       * The console driver calls this routine during kernel initialization
       * 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.
       */
 352  void register_console(struct console * console)
      {
      	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
      	 *	didn't select a console we take the first one
      	 *	that registers here.
      	 */
 366  	if (preferred_console < 0) {
 367  		if (console->index < 0)
      			console->index = 0;
      		if (console->setup == NULL ||
 370  		    console->setup(console, NULL) == 0) {
      			console->flags |= CON_ENABLED | CON_CONSDEV;
      			preferred_console = 0;
      		}
      	}
      
      	/*
      	 *	See if this console matches one we selected on
      	 *	the command line.
      	 */
 380  	for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
 381  		if (strcmp(console_cmdline[i].name, console->name) != 0)
 382  			continue;
      		if (console->index >= 0 &&
 384  		    console->index != console_cmdline[i].index)
 385  			continue;
 386  		if (console->index < 0)
      			console->index = console_cmdline[i].index;
      		if (console->setup &&
 389  		    console->setup(console, console_cmdline[i].options) != 0)
 390  			break;
      		console->flags |= CON_ENABLED;
      		console->index = console_cmdline[i].index;
 393  		if (i == preferred_console)
      			console->flags |= CON_CONSDEV;
 395  		break;
      	}
      
 398  	if (!(console->flags & CON_ENABLED))
 399  		return;
      
      	/*
      	 *	Put this console in the list - keep the
      	 *	preferred driver at the head of the list.
      	 */
 405  	spin_lock_irqsave(&console_lock, flags);
 406  	if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
      		console->next = console_drivers;
      		console_drivers = console;
 409  	} else {
      		console->next = console_drivers->next;
      		console_drivers->next = console;
      	}
 413  	if ((console->flags & CON_PRINTBUFFER) == 0)
 414  		goto done;
      	/*
      	 *	Print out buffered log messages.
      	 */
      	p = log_start & LOG_BUF_MASK;
      
 420  	for (i=0,j=0; i < log_size; i++) {
      		buf[j++] = log_buf[p];
      		p = (p+1) & LOG_BUF_MASK;
 423  		if (buf[j-1] != '\n' && i < log_size - 1 && j < sizeof(buf)-1)
 424  			continue;
      		buf[j] = 0;
      		q = buf;
      		len = j;
 428  		if (msg_level < 0) {
      			if(buf[0] == '<' &&
      				buf[1] >= '0' &&
      				buf[1] <= '7' &&
 432  				buf[2] == '>') {
      				msg_level = buf[1] - '0';
      				q = buf + 3;
      				len -= 3;
 436  			} else
      			{
      				msg_level = default_message_loglevel; 
      			}
      		}
 441  		if (msg_level < console_loglevel)
      			console->write(console, q, len);
 443  		if (buf[j-1] == '\n')
      			msg_level = -1;
      		j = 0;
      	}
      done:
 448  	spin_unlock_irqrestore(&console_lock, flags);
      }
      
      
 452  int unregister_console(struct console * console)
      {
              struct console *a,*b;
      	unsigned long flags;
      	int res = 1;
      
 458  	spin_lock_irqsave(&console_lock, flags);
 459  	if (console_drivers == console) {
      		console_drivers=console->next;
      		res = 0;
 462  	} else
      	{
      		for (a=console_drivers->next, b=console_drivers ;
 465  		     a; b=a, a=b->next) {
 466  			if (a == console) {
      				b->next = a->next;
      				res = 0;
 469  				break;
      			}  
      		}
      	}
      	
      	/* If last console is removed, we re-enable picking the first
      	 * one that gets registered. Without that, pmac early boot console
      	 * would prevent fbcon from taking over.
      	 */
 478  	if (console_drivers == NULL)
      		preferred_console = -1;
      		
      
 482  	spin_unlock_irqrestore(&console_lock, flags);
 483  	return res;
      }
      	
      /*
       * 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.
       */
 492  void tty_write_message(struct tty_struct *tty, char *msg)
      {
 494  	if (tty && tty->driver.write)
      		tty->driver.write(tty, 0, msg, strlen(msg));
 496  	return;
      }