Subject: Error LED on Soekris net4501
To: None <tech-kern@NetBSD.org>
From: Quentin Garnier <cube@NetBSD.org>
List: tech-kern
Date: 12/01/2003 09:08:32
This is a multi-part message in MIME format.

--Multipart=_Mon__1_Dec_2003_09_08_32_+0100_yfh=KeJ7LsUlH45i
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit

Hi all,

I have a net4501 at home and I wrote a little driver for it.  I plan on
committing at least parts of the attached patch (not necessarily the
way it is done), but I'd like a bit of input before.

The patch introduces a new option, CPU_SOEKRIS, that affects the Elan
SC520 chipset driver.  It adds a new character device that allows access
to the MMCR and to control the LED.  Then the MMCR can be just mmap'ed,
there is actually nothing specific to the net4501 on that part.

The LED can be controlled from userland by writing a program to it.  The
small language I cooked up is questionable, but let you do anything with
the LED.

LED status can be read on the device, which returns "0\n" or "1\n" so
you can use cat on it.

The part I really want to commit is the change to machdep.c that turns
on the error LED once the system can be shut off.  I also added on my
box a small rc.d script that echoes "F2S0,2A0;1W1;0W1L0,0" (ahem, blink
3 times with a clock running at 5Hz [1]) once sshd has started so I can
know when it is ok to login.

Poul-Henning Kamp recently introduced a led(4) interface in FreeBSD and
used it to map not only the error LED of the net4501 but also all the
PIO of the Elan SC520.

My questions, in no particular order:

 o Should I not bother and just port phk's led(4)?
 o Should I just commit enough bits so that light-on-halt works?
 o Should I commit the whole thing?
 o Should I make the programming interface be compatible with FreeBSD
   instead?

Optionally, I could make a led(4) driver with my own programming
interface.  I can use some ideas for a more simple interface (though I
don't really like the FreeBSD one, but it's not like I care much).  I'm
aware mine might be just silly.

Quentin Garnier.

[1] Here's how it works (';' is a convenient instruction separator):

F2    -> Set period to 2 tenth of second [yeah, period, not frequency]
S0,2  -> Set counter #0 to 2 [to makes 3 loops]
A0    -> Set mark #0 at current address
1     -> Turn LED on
W1    -> Sleep for one tick
0     -> Turn LED off
W1    -> Sleep for one tick
L0,0  -> Loop decreasing counter #0 and jumping to mark #0

--Multipart=_Mon__1_Dec_2003_09_08_32_+0100_yfh=KeJ7LsUlH45i
Content-Type: text/plain;
 name="soekris.diff"
Content-Disposition: attachment;
 filename="soekris.diff"
Content-Transfer-Encoding: 7bit

Index: conf/files.i386
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/conf/files.i386,v
retrieving revision 1.246
diff -u -r1.246 files.i386
--- conf/files.i386	2003/11/16 12:02:15	1.246
+++ conf/files.i386	2003/11/30 17:03:46
@@ -179,6 +179,7 @@
 device	elansc: sysmon_wdog
 attach	elansc at pci
 file	arch/i386/pci/elan520.c		elansc
+defflag	opt_soekris.h	CPU_SOEKRIS
 
 # PCI-EISA bridges
 device	pceb: eisabus, isabus
Index: conf/majors.i386
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/conf/majors.i386,v
retrieving revision 1.22
diff -u -r1.22 majors.i386
--- conf/majors.i386	2003/10/11 07:54:26	1.22
+++ conf/majors.i386	2003/11/30 17:03:46
@@ -107,6 +107,7 @@
 device-major	rd		char 105 block 22	rd
 device-major	ct		char 106 block 23	ct
 device-major	mt		char 107 block 24	mt
+device-major	elansc		char 108		elansc
 
 # Majors up to 143 are reserved for machine-dependant drivers.
 # New machine-independant driver majors are assigned in 
Index: i386/machdep.c
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/i386/machdep.c,v
retrieving revision 1.543
diff -u -r1.543 machdep.c
--- i386/machdep.c	2003/10/28 22:52:53	1.543
+++ i386/machdep.c	2003/11/30 17:03:47
@@ -87,6 +87,7 @@
 #include "opt_mtrr.h"
 #include "opt_multiprocessor.h"
 #include "opt_realmem.h"
+#include "opt_soekris.h"
 #include "opt_user_ldt.h"
 #include "opt_vm86.h"
 
@@ -199,6 +200,10 @@
 #define BEEP_ONHALT_PERIOD 250
 #endif
 
+#ifdef CPU_SOEKRIS
+void	soekris_errled_on(void);
+#endif
+
 /* the following is used externally (sysctl_hw) */
 char machine[] = "i386";		/* cpu "architecture" */
 char machine_arch[] = "i386";		/* machine == machine_arch */
@@ -815,6 +820,9 @@
 				delay(BEEP_ONHALT_PERIOD * 1000);
 			}
 		}
+#endif
+#ifdef CPU_SOEKRIS
+		soekris_errled_on();
 #endif
 
 		cnpollc(1);	/* for proper keyboard command handling */
Index: pci/elan520.c
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/pci/elan520.c,v
retrieving revision 1.7
diff -u -r1.7 elan520.c
--- pci/elan520.c	2003/10/25 21:34:07	1.7
+++ pci/elan520.c	2003/11/30 17:03:47
@@ -52,6 +52,10 @@
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/device.h>
+#include <sys/conf.h>
+#include <sys/callout.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
 #include <sys/wdog.h>
 
 #include <uvm/uvm_extern.h>
@@ -66,6 +70,21 @@
 
 #include <dev/sysmon/sysmonvar.h>
 
+#include "opt_soekris.h"
+
+#ifdef CPU_SOEKRIS
+struct soekris_errled_prog {
+	uint8_t	*slp_code;
+	uint8_t	slp_freq;	/* in tenth of Hz */
+	uint8_t	slp_sp;		/* stack index */
+	uint16_t	slp_ip;		/* instruction pointer */
+	uint8_t	slp_counters[4];
+	uint16_t	slp_markers[4];
+	uint16_t	slp_stack[6];
+	uint16_t	slp_size;
+};
+#endif
+
 struct elansc_softc {
 	struct device sc_dev;
 	bus_space_tag_t sc_memt;
@@ -73,8 +92,16 @@
 	int sc_echobug;
 
 	struct sysmon_wdog sc_smw;
+#ifdef CPU_SOEKRIS
+	struct callout sc_errled;
+	struct soekris_errled_prog sc_slp;
+#endif
 };
 
+#ifdef CPU_SOEKRIS
+static void	soekris_errled_timer(void *);
+#endif
+
 static void
 elansc_wdogctl_write(struct elansc_softc *sc, uint16_t val)
 {
@@ -282,7 +309,307 @@
 
 	/* ...and clear it. */
 	elansc_wdogctl_reset(sc);
+#ifdef CPU_SOEKRIS
+	callout_init(&sc->sc_errled);
+	callout_setfunc(&sc->sc_errled, soekris_errled_timer, sc);
+	sc->sc_slp.slp_code = NULL;
+#endif
 }
 
 CFATTACH_DECL(elansc, sizeof(struct elansc_softc),
     elansc_match, elansc_attach, NULL, NULL);
