[parisc-linux] [patch] /proc interface for LED/LCD

Randolph Chung randolph@tausq.org
Sat, 6 Oct 2001 13:42:50 -0700


hi all,

the attached patch creates a /proc interface for boxes with LED/LCD
support. For example:

legolas:/home/randolph# ls -l /proc/pdc/
total 0
-rw-r--r--    1 root     root            0 Oct  6 13:35 lcd
-rw-r--r--    1 root     root            0 Oct  6 13:35 led
legolas:/home/randolph# cat /proc/pdc/led 
Heartbeat: 1
Disk IO: 1
LAN Rx/Tx: 1
legolas:/home/randolph# cat /proc/pdc/lcd 
Linux 2.4.9-pa40
legolas:/home/randolph# echo -n "debian/rules" > /proc/pdc/lcd

("debian/rules" appears on the LCD on the chassis....)

legolas:/home/randolph# cat /proc/pdc/lcd 
debian/rules
legolas:/home/randolph# echo > /proc/pdc/lcd

(Chassis reverts to original Linux version display)

legolas:/home/randolph# cat /proc/pdc/lcd 
Linux 2.4.9-pa40
legolas:/home/randolph# echo "1 1 0" > /proc/pdc/led

(invalid strings will cause an error to go to your kernel log)

legolas:/home/randolph# cat /proc/pdc/led 
Heartbeat: 1
Disk IO: 1
LAN Rx/Tx: 0

I've only tested it on a c3000 (only machine i have with led/lcd
support). Would appreciate if folks can try it on other machines and let
me know if it works :)

This also requires a small fix to arch/parisc/kernel/firmware.c which i
checked into cvs this morning.

enjoy! :)
randolph
-- 
   @..@                                         http://www.TauSq.org/
  (----)
 ( >__< )
 ^^ ~~ ^^


Index: arch/parisc/kernel/led.c
===================================================================
RCS file: /home/cvs/parisc/linux/arch/parisc/kernel/led.c,v
retrieving revision 1.22
diff -u -r1.22 led.c
--- led.c	2001/08/14 16:54:52	1.22
+++ led.c	2001/10/06 20:31:47
@@ -4,6 +4,7 @@
  *      (c) Copyright 2000 Red Hat Software
  *      (c) Copyright 2000 Helge Deller <hdeller@redhat.com>
  *      (c) Copyright 2001 Helge Deller <deller@gmx.de>
+ *      (c) Copyright 2001 Randolph Chung <tausq@debian.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
@@ -11,9 +12,8 @@
  *      (at your option) any later version.
  *
  * TODO:
- *	- LCD functionality is mostly untested (lack of hardware :-() 
- *	- add procfs entry to (maybe partially) enable & disable LEDs
  *	- speed-up calculations with inlined assembler
+ *	- interface to write to second row of LCD from /proc
  */
 
 #include <linux/config.h>
@@ -28,6 +28,8 @@
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
 #include <linux/reboot.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
 #include <asm/io.h>
 #include <asm/gsc.h>
 #include <asm/processor.h>
@@ -35,16 +37,20 @@
 #include <asm/param.h>		/* HZ */
 #include <asm/led.h>
 #include <asm/pdc.h>
+#include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
    completely in software. The necessary calculations are done in a tasklet
    which is scheduled at every timer interrupt and since the calculations 
    may consume relatively much CPU-time some of the calculations can be 
-   turned off with the following defines */
-#undef NO_HEARTBEAT
-#undef NO_DISKIO
-#undef NO_LAN_RXTX
+   turned off with the following variables (controlled via procfs) */
 
+static int led_type = -1;
+static int led_heartbeat = 1;
+static int led_diskio = 1;
+static int led_lanrxtx = 1;
+static char lcd_text[32] = {0};
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
@@ -110,6 +116,132 @@
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+#ifdef CONFIG_PROC_FS
+static int led_proc_read(char *page, char **start, off_t off, int count, 
+	int *eof, void *data)
+{
+	char *out = page;
+	int len;
+
+	switch ((long)data)
+	{
+	case LED_NOLCD:
+		out += sprintf(out, "Heartbeat: %d\n", led_heartbeat);
+		out += sprintf(out, "Disk IO: %d\n", led_diskio);
+		out += sprintf(out, "LAN Rx/Tx: %d\n", led_lanrxtx);
+		break;
+	case LED_HASLCD:
+		out += sprintf(out, "%s\n", lcd_text);
+		break;
+	default:
+		*eof = 1;
+		return 0;
+	}
+
+	len = out - page - off;
+	if (len < count) {
+		*eof = 1;
+		if (len <= 0) return 0;
+	} else {
+		len = count;
+	}
+	*start = page + off;
+	return len;
+}
+
+static int led_proc_write(struct file *file, const char *buf, 
+	unsigned long count, void *data)
+{
+	const char *cur = NULL;
+	char lbuf[count];
+	int d;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	lcopy_from_user(lbuf, buf, count);
+	cur = lbuf;
+
+	/* skip initial spaces */
+	while (*cur && isspace(*cur))
+	{
+		cur++;
+	}
+
+	switch ((long)data)
+	{
+	case LED_NOLCD:
+		d = *cur++ - '0';
+		if (d != 0 && d != 1) goto parse_error;
+		led_heartbeat = d;
+
+		if (*cur++ != ' ') goto parse_error;
+
+		d = *cur++ - '0';
+		if (d != 0 && d != 1) goto parse_error;
+		led_diskio = d;
+
+		if (*cur++ != ' ') goto parse_error;
+
+		d = *cur++ - '0';
+		if (d != 0 && d != 1) goto parse_error;
+		led_lanrxtx = d;
+
+		break;
+	case LED_HASLCD:
+		if (*cur == 0) 
+		{
+			/* reset to default */
+			lcd_print("Linux " UTS_RELEASE);
+		}
+		else
+		{
+			lcd_print(cur);
+		}
+		break;
+	default:
+		return 0;
+	}
+	
+	return count;
+
+parse_error:
+	if ((long)data == LED_NOLCD)
+		printk(KERN_CRIT "Parse error: expect \"n n n\" (n == 0 or 1) for heartbeat,\ndisk io and lan tx/rx indicators\n");
+	return -EINVAL;
+}
+
+static int __init led_create_procfs(void)
+{
+	struct proc_dir_entry *proc_pdc_root = NULL;
+	struct proc_dir_entry *ent;
+
+	if (led_type == -1) return -1;
+
+	proc_pdc_root = proc_mkdir("pdc", 0);
+	if (!proc_pdc_root) return -1;
+	ent = create_proc_entry("led", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root);
+	if (!ent) return -1;
+	ent->nlink = 1;
+	ent->data = (void *)LED_NOLCD; /* LED */
+	ent->read_proc = led_proc_read;
+	ent->write_proc = led_proc_write;
+
+	if (led_type == LED_HASLCD)
+	{
+		ent = create_proc_entry("lcd", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root);
+		if (!ent) return -1;
+		ent->nlink = 1;
+		ent->data = (void *)LED_HASLCD; /* LCD */
+		ent->read_proc = led_proc_read;
+		ent->write_proc = led_proc_write;
+	}
+
+	return 0;
+}
+#endif
 
 /*
    ** 
@@ -208,7 +340,6 @@
    ** (analog to dev_get_info() from net/core/dev.c)
    **   
  */
-#ifndef NO_LAN_RXTX
 static unsigned long led_net_rx_counter, led_net_tx_counter;
 
 static void led_get_net_stats(int addvalue)
@@ -244,7 +375,6 @@
 	rx_total_last += rx_total;
         tx_total_last += tx_total;
 }
-#endif /* NO_LAN_RXTX */
 
 
 /*
@@ -255,7 +385,6 @@
    ** (analog to linux/fs/proc/proc_misc.c)
    **   
  */
-#ifndef NO_DISKIO
 static unsigned long led_diskio_counter;
 
 static void led_get_diskio_stats(int addvalue)
@@ -280,7 +409,6 @@
 	
 	diskio_total_last += total; 
 }
-#endif /* NO_DISKIO */
 
 
 
@@ -315,49 +443,52 @@
 	if (++count_HZ == HZ)
 	    count_HZ = 0;
 
