Page MenuHomeFreeBSD

D27398.diff
No OneTemporary

D27398.diff

Index: head/sys/dev/gpio/gpiobus.c
===================================================================
--- head/sys/dev/gpio/gpiobus.c
+++ head/sys/dev/gpio/gpiobus.c
@@ -143,6 +143,15 @@
/* 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);
}
Index: head/sys/dev/gpio/gpioc.c
===================================================================
--- head/sys/dev/gpio/gpioc.c
+++ head/sys/dev/gpio/gpioc.c
@@ -35,8 +35,15 @@
#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 @@
#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 @@
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 @@
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 @@
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 @@
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)) {
+ 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[] = {
Index: head/sys/sys/gpio.h
===================================================================
--- head/sys/sys/gpio.h
+++ head/sys/sys/gpio.h
@@ -53,6 +53,9 @@
#define __GPIO_H__
#include <sys/ioccom.h>
+#ifndef _KERNEL
+#include <stdbool.h>
+#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 */
@@ -99,6 +104,53 @@
};
/*
+ * 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
*
* Simultaneously read and/or change up to 32 adjacent pins.
@@ -147,7 +199,7 @@
* 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 @@
* 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 @@
#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__ */
Index: head/tools/test/README
===================================================================
--- head/tools/test/README
+++ head/tools/test/README
@@ -10,6 +10,7 @@
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().
Index: head/tools/test/gpioevents/Makefile
===================================================================
--- head/tools/test/gpioevents/Makefile
+++ head/tools/test/gpioevents/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= gpioevents
+SRCS= gpioevents.c
+
+LIBADD= gpio
+
+MK_MAN= no
+
+BINDIR= /usr/bin
+
+.include <bsd.prog.mk>
Index: head/tools/test/gpioevents/gpioevents.c
===================================================================
--- head/tools/test/gpioevents/gpioevents.c
+++ head/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 <ian@FreeBSD.org>
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <aio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <err.h>
+
+#include <sys/endian.h>
+#include <sys/event.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/time.h>
+
+#include <libgpio.h>
+
+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;
+}
Index: head/usr.sbin/gpioctl/gpioctl.c
===================================================================
--- head/usr.sbin/gpioctl/gpioctl.c
+++ head/usr.sbin/gpioctl/gpioctl.c
@@ -332,6 +332,8 @@
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;

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 23, 2:57 PM (18 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15571606
Default Alt Text
D27398.diff (50 KB)

Event Timeline