This adds support for panic and boot console.

Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
 drivers/gpu/drm/drm-text/Makefile           |   2 +-
 drivers/gpu/drm/drm-text/drm-text-buffer.c  |   4 +-
 drivers/gpu/drm/drm-text/drm-text-console.c | 205 ++++++++++++++++++++++++++++
 drivers/gpu/drm/drm-text/drm-text-debugfs.c |  89 ++++++++++++
 drivers/gpu/drm/drm-text/drm-text.h         |   4 +
 5 files changed, 302 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/drm-text/drm-text-console.c

diff --git a/drivers/gpu/drm/drm-text/Makefile 
b/drivers/gpu/drm/drm-text/Makefile
index 48f55bc..be042b0 100644
--- a/drivers/gpu/drm/drm-text/Makefile
+++ b/drivers/gpu/drm/drm-text/Makefile
@@ -1,4 +1,4 @@
-drm-text-y := drm-text-buffer.o
+drm-text-y := drm-text-console.o drm-text-buffer.o
 drm-text-$(CONFIG_DEBUG_FS) += drm-text-debugfs.o

 obj-m += drm-text.o
diff --git a/drivers/gpu/drm/drm-text/drm-text-buffer.c 
b/drivers/gpu/drm/drm-text/drm-text-buffer.c
index 187dd4b..91beb48 100644
--- a/drivers/gpu/drm/drm-text/drm-text-buffer.c
+++ b/drivers/gpu/drm/drm-text/drm-text-buffer.c
@@ -308,7 +308,7 @@ static void drm_text_free(struct drm_text_buffer *text)

 static int __init drm_text_init(void)
 {
-       int ret = 0;
+       int ret;

        ret = drm_text_debugfs_init();
        if (ret)
@@ -316,6 +316,7 @@ static int __init drm_text_init(void)

        drm_text_scan_fbdev();

+       ret = drm_text_console_init();

        return ret;
 }
