---
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
_______________________________________________
dri-devel mailing list
dri-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/dri-devel