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