svn commit: r368585 - in head: sys/dev/gpio sys/sys tools/test tools/test/gpioevents usr.sbin/gpioctl
Warner Losh
imp at bsdimp.com
Sat Dec 12 20:34:32 UTC 2020
FWIW I generally approved the code, but didn't have the time to review it
in detail. I got the impression people were at least generally happy from
the review.
Thanks so much for picking this up.
Warner
On Sat, Dec 12, 2020, 11:34 AM Ian Lepore <ian at freebsd.org> wrote:
> Author: ian
> Date: Sat Dec 12 18:34:15 2020
> New Revision: 368585
> URL: https://svnweb.freebsd.org/changeset/base/368585
>
> Log:
> Provide userland notification of gpio pin changes ("userland gpio
> interrupts").
>
> This is an import of the Google Summer of Code 2018 project completed by
> Christian Kramer (and, sadly, ignored by us for two years now). The
> goals
> stated for that project were:
>
> FreeBSD already has support for interrupts implemented in the GPIO
> controller drivers of several SoCs, but there are no interfaces to
> take
> advantage of them out of user space yet. The goal of this work is to
> implement such an interface by providing descriptors which integrate
> with the common I/O system calls and multiplexing mechanisms.
>
> The initial imported code supports the following functionality:
>
> - A kernel driver that provides an interface to the user space; the
> existing gpioc(4) driver was enhanced with this functionality.
> - Implement support for the most common I/O system calls / multiplexing
> mechanisms:
> - read() Places the pin number on which the interrupt occurred in
> the
> buffer. Blocking and non-blocking behaviour supported.
> - poll()/select()
> - kqueue()
> - signal driven I/O. Posting SIGIO when the O_ASYNC was set.
> - Many-to-many relationship between pins and file descriptors.
> - A file descriptor can monitor several GPIO pins.
> - A GPIO pin can be monitored by multiple file descriptors.
> - Integration with gpioctl and libgpio.
>
> I added some fixes (mostly to locking) and feature enhancements on top of
> the original gsoc code. The feature ehancements allow the user to choose
> between detailed and summary event reporting. Detailed reporting
> provides
> a record describing each pin change event. Summary reporting provides
> the
> time of the first and last change of each pin, and a count of how many
> times
> it changed state since the last read(2) call. Another enhancement allows
> the recording of multiple state change events on multiple pins between
> each
> call to read(2) (the original code would track only a single event at a
> time).
>
> The phabricator review for these changes timed out without approval, but
> I
> cite it below anyway, because the review contains a series of diffs that
> show how I evolved the code from its original state in Christian's github
> repo for the gsoc project to what is being commited here. (In effect,
> the phab review extends the VC history back to the original code.)
>
> Submitted by: Christian Kramer
> Obtained from: https://github.com/ckraemer/freebsd/tree/gsoc2018
> Differential Revision: https://reviews.freebsd.org/D27398
>
> Added:
> head/tools/test/gpioevents/
> head/tools/test/gpioevents/Makefile (contents, props changed)
> head/tools/test/gpioevents/gpioevents.c (contents, props changed)
> Modified:
> head/sys/dev/gpio/gpiobus.c
> head/sys/dev/gpio/gpioc.c
> head/sys/sys/gpio.h
> head/tools/test/README
> head/usr.sbin/gpioctl/gpioctl.c
>
> Modified: head/sys/dev/gpio/gpiobus.c
>
> ==============================================================================
> --- head/sys/dev/gpio/gpiobus.c Sat Dec 12 17:11:22 2020 (r368584)
> +++ head/sys/dev/gpio/gpiobus.c Sat Dec 12 18:34:15 2020 (r368585)
> @@ -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);
> }
>
> Modified: head/sys/dev/gpio/gpioc.c
>
> ==============================================================================
> --- head/sys/dev/gpio/gpioc.c Sat Dec 12 17:11:22 2020 (r368584)
> +++ head/sys/dev/gpio/gpioc.c Sat Dec 12 18:34:15 2020 (r368585)
> @@ -35,8 +35,15 @@ __FBSDID("$FreeBSD$");
> #include <sys/conf.h>
> #include <sys/gpio.h>
> #include <sys/ioccom.h>
> +#include <sys/filio.h>
> +#include <sys/fcntl.h>
> +#include <sys/sigio.h>
> +#include <sys/signalvar.h>
> #include <sys/kernel.h>
> #include <sys/malloc.h>
> +#include <sys/uio.h>
> +#include <sys/poll.h>
> +#include <sys/selinfo.h>
> #include <sys/module.h>
>
> #include <dev/gpio/gpiobusvar.h>
> @@ -47,30 +54,510 @@ __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)
> {
> device_set_desc(dev, "GPIO controller");
> @@ -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,86 +777,268 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t
> arg
> 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);
> + case GPIO_INTR_EDGE_RISING:
> + case GPIO_INTR_EDGE_FALLING:
> + case GPIO_INTR_EDGE_BOTH:
> + if ((intrflags & caps) == 0)
> + res = EOPNOTSUPP;
> 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);
> + default:
> + res = EINVAL;
> 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);
> + }
> + if (res != 0)
> break;
> - default:
> - return (ENOTTY);
> + 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)) {
>
> *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
>
More information about the svn-src-head
mailing list