This adds support for outputting kernel messages on panic().
The drivers that supports it, provides a framebuffer that the
messages can be rendered on.

Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
 drivers/gpu/drm/Makefile       |   2 +-
 drivers/gpu/drm/drm_drv.c      |   3 +
 drivers/gpu/drm/drm_internal.h |   4 +
 drivers/gpu/drm/drm_panic.c    | 606 +++++++++++++++++++++++++++++++++++++++++
 include/drm/drmP.h             |  22 ++
 5 files changed, 636 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/drm_panic.c

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index eba32ad..ff04e41 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -12,7 +12,7 @@ drm-y       :=        drm_auth.o drm_bufs.o drm_cache.o \
                drm_info.o drm_debugfs.o drm_encoder_slave.o \
                drm_trace_points.o drm_global.o drm_prime.o \
                drm_rect.o drm_vma_manager.o drm_flip_work.o \
-               drm_modeset_lock.o drm_atomic.o drm_bridge.o
+               drm_modeset_lock.o drm_atomic.o drm_bridge.o drm_panic.o

 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 3b14366..457ee91 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -861,6 +861,8 @@ static int __init drm_core_init(void)
                goto err_p3;
        }

+       drm_panic_init();
+
        DRM_INFO("Initialized %s %d.%d.%d %s\n",
                 CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
        return 0;
@@ -876,6 +878,7 @@ err_p1:

 static void __exit drm_core_exit(void)
 {
+       drm_panic_exit();
        debugfs_remove(drm_debugfs_root);
        drm_sysfs_destroy();

diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index b86dc9b..7463d9d 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -90,6 +90,10 @@ int drm_gem_open_ioctl(struct drm_device *dev, void *data,
 void drm_gem_open(struct drm_device *dev, struct drm_file *file_private);
 void drm_gem_release(struct drm_device *dev, struct drm_file *file_private);

+/* drm_panic.c */
+void drm_panic_init(void);
+void drm_panic_exit(void);
+
 /* drm_debugfs.c */
 #if defined(CONFIG_DEBUG_FS)
 int drm_debugfs_init(struct drm_minor *minor, int minor_id,
diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
new file mode 100644
index 0000000..e185c9d
--- /dev/null
+++ b/drivers/gpu/drm/drm_panic.c
@@ -0,0 +1,606 @@
+/*
+ * 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 <asm/unaligned.h>
+#include <drm/drmP.h>
+#include <linux/console.h>
+#include <linux/debugfs.h>
+#include <linux/font.h>
+#include <linux/kernel.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+struct drm_panic_fb {
+       struct drm_framebuffer *fb;
+       void *vmem;
+       const struct font_desc *font;
+       unsigned int cols;
+       unsigned int rows;
+       unsigned int xpos;
+       unsigned int ypos;
+};
+
+#define DRM_PANIC_MAX_FBS      64
+static struct drm_panic_fb drm_panic_fbs[DRM_PANIC_MAX_FBS];
+
+#define DRM_PANIC_MAX_KMSGS    SZ_4K
+static char *drm_panic_kmsgs;
+static size_t drm_panic_kmsgs_pos;
+
+static bool drm_panic_active;
+
+static void drm_panic_log(const char *fmt, ...);
+
+static inline void drm_panic_draw_pixel(u8 *dst, u32 pixel_format, bool val)
+{
+       switch (pixel_format & ~DRM_FORMAT_BIG_ENDIAN) {
+
+       case DRM_FORMAT_C8:
+       case DRM_FORMAT_RGB332:
+       case DRM_FORMAT_BGR233:
+               *dst = val ? 0xff : 0x00;
+               break;
+
+       case DRM_FORMAT_XRGB4444:
+       case DRM_FORMAT_ARGB4444:
+       case DRM_FORMAT_XBGR4444:
+       case DRM_FORMAT_ABGR4444:
+               put_unaligned(val ? 0x0fff : 0x0000, (u16 *)dst);
+               break;
+
+       case DRM_FORMAT_RGBX4444:
+       case DRM_FORMAT_RGBA4444:
+       case DRM_FORMAT_BGRX4444:
+       case DRM_FORMAT_BGRA4444:
+               put_unaligned(val ? 0xfff0 : 0x0000, (u16 *)dst);
+               break;
+
+       case DRM_FORMAT_XRGB1555:
+       case DRM_FORMAT_ARGB1555:
+       case DRM_FORMAT_XBGR1555:
+       case DRM_FORMAT_ABGR1555:
+               put_unaligned(val ? 0x7fff : 0x0000, (u16 *)dst);
+               break;
+
+       case DRM_FORMAT_RGBX5551:
+       case DRM_FORMAT_RGBA5551:
+       case DRM_FORMAT_BGRX5551:
+       case DRM_FORMAT_BGRA5551:
+               put_unaligned(val ? 0xfffe : 0x0000, (u16 *)dst);
+               break;
+
+       case DRM_FORMAT_RGB565:
+       case DRM_FORMAT_BGR565:
+               put_unaligned(val ? 0xffff : 0x0000, (u16 *)dst);
+               break;
+
+       case DRM_FORMAT_RGB888:
+       case DRM_FORMAT_BGR888:
+               dst[0] = val ? 0xff : 0x00;
+               dst[1] = val ? 0xff : 0x00;
+               dst[2] = val ? 0xff : 0x00;
+               break;
+
+       case DRM_FORMAT_XRGB8888:
+       case DRM_FORMAT_ARGB8888:
+       case DRM_FORMAT_XBGR8888:
+       case DRM_FORMAT_ABGR8888:
+               put_unaligned(val ? 0x00ffffff : 0x00000000, (u32 *)dst);
+               break;
+
+       case DRM_FORMAT_RGBX8888:
+       case DRM_FORMAT_RGBA8888:
+       case DRM_FORMAT_BGRX8888:
+       case DRM_FORMAT_BGRA8888:
+               put_unaligned(val ? 0xffffff00 : 0x00000000, (u32 *)dst);
+               break;
+
+       case DRM_FORMAT_XRGB2101010:
+       case DRM_FORMAT_ARGB2101010:
+       case DRM_FORMAT_XBGR2101010:
+       case DRM_FORMAT_ABGR2101010:
+               put_unaligned(val ? 0x3fffffff : 0x00000000, (u32 *)dst);
+               break;
+
+       case DRM_FORMAT_RGBX1010102:
+       case DRM_FORMAT_RGBA1010102:
+       case DRM_FORMAT_BGRX1010102:
+       case DRM_FORMAT_BGRA1010102:
+               put_unaligned(val ? 0xfffffffc : 0x00000000, (u32 *)dst);
+               break;
+       }
+}
+
+static void drm_panic_render(struct drm_panic_fb *pfb,
+                            const char *text, unsigned int len)
+{
+       const struct font_desc *font = pfb->font;
+       unsigned int pix_depth, pix_bpp, cpp;
+       unsigned int col = pfb->xpos;
+       unsigned int row = pfb->ypos;
+       unsigned int i, h, w;
+       void *dst, *pos;
+       u8 fontline;
+
+       if ((row + 1) * font->height > pfb->fb->height)
+               return;
+
+       if ((col + len) * font->width > pfb->fb->width)
+               return;
+
+       drm_fb_get_bpp_depth(pfb->fb->pixel_format, &pix_depth, &pix_bpp);
+       cpp = DIV_ROUND_UP(pix_bpp, 8);
+
+       /* TODO: should fb->offsets[0] be added here? */
+       dst = pfb->vmem + (row * font->height * pfb->fb->pitches[0]) +
+             (col * font->width * cpp);
+
+       for (h = 0; h < font->height; h++) {
+               pos = dst;
+
+               for (i = 0; i < len; i++) {
+                       fontline = *(u8 *)(font->data + text[i] * font->height 
+ h);
+
+                       for (w = 0; w < font->width; w++) {
+                               drm_panic_draw_pixel(pos, pfb->fb->pixel_format,
+                                                    fontline & BIT(7 - w));
+                               pos += cpp;
+                       }
+               }
+
+               dst += pfb->fb->pitches[0];
+       }
+}
+
+static void drm_panic_scroll_up(struct drm_panic_fb *pfb)
+{
+       void *src = pfb->vmem + (pfb->font->height * pfb->fb->pitches[0]);
+       size_t len = (pfb->fb->height - pfb->font->height) *
+                    pfb->fb->pitches[0];
+
+       drm_panic_log("%s\n", __func__);
+
+       memmove(pfb->vmem, src, len);
+       memset(pfb->vmem + len, 0, pfb->font->height * pfb->fb->pitches[0]);
+}
+
+static void drm_panic_clear_screen(struct drm_panic_fb *pfb)
+{
+       memset(pfb->vmem, 0, pfb->fb->height * pfb->fb->pitches[0]);
+}
+
+static void drm_panic_log_msg(char *pre, const char *str, unsigned int len)
+{
+       char buf[512];
+
+       if (len > 510)
+               len = 510;
+
+       memcpy(buf, str, len);
+       buf[len] = '\n';
+       buf[len + 1] = '\0';
+
+       drm_panic_log("%s%s", pre, buf);
+}
+
+static void drm_panic_putcs_no_lf(struct drm_panic_fb *pfb,
+                                 const char *str, unsigned int len)
+{
+       drm_panic_log("%s(len=%u) x=%u, y=%u\n", __func__, len,
+                       pfb->xpos, pfb->ypos);
+
+       if (len <= 0)
+               return;
+
+       drm_panic_log_msg("", str, len);
+
+       drm_panic_render(pfb, str, len);
+
+}
+
+static void drm_panic_putcs(struct drm_panic_fb *pfb,
+                           const char *str, unsigned int num)
+{
+       unsigned int slen;
+       int len = num;
+       char *lf;
+
+       drm_panic_log("%s(num=%u)\n", __func__, num);
+
+       while (len > 0) {
+
+               if (pfb->ypos == pfb->rows) {
+                       pfb->ypos--;
+                       drm_panic_scroll_up(pfb);
+               }
+
+               lf = strpbrk(str, "\n");
+               if (lf)
+                       slen = lf - str;
+               else
+                       slen = len;
+
+               if (pfb->xpos + slen > pfb->cols)
+                       slen = pfb->cols - pfb->xpos;
+
+               drm_panic_putcs_no_lf(pfb, str, slen);
+
+               len -= slen;
+               str += slen;
+               pfb->xpos += slen;
+
+               if (lf) {
+                       str++;
+                       len--;
+                       pfb->xpos = 0;
+                       pfb->ypos++;
+               }
+       }
+}
+
+static void drm_panic_write(const char *str, unsigned int num)
+{
+       unsigned int i;
+
+       if (!num)
+               return;
+
+       drm_panic_log("%s(num=%u)\n", __func__, num);
+
+       for (i = 0; i < DRM_PANIC_MAX_FBS; i++) {
+               if (!drm_panic_fbs[i].fb)
+                       break;
+               drm_panic_putcs(&drm_panic_fbs[i], str, num);
+       }
+}
+
+/* this one is serialized by console_lock() */
+static void drm_panic_console_write(struct console *con,
+                                   const char *str, unsigned int num)
+{
+       unsigned int i;
+
+       drm_panic_log_msg("->", str, num);
+
+       /* Buffer up messages to be replayed on panic */
+       if (!drm_panic_active) {
+               for (i = 0; i < num; i++) {
+                       drm_panic_kmsgs[drm_panic_kmsgs_pos++] = *str++;
+                       if (drm_panic_kmsgs_pos == DRM_PANIC_MAX_KMSGS)
+                               drm_panic_kmsgs_pos = 0;
+               }
+               return;
+       }
+
+       drm_panic_write(str, num);
+}
+
+static struct console drm_panic_console = {
+       .name =         "drmpanic",
+       .write =        drm_panic_console_write,
+       .flags =        CON_PRINTBUFFER | CON_ENABLED,
+       .index =        0,
+};
+
+/*
+ * The panic() function makes sure that only one CPU is allowed to run it's
+ * code. So when this handler is called, we're alone. No racing with
+ * console.write() is possible.
+ */
+static int drm_panic_handler(struct notifier_block *this, unsigned long ev,
+                            void *ptr)
+{
+       const struct font_desc *font;
+       struct drm_framebuffer *fb;
+       struct drm_panic_fb *pfb;
+       struct drm_minor *minor;
+       unsigned int fbs = 0;
+       void *vmem;
+       int i;
+
+       drm_panic_log("%s\n", __func__);
+
+       drm_panic_active = true;
+
+       drm_minor_for_each(minor, DRM_MINOR_LEGACY, i) {
+               drm_panic_log("Found minor=%d\n", minor->index);
+               if (!minor->dev || !minor->dev->driver ||
+                   !minor->dev->driver->panic) {
+                       drm_panic_log("Skipping: No panic handler\n");
+                       continue;
+               }
+
+               fb = minor->dev->driver->panic(minor->dev, &vmem);
+               if (!fb) {
+                       drm_panic_log("Skipping: Driver returned NULL\n");
+                       continue;
+               }
+
+               if (!fb || !vmem || fb->dev != minor->dev || !fb->pitches[0]) {
+                       drm_panic_log("Skipping: Failed check\n");
+                       continue;
+               }
+
+               /* only 8-bit wide fonts are supported */
+               font = get_default_font(fb->width, fb->height, BIT(7), -1);
+               if (!font) {
+                       drm_panic_log("Skipping: No font available\n");
+                       continue;
+               }
+
+               pfb = &drm_panic_fbs[fbs++];
+
+               pfb->fb = fb;
+               pfb->vmem = vmem;
+               pfb->font = font;
+               pfb->cols = fb->width / font->width;
+               pfb->rows = fb->height / font->height;
+
+               drm_panic_clear_screen(pfb);
+
+               drm_panic_log("    %ux%u -> %ux%u, %s, %s\n", fb->width,
+                               fb->height, pfb->cols, pfb->rows, font->name,
+                               drm_get_format_name(fb->pixel_format));
+       }
+
+       if (drm_panic_kmsgs[0]) {
+               /* safeguard in case we interrupted drm_panic_console_write */
+               if (drm_panic_kmsgs_pos >= DRM_PANIC_MAX_KMSGS)
+                       drm_panic_kmsgs_pos = 0;
+
+               drm_panic_write(&drm_panic_kmsgs[drm_panic_kmsgs_pos],
+                               DRM_PANIC_MAX_KMSGS - drm_panic_kmsgs_pos);
+               drm_panic_write(drm_panic_kmsgs, drm_panic_kmsgs_pos);
+       }
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block drm_panic_block = {
+       .notifier_call = drm_panic_handler,
+};
+
+
+
+#ifdef CONFIG_DEBUG_FS
+
+/* Out of band logging is useful at least in the initial development phase */
+#define DRM_PANIC_LOG_SIZE     SZ_64K
+#define DRM_PANIC_LOG_LINE     128
+#define DRM_PANIC_LOG_ENTRIES  (DRM_PANIC_LOG_SIZE / DRM_PANIC_LOG_LINE)
+
+static char *log_buf;
+static size_t log_pos;
+static struct dentry *drm_panic_logfs_root;
+
+static void drm_panic_log(const char *fmt, ...)
+{
+       va_list args;
+       u32 rem_nsec;
+       char *text;
+       size_t len;
+       u64 sec;
+
+       if (!log_buf || oops_in_progress)
+               return;
+
+       va_start(args, fmt);
+
+       if (log_pos >= DRM_PANIC_LOG_ENTRIES)
+               log_pos = 0;
+
+       text = log_buf + (log_pos++ * DRM_PANIC_LOG_LINE);
+       if (log_pos == DRM_PANIC_LOG_ENTRIES)
+               log_pos = 0;
+
+       sec = div_u64_rem(local_clock(), 1000000000, &rem_nsec);
+
+       len = scnprintf(text, DRM_PANIC_LOG_LINE, "[%5llu.%06u] ", sec,
+                       rem_nsec / 1000);
+
+       vscnprintf(text + len, DRM_PANIC_LOG_LINE - len, fmt, args);
+
+       /* Make sure to always have a newline in case of overflow */
+       if (text[DRM_PANIC_LOG_LINE - 2] != '\0')
+               text[DRM_PANIC_LOG_LINE - 2] = '\n';
+
+       va_end(args);
+}
+
+static int drm_panic_log_show(struct seq_file *m, void *v)
+{
+       size_t pos = log_pos;
+       unsigned int i;
+       char *text;
+
+       for (i = 0; i < DRM_PANIC_LOG_ENTRIES; i++) {
+               text = log_buf + (pos++ * DRM_PANIC_LOG_LINE);
+               if (pos == DRM_PANIC_LOG_ENTRIES)
+                       pos = 0;
+               if (*text == '\0')
+                       continue;
+               seq_puts(m, text);
+       }
+
+       return 0;
+}
+
+static int drm_panic_log_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, drm_panic_log_show, NULL);
+}
+
+static const struct file_operations drm_panic_log_ops = {
+       .owner   = THIS_MODULE,
+       .open    = drm_panic_log_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = single_release,
+};
+
+/*
+ * Fake/simulate panic() at different levels:
+ * 1: only trigger panic handling internally
+ * 2: add local_irq_disable()
+ * 3: add bust_spinlocks();
+ * 100: don't fake it, do call panic()
+ */
+static int drm_text_fake_panic(unsigned int level)
+{
+#ifndef MODULE
+       int old_loglevel = console_loglevel;
+#endif
+
+       if (!level && level != 100 && level > 3)
+               return -EINVAL;
+
+       if (level == 100)
+               panic("TESTING");
+
+       if (level > 1)
+               local_irq_disable();
+
+#ifndef MODULE
+       console_verbose();
+#endif
+       if (level > 2)
+               bust_spinlocks(1);
+
+       pr_emerg("Kernel panic - not syncing: FAKING=%u, oops_in_progress=%d\n",
+                level, oops_in_progress);
+
+#ifdef CONFIG_DEBUG_BUGVERBOSE
+       dump_stack();
+#endif
+       /* simulate calling panic_notifier_list */
+       drm_panic_handler(NULL, 0, NULL);
+
+       if (level > 2)
+               bust_spinlocks(0);
+
+#ifndef MODULE
+       console_flush_on_panic();
+#endif
+       pr_emerg("---[ end Kernel panic - not syncing: FAKING\n");
+
+       if (level > 1)
+               local_irq_enable();
+
+#ifndef MODULE
+       console_loglevel = old_loglevel;
+#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;
+       ssize_t ret = 0;
+       char buf[24];
+       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(val);
+
+       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,
+};
+
+static int drm_panic_logfs_init(void)
+{
+       drm_panic_logfs_root = debugfs_create_dir("drm-panic", NULL);
+       if (!drm_panic_logfs_root)
+               return -ENOMEM;
+
+       if (!debugfs_create_file("log", S_IRUGO, drm_panic_logfs_root, NULL,
+                           &drm_panic_log_ops))
+               goto err_remove;
+
+       log_buf = kzalloc(DRM_PANIC_LOG_SIZE, GFP_KERNEL);
+       if (!log_buf)
+               goto err_remove;
+
+       debugfs_create_file("panic", S_IWUSR, drm_panic_logfs_root, NULL,
+                           &drm_text_panic_ops);
+
+       return 0;
+
+err_remove:
+       debugfs_remove_recursive(drm_panic_logfs_root);
+
+       return -ENOMEM;
+}
+
+static void drm_panic_logfs_exit(void)
+{
+       debugfs_remove_recursive(drm_panic_logfs_root);
+       kfree(log_buf);
+       log_buf = NULL;
+}
+
+#else
+
+static int drm_panic_logfs_init(void)
+{
+}
+
+static void drm_panic_logfs_exit(void)
+{
+}
+
+static void drm_panic_log(const char *fmt, ...)
+{
+}
+
+#endif
+
+
+void __init drm_panic_init(void)
+{
+       drm_panic_kmsgs = kzalloc(DRM_PANIC_MAX_KMSGS, GFP_KERNEL);
+       if (!drm_panic_kmsgs) {
+               DRM_ERROR("Failed to setup panic handler\n");
+               return;
+       }
+
+       drm_panic_logfs_init();
+drm_panic_log("%s\n", __func__);
+       register_console(&drm_panic_console);
+       atomic_notifier_chain_register(&panic_notifier_list,
+                                      &drm_panic_block);
+}
+
+void __exit drm_panic_exit(void)
+{
+       if (!drm_panic_kmsgs)
+               return;
+
+       drm_panic_logfs_exit();
+       atomic_notifier_chain_unregister(&panic_notifier_list,
+                                        &drm_panic_block);
+       unregister_console(&drm_panic_console);
+       kfree(drm_panic_kmsgs);
+}
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index bc7006e..4e84654 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -550,6 +550,28 @@ struct drm_driver {
                          bool from_open);
        void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);

+       /**
+        * @panic:
+        *
+        * This function is called on panic() asking for a framebuffer to
+        * display the panic messages on. It also needs the virtual address
+        * of the backing buffer.
+        * This function is optional.
+        *
+        * NOTE:
+        *
+        * This function is called in an atomic notifier chain and it cannot
+        * sleep. Care must be taken so the machine is not killed even harder,
+        * preventing output from going out on serial/netconsole.
+        *
+        * RETURNS:
+        *
+        * Framebuffer that should be used to display the panic messages,
+        * alongside the virtual address of the backing buffer, or NULL if
+        * the driver is unable to provide this.
+        */
+       struct drm_framebuffer *(*panic)(struct drm_device *dev, void **vmem);
+
        int (*debugfs_init)(struct drm_minor *minor);
        void (*debugfs_cleanup)(struct drm_minor *minor);

-- 
2.8.2

Reply via email to