I am currently writing a control and monitoring software system that makes use of GPIO pins. Since I was at the CCC congress in Berlin I did only have my laptop and no real GPIO hardware... With only a laptop it's a bit hard to write GPIO software, since laptops usually don't have any GPIO pins. So what I needed to test my new software was either a real device (out of reach) or... writing a simulator.
A GPIO simulator would allow me to test all aspects of my newly written software without the need of hardware. Technically it would mean writing a driver that provides the gpio(4) interface on one side while providing a second channel or method to control the GPIO pins from a userland program. Such userland program would be similar in nature to gpioctl(8), but use a different way to communicate with the simulated GPIO pins. Creating a device node under /dev just to transfer a few bits back and forth seemed like a bit gross, so I was looking for a different way to talk to the driver. Marco Pereboom's bio(4) interface looked like something I could (ab)use: While it was made to control RAID arrays, it's nature is more of an ioctl proxy: A driver registers with the bio(4) driver and a userland tool can then lookup the proper driver by opening /dev/bio and using the BIOCLOCATE ioctl call. Using a cookie, subsequent ioctl calls are then routed by bio(4) to the actual device driver. So bio(4) provides a form of indirect I/O which is simple to setup and use. The new driver, gpiosim(4), registers itself with bio(4) by calling bio_register and otherwise acts as 32-bit wide GPIO device. A slightly modified version of gpioctl(8) looks up the gpiosim0 device and uses two device specific ioctl calls, GPIOSIMREAD and GPIOSIMWRITE to read or set the bits. Note that there is no locking mechanism while accessing the GPIO state at the moment, I simply assume that reading or writing a 32 bit integer variable is atomic.
gpiosim(4), The Kernel Driver
(The following code is shortened for display reasons.)
/* 32 bit wide GPIO simulator */ #include <sys/ioccom.h> #include <dev/gpio/gpiovar.h> #include <dev/biovar.h> #define GPIOSIM_NPINS 32 struct gpiosim_softc { struct device sc_dev; u_int32_t sc_state; struct gpio_chipset_tag sc_gpio_gc; gpio_pin_t sc_gpio_pins[GPIOSIM_NPINS]; }; struct gpiosim_op { void *cookie; u_int32_t mask; u_int32_t state; }; #define GPIOSIMREAD _IOWR('G', 0, struct gpiosim_op) #define GPIOSIMWRITE _IOW('G', 1, struct gpiosim_op) struct cfattach gpiosim_ca = { sizeof(struct gpiosim_softc), gpiosim_match, gpiosim_attach }; struct cfdriver gpiosim_cd = { NULL, "gpiosim", DV_DULL }; int gpiosim_match(struct device *parent, void *match, void *aux) { return 1; } void gpiosim_attach(struct device *parent, struct device *self, void *aux) { struct gpiosim_softc *sc = (void *)self; struct gpiobus_attach_args gba; int i; /* initialize pin array */ for (i = 0; i < GPIOSIM_NPINS; i++) { sc->sc_gpio_pins[i].pin_num = i; sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; /* read initial state */ sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT; sc->sc_state = 0; /* create controller tag */ sc->sc_gpio_gc.gp_cookie = sc; sc->sc_gpio_gc.gp_pin_read = gpiosim_pin_read; sc->sc_gpio_gc.gp_pin_write = gpiosim_pin_write; sc->sc_gpio_gc.gp_pin_ctl = gpiosim_pin_ctl; gba.gba_name = "gpio"; gba.gba_gc = &sc->sc_gpio_gc; gba.gba_pins = sc->sc_gpio_pins; gba.gba_npins = GPIOSIM_NPINS; } printf("\n"); config_found(&sc->sc_dev, &gba, gpiobus_print); bio_register(&sc->sc_dev, gpiosim_ioctl); } int gpiosim_ioctl(struct device *self, u_long cmd, caddr_t data) { struct gpiosim_softc *sc = (void *)self; struct gpiosim_op *op = (void *)data; switch (cmd) { case GPIOSIMREAD: op->state = sc->sc_state; break; case GPIOSIMWRITE: sc->sc_state = (sc->sc_state & ~op->mask) | (op->state & op->mask); break; } return 0; } int gpiosim_pin_read(void *arg, int pin) { struct gpiosim_softc *sc = (struct gpiosim_softc *)arg; if (sc->sc_state & (1 << pin)) return GPIO_PIN_HIGH; else return GPIO_PIN_LOW; } void gpiosim_pin_write(void *arg, int pin, int value) { struct gpiosim_softc *sc = (struct gpiosim_softc *)arg; if (value == 0) sc->sc_state &= ~(1 << pin); else sc->sc_state |= (1 << pin); } void gpiosim_pin_ctl(void *arg, int pin, int flags) { struct gpiosim_softc *sc = (struct gpiosim_softc *)arg; sc->sc_gpio_pins[pin].pin_flags = flags; }
gpiosim(8), the Userland Tool
(gpiosim(8) is an modification of gpioctl(8), which is Copyright © 2004 Alexander Yurchenko <grange@openbsd.org>. The code is shortened.)
#include <dev/biovar.h> #define _PATH_DEV_BIO "/dev/bio" struct gpiosim_op { void *cookie; u_int32_t mask; u_int32_t state; }; #define GPIOSIMREAD _IOWR('G', 0, struct gpiosim_op) #define GPIOSIMWRITE _IOW('G', 1, struct gpiosim_op) char *device = "gpiosim0"; int devfd = -1; void *cookie; int main(int argc, char *argv[]) { struct bio_locate loc; u_int32_t state; int ch; const char *errstr; int do_ctl = 0; int pin = 0, value = 0, setstat = 0; while ((ch = getopt(argc, argv, "d:s:")) != -1) switch (ch) { case 'd': device = optarg; break; case 's': state = strtol(optarg, NULL, 0); setstat = 1; break; default: /* NOTREACHED */ } argc -= optind; argv += optind; if (argc > 0) { pin = strtonum(argv[0], 0, INT_MAX, &errstr); if (errstr) errx(1, "%s: invalid pin", argv[0]); } if ((devfd = open(_PATH_DEV_BIO, O_RDWR)) == -1) err(1, "%s", device); loc.bl_name = device; if (ioctl(devfd, BIOCLOCATE, &loc)) err(1, "%s", device); cookie = loc.bl_cookie; if (argc == 0) { if (setstat) stateset(state); else printf("0x%08x\n", stateget()); } else if (argc == 1) { pinread(pin); } else if (argc > 1) { value = strtonum(argv[1], INT_MIN, INT_MAX, &errstr); if (errstr) errx(1, "%s: invalid value", argv[1]); pinwrite(pin, value); return 0; } void pinread(int pin) { struct gpiosim_op op; bzero(&op, sizeof(op)); op.cookie = cookie; if (ioctl(devfd, GPIOSIMREAD, &op) == -1) err(1, "GPIOSIMREAD"); printf("pin %d: state %d\n", pin, op.state & (1 << pin) ? 1 : 0); } void pinwrite(int pin, int value) { struct gpiosim_op op; if (value < 0 || value > 2) errx(1, "%d: invalid value", value); bzero(&op, sizeof(op)); op.cookie = cookie; if (value) op.state = 1 << pin; else op.state = 0; op.mask = 1 << pin; if (value < 2) { if (ioctl(devfd, GPIOSIMWRITE, &op) == -1) err(1, "GPIOSIMWRITE"); } } u_int32_t stateget(void) { struct gpiosim_op op; bzero(&op, sizeof(op)); op.cookie = cookie; if (ioctl(devfd, GPIOSIMREAD, &op) == -1) err(1, "GPIOSIMREAD"); return op.state; } void stateset(u_int32_t state) { struct gpiosim_op op; bzero(&op, sizeof(op)); op.cookie = cookie; op.state = state; op.mask = 0xffffffff; if (ioctl(devfd, GPIOSIMWRITE, &op) == -1) err(1, "GPIOSIMWRITE"); }
A diff adding gpiosim(4) to OpenBSD 4.2-current as of january 2008 can be found here.
category:
- Zum Verfassen von Kommentaren bitte Anmelden.