diff mbox

[RFC,2/3] drm/text: Add panic and boot console support

Message ID 1469715307-32708-3-git-send-email-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes July 28, 2016, 2:15 p.m. UTC
This adds support for panic and boot console.

Signed-off-by: Noralf Trønnes <noralf@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 mbox

Patch

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__)