Module Name: src Committed By: brad Date: Wed Nov 8 23:46:55 UTC 2023
Modified Files: src/doc: CHANGES src/share/man/man4: gpiosim.4 src/sys/dev/gpio: gpiosim.c Log Message: Simple simulated interrupts for the simulated GPIO device gpiosim(4) To generate a diff of this commit: cvs rdiff -u -r1.3016 -r1.3017 src/doc/CHANGES cvs rdiff -u -r1.6 -r1.7 src/share/man/man4/gpiosim.4 cvs rdiff -u -r1.24 -r1.25 src/sys/dev/gpio/gpiosim.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/doc/CHANGES diff -u src/doc/CHANGES:1.3016 src/doc/CHANGES:1.3017 --- src/doc/CHANGES:1.3016 Mon Nov 6 00:35:06 2023 +++ src/doc/CHANGES Wed Nov 8 23:46:54 2023 @@ -1,4 +1,4 @@ -# LIST OF CHANGES FROM LAST RELEASE: <$Revision: 1.3016 $> +# LIST OF CHANGES FROM LAST RELEASE: <$Revision: 1.3017 $> # # # [Note: This file does not mention every change made to the NetBSD source tree. @@ -261,3 +261,5 @@ Changes from NetBSD 10.0 to NetBSD 11.0: gpioirq(4): allow multiple pins per gpioirq instance, add the ability to use a /dev/gpioirqN device to get pin interrupts into userland. [brad 20231105] + gpiosim(4): Simulated interrupts + [brad 20231108] Index: src/share/man/man4/gpiosim.4 diff -u src/share/man/man4/gpiosim.4:1.6 src/share/man/man4/gpiosim.4:1.7 --- src/share/man/man4/gpiosim.4:1.6 Mon Jul 3 21:30:58 2017 +++ src/share/man/man4/gpiosim.4 Wed Nov 8 23:46:54 2023 @@ -1,4 +1,4 @@ -.\" $NetBSD: gpiosim.4,v 1.6 2017/07/03 21:30:58 wiz Exp $ +.\" $NetBSD: gpiosim.4,v 1.7 2023/11/08 23:46:54 brad Exp $ .\" .\" Copyright (c) 2009, 2013 Marc Balmer <m...@msys.ch> .\" All rights reserved. @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd May 20, 2013 +.Dd November 8, 2013 .Dt GPIOSIM 4 .Os .Sh NAME @@ -37,12 +37,21 @@ For this purpose, access the "hw.gpiosim variable, where "<N>" denotes the number of the .Nm instance. +.Pp +Both edge and level interrupts are simulated. The "hw.gpiosim<N>.ms" +.Xr sysctl 8 +variable will change the amount of time between +.Xr callout 9 +ticks for simulated level interrupts. .Sh SEE ALSO .Xr gpio 4 , +.Xr gpioirq 4 , .Xr sysctl 8 .Sh AUTHORS .An -nosplit The .Nm driver was written by -.An Marc Balmer Aq Mt m...@msys.ch . +.An Marc Balmer Aq Mt m...@msys.ch +Simulated interrupts added by +.An Brad Spencer Aq Mt b...@anduin.eldar.org . Index: src/sys/dev/gpio/gpiosim.c diff -u src/sys/dev/gpio/gpiosim.c:1.24 src/sys/dev/gpio/gpiosim.c:1.25 --- src/sys/dev/gpio/gpiosim.c:1.24 Wed May 10 00:09:39 2023 +++ src/sys/dev/gpio/gpiosim.c Wed Nov 8 23:46:54 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: gpiosim.c,v 1.24 2023/05/10 00:09:39 riastradh Exp $ */ +/* $NetBSD: gpiosim.c,v 1.25 2023/11/08 23:46:54 brad Exp $ */ /* $OpenBSD: gpiosim.c,v 1.1 2008/11/23 18:46:49 mbalmer Exp $ */ /* @@ -29,34 +29,65 @@ #include <sys/sysctl.h> #include <sys/ioccom.h> #include <dev/gpio/gpiovar.h> +#include <sys/callout.h> +#include <sys/workqueue.h> #include "gpiosim.h" #include "ioconf.h" #define GPIOSIM_NPINS 64 +struct gpiosim_irq { + int (*sc_gpio_irqfunc)(void *); + void *sc_gpio_irqarg; + int sc_gpio_irqmode; + bool sc_gpio_irqtriggered; +}; + struct gpiosim_softc { device_t sc_dev; device_t sc_gdev; /* gpio that attaches here */ uint64_t sc_state; struct gpio_chipset_tag sc_gpio_gc; gpio_pin_t sc_gpio_pins[GPIOSIM_NPINS]; + struct gpiosim_irq sc_gpio_irqs[GPIOSIM_NPINS]; struct sysctllog *sc_log; + struct workqueue *sc_wq; + callout_t sc_co; + bool sc_co_init; + bool sc_co_running; + int sc_ms; + kmutex_t sc_intr_mutex; }; static int gpiosim_match(device_t, cfdata_t, void *); static void gpiosim_attach(device_t, device_t, void *); static int gpiosim_detach(device_t, int); static int gpiosim_sysctl(SYSCTLFN_PROTO); +static int gpiosim_ms_sysctl(SYSCTLFN_PROTO); static int gpiosim_pin_read(void *, int); static void gpiosim_pin_write(void *, int, int); static void gpiosim_pin_ctl(void *, int, int); +static void * gpiosim_intr_establish(void *, int, int, int, + int (*)(void *), void *); +static void gpiosim_intr_disestablish(void *, void *); +static bool gpiosim_gpio_intrstr(void *, int, int, char *, size_t); + +void gpiosim_wq(struct work *,void *); +void gpiosim_co(void *); + CFATTACH_DECL_NEW(gpiosim, sizeof(struct gpiosim_softc), gpiosim_match, gpiosim_attach, gpiosim_detach, NULL); +int gpiosim_work; + +#ifndef GPIOSIM_MS +#define GPIOSIM_MS 1000 +#endif + static int gpiosim_match(device_t parent, cfdata_t match, void *aux) { @@ -90,6 +121,7 @@ gpiosim_attach(device_t parent, device_t struct gpiobus_attach_args gba; const struct sysctlnode *node; int i; + int error = 0; sc->sc_dev = self; @@ -103,16 +135,36 @@ gpiosim_attach(device_t parent, device_t GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; + /* Set up what interrupt types are allowed */ + sc->sc_gpio_pins[i].pin_intrcaps = + GPIO_INTR_POS_EDGE | + GPIO_INTR_NEG_EDGE | + GPIO_INTR_DOUBLE_EDGE | + GPIO_INTR_HIGH_LEVEL | + GPIO_INTR_LOW_LEVEL | + GPIO_INTR_MPSAFE; + sc->sc_gpio_irqs[i].sc_gpio_irqfunc = NULL; + sc->sc_gpio_irqs[i].sc_gpio_irqarg = NULL; + sc->sc_gpio_irqs[i].sc_gpio_irqmode = 0; + sc->sc_gpio_irqs[i].sc_gpio_irqtriggered = false; + /* read initial state */ - sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT; - } + sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT;} + sc->sc_state = 0; + sc->sc_ms = GPIOSIM_MS; + sc->sc_co_init = false; + + mutex_init(&sc->sc_intr_mutex, MUTEX_DEFAULT, IPL_VM); /* 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; + sc->sc_gpio_gc.gp_intr_establish = gpiosim_intr_establish; + sc->sc_gpio_gc.gp_intr_disestablish = gpiosim_intr_disestablish; + sc->sc_gpio_gc.gp_intr_str = gpiosim_gpio_intrstr; /* gba.gba_name = "gpio"; */ gba.gba_gc = &sc->sc_gpio_gc; @@ -141,6 +193,24 @@ gpiosim_attach(device_t parent, device_t gpiosim_sysctl, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); + sysctl_createv(&sc->sc_log, 0, &node, NULL, + CTLFLAG_READWRITE, + CTLTYPE_INT, "ms", + SYSCTL_DESCR("Number of ms for level interrupts"), + gpiosim_ms_sysctl, 0, &sc->sc_ms, 0, + CTL_CREATE, CTL_EOL); + + error = workqueue_create(&sc->sc_wq,"gsimwq",gpiosim_wq,sc,PRI_NONE,IPL_VM,WQ_MPSAFE); + if (error != 0) { + aprint_error(": can't create workqueue for interrupts\n"); + return; + } + + callout_init(&sc->sc_co,CALLOUT_MPSAFE); + callout_setfunc(&sc->sc_co,gpiosim_co, sc); + sc->sc_co_running = false; + sc->sc_co_init = true; + aprint_normal(": simulating %d pins\n", GPIOSIM_NPINS); sc->sc_gdev = config_found(self, &gba, gpiobus_print, CFARGS_NONE); } @@ -157,10 +227,24 @@ gpiosim_detach(device_t self, int flags) return error; pmf_device_deregister(self); + if (sc->sc_log != NULL) { sysctl_teardown(&sc->sc_log); sc->sc_log = NULL; } + + /* Destroy the workqueue, hope that it is empty */ + if (sc->sc_wq != NULL) { + workqueue_destroy(sc->sc_wq); + } + + sc->sc_co_running = false; + + /* Destroy any callouts */ + if (sc->sc_co_init) { + callout_halt(&sc->sc_co,NULL); + callout_destroy(&sc->sc_co); + } return 0; } @@ -170,6 +254,10 @@ gpiosim_sysctl(SYSCTLFN_ARGS) struct sysctlnode node; struct gpiosim_softc *sc; uint64_t val, error; + uint64_t previous_val; + int i; + struct gpiosim_irq *irq; + int t = 0; node = *rnode; sc = node.sysctl_data; @@ -181,10 +269,79 @@ gpiosim_sysctl(SYSCTLFN_ARGS) if (error || newp == NULL) return error; + mutex_enter(&sc->sc_intr_mutex); + previous_val = sc->sc_state; sc->sc_state = val; + for (i = 0; i < GPIOSIM_NPINS; i++) { + irq = &sc->sc_gpio_irqs[i]; + /* Simulate edge interrupts ... */ + if ((previous_val & (1LL << i)) == 0 && (sc->sc_state & (1LL << i)) && + irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & (GPIO_INTR_POS_EDGE | GPIO_INTR_DOUBLE_EDGE))) { + irq->sc_gpio_irqtriggered = true; + t++; + } + if ((previous_val & (1LL << i)) && (sc->sc_state & (1LL << i)) == 0 && + irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & (GPIO_INTR_NEG_EDGE | GPIO_INTR_DOUBLE_EDGE))) { + irq->sc_gpio_irqtriggered = true; + t++; + } + /* Simulate level interrupts ... */ + if ((sc->sc_state & (1LL << i)) && irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & GPIO_INTR_HIGH_LEVEL)) { + irq->sc_gpio_irqtriggered = true; + } + if ((sc->sc_state & (1LL << i)) == 0 && irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & GPIO_INTR_LOW_LEVEL)) { + irq->sc_gpio_irqtriggered = true; + } + if ((sc->sc_state & (1LL << i)) && irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & GPIO_INTR_LOW_LEVEL)) { + irq->sc_gpio_irqtriggered = false; + } + if ((sc->sc_state & (1LL << i)) == 0 && irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & GPIO_INTR_HIGH_LEVEL)) { + irq->sc_gpio_irqtriggered = false; + } + } + mutex_exit(&sc->sc_intr_mutex); + + if (t > 0) { + workqueue_enqueue(sc->sc_wq,(struct work *)&gpiosim_work,NULL); + } + return 0; } +int +gpiosim_ms_sysctl(SYSCTLFN_ARGS) +{ + int error, t; + struct sysctlnode node; + + node = *rnode; + t = *(int*)rnode->sysctl_data; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return (error); + + if (t < 1) + return (EINVAL); + + *(int*)rnode->sysctl_data = t; + + return (0); +} + +/* Interrupts though the read and write path are not simulated, + * that is, an interrupt on the setting of an output or an + * interrupt on a pin read. It is not at all clear that it makes + * any sense to do any of that, although real hardware in some cases + * might trigger an interrupt on an output pin. + */ + static int gpiosim_pin_read(void *arg, int pin) { @@ -215,6 +372,127 @@ gpiosim_pin_ctl(void *arg, int pin, int sc->sc_gpio_pins[pin].pin_flags = flags; } +static void * +gpiosim_intr_establish(void *vsc, int pin, int ipl, int irqmode, + int (*func)(void *), void *arg) +{ + struct gpiosim_softc * const sc = vsc; + struct gpiosim_irq *irq; + + mutex_enter(&sc->sc_intr_mutex); + irq = &sc->sc_gpio_irqs[pin]; + irq->sc_gpio_irqfunc = func; + irq->sc_gpio_irqmode = irqmode; + irq->sc_gpio_irqarg = arg; + + /* The first level interrupt starts the callout if it is not running */ + if (((irqmode & GPIO_INTR_HIGH_LEVEL) || + (irqmode & GPIO_INTR_LOW_LEVEL)) && + (sc->sc_co_running == false)) { + callout_schedule(&sc->sc_co,mstohz(sc->sc_ms)); + sc->sc_co_running = true; + } + + /* Level interrupts can start as soon as a IRQ handler is installed */ + if (((irqmode & GPIO_INTR_HIGH_LEVEL) && (sc->sc_state & (1LL << pin))) || + ((irqmode & GPIO_INTR_LOW_LEVEL) && ((sc->sc_state & (1LL << pin)) == 0))) { + irq->sc_gpio_irqtriggered = true; + } + + mutex_exit(&sc->sc_intr_mutex); + + return(irq); +} + +static void +gpiosim_intr_disestablish(void *vsc, void *ih) +{ + struct gpiosim_softc * const sc = vsc; + struct gpiosim_irq *irq = ih; + struct gpiosim_irq *lirq; + int i; + bool has_level = false; + + mutex_enter(&sc->sc_intr_mutex); + irq->sc_gpio_irqfunc = NULL; + irq->sc_gpio_irqmode = 0; + irq->sc_gpio_irqarg = NULL; + irq->sc_gpio_irqtriggered = false; + + /* Check for any level interrupts and stop the callout + * if there are none. + */ + for (i = 0;i < GPIOSIM_NPINS; i++) { + lirq = &sc->sc_gpio_irqs[i]; + if (lirq->sc_gpio_irqmode & (GPIO_INTR_HIGH_LEVEL | GPIO_INTR_LOW_LEVEL)) { + has_level = true; + break; + } + } + if (has_level == false) { + sc->sc_co_running = false; + } + mutex_exit(&sc->sc_intr_mutex); +} + +static bool +gpiosim_gpio_intrstr(void *vsc, int pin, int irqmode, char *buf, size_t buflen) +{ + + if (pin < 0 || pin >= GPIOSIM_NPINS) + return (false); + + snprintf(buf, buflen, "GPIO %d", pin); + + return (true); +} + +/* The workqueue handles edge the simulation of edge interrupts */ +void +gpiosim_wq(struct work *wk, void *arg) +{ + struct gpiosim_softc *sc = arg; + struct gpiosim_irq *irq; + int i; + + mutex_enter(&sc->sc_intr_mutex); + for (i = 0; i < GPIOSIM_NPINS; i++) { + irq = &sc->sc_gpio_irqs[i]; + if (irq->sc_gpio_irqtriggered && + irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & (GPIO_INTR_POS_EDGE | GPIO_INTR_NEG_EDGE | GPIO_INTR_DOUBLE_EDGE))) { + (*irq->sc_gpio_irqfunc)(irq->sc_gpio_irqarg); + irq->sc_gpio_irqtriggered = false; + } + } + mutex_exit(&sc->sc_intr_mutex); +} + +/* This runs as long as there are level interrupts to simulate */ +void +gpiosim_co(void *arg) +{ + struct gpiosim_softc *sc = arg; + struct gpiosim_irq *irq; + int i; + + mutex_enter(&sc->sc_intr_mutex); + for (i = 0; i < GPIOSIM_NPINS; i++) { + irq = &sc->sc_gpio_irqs[i]; + if (irq->sc_gpio_irqtriggered && + irq->sc_gpio_irqfunc != NULL && + (irq->sc_gpio_irqmode & (GPIO_INTR_HIGH_LEVEL | GPIO_INTR_LOW_LEVEL))) { + (*irq->sc_gpio_irqfunc)(irq->sc_gpio_irqarg); + } + } + mutex_exit(&sc->sc_intr_mutex); + + if (sc->sc_co_running == true) { + callout_schedule(&sc->sc_co,mstohz(sc->sc_ms)); + } +} + + MODULE(MODULE_CLASS_DRIVER, gpiosim, "gpio"); #ifdef _MODULE