Subject: Kernel module to directly write to the parallel port registers
From: Carsten Emde <C.Emde@osadl.org>
Date: Sat, 15 Dec 2012 00:00:28 +0100

It sometimes is required to directly signal a computer state at an
output port without using a driver, e.g. in a crashed system that is no
longer able to handle interrupts but still has some kind of life in it.
The parallel port is ideally suitable for this purpose. This patch
provides a driver interface to facilitate determination of the parallel
port registers and to visualize system calls, hardware IRQs or software
IRQs as additional hints in a post-mortem diagnosis of a system crash.
In addition, an NMI callback is available to monitor NMI activity and
to possibly use it to find embers in the ashes of a crashed system.

Signed-off-by: Carsten Emde <C.Emde@osadl.org>

---
 drivers/misc/Kconfig      |   42 ++++++
 drivers/misc/Makefile     |    2 
 drivers/misc/setparport.c |  308 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/hardirq.h   |    4 
 include/linux/interrupt.h |    2 
 kernel/irq/manage.c       |    7 +
 6 files changed, 365 insertions(+)

Index: linux-3.2.46-rt67-32/drivers/misc/Kconfig
===================================================================
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:540 @ config USB_SWITCH_FSA9480
 	  stereo and mono audio, video, microphone and UART data to use
 	  a common connector port.
 
+config SETPARPORT_RAW
+	tristate "Raw output driver for parallel port"
+	help
+	  It sometimes is required to directly signal a specific state at
+	  the parallel port without using a driver, e.g. in a crashed
+	  system that still has some kind of life in it. Usage:
+	    echo 0 .. 255 >/dev/setparport -> set output byte
+	    echo 256 .. 511 >/dev/setparport -> or byte with output byte
+	    echo 512 >/dev/setparport clear all output bits
+	    echo 513 >/dev/setparport set all output bits
+	    echo 514 >/dev/setparport invert output bits
+	    echo 515 >/dev/setparport increment output bits
+	    echo 516 >/dev/setparport copy status register to output bits
+	  In addition, this driver contains a callback in the NMI handler.
+	  If installed, it allows to monitor NMI activity, e.g. using LEDs
+	  connected to the parallel port. The module parameter "nmicode" may
+	  then be used to define the code to be sent whenever the NMI fires.
+	  For example, to increment the 8-bit number at the parallel port at
+	  every NMI, use
+	    modprobe setparport nmicode=515
+	  or
+	    echo 515 >/sys/module/setparport/parameters/nmicode
+	  Last not least, this driver can be used to output the LSB of the
+	  most recent syscall, hardware IRQ or software IRQ vector at the
+	  parallel port which may provide useful post-mortem information in
+	  case of a system crash.
+	  System call:
+	    modprobe setparport sysenter=1 sysexit=0
+	  or
+	    echo 1 >/sys/module/setparport/parameters/sysenter
+	    echo 0 >/sys/module/setparport/parameters/sysexit
+	  Hardware IRQ number:
+	    modprobe setparport irqenter=1, irqexit=384
+	  or
+	    echo 1 >/sys/module/setparport/parameters/irqenter
+	    echo 384 >/sys/module/setparport/parameters/irqexit
+	  Software IRQ vector number:
+	    modprobe setparport sirqenter=1 sirqexit=384
+	  or
+	    echo 1 >/sys/module/setparport/parameters/sirqenter
+	    echo 384 >/sys/module/setparport/parameters/sirqexit
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
Index: linux-3.2.46-rt67-32/drivers/misc/Makefile
===================================================================
--- linux-3.2.46-rt67-32.orig/drivers/misc/Makefile
+++ linux-3.2.46-rt67-32/drivers/misc/Makefile
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:52 @ obj-y				+= carma/
 obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
 obj-$(CONFIG_ALTERA_STAPL)	+=altera-stapl/
 obj-$(CONFIG_HWLAT_DETECTOR)	+= hwlat_detector.o
