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 prallel
port registers and to visualize system calls, hardware IRQs or software
IRQs as additional hints in a post-mortem diagnosis of a system crash. 

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

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

Index: linux-2.6.33.7.2-rt30/drivers/misc/Kconfig
===================================================================
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:332 @ config TI_DAC7512
 	  This driver can also be built as a module. If so, the module
 	  will be calles ti_dac7512.
 
+config SETPARPORT_RAW
+	tristate "Raw output driver for parallel port"
+	select LOCKUP_DETECTOR
+	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 is used as a callback in the NMI touch
+	  function that is part of the lockup detector. If installed, it
+	  allows to monitor NMI activity, e.g. using LEDs connected to the
+	  parallel port. The module parameter "nmicode" is then used to
+	  define the code to be sent at every NMI call to increment the
+	  8-bit number at the parallel port every four seconds, e.g.
+	    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-2.6.33.7.2-rt30/drivers/misc/Makefile
===================================================================
--- linux-2.6.33.7.2-rt30.orig/drivers/misc/Makefile
+++ linux-2.6.33.7.2-rt30/drivers/misc/Makefile
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:32 @ obj-$(CONFIG_IWMC3200TOP)      += iwmc32
 obj-y				+= eeprom/
 obj-y				+= cb710/
 obj-$(CONFIG_HWLAT_DETECTOR)	+= hwlat_detector.o
+obj-$(CONFIG_SETPARPORT_RAW)	+= setparport.o
+
Index: linux-2.6.33.7.2-rt30/drivers/misc/setparport.c
===================================================================
--- /dev/null
+++ linux-2.6.33.7.2-rt30/drivers/misc/setparport.c
@ linux-2.6.33.7.2-rt30/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 <linux/reboot.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");
+
+static bool actions;
+module_param(actions, bool, 0600);
+MODULE_PARM_DESC(actions, "enable debug actions");
+
+#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 (actions) {
+			pr_info("Show state\n");
+			show_state();
+		}
+	}
+	if (S5(status)) {
+		pr_info("setparport: S5\n");
+		if (actions) {
+			emergency_sync();
+			pr_info("Sync done\n");
+		}
+	}
+	if (S6(status)) {
+		pr_info("setparport: S6\n");
+		if (actions) {
+			emergency_remount();
+			pr_info("Remount RO done\n");
+		}
+	}
+	if (S7(status)) {
+		pr_info("setparport: S7\n");
+		if (actions) {
+			pr_info("Restarting\n");
+			lockdep_off();
+			local_irq_enable();
+			emergency_restart();
+		}
+	}
+
+	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(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(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(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(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(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(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);
+			register_trace_irq_handler_exit(probe_irq_exit);
+			register_trace_softirq_entry(probe_sirq_enter);
+			register_trace_softirq_exit(probe_sirq_exit);
+#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
+			register_trace_sys_enter(probe_syscall_enter);
+			register_trace_sys_exit(probe_syscall_exit);
+#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);
+	unregister_trace_irq_handler_exit(probe_irq_exit);
+	unregister_trace_softirq_entry(probe_sirq_enter);
+	unregister_trace_softirq_exit(probe_sirq_exit);
+#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
+	unregister_trace_sys_enter(probe_syscall_enter);
+	unregister_trace_sys_exit(probe_syscall_exit);
+#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-2.6.33.7.2-rt30/include/linux/hardirq.h
===================================================================
--- linux-2.6.33.7.2-rt30.orig/include/linux/hardirq.h
+++ linux-2.6.33.7.2-rt30/include/linux/hardirq.h
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:211 @ extern void irq_enter(void);
  */
 extern void irq_exit(void);
 
+extern void (*nmicallback)(void);
+
 #define nmi_enter()						\
 	do {							\
 		ftrace_nmi_enter();				\
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:221 @ extern void irq_exit(void);
 		lockdep_off();					\
 		rcu_nmi_enter();				\
 		trace_hardirq_enter();				\
+		if (nmicallback)				\
+			nmicallback();				\
 	} while (0)
 
 #define nmi_exit()						\
Index: linux-2.6.33.7.2-rt30/include/linux/interrupt.h
===================================================================
--- linux-2.6.33.7.2-rt30.orig/include/linux/interrupt.h
+++ linux-2.6.33.7.2-rt30/include/linux/interrupt.h
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:648 @ extern int arch_init_chip_data(struct ir
 # define local_irq_restore_nort(flags)	local_irq_restore(flags)
 #endif
 
+void set_nmicallback(void (*new_nmicallback)(void));
+
 #endif
Index: linux-2.6.33.7.2-rt30/kernel/irq/manage.c
===================================================================
--- linux-2.6.33.7.2-rt30.orig/kernel/irq/manage.c
+++ linux-2.6.33.7.2-rt30/kernel/irq/manage.c
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:1219 @ int request_threaded_irq(unsigned int ir
 	return retval;
 }
 EXPORT_SYMBOL(request_threaded_irq);
+
+void (*nmicallback)(void);
+void set_nmicallback(void (*new_nmicallback)(void))
+{
+	nmicallback = new_nmicallback;
+}
+EXPORT_SYMBOL_GPL(set_nmicallback);
Index: linux-2.6.33.7.2-rt30/fs/super.c
===================================================================
--- linux-2.6.33.7.2-rt30.orig/fs/super.c
+++ linux-2.6.33.7.2-rt30/fs/super.c
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:646 @ void emergency_remount(void)
 		schedule_work(work);
 	}
 }
+EXPORT_SYMBOL(emergency_remount);
 
 /*
  * Unnamed block devices are dummy devices used by virtual
Index: linux-2.6.33.7.2-rt30/fs/sync.c
===================================================================
--- linux-2.6.33.7.2-rt30.orig/fs/sync.c
+++ linux-2.6.33.7.2-rt30/fs/sync.c
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:160 @ void emergency_sync(void)
 		schedule_work(work);
 	}
 }
+EXPORT_SYMBOL(emergency_sync);
+
 
 /*
  * Generic function to fsync a file.
Index: linux-2.6.33.7.2-rt30/kernel/sched.c
===================================================================
--- linux-2.6.33.7.2-rt30.orig/kernel/sched.c
+++ linux-2.6.33.7.2-rt30/kernel/sched.c
@ linux-2.6.33.7.2-rt30/drivers/misc/Kconfig:7414 @ void show_state_filter(unsigned long sta
 	if (!state_filter)
 		debug_show_all_locks();
 }
+EXPORT_SYMBOL(show_state_filter);
 
 void __cpuinit init_idle_bootup_task(struct task_struct *idle)
 {