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