Merge tag 'mfd-3.5-1' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6
[pandora-kernel.git] / kernel / printk.c
index 7b43295..32462d2 100644 (file)
@@ -227,8 +227,13 @@ static u32 clear_idx;
 #define LOG_LINE_MAX 1024
 
 /* record buffer */
+#if !defined(CONFIG_64BIT) || defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
+#define LOG_ALIGN 4
+#else
+#define LOG_ALIGN 8
+#endif
 #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
-static char __log_buf[__LOG_BUF_LEN];
+static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
 static char *log_buf = __log_buf;
 static u32 log_buf_len = __LOG_BUF_LEN;
 
@@ -279,12 +284,6 @@ static u32 log_next(u32 idx)
        return idx + msg->len;
 }
 
-#if !defined(CONFIG_64BIT) || defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
-#define LOG_ALIGN 4
-#else
-#define LOG_ALIGN 8
-#endif
-
 /* insert record into the buffer, discard old ones, update heads */
 static void log_store(int facility, int level,
                      const char *dict, u16 dict_len,
@@ -449,7 +448,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 
        /* escape non-printable characters */
        for (i = 0; i < msg->text_len; i++) {
-               char c = log_text(msg)[i];
+               unsigned char c = log_text(msg)[i];
 
                if (c < ' ' || c >= 128)
                        len += sprintf(user->buf + len, "\\x%02x", c);
@@ -462,7 +461,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
                bool line = true;
 
                for (i = 0; i < msg->dict_len; i++) {
-                       char c = log_dict(msg)[i];
+                       unsigned char c = log_dict(msg)[i];
 
                        if (line) {
                                user->buf[len++] = ' ';
@@ -786,50 +785,81 @@ static bool printk_time;
 #endif
 module_param_named(time, printk_time, bool, S_IRUGO | S_IWUSR);
 
-static int syslog_print_line(u32 idx, char *text, size_t size)
+static size_t print_prefix(const struct log *msg, bool syslog, char *buf)
 {
-       struct log *msg;
-       size_t len;
+       size_t len = 0;
 
-       msg = log_from_idx(idx);
-       if (!text) {
-               /* calculate length only */
-               len = 3;
+       if (syslog) {
+               if (buf) {
+                       len += sprintf(buf, "<%u>", msg->level);
+               } else {
+                       len += 3;
+                       if (msg->level > 9)
+                               len++;
+                       if (msg->level > 99)
+                               len++;
+               }
+       }
 
-               if (msg->level > 9)
-                       len++;
-               if (msg->level > 99)
-                       len++;
+       if (printk_time) {
+               if (buf) {
+                       unsigned long long ts = msg->ts_nsec;
+                       unsigned long rem_nsec = do_div(ts, 1000000000);
 
-               if (printk_time)
+                       len += sprintf(buf + len, "[%5lu.%06lu] ",
+                                        (unsigned long) ts, rem_nsec / 1000);
+               } else {
                        len += 15;
-
-               len += msg->text_len;
-               len++;
-               return len;
+               }
        }
 
-       len = sprintf(text, "<%u>", msg->level);
+       return len;
+}
+
+static size_t msg_print_text(const struct log *msg, bool syslog,
+                            char *buf, size_t size)
+{
+       const char *text = log_text(msg);
+       size_t text_size = msg->text_len;
+       size_t len = 0;
 
-       if (printk_time) {
-               unsigned long long t = msg->ts_nsec;
-               unsigned long rem_ns = do_div(t, 1000000000);
+       do {
+               const char *next = memchr(text, '\n', text_size);
+               size_t text_len;
 
-               len += sprintf(text + len, "[%5lu.%06lu] ",
-                                  (unsigned long) t, rem_ns / 1000);
-       }
+               if (next) {
+                       text_len = next - text;
+                       next++;
+                       text_size -= next - text;
+               } else {
+                       text_len = text_size;
+               }
+
+               if (buf) {
+                       if (print_prefix(msg, syslog, NULL) +
+                           text_len + 1>= size - len)
+                               break;
+
+                       len += print_prefix(msg, syslog, buf + len);
+                       memcpy(buf + len, text, text_len);
+                       len += text_len;
+                       buf[len++] = '\n';
+               } else {
+                       /* SYSLOG_ACTION_* buffer size only calculation */
+                       len += print_prefix(msg, syslog, NULL);
+                       len += text_len + 1;
+               }
+
+               text = next;
+       } while (text);
 
-       if (len + msg->text_len > size)
-               return -EINVAL;
-       memcpy(text + len, log_text(msg), msg->text_len);
-       len += msg->text_len;
-       text[len++] = '\n';
        return len;
 }
 
 static int syslog_print(char __user *buf, int size)
 {
        char *text;
+       struct log *msg;
        int len;
 
        text = kmalloc(LOG_LINE_MAX, GFP_KERNEL);
@@ -842,7 +872,8 @@ static int syslog_print(char __user *buf, int size)
                syslog_seq = log_first_seq;
                syslog_idx = log_first_idx;
        }
-       len = syslog_print_line(syslog_idx, text, LOG_LINE_MAX);
+       msg = log_from_idx(syslog_idx);
+       len = msg_print_text(msg, true, text, LOG_LINE_MAX);
        syslog_idx = log_next(syslog_idx);
        syslog_seq++;
        raw_spin_unlock_irq(&logbuf_lock);
@@ -882,14 +913,18 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
                seq = clear_seq;
                idx = clear_idx;
                while (seq < log_next_seq) {
-                       len += syslog_print_line(idx, NULL, 0);
+                       struct log *msg = log_from_idx(idx);
+
+                       len += msg_print_text(msg, true, NULL, 0);
                        idx = log_next(idx);
                        seq++;
                }
                seq = clear_seq;
                idx = clear_idx;
                while (len > size && seq < log_next_seq) {
-                       len -= syslog_print_line(idx, NULL, 0);
+                       struct log *msg = log_from_idx(idx);
+
+                       len -= msg_print_text(msg, true, NULL, 0);
                        idx = log_next(idx);
                        seq++;
                }
@@ -899,9 +934,10 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
 
                len = 0;
                while (len >= 0 && seq < next_seq) {
+                       struct log *msg = log_from_idx(idx);
                        int textlen;
 
-                       textlen = syslog_print_line(idx, text, LOG_LINE_MAX);
+                       textlen = msg_print_text(msg, true, text, LOG_LINE_MAX);
                        if (textlen < 0) {
                                len = textlen;
                                break;
@@ -1039,7 +1075,9 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)
                        seq = syslog_seq;
                        idx = syslog_idx;
                        while (seq < log_next_seq) {
-                               error += syslog_print_line(idx, NULL, 0);
+                               struct log *msg = log_from_idx(idx);
+
+                               error += msg_print_text(msg, true, NULL, 0);
                                idx = log_next(idx);
                                seq++;
                        }
@@ -1226,16 +1264,17 @@ asmlinkage int vprintk_emit(int facility, int level,
                            const char *fmt, va_list args)
 {
        static int recursion_bug;
-       static char buf[LOG_LINE_MAX];
-       static size_t buflen;
-       static int buflevel;
+       static char cont_buf[LOG_LINE_MAX];
+       static size_t cont_len;
+       static int cont_level;
+       static struct task_struct *cont_task;
        static char textbuf[LOG_LINE_MAX];
        char *text = textbuf;
-       size_t textlen;
+       size_t text_len;
        unsigned long flags;
        int this_cpu;
        bool newline = false;
-       bool cont = false;
+       bool prefix = false;
        int printed_len = 0;
 
        boot_delay_msec();
@@ -1281,66 +1320,84 @@ asmlinkage int vprintk_emit(int facility, int level,
         * The printf needs to come first; we need the syslog
         * prefix which might be passed-in as a parameter.
         */
-       textlen = vscnprintf(text, sizeof(textbuf), fmt, args);
+       text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
 
        /* mark and strip a trailing newline */
-       if (textlen && text[textlen-1] == '\n') {
-               textlen--;
+       if (text_len && text[text_len-1] == '\n') {
+               text_len--;
                newline = true;
        }
 
-       /* strip syslog prefix and extract log level or flags */
+       /* strip syslog prefix and extract log level or control flags */
        if (text[0] == '<' && text[1] && text[2] == '>') {
                switch (text[1]) {
                case '0' ... '7':
                        if (level == -1)
                                level = text[1] - '0';
-                       text += 3;
-                       textlen -= 3;
-                       break;
-               case 'c':       /* KERN_CONT */
-                       cont = true;
                case 'd':       /* KERN_DEFAULT */
+                       prefix = true;
+               case 'c':       /* KERN_CONT */
                        text += 3;
-                       textlen -= 3;
-                       break;
+                       text_len -= 3;
                }
        }
 
-       if (buflen && (!cont || dict)) {
-               /* no continuation; flush existing buffer */
-               log_store(facility, buflevel, NULL, 0, buf, buflen);
-               printed_len += buflen;
-               buflen = 0;
-       }
+       if (level == -1)
+               level = default_message_loglevel;
 
-       if (buflen == 0) {
-               /* remember level for first message in the buffer */
-               if (level == -1)
-                       buflevel = default_message_loglevel;
-               else
-                       buflevel = level;
+       if (dict) {
+               prefix = true;
+               newline = true;
        }
 
-       if (buflen || !newline) {
-               /* append to existing buffer, or buffer until next message */
-               if (buflen + textlen > sizeof(buf))
-                       textlen = sizeof(buf) - buflen;
-               memcpy(buf + buflen, text, textlen);
-               buflen += textlen;
-       }
+       if (!newline) {
+               if (cont_len && (prefix || cont_task != current)) {
+                       /*
+                        * Flush earlier buffer, which is either from a
+                        * different thread, or when we got a new prefix.
+                        */
+                       log_store(facility, cont_level, NULL, 0, cont_buf, cont_len);
+                       cont_len = 0;
+               }
+
+               if (!cont_len) {
+                       cont_level = level;
+                       cont_task = current;
+               }
 
-       if (newline) {
-               /* end of line; flush buffer */
-               if (buflen) {
-                       log_store(facility, buflevel,
-                                 dict, dictlen, buf, buflen);
-                       printed_len += buflen;
-                       buflen = 0;
+               /* buffer or append to earlier buffer from the same thread */
+               if (cont_len + text_len > sizeof(cont_buf))
+                       text_len = sizeof(cont_buf) - cont_len;
+               memcpy(cont_buf + cont_len, text, text_len);
+               cont_len += text_len;
+       } else {
+               if (cont_len && cont_task == current) {
+                       if (prefix) {
+                               /*
+                                * New prefix from the same thread; flush. We
+                                * either got no earlier newline, or we race
+                                * with an interrupt.
+                                */
+                               log_store(facility, cont_level,
+                                         NULL, 0, cont_buf, cont_len);
+                               cont_len = 0;
+                       }
+
+                       /* append to the earlier buffer and flush */
+                       if (cont_len + text_len > sizeof(cont_buf))
+                               text_len = sizeof(cont_buf) - cont_len;
+                       memcpy(cont_buf + cont_len, text, text_len);
+                       cont_len += text_len;
+                       log_store(facility, cont_level,
+                                 NULL, 0, cont_buf, cont_len);
+                       cont_len = 0;
+                       cont_task = NULL;
+                       printed_len = cont_len;
                } else {
-                       log_store(facility, buflevel,
-                                 dict, dictlen, text, textlen);
-                       printed_len += textlen;
+                       /* ordinary single and terminated line */
+                       log_store(facility, level,
+                                 dict, dictlen, text, text_len);
+                       printed_len = text_len;
                }
        }
 
@@ -1431,8 +1488,9 @@ EXPORT_SYMBOL(printk);
 #define LOG_LINE_MAX 0
 static struct log *log_from_idx(u32 idx) { return NULL; }
 static u32 log_next(u32 idx) { return 0; }
-static char *log_text(const struct log *msg) { return NULL; }
 static void call_console_drivers(int level, const char *text, size_t len) {}
+static size_t msg_print_text(const struct log *msg, bool syslog,
+                            char *buf, size_t size) { return 0; }
 
 #endif /* CONFIG_PRINTK */
 
@@ -1760,11 +1818,8 @@ again:
 
                msg = log_from_idx(console_idx);
                level = msg->level & 7;
-               len = msg->text_len;
-               if (len+1 >= sizeof(text))
-                       len = sizeof(text)-1;
-               memcpy(text, log_text(msg), len);
-               text[len++] = '\n';
+
+               len = msg_print_text(msg, false, text, sizeof(text));
 
                console_idx = log_next(console_idx);
                console_seq++;