+obj-$(CONFIG_SETPARPORT_RAW)	+= setparport.o
+
Index: linux-3.2.46-rt67-32/drivers/misc/setparport.c
===================================================================
--- /dev/null
+++ linux-3.2.46-rt67-32/drivers/misc/setparport.c
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:4 @
+/*
+ * setparport - set bits of the parallel port by writing immediately
+ *              into the controller's data register
+ *
+ * Copyright (C) 2012  Carsten Emde <C.Emde@osadl.org>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <linux/module.h>
+
+#include <linux/sched.h>
+#include <linux/cpumask.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+
+#include <trace/events/syscalls.h>
+#include <trace/events/irq.h>
+
+#define SETPARPORT_MINOR MISC_DYNAMIC_MINOR
+
+#define MAX_NMICODE 516
+
+static unsigned int ioport;
+static raw_spinlock_t parport_writelock;
+
+static void __write(int bit)
+{
+	if (bit >= 0 && bit < 256)
+		outb(bit, ioport);
+	else if (bit >= 256 && bit < 512) {
+		raw_spin_lock(&parport_writelock);
+		outb(inb(ioport) | bit, ioport);
+		raw_spin_unlock(&parport_writelock);
+	} else if (bit == 512)
+		outb(0x0, ioport);
+	else if (bit == 513)
+		outb(0xff, ioport);
+	else if (bit == 514) {
+		raw_spin_lock(&parport_writelock);
+		outb(~inb(ioport), ioport);
+		raw_spin_unlock(&parport_writelock);
+	} else if (bit == 515) {
+		raw_spin_lock(&parport_writelock);
+		outb(inb(ioport) + 1, ioport);
+		raw_spin_unlock(&parport_writelock);
+	} else if (bit == MAX_NMICODE) {
+		outb(inb(ioport + 1), ioport);
+	} else
+		pr_err("setparport: could not understand code %d\n", bit);
+}
+
+
+static unsigned long get_timestamp_ms(int cpu)
+{
+	return cpu_clock(cpu) >> 27LL;  /* resolution roughly 1 ms */
+}
+static unsigned long lastaccess;
+static int lastcpu;
+
+static ssize_t
+setparport_read(struct file *fp, char __user *buf, size_t len, loff_t *off)
+{
+	if (!ioport)
+		return -ENODEV;
+
+	if (lastaccess && get_timestamp_ms(lastcpu) == lastaccess)
+		return 0;
+
+	if (snprintf(buf, len, "0x%02x\n", inb(ioport)) < 0)
+		return -EIO;
+
+	lastcpu = raw_smp_processor_id();
+	lastaccess = get_timestamp_ms(lastcpu);
+	len = strlen(buf);
+	return len;
+}
+
+static ssize_t
+setparport_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
+{
+	int bit;
+
+	if (!ioport)
+		return -ENODEV;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (sscanf(buf, "%d", &bit) == 1)
+		__write(bit);
+
+	return strlen(buf);
+}
+
+static int
+setparport_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int
+setparport_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static const struct file_operations setparport_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= setparport_open,
+	.read		= setparport_read,
+	.write		= setparport_write,
+	.release	= setparport_release,
+};
+
+static struct miscdevice setparport_dev = {
+	SETPARPORT_MINOR,
+	"setparport",
+	&setparport_fops
+};
+
+static long nmicode = -1;
+module_param(nmicode, long, 0600);
+MODULE_PARM_DESC(nmicode, "code to send by NMI LOCKUP_DETECTOR:\n"
+"   -1: none, 0-255: set, 256-511: or, 512: clear all, 513: set all,\n"
+"  514: invert, 515: incr, 516: ctrl reg");
+
+#define biton(s, b) ((s)&(1<<(b)))
+#define bitoff(s, b) (!biton(s, b))
+#define S4(s) bitoff(s, 4)
+#define S5(s) bitoff(s, 5)
+#define S6(s) bitoff(s, 6)
+#define S7(s) biton(s, 7)
+
+static void doitfromkernel(void)
+{
+	int status = inb(ioport + 1);
+
+	if (S4(status))
+		pr_info("setparport: S4\n");
+	if (S5(status))
+		pr_info("setparport: S5\n");
+	if (S6(status))
+		pr_info("setparport: S6\n");
+	if (S7(status))
+		pr_info("setparport: S7\n");
+
+	if (nmicode != -1)
+		__write(nmicode);
+}
+
+#ifdef CONFIG_TRACEPOINTS
+static bool irqenter;
+module_param(irqenter, bool, 0600);
+MODULE_PARM_DESC(irqenter, "output irq number of most recent irq handler");
+static notrace void probe_irq_enter(void *v, int irq, struct irqaction *action)
+{
+	if (irqenter)
+		__write(irq);
+}
+
+static int irqexit = -1;
+module_param(irqexit, int, 0600);
+MODULE_PARM_DESC(irqexit, "byte to output when exiting from irq handler");
+static notrace void probe_irq_exit(void *v, int irq, struct irqaction *action,
+				   int ret)
+{
+	if (irqexit != -1)
+		__write(irqexit);
+}
+
+static bool sirqenter;
+module_param(sirqenter, bool, 0600);
+MODULE_PARM_DESC(sirqenter, "output vector of most recent soft irq handler");
+static notrace void probe_sirq_enter(void *v, unsigned int vec_nr)
+{
+	if (sirqenter)
+		__write(vec_nr);
+}
+
+static int sirqexit = -1;
+module_param(sirqexit, int, 0600);
+MODULE_PARM_DESC(sirqexit, "byte to output when exiting from soft irq handler");
+static notrace void probe_sirq_exit(void *v, unsigned int vec_nr)
+{
+	if (sirqexit != -1)
+		__write(sirqexit);
+}
+
+#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
+static bool sysenter;
+module_param(sysenter, bool, 0600);
+MODULE_PARM_DESC(sysenter, "output LSB of most recent syscall");
+static notrace void probe_syscall_enter(void *v, struct pt_regs *regs, long ret)
+{
+	if (sysenter)
+		__write(ret & 0xff);
+}
+
+static int sysexit = -1;
+module_param(sysexit, int, 0600);
+MODULE_PARM_DESC(sysexit, "byte to output when exiting from syscall");
+static notrace void probe_syscall_exit(void *v, struct pt_regs *regs, long ret)
+{
+	if (sysexit != -1)
+		__write(sysexit);
+}
+#endif
+#endif
+
+static DEFINE_RWLOCK(resource_lock);
+static int __init setparport_init(void)
+{
+	int ret;
+	struct resource *root = &ioport_resource, *res;
+	char *ourport = "parport";
+
+	read_lock(&resource_lock);
+	for (res = root->child; res; ) {
+		if (!strncmp(res->name, ourport, strlen(ourport))) {
+			ioport = res->start;
+			break;
+		}
+		if (res->child)
+			res = res->child;
+		else {
+			while (!res->sibling && res->parent)
+				res = res->parent;
+			res = res->sibling;
+		}
+	}
+	read_unlock(&resource_lock);
+
+	if (ioport) {
+		pr_info("setparport: found ioport 0x%04x\n", ioport);
+		ret = misc_register(&setparport_dev);
+		if (ret)
+			pr_err("setparport: can't register device\n");
+		else {
+			pr_err("setparport: device %d\n", setparport_dev.minor);
+			outb(0, ioport);          /* clear all output bits */
+			outb(1 << 2, ioport + 2); /* provide +5V for pull-up */
+			set_nmicallback(doitfromkernel);
+#ifdef CONFIG_TRACEPOINTS
+			register_trace_irq_handler_entry(probe_irq_enter, NULL);
+			register_trace_irq_handler_exit(probe_irq_exit, NULL);
+			register_trace_softirq_entry(probe_sirq_enter, NULL);
+			register_trace_softirq_exit(probe_sirq_exit, NULL);
+#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
+			register_trace_sys_enter(probe_syscall_enter, NULL);
+			register_trace_sys_exit(probe_syscall_exit, NULL);
+#endif
+#endif
+		}
+	} else {
+		pr_err("setparport: no parallel port found\n");
+		ret = -ENODEV;
+	}
+	return ret;
+}
+
+static void __exit setparport_exit(void)
+{
+	set_nmicallback(NULL);
+#ifdef CONFIG_TRACEPOINTS
+	unregister_trace_irq_handler_entry(probe_irq_enter, NULL);
+	unregister_trace_irq_handler_exit(probe_irq_exit, NULL);
+	unregister_trace_softirq_entry(probe_sirq_enter, NULL);
+	unregister_trace_softirq_exit(probe_sirq_exit, NULL);
+#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
+	unregister_trace_sys_enter(probe_syscall_enter, NULL);
+	unregister_trace_sys_exit(probe_syscall_exit, NULL);
+#endif
+#endif
+	misc_deregister(&setparport_dev);
+	pr_info("setparport: exit\n");
+}
+
+module_init(setparport_init);
+module_exit(setparport_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Carsten Emde <C.Emde@osadl.org>");
+MODULE_DESCRIPTION("Set bits of the parallel port without driver overhead");
Index: linux-3.2.46-rt67-32/include/linux/hardirq.h
===================================================================
--- linux-3.2.46-rt67-32.orig/include/linux/hardirq.h
+++ linux-3.2.46-rt67-32/include/linux/hardirq.h
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:221 @ extern void irq_enter(void);
  */
 extern void irq_exit(void);
 
+extern void (*nmicallback)(void);
+
 #define nmi_enter()						\
 	do {							\
 		ftrace_nmi_enter();				\
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:231 @ extern void irq_exit(void);
 		lockdep_off();					\
 		rcu_nmi_enter();				\
 		trace_hardirq_enter();				\
+		if (nmicallback)				\
+			nmicallback();				\
 	} while (0)
 
 #define nmi_exit()						\
Index: linux-3.2.46-rt67-32/include/linux/interrupt.h
===================================================================
--- linux-3.2.46-rt67-32.orig/include/linux/interrupt.h
+++ linux-3.2.46-rt67-32/include/linux/interrupt.h
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:730 @ extern int early_irq_init(void);
 extern int arch_probe_nr_irqs(void);
 extern int arch_early_irq_init(void);
 
+void set_nmicallback(void (*new_nmicallback)(void));
+
 #endif
Index: linux-3.2.46-rt67-32/kernel/irq/manage.c
===================================================================
--- linux-3.2.46-rt67-32.orig/kernel/irq/manage.c
+++ linux-3.2.46-rt67-32/kernel/irq/manage.c
@ linux-3.2.46-rt67-32/drivers/misc/Kconfig:1685 @ int request_percpu_irq(unsigned int irq,
 
 	return retval;
 }
+
+void (*nmicallback)(void);
+void set_nmicallback(void (*new_nmicallback)(void))
+{
+	nmicallback = new_nmicallback;
+}
+EXPORT_SYMBOL_GPL(set_nmicallback);