+
+#ifdef CPU_SOEKRIS
+
+/*
+ * TODO
+ * 1. Allow upload of a program in several write()
+ * 2. Add a 'morse' instruction
+ */
+
+extern struct cfdriver elansc_cd;
+
+static void	soekris_errled_set(struct elansc_softc *, int);
+void		soekris_errled_on(void);	/* to be called when halting system */
+static void	soekris_init_prog(struct soekris_errled_prog *);
+static void	soekris_free_prog(struct soekris_errled_prog *);
+static uint16_t	soekris_read_word(struct soekris_errled_prog *);
+#define soekris_read_byte(p) ((uint8_t)soekris_read_word(p))
+
+#define	ELANSC_MMCR_MINOR	0
+#define	ELANSC_ERRLED_MINOR	1
+
+static int	elanscopen(dev_t, int, int, struct proc *);
+static int	elanscread(dev_t, struct uio *, int);
+static int	elanscwrite(dev_t, struct uio *, int);
+static paddr_t	elanscmmap(dev_t, off_t, int);
+
+const struct cdevsw elansc_cdevsw = {
+	elanscopen, nullclose, elanscread, elanscwrite,
+	noioctl, nostop, notty, nopoll, elanscmmap,
+	nokqfilter,
+};
+
+static void
+soekris_errled_set(struct elansc_softc *sc, int state)
+{
+	bus_space_write_2(sc->sc_memt, sc->sc_memh,
+	    state ? MMCR_PIOSET15_0 : MMCR_PIOCLR15_0,
+	    SOEKRIS_ERRLED);
+}
+
+static int
+soekris_errled_get(struct elansc_softc *sc)
+{
+	return ((bus_space_read_2(sc->sc_memt, sc->sc_memh,
+	    MMCR_PIODATA15_0) & SOEKRIS_ERRLED) == SOEKRIS_ERRLED);
+}
+
+void
+soekris_errled_on(void)
+{
+	soekris_errled_set((struct elansc_softc *)elansc_cd.cd_devs[0], 1);
+}
+
+static void
+soekris_init_prog(struct soekris_errled_prog *p)
+{
+	p->slp_ip = 0;
+	p->slp_sp = 0;
+	p->slp_freq = 10;	/* 1 Hz by default */
+	memset(&p->slp_counters, 0, 4*sizeof(uint8_t));
+	memset(&p->slp_markers, 0, 4*sizeof(uint16_t));
+	memset(&p->slp_stack, 0, 6*sizeof(uint16_t));
+}
+
+static void
+soekris_free_prog(struct soekris_errled_prog *p)
+{
+	free(p->slp_code, M_DEVBUF);
+	p->slp_size = 0;
+}
+
+static uint16_t
+soekris_read_word(struct soekris_errled_prog *p)
+{
+	uint16_t r;
+	uint8_t i;
+
+	for (r = 0; p->slp_ip < p->slp_size; p->slp_ip++) {
+		i = p->slp_code[p->slp_ip];
+		if (i < '0' || i > '9')
+			break;
+		else
+			r = 10*r + (i - '0');
+	}
+
+	return (r);
+}
+
+static void
+soekris_errled_timer(void *v)
+{
+	struct elansc_softc *sc = v;
+	struct soekris_errled_prog *p = &sc->sc_slp;
+	uint8_t i, n;
+	uint16_t w = 1;
+
+	/* Don't process more than 10 instructions at a time */
+	for (n = 0; n < 10; n++) {
+		/* Pop an instruction */
+		i = p->slp_code[p->slp_ip];
+		p->slp_ip++;
+
+		switch (i) {
+		case '0':	/* LED off */
+		case '1':	/* LED on */
+			soekris_errled_set(sc, i - '0');
+			break;
+		case 'X':	/* End program */
+			soekris_free_prog(p);
+			return;
+			break;
+		case 'R':	/* Restart program */
+			soekris_init_prog(p);
+			goto out;
+			break;
+		case ';':	/* NOOP */
+		case '\n':	/* Ignore new lines too */
+		case ' ':
+		case '\t':
+			break;
+		case 'F':	/* Set frequency */
+			p->slp_freq = soekris_read_byte(p);
+			if (p->slp_freq == 0)
+				p->slp_freq = 10;
+			break;
+		case 'W':	/* Sleep */
+			w = soekris_read_word(p);
+			if (w == 0)
+				w = 1;
+			goto out;
+			break;
+		case 'S':	/* Set counter value */
+			{
+				uint16_t v = 0;
+				uint8_t c = soekris_read_byte(p);
+				if (p->slp_code[p->slp_ip] == ',') {
+					p->slp_ip++;
+					v = soekris_read_word(p);
+				}
+
+				if (c > 3)
+					aprint_error("%s/LED: invalid counter (%u)\n",
+					    sc->sc_dev.dv_xname, c);
+				else
+					p->slp_counters[c] = v;
+			} break;
+		case 'L':	/* Loop */
+			{
+				uint8_t a = 0, c = soekris_read_byte(p);
+				if (p->slp_code[p->slp_ip] == ',') {
+					p->slp_ip++;
+					a = soekris_read_byte(p);
+				}
+
+				if (c > 3 || a > 3)
+					aprint_error("%s/LED: invalid value (%u, %u)\n",
+					    sc->sc_dev.dv_xname, c, a);
+				else
+					if (p->slp_counters[c]) {
+						p->slp_ip = p->slp_markers[a];
+						p->slp_counters[c]--;
+					}
+			} break;
+		case 'A':	/* Address label for loops */
+			{
+				uint8_t a = soekris_read_byte(p);
+				if (a > 3)
+					aprint_error("%s/LED: invalid address register (%u)\n",
+					    sc->sc_dev.dv_xname, a);
+				else
+					p->slp_markers[a] = p->slp_ip;
+			} break;
+		case 'C':	/* Call sub-routine */
+			{
+				uint16_t s = soekris_read_word(p);
+				if (s >= p->slp_size) {
+					aprint_error("%s/LED: invalid address (%u)\n",
+					    sc->sc_dev.dv_xname, s);
+					break;
+				}
+				if (p->slp_sp > 6) {
+					aprint_error("%s/LED: stack overflow\n",
+					    sc->sc_dev.dv_xname);
+					break;
+				}
+				p->slp_stack[p->slp_sp++] = p->slp_ip;
+				p->slp_ip = s;
+			} break;
+		case 'E':	/* Exit from sub-routine */
+			if (p->slp_sp == 0)
+				aprint_error("%s/LED: empty stack\n", sc->sc_dev.dv_xname);
+			else
+				p->slp_ip = p->slp_stack[--p->slp_sp];
+			break;
+		default:
+			/* Stop program on invalid input */
+			aprint_error("%s/LED: invalid instruction (%c)\n",
+			    sc->sc_dev.dv_xname, i);
+			soekris_free_prog(p);
+			return;
+			break;
+		}
+
+		if (p->slp_ip >= p->slp_size) {
+			soekris_free_prog(p);
+			return;
+		}
+	}
+
+out:
+	callout_schedule(&sc->sc_errled, w * p->slp_freq * hz / 10);
+}
+
+static int
+elanscopen(dev_t dev, int flags, int fmt, struct proc *p)
+{
+	if (minor(dev) != ELANSC_MMCR_MINOR &&
+	    minor(dev) != ELANSC_ERRLED_MINOR)
+		return (ENXIO);
+
+	return (0);
+}
+
+static int
+elanscread(dev_t dev, struct uio *uio, int flags)
+{
+	char state[2] = { 0, '\n' };
+	struct elansc_softc *sc = elansc_cd.cd_devs[0];
+	int error;
+
+	if (minor(dev) != ELANSC_ERRLED_MINOR)
+		return (ENODEV);
+
+	/*
+	 * Return something only if we're reading at the start of
+	 * "file".  This allows 'cat </dev/soekris_errled' to work
+	 * as expected.
+	 */
+	if (uio->uio_offset > 0)
+		return (0);
+
+	state[0] = soekris_errled_get(sc) ? '1' : '0';
+
+	error = uiomove(state, min(2, uio->uio_resid), uio);
+	if (error)
+		return (error);
+
+	return (0);
+}
+
+static int
+elanscwrite(dev_t dev, struct uio *uio, int flags)
+{
+	struct elansc_softc *sc = elansc_cd.cd_devs[0];
+	int error;
+
+	if (minor(dev) != ELANSC_ERRLED_MINOR)
+		return (ENODEV);
+
+	/* Stop any running program */
+	callout_stop(&sc->sc_errled);
+	if (sc->sc_slp.slp_code != NULL)
+		soekris_free_prog(&sc->sc_slp);
+
+	if (uio->uio_resid == 0)
+		return (0);
+
+	sc->sc_slp.slp_code = malloc(uio->uio_resid, M_DEVBUF, M_WAITOK);
+	sc->sc_slp.slp_size = uio->uio_resid;
+
+	error = uiomove(sc->sc_slp.slp_code, uio->uio_resid, uio);
+	if (error) {
+		soekris_free_prog(&sc->sc_slp);
+		return (error);
+	}
+
+	soekris_init_prog(&sc->sc_slp);
+	callout_schedule(&sc->sc_errled, hz/10); /* Start next tenth of second */
+
+	return (0);
+}
+
+static paddr_t
+elanscmmap(dev_t dev, off_t offset, int prot)
+{
+	if (minor(dev) != ELANSC_MMCR_MINOR)
+		return (EOPNOTSUPP);
+
+	/* There's only one page available */
+	if (offset >= 0x1000)
+		return (-1);
+
+	return (atop(MMCR_BASE_ADDR));
+}
+#endif /* CPU_SOEKRIS */

--Multipart=_Mon__1_Dec_2003_09_08_32_+0100_yfh=KeJ7LsUlH45i--