@@ -325,6 +326,7 @@ static void __exit drm_text_exit(void)
 {
        unsigned int i;

+       drm_text_console_exit();
        drm_text_debugfs_exit();

        for (i = 0; i < MAX_DRM_TEXT_BUFFERS; i++)
diff --git a/drivers/gpu/drm/drm-text/drm-text-console.c 
b/drivers/gpu/drm/drm-text/drm-text-console.c
new file mode 100644
index 0000000..e7ef244
--- /dev/null
+++ b/drivers/gpu/drm/drm-text/drm-text-console.c
@@ -0,0 +1,205 @@
+#define DEBUG
+/*
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/console.h>
+#include <linux/slab.h>
+
+#include "drm-text.h"
+
+static bool drm_text_console_panic;
+
+/**
+ * DOC: message/boot console
+ *
+ * The kernel message &console is enabled by using console=drm[n] where n is
+ * the primary minor number. Only one console is supported.
+ */
+
+static void drm_text_console_write(struct console *con, const char *str,
+                                  unsigned int num)
+{
+       struct drm_text_buffer *text;
+
+       drm_text_debug("%s(num=%u)\n", __func__, num);
+       if (drm_text_console_panic)
+               return;
+
+       text = drm_text_get(con->index);
+       if (!text)
+               return;
+
+       drm_text_write(text, str, num);
+       drm_text_flush(text, false);
+}
+
+static int drm_text_console_setup(struct console *con, char *options)
+{
+       struct drm_text_buffer *text;
+
+       drm_text_debug("%s[%u](%s) data=%p\n", __func__, con->index, options, 
con->data);
+
+       text = drm_text_get(con->index);
+       if (text)
+               drm_text_enable(text);
+
+       return 0;
+}
+
+/* TODO: test as a boot console */
+static struct console drm_text_console = {
+       .name =         "drm",
+       .write =        drm_text_console_write,
+       .setup =        drm_text_console_setup,
+       .flags =        CON_PRINTBUFFER,
+       .index =        -1,
+};
+
+/**
+ * DOC: panic console
+ *
+ * The panic &console is always enabled and collects kernel messages in a
+ * buffer as they come in.
+ * When the kernel does panic(), a panic notifier enables all text buffers.
+ * On the next &console->write it replays the message buffer and starts
+ * writing to all text buffers. Flushing of the text buffer to the pixel
+ * buffer is done inline instead of using the worker.
+ */
+
+#define DRM_TEXT_PANIC_KMSGS_MAX SZ_1K
+
+struct drm_text_panic_console {
+       char kmsgs[DRM_TEXT_PANIC_KMSGS_MAX];
+       size_t kmsg_pos;
+};
+
+static void drm_text_panic_write(const char *str, unsigned int num)
+{
+       unsigned int i;
+
+       drm_text_debug("%s(num=%u)\n", __func__, num);
+
+       for (i = 0; i < MAX_DRM_TEXT_BUFFERS; i++)
+               if (drm_text_buffers[i])
+                       drm_text_write(drm_text_buffers[i], str, num);
+}
+
+static void drm_text_panic_flush(void)
+{
+       unsigned int i;
+
+       drm_text_debug("%s()\n", __func__);
+
+       for (i = 0; i < MAX_DRM_TEXT_BUFFERS; i++)
+               if (drm_text_buffers[i])
+                       drm_text_flush(drm_text_buffers[i], true);
+}
+
+static void drm_text_panic_console_write(struct console *con, const char *str,
+                                        unsigned int num)
+{
+       struct drm_text_panic_console *pcon = con->data;
+       unsigned int i;
+
+       drm_text_debug("%s(num=%u)\n", __func__, num);
+
+       if (!pcon)
+               return;
+
+       /* Buffer up messages to be replayed on panic */
+       if (!drm_text_console_panic) {
+               for (i = 0; i < num; i++) {
+                       pcon->kmsgs[pcon->kmsg_pos++] = *str++;
+                       if (pcon->kmsg_pos == DRM_TEXT_PANIC_KMSGS_MAX)
+                               pcon->kmsg_pos = 0;
+               }
+               return;
+       }
+
+       if (pcon->kmsgs[0]) {
+               /* replay messages, the first one might be partial */
+               if (pcon->kmsgs[pcon->kmsg_pos]) { /* buffer wrap around */
+                       drm_text_panic_write(&pcon->kmsgs[pcon->kmsg_pos],
+                               DRM_TEXT_PANIC_KMSGS_MAX - pcon->kmsg_pos);
+                       drm_text_panic_write(pcon->kmsgs, pcon->kmsg_pos);
+               } else {
+                       drm_text_panic_write(pcon->kmsgs, pcon->kmsg_pos);
+               }
+               pcon->kmsgs[0] = '\0';
+       }
+
+       drm_text_panic_write(str, num);
+       drm_text_panic_flush();
+}
+
+static struct console drm_text_panic_console = {
+       .name =         "drmpanic",
+       .write =        drm_text_panic_console_write,
+       .flags =        CON_PRINTBUFFER | CON_ENABLED,
+       .index =        0,
+};
+
+static int drm_text_panic_console_setup(struct console *con)
+{
+       struct drm_text_panic_console *pcon;
+
+       drm_text_debug("%s[%u]() data=%p\n", __func__, con->index, con->data);
+
+       pcon = kzalloc(sizeof(*pcon), GFP_KERNEL);
+       if (!pcon)
+               return -ENOMEM;
+
+       con->data = pcon;
+
+       return 0;
+}
+
+int drm_text_panic(struct notifier_block *this, unsigned long ev, void *ptr)
+{
+       unsigned int i;
+
+       drm_text_console_panic = true;
+       for (i = 0; i < MAX_DRM_TEXT_BUFFERS; i++)
+               if (drm_text_buffers[i])
+                       drm_text_enable(drm_text_buffers[i]);
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block drm_text_panic_block = {
+       .notifier_call = drm_text_panic,
+};
+
+int drm_text_console_init(void)
+{
+       drm_text_log("%s\n", __func__);
+
+       register_console(&drm_text_panic_console);
+       drm_text_panic_console_setup(&drm_text_panic_console);
+       atomic_notifier_chain_register(&panic_notifier_list,
+                                      &drm_text_panic_block);
+       drm_text_debug("drm_text_panic_console: index=%d, flags=0x%x\n",
+               drm_text_panic_console.index, drm_text_panic_console.flags);
+
+       register_console(&drm_text_console);
+       drm_text_debug("drm_text_console: index=%d, flags=0x%x\n",
+               drm_text_console.index, drm_text_console.flags);
+
+       return 0;
+}
+
+void drm_text_console_exit(void)
+{
+       atomic_notifier_chain_unregister(&panic_notifier_list,
+                                        &drm_text_panic_block);
+       unregister_console(&drm_text_panic_console);
+       kfree(drm_text_panic_console.data);
+
+       unregister_console(&drm_text_console);
+}
diff --git a/drivers/gpu/drm/drm-text/drm-text-debugfs.c 
b/drivers/gpu/drm/drm-text/drm-text-debugfs.c
index d01f995..542ccac 100644
--- a/drivers/gpu/drm/drm-text/drm-text-debugfs.c
+++ b/drivers/gpu/drm/drm-text/drm-text-debugfs.c
@@ -173,6 +173,93 @@ static const struct file_operations drm_text_flush_ops = {
        .llseek =       default_llseek,
 };

+/*
+ * Fake/simulate panic() at different levels:
+ * 1: only trigger panic handling internally
+ * 2: local_irq_disable()
+ * 3: bust_spinlocks();
+ * 100: don't fake it, do call panic()
+ */
+static int drm_text_fake_panic(unsigned int level)
+{
+/* The console_* symbols are not exported */
+//     int old_loglevel = console_loglevel;
+
+       if (!level && level != 100 && level > 3)
+               return -EINVAL;
+
+       if (level == 100)
+               panic("TESTING");
+
+       if (level > 1)
+               local_irq_disable();
+
+//     console_verbose();
+
+       if (level > 2)
+               bust_spinlocks(1);
+
+       pr_emerg("Kernel panic - not syncing: TESTING\n");
+
+#ifdef CONFIG_DEBUG_BUGVERBOSE
+       dump_stack();
+#endif
+
+       /* simulate calling panic_notifier_list */
+       drm_text_panic(NULL, 0 , NULL);
+
+       if (level > 2)
+               bust_spinlocks(0);
+
+//     console_flush_on_panic();
+
+       pr_emerg("---[ end Kernel panic - not syncing: TESTING\n");
+
+       if (level > 1)
+               local_irq_enable();
+
+//     console_loglevel = old_loglevel;
+
+#ifdef HACK_NEED_FLUSHING
+{
+       struct drm_text_buffer *text = drm_text_get(0);
+
+       if (text && text->fb->funcs->dirty)
+               text->fb->funcs->dirty(text->fb, NULL, 0, 0, NULL, 0);
+}
+#endif
+       return 0;
+}
+
+static ssize_t drm_text_panic_write(struct file *file,
+                                   const char __user *user_buf,
+                                   size_t count, loff_t *ppos)
+{
+       unsigned long long val;
+       char buf[24];
+       ssize_t ret = 0;
+       size_t size;
+
+       size = min(sizeof(buf) - 1, count);
+       if (copy_from_user(buf, user_buf, size))
+               return -EFAULT;
+
+       buf[size] = '\0';
+       ret = kstrtoull(buf, 0, &val);
+       if (ret)
+               return ret;
+
+       ret = drm_text_fake_panic(1);
+
+       return ret < 0 ? ret : count;
+}
+
+static const struct file_operations drm_text_panic_ops = {
+       .write =        drm_text_panic_write,
+       .open =         simple_open,
+       .llseek =       default_llseek,
+};
+
 int drm_text_debugfs_init(void)
 {
        drm_text_debugfs_root = debugfs_create_dir("drm-text", NULL);
@@ -189,6 +276,8 @@ int drm_text_debugfs_init(void)

        debugfs_create_file("flush", S_IWUSR, drm_text_debugfs_root, NULL,
                            &drm_text_flush_ops);
+       debugfs_create_file("panic", S_IWUSR, drm_text_debugfs_root, NULL,
+                           &drm_text_panic_ops);

        return 0;

diff --git a/drivers/gpu/drm/drm-text/drm-text.h 
b/drivers/gpu/drm/drm-text/drm-text.h
index 77e7429..43ba76c 100644
--- a/drivers/gpu/drm/drm-text/drm-text.h
+++ b/drivers/gpu/drm/drm-text/drm-text.h
@@ -39,6 +39,10 @@ int drm_text_enable(struct drm_text_buffer *text);
 int drm_text_disable(struct drm_text_buffer *text);
 struct drm_text_buffer *drm_text_get(unsigned int index);

+int drm_text_console_init(void);
+void drm_text_console_exit(void);
+int drm_text_panic(struct notifier_block *this, unsigned long ev, void *ptr);
+
 #ifdef DEBUG
 #define drm_text_debug(fmt, ...) \
        drm_text_log(fmt, ##__VA_ARGS__)
-- 
2.8.2

Reply via email to