-#ifndef NO_HEARTBEAT
-	/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
-	if (count_HZ<HEARTBEAT_LEN || 
-	    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
-	    currentleds |= LED_HEARTBEAT;
-	else
-	    currentleds &= ~LED_HEARTBEAT;
-#endif
+	if (led_heartbeat)
+	{
+		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
+		if (count_HZ<HEARTBEAT_LEN || 
+		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
+		    currentleds |= LED_HEARTBEAT;
+		else
+		    currentleds &= ~LED_HEARTBEAT;
+	}
 
 	/* gather network and diskio statistics and flash LEDs respectively */
 
-#ifndef NO_LAN_RXTX
-	if ((count & 31) == 0)
-	    led_get_net_stats(30);
-
-	if (led_net_rx_counter) {
-	    led_net_rx_counter--;
-	    currentleds |= LED_LAN_RCV;
-	}
-	else    
-	    currentleds &= ~LED_LAN_RCV;
-
-	if (led_net_tx_counter) {
-	    led_net_tx_counter--;
-	    currentleds |= LED_LAN_TX;
+	if (led_lanrxtx)
+	{
+		if ((count & 31) == 0)
+			led_get_net_stats(30);
+
+		if (led_net_rx_counter) {
+			led_net_rx_counter--;
+			currentleds |= LED_LAN_RCV;
+		}
+		else    
+			currentleds &= ~LED_LAN_RCV;
+
+		if (led_net_tx_counter) {
+			led_net_tx_counter--;
+			currentleds |= LED_LAN_TX;
+		}
+		else    
+			currentleds &= ~LED_LAN_TX;
+	}
+
+	if (led_diskio)
+	{
+		/* avoid to calculate diskio-stats at same irq as netio-stats ! */
+		if ((count & 31) == 15) 
+			led_get_diskio_stats(30);
+
+		if (led_diskio_counter) {
+			led_diskio_counter--;
+			currentleds |= LED_DISK_IO;
+		}
+		else    
+			currentleds &= ~LED_DISK_IO;
 	}
-	else    
-	    currentleds &= ~LED_LAN_TX;
-#endif
 
-#ifndef NO_DISKIO
-	/* avoid to calculate diskio-stats at same irq as netio-stats ! */
-	if ((count & 31) == 15) 
-	    led_get_diskio_stats(30);
-
-	if (led_diskio_counter) {
-	    led_diskio_counter--;
-	    currentleds |= LED_DISK_IO;
-	}
-	else    
-	    currentleds &= ~LED_DISK_IO;
-#endif
-
 	/* update the LCD/LEDs */
 	if (currentleds != lastleds) {
 	    led_func_ptr(currentleds);
@@ -435,12 +566,14 @@
 			LCD_CMD_REG , LCD_DATA_REG);
 		led_func_ptr = led_LCD_driver;
 		lcd_print( "Linux " UTS_RELEASE );
+		led_type = LED_HASLCD;
 		break;
 
 	case DISPLAY_MODEL_LASI:
 		LED_DATA_REG = data_reg;
 		led_func_ptr = led_LASI_driver;
 		printk(KERN_INFO "LED display at %p registered\n", LED_DATA_REG);
+		led_type = LED_NOLCD;
 		break;
 
 	case DISPLAY_MODEL_OLD_ASP:
@@ -448,6 +581,7 @@
 		led_func_ptr = led_ASP_driver;
 		printk(KERN_INFO "LED (ASP-style) display at %p registered\n", 
 		    LED_DATA_REG);
+		led_type = LED_NOLCD;
 		break;
 
 	default:
@@ -509,6 +643,9 @@
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
+	/* copy display string to buffer for procfs */
+	strncpy(lcd_text, str, sizeof(lcd_text)-1);
+	
 	/* temporarily disable the led tasklet */
 	tasklet_disable(&led_tasklet);
 
@@ -619,3 +756,7 @@
 	lcd_info.model = DISPLAY_MODEL_NONE;
 	return 1;
 }
+
+#ifdef CONFIG_PROC_FS
+module_init(led_create_procfs)
+#endif