Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F105916023
D27398.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
50 KB
Referenced Files
None
Subscribers
None
D27398.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D27398: Provide userland notification of gpio pin changes (aka "userland gpio interrupts").
Attached
Detach File
Event Timeline
Log In to Comment