Sie sind hier

Simulating GPIO Pins

Gespeichert von Marc Balmer am Do., 01/03/2008 - 17:35

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.

<Nick> What is a GPIO Simulator? <kettenis> You write to a pin and nothing happens...

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: