diff --git a/sys/dev/gpio/gpiobus.c b/sys/dev/gpio/gpiobus.c index bd0bae96b1df..e1f74d81955e 100644 --- a/sys/dev/gpio/gpiobus.c +++ b/sys/dev/gpio/gpiobus.c @@ -143,6 +143,15 @@ gpio_check_flags(uint32_t caps, uint32_t flags) /* Cannot mix pull-up/pull-down together. */ if (flags & GPIO_PIN_PULLUP && flags & GPIO_PIN_PULLDOWN) return (EINVAL); + /* Cannot mix output and interrupt flags together */ + if (flags & GPIO_PIN_OUTPUT && flags & GPIO_INTR_MASK) + return (EINVAL); + /* Only one interrupt flag can be defined at once */ + if ((flags & GPIO_INTR_MASK) & ((flags & GPIO_INTR_MASK) - 1)) + return (EINVAL); + /* The interrupt attached flag cannot be set */ + if (flags & GPIO_INTR_ATTACHED) + return (EINVAL); return (0); } diff --git a/sys/dev/gpio/gpioc.c b/sys/dev/gpio/gpioc.c index dfa421ed49af..727b07a70589 100644 --- a/sys/dev/gpio/gpioc.c +++ b/sys/dev/gpio/gpioc.c @@ -35,8 +35,15 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include @@ -47,29 +54,509 @@ __FBSDID("$FreeBSD$"); #undef GPIOC_DEBUG #ifdef GPIOC_DEBUG #define dprintf printf +#define ddevice_printf device_printf #else #define dprintf(x, arg...) +#define ddevice_printf(dev, x, arg...) #endif -static int gpioc_probe(device_t dev); -static int gpioc_attach(device_t dev); -static int gpioc_detach(device_t dev); +struct gpioc_softc { + device_t sc_dev; /* gpiocX dev */ + device_t sc_pdev; /* gpioX dev */ + struct cdev *sc_ctl_dev; /* controller device */ + int sc_unit; + int sc_npins; + struct gpioc_pin_intr *sc_pin_intr; +}; +struct gpioc_pin_intr { + struct gpioc_softc *sc; + gpio_pin_t pin; + bool config_locked; + int intr_rid; + struct resource *intr_res; + void *intr_cookie; + struct mtx mtx; + SLIST_HEAD(gpioc_privs_list, gpioc_privs) privs; +}; + + +struct gpioc_cdevpriv { + struct gpioc_softc *sc; + struct selinfo selinfo; + bool async; + uint8_t report_option; + struct sigio *sigio; + struct mtx mtx; + struct gpioc_pin_event *events; + int numevents; + int evidx_head; + int evidx_tail; + SLIST_HEAD(gpioc_pins_list, gpioc_pins) pins; +}; + +struct gpioc_privs { + struct gpioc_cdevpriv *priv; + SLIST_ENTRY(gpioc_privs) next; +}; + +struct gpioc_pins { + struct gpioc_pin_intr *pin; + int eventcount; + int firstevent; + SLIST_ENTRY(gpioc_pins) next; +}; + +struct gpioc_pin_event { + struct gpioc_pins *privpin; + sbintime_t event_time; + bool event_pin_state; +}; + +static MALLOC_DEFINE(M_GPIOC, "gpioc", "gpioc device data"); + +static int gpioc_allocate_pin_intr(struct gpioc_pin_intr*, uint32_t); +static int gpioc_release_pin_intr(struct gpioc_pin_intr*); +static int gpioc_attach_priv_pin(struct gpioc_cdevpriv*, + struct gpioc_pin_intr*); +static int gpioc_detach_priv_pin(struct gpioc_cdevpriv*, + struct gpioc_pin_intr*); +static bool gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv*, + struct gpioc_pin_intr *intr_conf); +static uint32_t gpioc_get_intr_config(struct gpioc_softc*, + struct gpioc_cdevpriv*, uint32_t pin); +static int gpioc_set_intr_config(struct gpioc_softc*, + struct gpioc_cdevpriv*, uint32_t, uint32_t); +static void gpioc_interrupt_handler(void*); + +static int gpioc_kqread(struct knote*, long); +static void gpioc_kqdetach(struct knote*); + +static int gpioc_probe(device_t dev); +static int gpioc_attach(device_t dev); +static int gpioc_detach(device_t dev); + +static void gpioc_cdevpriv_dtor(void*); + +static d_open_t gpioc_open; +static d_read_t gpioc_read; static d_ioctl_t gpioc_ioctl; +static d_poll_t gpioc_poll; +static d_kqfilter_t gpioc_kqfilter; static struct cdevsw gpioc_cdevsw = { .d_version = D_VERSION, + .d_open = gpioc_open, + .d_read = gpioc_read, .d_ioctl = gpioc_ioctl, + .d_poll = gpioc_poll, + .d_kqfilter = gpioc_kqfilter, .d_name = "gpioc", }; -struct gpioc_softc { - device_t sc_dev; /* gpiocX dev */ - device_t sc_pdev; /* gpioX dev */ - struct cdev *sc_ctl_dev; /* controller device */ - int sc_unit; +static struct filterops gpioc_read_filterops = { + .f_isfd = true, + .f_attach = NULL, + .f_detach = gpioc_kqdetach, + .f_event = gpioc_kqread, + .f_touch = NULL }; +static struct gpioc_pin_event * +next_head_event(struct gpioc_cdevpriv *priv) +{ + struct gpioc_pin_event *rv; + + rv = &priv->events[priv->evidx_head++]; + if (priv->evidx_head == priv->numevents) + priv->evidx_head = 0; + return (rv); +} + +static struct gpioc_pin_event * +next_tail_event(struct gpioc_cdevpriv *priv) +{ + struct gpioc_pin_event *rv; + + rv = &priv->events[priv->evidx_tail++]; + if (priv->evidx_tail == priv->numevents) + priv->evidx_tail = 0; + return (rv); +} + +static size_t +number_of_events(struct gpioc_cdevpriv *priv) +{ + if (priv->evidx_head >= priv->evidx_tail) + return (priv->evidx_head - priv->evidx_tail); + else + return (priv->numevents + priv->evidx_head - priv->evidx_tail); +} + +static int +gpioc_allocate_pin_intr(struct gpioc_pin_intr *intr_conf, uint32_t flags) +{ + int err; + + intr_conf->config_locked = true; + mtx_unlock(&intr_conf->mtx); + + intr_conf->intr_res = gpio_alloc_intr_resource(intr_conf->pin->dev, + &intr_conf->intr_rid, RF_ACTIVE, intr_conf->pin, flags); + if (intr_conf->intr_res == NULL) { + err = ENXIO; + goto error_exit; + } + + err = bus_setup_intr(intr_conf->pin->dev, intr_conf->intr_res, + INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpioc_interrupt_handler, + intr_conf, &intr_conf->intr_cookie); + if (err != 0) + goto error_exit; + + intr_conf->pin->flags = flags; + +error_exit: + mtx_lock(&intr_conf->mtx); + intr_conf->config_locked = false; + wakeup(&intr_conf->config_locked); + + return (err); +} + +static int +gpioc_release_pin_intr(struct gpioc_pin_intr *intr_conf) +{ + int err; + + intr_conf->config_locked = true; + mtx_unlock(&intr_conf->mtx); + + if (intr_conf->intr_cookie != NULL) { + err = bus_teardown_intr(intr_conf->pin->dev, + intr_conf->intr_res, intr_conf->intr_cookie); + if (err != 0) + goto error_exit; + else + intr_conf->intr_cookie = NULL; + } + + if (intr_conf->intr_res != NULL) { + err = bus_release_resource(intr_conf->pin->dev, SYS_RES_IRQ, + intr_conf->intr_rid, intr_conf->intr_res); + if (err != 0) + goto error_exit; + else { + intr_conf->intr_rid = 0; + intr_conf->intr_res = NULL; + } + } + + intr_conf->pin->flags = 0; + err = 0; + +error_exit: + mtx_lock(&intr_conf->mtx); + intr_conf->config_locked = false; + wakeup(&intr_conf->config_locked); + + return (err); +} + +static int +gpioc_attach_priv_pin(struct gpioc_cdevpriv *priv, + struct gpioc_pin_intr *intr_conf) +{ + struct gpioc_privs *priv_link; + struct gpioc_pins *pin_link; + unsigned int consistency_a, consistency_b; + + consistency_a = 0; + consistency_b = 0; + mtx_assert(&intr_conf->mtx, MA_OWNED); + mtx_lock(&priv->mtx); + SLIST_FOREACH(priv_link, &intr_conf->privs, next) { + if (priv_link->priv == priv) + consistency_a++; + } + KASSERT(consistency_a <= 1, + ("inconsistent links between pin config and cdevpriv")); + SLIST_FOREACH(pin_link, &priv->pins, next) { + if (pin_link->pin == intr_conf) + consistency_b++; + } + KASSERT(consistency_a == consistency_b, + ("inconsistent links between pin config and cdevpriv")); + if (consistency_a == 1 && consistency_b == 1) { + mtx_unlock(&priv->mtx); + return (EEXIST); + } + priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC, + M_NOWAIT | M_ZERO); + if (priv_link == NULL) + { + mtx_unlock(&priv->mtx); + return (ENOMEM); + } + pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC, + M_NOWAIT | M_ZERO); + if (pin_link == NULL) { + mtx_unlock(&priv->mtx); + return (ENOMEM); + } + priv_link->priv = priv; + pin_link->pin = intr_conf; + SLIST_INSERT_HEAD(&intr_conf->privs, priv_link, next); + SLIST_INSERT_HEAD(&priv->pins, pin_link, next); + mtx_unlock(&priv->mtx); + + return (0); +} + +static int +gpioc_detach_priv_pin(struct gpioc_cdevpriv *priv, + struct gpioc_pin_intr *intr_conf) +{ + struct gpioc_privs *priv_link, *priv_link_temp; + struct gpioc_pins *pin_link, *pin_link_temp; + unsigned int consistency_a, consistency_b; + + consistency_a = 0; + consistency_b = 0; + mtx_assert(&intr_conf->mtx, MA_OWNED); + mtx_lock(&priv->mtx); + SLIST_FOREACH_SAFE(priv_link, &intr_conf->privs, next, priv_link_temp) { + if (priv_link->priv == priv) { + SLIST_REMOVE(&intr_conf->privs, priv_link, gpioc_privs, + next); + free(priv_link, M_GPIOC); + consistency_a++; + } + } + KASSERT(consistency_a <= 1, + ("inconsistent links between pin config and cdevpriv")); + SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) { + if (pin_link->pin == intr_conf) { + /* + * If the pin we're removing has events in the priv's + * event fifo, we can't leave dangling pointers from + * those events to the gpioc_pins struct we're about to + * free. We also can't remove random items and leave + * holes in the events fifo, so just empty it out. + */ + if (pin_link->eventcount > 0) { + priv->evidx_head = priv->evidx_tail = 0; + } + SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next); + free(pin_link, M_GPIOC); + consistency_b++; + } + } + KASSERT(consistency_a == consistency_b, + ("inconsistent links between pin config and cdevpriv")); + mtx_unlock(&priv->mtx); + + return (0); +} + +static bool +gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv *priv, + struct gpioc_pin_intr *intr_conf) +{ + struct gpioc_privs *priv_link; + + mtx_assert(&intr_conf->mtx, MA_OWNED); + + if (SLIST_EMPTY(&intr_conf->privs)) + return (true); + + SLIST_FOREACH(priv_link, &intr_conf->privs, next) { + if (priv_link->priv != priv) + return (false); + } + + return (true); +} + + +static uint32_t +gpioc_get_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv, + uint32_t pin) +{ + struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin]; + struct gpioc_privs *priv_link; + uint32_t flags; + + flags = intr_conf->pin->flags; + + if (flags == 0) + return (0); + + mtx_lock(&intr_conf->mtx); + SLIST_FOREACH(priv_link, &intr_conf->privs, next) { + if (priv_link->priv == priv) { + flags |= GPIO_INTR_ATTACHED; + break; + } + } + mtx_unlock(&intr_conf->mtx); + + return (flags); +} + +static int +gpioc_set_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv, + uint32_t pin, uint32_t flags) +{ + struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin]; + int res; + + res = 0; + if (intr_conf->pin->flags == 0 && flags == 0) { + /* No interrupt configured and none requested: Do nothing. */ + return (0); + } + mtx_lock(&intr_conf->mtx); + while (intr_conf->config_locked == true) + mtx_sleep(&intr_conf->config_locked, &intr_conf->mtx, 0, + "gpicfg", 0); + if (intr_conf->pin->flags == 0 && flags != 0) { + /* + * No interrupt is configured, but one is requested: Allocate + * and setup interrupt on the according pin. + */ + res = gpioc_allocate_pin_intr(intr_conf, flags); + if (res == 0) + res = gpioc_attach_priv_pin(priv, intr_conf); + if (res == EEXIST) + res = 0; + } else if (intr_conf->pin->flags == flags) { + /* + * Same interrupt requested as already configured: Attach the + * cdevpriv to the corresponding pin. + */ + res = gpioc_attach_priv_pin(priv, intr_conf); + if (res == EEXIST) + res = 0; + } else if (intr_conf->pin->flags != 0 && flags == 0) { + /* + * Interrupt configured, but none requested: Teardown and + * release the pin when no other cdevpriv is attached. Otherwise + * just detach pin and cdevpriv from each other. + */ + if (gpioc_intr_reconfig_allowed(priv, intr_conf)) { + res = gpioc_release_pin_intr(intr_conf); + } + if (res == 0) + res = gpioc_detach_priv_pin(priv, intr_conf); + } else { + /* + * Other flag requested than configured: Reconfigure when no + * other cdevpriv is are attached to the pin. + */ + if (!gpioc_intr_reconfig_allowed(priv, intr_conf)) + res = EBUSY; + else { + res = gpioc_release_pin_intr(intr_conf); + if (res == 0) + res = gpioc_allocate_pin_intr(intr_conf, flags); + if (res == 0) + res = gpioc_attach_priv_pin(priv, intr_conf); + if (res == EEXIST) + res = 0; + } + } + mtx_unlock(&intr_conf->mtx); + + return (res); +} + +static void +gpioc_interrupt_handler(void *arg) +{ + struct gpioc_pin_intr *intr_conf; + struct gpioc_privs *privs; + struct gpioc_softc *sc; + sbintime_t evtime; + uint32_t pin_state; + + intr_conf = arg; + sc = intr_conf->sc; + + /* Capture time and pin state first. */ + evtime = sbinuptime(); + if (intr_conf->pin->flags & GPIO_INTR_EDGE_BOTH) + GPIO_PIN_GET(sc->sc_pdev, intr_conf->pin->pin, &pin_state); + else if (intr_conf->pin->flags & GPIO_INTR_EDGE_RISING) + pin_state = true; + else + pin_state = false; + + mtx_lock(&intr_conf->mtx); + + if (intr_conf->config_locked == true) { + ddevice_printf(sc->sc_dev, "Interrupt configuration in " + "progress. Discarding interrupt on pin %d.\n", + intr_conf->pin->pin); + mtx_unlock(&intr_conf->mtx); + return; + } + + if (SLIST_EMPTY(&intr_conf->privs)) { + ddevice_printf(sc->sc_dev, "No file descriptor associated with " + "occurred interrupt on pin %d.\n", intr_conf->pin->pin); + mtx_unlock(&intr_conf->mtx); + return; + } + + SLIST_FOREACH(privs, &intr_conf->privs, next) { + struct gpioc_cdevpriv *priv = privs->priv; + struct gpioc_pins *privpin; + struct gpioc_pin_event *event; + mtx_lock(&priv->mtx); + SLIST_FOREACH(privpin, &priv->pins, next) { + if (privpin->pin == intr_conf) + break; + } + if (privpin == NULL) { + /* Should be impossible. */ + ddevice_printf(sc->sc_dev, "Cannot find privpin\n"); + mtx_unlock(&priv->mtx); + continue; + } + + if (priv->report_option == GPIO_EVENT_REPORT_DETAIL) { + event = next_head_event(priv); + /* If head is overtaking tail, advance tail. */ + if (priv->evidx_head == priv->evidx_tail) + next_tail_event(priv); + } else { + if (privpin->eventcount > 0) + event = &priv->events[privpin->firstevent + 1]; + else { + privpin->firstevent = priv->evidx_head; + event = next_head_event(priv); + event->privpin = privpin; + event->event_time = evtime; + event->event_pin_state = pin_state; + event = next_head_event(priv); + } + ++privpin->eventcount; + } + event->privpin = privpin; + event->event_time = evtime; + event->event_pin_state = pin_state; + wakeup(priv); + selwakeup(&priv->selinfo); + KNOTE_LOCKED(&priv->selinfo.si_note, 0); + if (priv->async == true && priv->sigio != NULL) + pgsigio(&priv->sigio, SIGIO, 0); + mtx_unlock(&priv->mtx); + } + + mtx_unlock(&intr_conf->mtx); +} + static int gpioc_probe(device_t dev) { @@ -88,6 +575,23 @@ gpioc_attach(device_t dev) sc->sc_dev = dev; sc->sc_pdev = device_get_parent(dev); sc->sc_unit = device_get_unit(dev); + + err = GPIO_PIN_MAX(sc->sc_pdev, &sc->sc_npins); + sc->sc_npins++; /* Number of pins is one more than max pin number. */ + if (err != 0) + return (err); + sc->sc_pin_intr = malloc(sizeof(struct gpioc_pin_intr) * sc->sc_npins, + M_GPIOC, M_WAITOK | M_ZERO); + for (int i = 0; i <= sc->sc_npins; i++) { + sc->sc_pin_intr[i].pin = malloc(sizeof(struct gpiobus_pin), + M_GPIOC, M_WAITOK | M_ZERO); + sc->sc_pin_intr[i].sc = sc; + sc->sc_pin_intr[i].pin->pin = i; + sc->sc_pin_intr[i].pin->dev = sc->sc_pdev; + mtx_init(&sc->sc_pin_intr[i].mtx, "gpioc pin", NULL, MTX_DEF); + SLIST_INIT(&sc->sc_pin_intr[i].privs); + } + make_dev_args_init(&devargs); devargs.mda_devsw = &gpioc_cdevsw; devargs.mda_uid = UID_ROOT; @@ -96,7 +600,7 @@ gpioc_attach(device_t dev) devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit); if (err != 0) { - printf("Failed to create gpioc%d", sc->sc_unit); + device_printf(dev, "Failed to create gpioc%d", sc->sc_unit); return (ENXIO); } @@ -112,12 +616,160 @@ gpioc_detach(device_t dev) if (sc->sc_ctl_dev) destroy_dev(sc->sc_ctl_dev); + for (int i = 0; i <= sc->sc_npins; i++) { + mtx_destroy(&sc->sc_pin_intr[i].mtx); + free(&sc->sc_pin_intr[i].pin, M_GPIOC); + } + free(sc->sc_pin_intr, M_GPIOC); + if ((err = bus_generic_detach(dev)) != 0) return (err); return (0); } +static void +gpioc_cdevpriv_dtor(void *data) +{ + struct gpioc_cdevpriv *priv; + struct gpioc_privs *priv_link, *priv_link_temp; + struct gpioc_pins *pin_link, *pin_link_temp; + unsigned int consistency; + + priv = data; + + SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) { + consistency = 0; + mtx_lock(&pin_link->pin->mtx); + while (pin_link->pin->config_locked == true) + mtx_sleep(&pin_link->pin->config_locked, + &pin_link->pin->mtx, 0, "gpicfg", 0); + SLIST_FOREACH_SAFE(priv_link, &pin_link->pin->privs, next, + priv_link_temp) { + if (priv_link->priv == priv) { + SLIST_REMOVE(&pin_link->pin->privs, priv_link, + gpioc_privs, next); + free(priv_link, M_GPIOC); + consistency++; + } + } + KASSERT(consistency == 1, + ("inconsistent links between pin config and cdevpriv")); + if (gpioc_intr_reconfig_allowed(priv, pin_link->pin)) { + gpioc_release_pin_intr(pin_link->pin); + } + mtx_unlock(&pin_link->pin->mtx); + SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next); + free(pin_link, M_GPIOC); + } + + wakeup(&priv); + knlist_clear(&priv->selinfo.si_note, 0); + seldrain(&priv->selinfo); + knlist_destroy(&priv->selinfo.si_note); + funsetown(&priv->sigio); + + mtx_destroy(&priv->mtx); + free(priv->events, M_GPIOC); + free(data, M_GPIOC); +} + +static int +gpioc_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + struct gpioc_cdevpriv *priv; + int err; + + priv = malloc(sizeof(*priv), M_GPIOC, M_WAITOK | M_ZERO); + priv->sc = dev->si_drv1; + priv->report_option = GPIO_EVENT_REPORT_DETAIL; + err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor); + if (err != 0) { + gpioc_cdevpriv_dtor(priv); + return (err); + } + mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF); + knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx); + + /* + * Allocate a circular buffer for events. The scheme we use for summary + * reporting assumes there will always be a pair of events available to + * record the first/last events on any pin, so we allocate 2 * npins. + * Even though we actually default to detailed event reporting, 2 * + * npins isn't a horrible fifo size for that either. + */ + priv->numevents = priv->sc->sc_npins * 2; + priv->events = malloc(priv->numevents * sizeof(struct gpio_event_detail), + M_GPIOC, M_WAITOK | M_ZERO); + + return (0); +} + +static int +gpioc_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct gpioc_cdevpriv *priv; + struct gpioc_pin_event *event; + union { + struct gpio_event_summary sum; + struct gpio_event_detail evt; + uint8_t data[1]; + } recbuf; + size_t recsize; + int err; + + if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) + return (err); + + if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) + recsize = sizeof(struct gpio_event_summary); + else + recsize = sizeof(struct gpio_event_detail); + + if (uio->uio_resid < recsize) + return (EINVAL); + + mtx_lock(&priv->mtx); + while (priv->evidx_head == priv->evidx_tail) { + if (SLIST_EMPTY(&priv->pins)) { + err = ENXIO; + break; + } else if (ioflag & O_NONBLOCK) { + err = EWOULDBLOCK; + break; + } else { + err = mtx_sleep(priv, &priv->mtx, PCATCH, "gpintr", 0); + if (err != 0) + break; + } + } + + while (err == 0 && uio->uio_resid >= recsize && + priv->evidx_tail != priv->evidx_head) { + event = next_tail_event(priv); + if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) { + recbuf.sum.gp_first_time = event->event_time; + recbuf.sum.gp_pin = event->privpin->pin->pin->pin; + recbuf.sum.gp_count = event->privpin->eventcount; + recbuf.sum.gp_first_state = event->event_pin_state; + event = next_tail_event(priv); + recbuf.sum.gp_last_time = event->event_time; + recbuf.sum.gp_last_state = event->event_pin_state; + event->privpin->eventcount = 0; + event->privpin->firstevent = 0; + } else { + recbuf.evt.gp_time = event->event_time; + recbuf.evt.gp_pin = event->privpin->pin->pin->pin; + recbuf.evt.gp_pinstate = event->event_pin_state; + } + mtx_unlock(&priv->mtx); + err = uiomove(recbuf.data, recsize, uio); + mtx_lock(&priv->mtx); + } + mtx_unlock(&priv->mtx); + return (err); +} + static int gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, struct thread *td) @@ -125,88 +777,270 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, device_t bus; int max_pin, res; struct gpioc_softc *sc = cdev->si_drv1; + struct gpioc_cdevpriv *priv; struct gpio_pin pin; struct gpio_req req; struct gpio_access_32 *a32; struct gpio_config_32 *c32; - uint32_t caps; + struct gpio_event_config *evcfg; + uint32_t caps, intrflags; bus = GPIO_GET_BUS(sc->sc_pdev); if (bus == NULL) return (EINVAL); switch (cmd) { - case GPIOMAXPIN: - max_pin = -1; - res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin); - bcopy(&max_pin, arg, sizeof(max_pin)); + case GPIOMAXPIN: + max_pin = -1; + res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin); + bcopy(&max_pin, arg, sizeof(max_pin)); + break; + case GPIOGETCONFIG: + bcopy(arg, &pin, sizeof(pin)); + dprintf("get config pin %d\n", pin.gp_pin); + res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin, + &pin.gp_flags); + /* Fail early */ + if (res) break; - case GPIOGETCONFIG: - bcopy(arg, &pin, sizeof(pin)); - dprintf("get config pin %d\n", pin.gp_pin); - res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin, - &pin.gp_flags); - /* Fail early */ - if (res) - break; - GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps); - GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name); - bcopy(&pin, arg, sizeof(pin)); + res = devfs_get_cdevpriv((void **)&priv); + if (res) break; - case GPIOSETCONFIG: - bcopy(arg, &pin, sizeof(pin)); - dprintf("set config pin %d\n", pin.gp_pin); - res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps); - if (res == 0) - res = gpio_check_flags(caps, pin.gp_flags); - if (res == 0) - res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin, - pin.gp_flags); + pin.gp_flags |= gpioc_get_intr_config(sc, priv, + pin.gp_pin); + GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps); + GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name); + bcopy(&pin, arg, sizeof(pin)); + break; + case GPIOSETCONFIG: + bcopy(arg, &pin, sizeof(pin)); + dprintf("set config pin %d\n", pin.gp_pin); + res = devfs_get_cdevpriv((void **)&priv); + if (res != 0) break; - case GPIOGET: - bcopy(arg, &req, sizeof(req)); - res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin, - &req.gp_value); - dprintf("read pin %d -> %d\n", - req.gp_pin, req.gp_value); - bcopy(&req, arg, sizeof(req)); + res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps); + if (res != 0) break; - case GPIOSET: - bcopy(arg, &req, sizeof(req)); - res = GPIO_PIN_SET(sc->sc_pdev, req.gp_pin, - req.gp_value); - dprintf("write pin %d -> %d\n", - req.gp_pin, req.gp_value); + res = gpio_check_flags(caps, pin.gp_flags); + if (res != 0) break; - case GPIOTOGGLE: - bcopy(arg, &req, sizeof(req)); - dprintf("toggle pin %d\n", - req.gp_pin); - res = GPIO_PIN_TOGGLE(sc->sc_pdev, req.gp_pin); + intrflags = pin.gp_flags & GPIO_INTR_MASK; + /* + * We can do only edge interrupts, and only if the + * hardware supports that interrupt type on that pin. + */ + switch (intrflags) { + case GPIO_INTR_NONE: break; - case GPIOSETNAME: - bcopy(arg, &pin, sizeof(pin)); - dprintf("set name on pin %d\n", pin.gp_pin); - res = GPIOBUS_PIN_SETNAME(bus, pin.gp_pin, - pin.gp_name); - break; - case GPIOACCESS32: - a32 = (struct gpio_access_32 *)arg; - res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin, - a32->clear_pins, a32->change_pins, &a32->orig_pins); - break; - case GPIOCONFIG32: - c32 = (struct gpio_config_32 *)arg; - res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin, - c32->num_pins, c32->pin_flags); + case GPIO_INTR_EDGE_RISING: + case GPIO_INTR_EDGE_FALLING: + case GPIO_INTR_EDGE_BOTH: + if ((intrflags & caps) == 0) + res = EOPNOTSUPP; break; default: - return (ENOTTY); + res = EINVAL; break; + } + if (res != 0) + break; + res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin, + (pin.gp_flags & ~GPIO_INTR_MASK)); + if (res != 0) + break; + res = gpioc_set_intr_config(sc, priv, pin.gp_pin, + intrflags); + break; + case GPIOGET: + bcopy(arg, &req, sizeof(req)); + res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin, + &req.gp_value); + dprintf("read pin %d -> %d\n", + req.gp_pin, req.gp_value); + bcopy(&req, arg, sizeof(req)); + break; + case GPIOSET: + bcopy(arg, &req, sizeof(req)); + res = GPIO_PIN_SET(sc->sc_pdev, req.gp_pin, + req.gp_value); + dprintf("write pin %d -> %d\n", + req.gp_pin, req.gp_value); + break; + case GPIOTOGGLE: + bcopy(arg, &req, sizeof(req)); + dprintf("toggle pin %d\n", + req.gp_pin); + res = GPIO_PIN_TOGGLE(sc->sc_pdev, req.gp_pin); + break; + case GPIOSETNAME: + bcopy(arg, &pin, sizeof(pin)); + dprintf("set name on pin %d\n", pin.gp_pin); + res = GPIOBUS_PIN_SETNAME(bus, pin.gp_pin, + pin.gp_name); + break; + case GPIOACCESS32: + a32 = (struct gpio_access_32 *)arg; + res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin, + a32->clear_pins, a32->change_pins, &a32->orig_pins); + break; + case GPIOCONFIG32: + c32 = (struct gpio_config_32 *)arg; + res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin, + c32->num_pins, c32->pin_flags); + break; + case GPIOCONFIGEVENTS: + evcfg = (struct gpio_event_config *)arg; + res = devfs_get_cdevpriv((void **)&priv); + if (res != 0) + break; + /* If any pins have been configured, changes aren't allowed. */ + if (!SLIST_EMPTY(&priv->pins)) { + res = EINVAL; + break; + } + if (evcfg->gp_report_type != GPIO_EVENT_REPORT_DETAIL && + evcfg->gp_report_type != GPIO_EVENT_REPORT_SUMMARY) { + res = EINVAL; + break; + } + priv->report_option = evcfg->gp_report_type; + /* Reallocate the events buffer if the user wants it bigger. */ + if (priv->report_option == GPIO_EVENT_REPORT_DETAIL && + priv->numevents < evcfg->gp_fifo_size) { + free(priv->events, M_GPIOC); + priv->numevents = evcfg->gp_fifo_size; + priv->events = malloc(priv->numevents * + sizeof(struct gpio_event_detail), M_GPIOC, + M_WAITOK | M_ZERO); + priv->evidx_head = priv->evidx_tail = 0; + } + break; + case FIONBIO: + /* + * This dummy handler is necessary to prevent fcntl() + * from failing. The actual handling of non-blocking IO + * is done using the O_NONBLOCK ioflag passed to the + * read() syscall. + */ + res = 0; + break; + case FIOASYNC: + res = devfs_get_cdevpriv((void **)&priv); + if (res == 0) { + if (*(int *)arg == FASYNC) + priv->async = true; + else + priv->async = false; + } + break; + case FIOGETOWN: + res = devfs_get_cdevpriv((void **)&priv); + if (res == 0) + *(int *)arg = fgetown(&priv->sigio); + break; + case FIOSETOWN: + res = devfs_get_cdevpriv((void **)&priv); + if (res == 0) + res = fsetown(*(int *)arg, &priv->sigio); + break; + default: + return (ENOTTY); + break; } return (res); } +static int +gpioc_poll(struct cdev *dev, int events, struct thread *td) +{ + struct gpioc_cdevpriv *priv; + int err; + int revents; + + revents = 0; + + err = devfs_get_cdevpriv((void **)&priv); + if (err != 0) { + revents = POLLERR; + return (revents); + } + + if (SLIST_EMPTY(&priv->pins)) { + revents = POLLHUP; + return (revents); + } + + if (events & (POLLIN | POLLRDNORM)) { + if (priv->evidx_head != priv->evidx_tail) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(td, &priv->selinfo); + } + + return (revents); +} + +static int +gpioc_kqfilter(struct cdev *dev, struct knote *kn) +{ + struct gpioc_cdevpriv *priv; + struct knlist *knlist; + int err; + + err = devfs_get_cdevpriv((void **)&priv); + if (err != 0) + return err; + + if (SLIST_EMPTY(&priv->pins)) + return (ENXIO); + + switch(kn->kn_filter) { + case EVFILT_READ: + kn->kn_fop = &gpioc_read_filterops; + kn->kn_hook = (void *)priv; + break; + default: + return (EOPNOTSUPP); + } + + knlist = &priv->selinfo.si_note; + knlist_add(knlist, kn, 0); + + return (0); +} + +static int +gpioc_kqread(struct knote *kn, long hint) +{ + struct gpioc_cdevpriv *priv = kn->kn_hook; + size_t recsize; + + + if (SLIST_EMPTY(&priv->pins)) { + kn->kn_flags |= EV_EOF; + return (1); + } else { + if (priv->evidx_head != priv->evidx_tail) { + if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) + recsize = sizeof(struct gpio_event_summary); + else + recsize = sizeof(struct gpio_event_detail); + kn->kn_data = recsize * number_of_events(priv); + return (1); + } + } + return (0); +} + +static void +gpioc_kqdetach(struct knote *kn) +{ + struct gpioc_cdevpriv *priv = kn->kn_hook; + struct knlist *knlist = &priv->selinfo.si_note; + + knlist_remove(knlist, kn, 0); +} + static device_method_t gpioc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioc_probe), diff --git a/sys/sys/gpio.h b/sys/sys/gpio.h index 2505269de64d..50b9b9dbb0e5 100644 --- a/sys/sys/gpio.h +++ b/sys/sys/gpio.h @@ -53,6 +53,9 @@ #define __GPIO_H__ #include +#ifndef _KERNEL +#include +#endif /* GPIO pin states */ #define GPIO_PIN_LOW 0x00 /* low level (logical 0) */ @@ -81,9 +84,11 @@ #define GPIO_INTR_EDGE_RISING 0x00040000 /* edge trigger, rising */ #define GPIO_INTR_EDGE_FALLING 0x00080000 /* edge trigger, falling */ #define GPIO_INTR_EDGE_BOTH 0x00100000 /* edge trigger, both */ -#define GPIO_INTR_MASK (GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | \ - GPIO_INTR_EDGE_RISING | \ - GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH) +#define GPIO_INTR_ATTACHED 0x00200000 /* interrupt attached to file */ +#define GPIO_INTR_MASK (GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | \ + GPIO_INTR_EDGE_RISING | \ + GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH | \ + GPIO_INTR_ATTACHED) struct gpio_pin { uint32_t gp_pin; /* pin number */ @@ -98,6 +103,53 @@ struct gpio_req { uint32_t gp_value; /* value */ }; +/* + * Reporting gpio pin-change per-event details to userland. + * + * When configured for detail reporting, each call to read(2) will return one or + * more of these structures (or will return EWOULDBLOCK in non-blocking IO mode + * when there are no new events to report). + */ +struct gpio_event_detail { + sbintime_t gp_time; /* Time of event */ + uint16_t gp_pin; /* Pin number */ + bool gp_pinstate; /* Pin state at time of event */ +}; + +/* + * Reporting gpio pin-change summary data to userland. + * + * When configured for summary reporting, each call to read(2) will return one + * or more of these structures (or will return EWOULDBLOCK in non-blocking IO + * mode when there are no new events to report). + */ +struct gpio_event_summary { + sbintime_t gp_first_time; /* Time of first event */ + sbintime_t gp_last_time; /* Time of last event */ + uint16_t gp_pin; /* Pin number */ + uint16_t gp_count; /* Event count */ + bool gp_first_state; /* Pin state at first event */ + bool gp_last_state; /* Pin state at last event */ +}; + +/* + * Configuring event reporting to userland. + * + * The default is to deliver gpio_event_detail reporting, with a default fifo + * size of 2 * number of pins belonging to the gpioc device instance. To change + * it, you must use the GPIOCONFIGEVENTS ioctl before using GPIOSETCONFIG to + * configure reporting interrupt events on any pins. This config is tracked on + * a per-open-descriptor basis. + */ +enum { + GPIO_EVENT_REPORT_DETAIL, /* Report detail on each event */ + GPIO_EVENT_REPORT_SUMMARY, /* Report summary of events */ +}; +struct gpio_event_config { + uint32_t gp_report_type; /* Detail or summary reporting */ + uint32_t gp_fifo_size; /* FIFO size (used for detail only) */ +}; + /* * gpio_access_32 / GPIOACCESS32 * @@ -147,7 +199,7 @@ struct gpio_access_32 { * contains any GPIO_PIN_OUTPUT flags, the driver will manipulate the hardware * such that all output pins become driven with the proper initial values * simultaneously if it can. The elements in the array map to pins in the same - * way that bits are mapped by pin_acces_32(), and the same restrictions may + * way that bits are mapped by pin_access_32(), and the same restrictions may * apply. For example, to configure pins 2 and 3 it may be necessary to set * first_pin to zero and only populate pin_flags[2] and pin_flags[3]. If a * given array entry doesn't contain GPIO_PIN_INPUT or GPIO_PIN_OUTPUT then no @@ -156,6 +208,9 @@ struct gpio_access_32 { * Some devices may limit the value of first_pin to 0, or to multiples of 16 or * 32 or some other hardware-specific number. Invalid values in first_pin or * num_pins result in an error return with errno set to EINVAL. + * + * You cannot configure interrupts (userland pin-change notifications) with + * this function; each interrupt pin must be individually configured. */ struct gpio_config_32 { uint32_t first_pin; @@ -175,5 +230,6 @@ struct gpio_config_32 { #define GPIOSETNAME _IOW('G', 6, struct gpio_pin) #define GPIOACCESS32 _IOWR('G', 7, struct gpio_access_32) #define GPIOCONFIG32 _IOW('G', 8, struct gpio_config_32) +#define GPIOCONFIGEVENTS _IOW('G', 9, struct gpio_event_config) #endif /* __GPIO_H__ */ diff --git a/tools/test/README b/tools/test/README index 1e4aef85253e..2bafa48a9764 100644 --- a/tools/test/README +++ b/tools/test/README @@ -10,6 +10,7 @@ Please make a subdir per program, and add a brief description to this file. auxinfo Return information on page sizes, CPUs, and OS release date. devrandom Programs to test /dev/*random. +gpioevents Test delivery of gpio pin-change events to userland. hwpmc Automatically trigger every event in hwpmc(4). iconv Character set conversion tests. malloc A program to test and benchmark malloc(). diff --git a/tools/test/gpioevents/Makefile b/tools/test/gpioevents/Makefile new file mode 100644 index 000000000000..89edfefa87ea --- /dev/null +++ b/tools/test/gpioevents/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG= gpioevents +SRCS= gpioevents.c + +LIBADD= gpio + +MK_MAN= no + +BINDIR= /usr/bin + +.include diff --git a/tools/test/gpioevents/gpioevents.c b/tools/test/gpioevents/gpioevents.c new file mode 100644 index 000000000000..20d18257f4a8 --- /dev/null +++ b/tools/test/gpioevents/gpioevents.c @@ -0,0 +1,642 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Christian Kramer + * Copyright (c) 2020 Ian Lepore + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * make LDFLAGS+=-lgpio gpioevents + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +static bool be_verbose = false; +static int report_format = GPIO_EVENT_REPORT_DETAIL; +static struct timespec utc_offset; + +static volatile sig_atomic_t sigio = 0; + +static void +sigio_handler(int sig __unused){ + sigio = 1; +} + +static void +usage() +{ + fprintf(stderr, "usage: %s [-f ctldev] [-m method] [-s] [-n] [-S] [-u]" + "[-t timeout] [-d delay-usec] pin intr-config [pin intr-config ...]\n\n", + getprogname()); + fprintf(stderr, " -d delay before each call to read/poll/select/etc\n"); + fprintf(stderr, " -n Non-blocking IO\n"); + fprintf(stderr, " -s Single-shot (else loop continuously)\n"); + fprintf(stderr, " -S Report summary data (else report each event)\n"); + fprintf(stderr, " -u Show timestamps as UTC (else monotonic time)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Possible options for method:\n\n"); + fprintf(stderr, " r\tread (default)\n"); + fprintf(stderr, " p\tpoll\n"); + fprintf(stderr, " s\tselect\n"); + fprintf(stderr, " k\tkqueue\n"); + fprintf(stderr, " a\taio_read (needs sysctl vfs.aio.enable_unsafe=1)\n"); + fprintf(stderr, " i\tsignal-driven I/O\n\n"); + fprintf(stderr, "Possible options for intr-config:\n\n"); + fprintf(stderr, " no\t no interrupt\n"); + fprintf(stderr, " er\t edge rising\n"); + fprintf(stderr, " ef\t edge falling\n"); + fprintf(stderr, " eb\t edge both\n"); +} + +static void +verbose(const char *fmt, ...) +{ + va_list args; + + if (!be_verbose) + return; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +static const char* +poll_event_to_str(short event) +{ + switch (event) { + case POLLIN: + return "POLLIN"; + case POLLPRI: + return "POLLPRI:"; + case POLLOUT: + return "POLLOUT:"; + case POLLRDNORM: + return "POLLRDNORM"; + case POLLRDBAND: + return "POLLRDBAND"; + case POLLWRBAND: + return "POLLWRBAND"; + case POLLINIGNEOF: + return "POLLINIGNEOF"; + case POLLERR: + return "POLLERR"; + case POLLHUP: + return "POLLHUP"; + case POLLNVAL: + return "POLLNVAL"; + default: + return "unknown event"; + } +} + +static void +print_poll_events(short event) +{ + short curr_event = 0; + bool first = true; + + for (size_t i = 0; i <= sizeof(short) * CHAR_BIT - 1; i++) { + curr_event = 1 << i; + if ((event & curr_event) == 0) + continue; + if (!first) { + printf(" | "); + } else { + first = false; + } + printf("%s", poll_event_to_str(curr_event)); + } +} + +static void +calc_utc_offset() +{ + struct timespec monotime, utctime; + + clock_gettime(CLOCK_MONOTONIC, &monotime); + clock_gettime(CLOCK_REALTIME, &utctime); + timespecsub(&utctime, &monotime, &utc_offset); +} + +static void +print_timestamp(const char *str, sbintime_t timestamp) +{ + struct timespec ts; + char timebuf[32]; + + ts = sbttots(timestamp); + + if (!timespecisset(&utc_offset)) { + printf("%s %jd.%09ld ", str, (intmax_t)ts.tv_sec, ts.tv_nsec); + } else { + timespecadd(&utc_offset, &ts, &ts); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", + gmtime(&ts.tv_sec)); + printf("%s %s.%09ld ", str, timebuf, ts.tv_nsec); + } +} + +static void +print_event_detail(const struct gpio_event_detail *det) +{ + print_timestamp("time", det->gp_time); + printf("pin %hu state %u\n", det->gp_pin, det->gp_pinstate); +} + +static void +print_event_summary(const struct gpio_event_summary *sum) +{ + print_timestamp("first_time", sum->gp_first_time); + print_timestamp("last_time", sum->gp_last_time); + printf("pin %hu count %hu first state %u last state %u\n", + sum->gp_pin, sum->gp_count, + sum->gp_first_state, sum->gp_last_state); +} + +static void +print_gpio_event(const void *buf) +{ + if (report_format == GPIO_EVENT_REPORT_DETAIL) + print_event_detail((const struct gpio_event_detail *)buf); + else + print_event_summary((const struct gpio_event_summary *)buf); +} + +static void +run_read(bool loop, int handle, const char *file, u_int delayus) +{ + const size_t numrecs = 64; + union { + const struct gpio_event_summary sum[numrecs]; + const struct gpio_event_detail det[numrecs]; + uint8_t data[1]; + } buffer; + ssize_t reccount, recsize, res; + + if (report_format == GPIO_EVENT_REPORT_DETAIL) + recsize = sizeof(struct gpio_event_detail); + else + recsize = sizeof(struct gpio_event_summary); + + do { + if (delayus != 0) { + verbose("sleep %f seconds before read()\n", + delayus / 1000000.0); + usleep(delayus); + } + verbose("read into %zd byte buffer\n", sizeof(buffer)); + res = read(handle, buffer.data, sizeof(buffer)); + if (res < 0) + err(EXIT_FAILURE, "Cannot read from %s", file); + + if ((res % recsize) != 0) { + fprintf(stderr, "%s: read() %zd bytes from %s; " + "expected a multiple of %zu\n", + getprogname(), res, file, recsize); + } else { + reccount = res / recsize; + verbose("read returned %zd bytes; %zd events\n", res, + reccount); + for (ssize_t i = 0; i < reccount; ++i) { + if (report_format == GPIO_EVENT_REPORT_DETAIL) + print_event_detail(&buffer.det[i]); + else + print_event_summary(&buffer.sum[i]); + } + } + } while (loop); +} + +static void +run_poll(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + struct pollfd fds; + int res; + + fds.fd = handle; + fds.events = POLLIN | POLLRDNORM; + fds.revents = 0; + + do { + if (delayus != 0) { + verbose("sleep %f seconds before poll()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = poll(&fds, 1, timeout); + if (res < 0) { + err(EXIT_FAILURE, "Cannot poll() %s", file); + } else if (res == 0) { + printf("%s: poll() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: poll() returned %i (revents: ", + getprogname(), res); + print_poll_events(fds.revents); + printf(") on %s\n", file); + if (fds.revents & (POLLHUP | POLLERR)) { + err(EXIT_FAILURE, "Recieved POLLHUP or POLLERR " + "on %s", file); + } + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_select(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + fd_set readfds; + struct timeval tv; + struct timeval *tv_ptr; + int res; + + FD_ZERO(&readfds); + FD_SET(handle, &readfds); + if (timeout != INFTIM) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + tv_ptr = &tv; + } else { + tv_ptr = NULL; + } + + do { + if (delayus != 0) { + verbose("sleep %f seconds before select()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = select(FD_SETSIZE, &readfds, NULL, NULL, tv_ptr); + if (res < 0) { + err(EXIT_FAILURE, "Cannot select() %s", file); + } else if (res == 0) { + printf("%s: select() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: select() returned %i on %s\n", + getprogname(), res, file); + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_kqueue(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + struct kevent event[1]; + struct kevent tevent[1]; + int kq = -1; + int nev = -1; + struct timespec tv; + struct timespec *tv_ptr; + + if (timeout != INFTIM) { + tv.tv_sec = timeout / 1000; + tv.tv_nsec = (timeout % 1000) * 10000000; + tv_ptr = &tv; + } else { + tv_ptr = NULL; + } + + kq = kqueue(); + if (kq == -1) + err(EXIT_FAILURE, "kqueue() %s", file); + + EV_SET(&event[0], handle, EVFILT_READ, EV_ADD, 0, 0, NULL); + nev = kevent(kq, event, 1, NULL, 0, NULL); + if (nev == -1) + err(EXIT_FAILURE, "kevent() %s", file); + + do { + if (delayus != 0) { + verbose("sleep %f seconds before kevent()\n", + delayus / 1000000.0); + usleep(delayus); + } + nev = kevent(kq, NULL, 0, tevent, 1, tv_ptr); + if (nev == -1) { + err(EXIT_FAILURE, "kevent() %s", file); + } else if (nev == 0) { + printf("%s: kevent() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: kevent() returned %i events (flags: %d) on " + "%s\n", getprogname(), nev, tevent[0].flags, file); + if (tevent[0].flags & EV_EOF) { + err(EXIT_FAILURE, "Recieved EV_EOF on %s", + file); + } + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_aio_read(bool loop, int handle, const char *file, u_int delayus) +{ + uint8_t buffer[1024]; + size_t recsize; + ssize_t res; + struct aiocb iocb; + + /* + * Note that async IO to character devices is no longer allowed by + * default (since freebsd 11). This code is still here (for now) + * because you can use sysctl vfs.aio.enable_unsafe=1 to bypass the + * prohibition and run this code. + */ + + if (report_format == GPIO_EVENT_REPORT_DETAIL) + recsize = sizeof(struct gpio_event_detail); + else + recsize = sizeof(struct gpio_event_summary); + + bzero(&iocb, sizeof(iocb)); + + iocb.aio_fildes = handle; + iocb.aio_nbytes = sizeof(buffer); + iocb.aio_offset = 0; + iocb.aio_buf = buffer; + + do { + if (delayus != 0) { + verbose("sleep %f seconds before aio_read()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = aio_read(&iocb); + if (res < 0) + err(EXIT_FAILURE, "Cannot aio_read from %s", file); + do { + res = aio_error(&iocb); + } while (res == EINPROGRESS); + if (res < 0) + err(EXIT_FAILURE, "aio_error on %s", file); + res = aio_return(&iocb); + if (res < 0) + err(EXIT_FAILURE, "aio_return on %s", file); + if ((res % recsize) != 0) { + fprintf(stderr, "%s: aio_read() %zd bytes from %s; " + "expected a multiple of %zu\n", + getprogname(), res, file, recsize); + } else { + for (ssize_t i = 0; i < res; i += recsize) + print_gpio_event(&buffer[i]); + } + } while (loop); +} + + +static void +run_sigio(bool loop, int handle, const char *file) +{ + int res; + struct sigaction sigact; + int flags; + int pid; + + bzero(&sigact, sizeof(sigact)); + sigact.sa_handler = sigio_handler; + if (sigaction(SIGIO, &sigact, NULL) < 0) + err(EXIT_FAILURE, "cannot set SIGIO handler on %s", file); + flags = fcntl(handle, F_GETFL); + flags |= O_ASYNC; + res = fcntl(handle, F_SETFL, flags); + if (res < 0) + err(EXIT_FAILURE, "fcntl(F_SETFL) on %s", file); + pid = getpid(); + res = fcntl(handle, F_SETOWN, pid); + if (res < 0) + err(EXIT_FAILURE, "fnctl(F_SETOWN) on %s", file); + + do { + if (sigio == 1) { + sigio = 0; + printf("%s: recieved SIGIO on %s\n", getprogname(), + file); + run_read(false, handle, file, 0); + } + pause(); + } while (loop); +} + +int +main(int argc, char *argv[]) +{ + int ch; + const char *file = "/dev/gpioc0"; + char method = 'r'; + bool loop = true; + bool nonblock = false; + u_int delayus = 0; + int flags; + int timeout = INFTIM; + int handle; + int res; + gpio_config_t pin_config; + + while ((ch = getopt(argc, argv, "d:f:m:sSnt:uv")) != -1) { + switch (ch) { + case 'd': + delayus = strtol(optarg, NULL, 10); + if (errno != 0) { + warn("Invalid delay value"); + usage(); + return EXIT_FAILURE; + } + break; + case 'f': + file = optarg; + break; + case 'm': + method = optarg[0]; + break; + case 's': + loop = false; + break; + case 'S': + report_format = GPIO_EVENT_REPORT_SUMMARY; + break; + case 'n': + nonblock= true; + break; + case 't': + errno = 0; + timeout = strtol(optarg, NULL, 10); + if (errno != 0) { + warn("Invalid timeout value"); + usage(); + return EXIT_FAILURE; + } + break; + case 'u': + calc_utc_offset(); + break; + case 'v': + be_verbose = true; + break; + default: + usage(); + return EXIT_FAILURE; + } + } + argv += optind; + argc -= optind; + + if (argc == 0) { + fprintf(stderr, "%s: No pin number specified.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + if (argc == 1) { + fprintf(stderr, "%s: No trigger type specified.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + if (argc % 2 == 1) { + fprintf(stderr, "%s: Invalid number of pin intr-conf pairs.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + handle = gpio_open_device(file); + if (handle == GPIO_INVALID_HANDLE) + err(EXIT_FAILURE, "Cannot open %s", file); + + if (report_format == GPIO_EVENT_REPORT_SUMMARY) { + struct gpio_event_config cfg = + {GPIO_EVENT_REPORT_SUMMARY, 0}; + + res = ioctl(handle, GPIOCONFIGEVENTS, &cfg); + if (res < 0) + err(EXIT_FAILURE, "GPIOCONFIGEVENTS failed on %s", file); + } + + if (nonblock == true) { + flags = fcntl(handle, F_GETFL); + flags |= O_NONBLOCK; + res = fcntl(handle, F_SETFL, flags); + if (res < 0) + err(EXIT_FAILURE, "cannot set O_NONBLOCK on %s", file); + } + + for (int i = 0; i <= argc - 2; i += 2) { + + errno = 0; + pin_config.g_pin = strtol(argv[i], NULL, 10); + if (errno != 0) { + warn("Invalid pin number"); + usage(); + return EXIT_FAILURE; + } + + if (strnlen(argv[i + 1], 2) < 2) { + fprintf(stderr, "%s: Invalid trigger type (argument " + "too short).\n", getprogname()); + usage(); + return EXIT_FAILURE; + } + + switch((argv[i + 1][0] << 8) + argv[i + 1][1]) { + case ('n' << 8) + 'o': + pin_config.g_flags = GPIO_INTR_NONE; + break; + case ('e' << 8) + 'r': + pin_config.g_flags = GPIO_INTR_EDGE_RISING; + break; + case ('e' << 8) + 'f': + pin_config.g_flags = GPIO_INTR_EDGE_FALLING; + break; + case ('e' << 8) + 'b': + pin_config.g_flags = GPIO_INTR_EDGE_BOTH; + break; + default: + fprintf(stderr, "%s: Invalid trigger type.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + pin_config.g_flags |= GPIO_PIN_INPUT | GPIO_PIN_PULLUP; + + res = gpio_pin_set_flags(handle, &pin_config); + if (res < 0) + err(EXIT_FAILURE, "configuration of pin %d on %s " + "failed (flags=%d)", pin_config.g_pin, file, + pin_config.g_flags); + } + + switch (method) { + case 'r': + run_read(loop, handle, file, delayus); + break; + case 'p': + run_poll(loop, handle, file, timeout, delayus); + break; + case 's': + run_select(loop, handle, file, timeout, delayus); + break; + case 'k': + run_kqueue(loop, handle, file, timeout, delayus); + break; + case 'a': + run_aio_read(loop, handle, file, delayus); + break; + case 'i': + run_sigio(loop, handle, file); + break; + default: + fprintf(stderr, "%s: Unknown method.\n", getprogname()); + usage(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/usr.sbin/gpioctl/gpioctl.c b/usr.sbin/gpioctl/gpioctl.c index 060ea4ef0e90..7adca26bbf28 100644 --- a/usr.sbin/gpioctl/gpioctl.c +++ b/usr.sbin/gpioctl/gpioctl.c @@ -332,6 +332,8 @@ main(int argc, char **argv) flag = str2cap(argv[i]); if (flag < 0) fail("Invalid flag: %s\n", argv[i]); + else if ((flag & GPIO_INTR_MASK) != 0) + fail("Interrupt capability %s cannot be set as configuration flag\n", argv[i]); flags |= flag; } pin.g_pin = pinn;