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