User Space GPIO Interrupt programming - GSoC-2018
Vladimir Goncharov
freebsd at viruzzz.org
Fri Nov 27 11:28:36 UTC 2020
Here it is.
There is struct gpioc_event with pin number and bintime which is send to
userspace.
Also I'm thinking about to implementation notofication without extra
reading from socket via struct kevent's extra fields (fflags/ext[4]),
looks like it is possible.
2020-11-27 14:09, Dr. Rolf Jansen пишет:
> Hello Vladimir,
>
> yes please, I am interested. Please can show us your version of the
> GSoC-2018 diff file?
>
> Best regards
>
> Rolf
>
>> Am 27.11.2020 um 05:02 schrieb Vladimir Goncharov <freebsd at viruzzz.org
>> <mailto:freebsd at viruzzz.org>>:
>>
>> Hello Rolf,
>>
>> I have implemented in kernel queue for generated GPIO interrupts with
>> timestamps, so now it's possible to catch all events even on busy
>> system. It's all for gpio_gsoc2018 code. In result I have no more
>> warnings in dmesg. Unfortunately it requires allocating memory on each
>> event to store pin number and timestamp. In case of unability allocating
>> memory it falls back to gpio_gsoc2018 method.
>> Does anybody interested in such patches?
>>
>>
>> 2020-11-27 04:18, Dr. Rolf Jansen пишет:
>>>> Am 26.11.2020 um 16:56 schrieb Ian Lepore <ian at freebsd.org
>>>> <mailto:ian at freebsd.org>>:
>>>>
>>>> On Tue, 2020-11-24 at 17:14 -0300, Dr. Rolf Jansen wrote:
>>>>> Hello
>>>>>
>>>>> Has anything of the GSoC-2018 efforts made it into the current code
>>>>> base?
>>>>>
>>>>>
>>>> https://wiki.freebsd.org/SummerOfCode2018Projects/UserSpaceGPIOinterrupts
>>>> <https://wiki.freebsd.org/SummerOfCode2018Projects/UserSpaceGPIOinterrupts>
>>>>>
>>>>> I installed the recent 13.0-CURRENT snapshot (2020-11-19) on a
>>>>> BeagleBone Black which was one of the implementation targets of said
>>>>> project, but when running the test tools, I either see cannot
>>>>> read/kevent/poll/aio_read - Operation not supported by device or
>>>>> Inappropriate ioctl for device.
>>>>>
>>>>> Perhaps I need to pull the project´s changes into the kernel by
>>>>> myself. However, before this I would like to ask whether it is worth
>>>>> the effort.
>>>>>
>>>>> Please, can anyone shed some light on this.
>>>>>
>>>>> Best regards
>>>>>
>>>>> Rolf
>>>>>
>>>>
>>>> I made some time this morning to review the gsoc2018 code. It turns
>>>> out this code is very high quality, nearly ready to commit as-is. The
>>>> main thing it needs is some style cleanup in its comment blocks, and
>>>> documentation. I'd be inclined to commit the code first and write the
>>>> documentation over the next little while and commit it separately.
>>>>
>>>> If you'd like to give it a try, here's a diff that should apply and
>>>> build cleanly on freebsd 12 or 13:
>>>>
>>>> https://people.freebsd.org/~ian/gpio_gsoc2018.diff
>>>>
>>>> While there isn't any documentation yet, there is a test program (I
>>>> haven't run it yet) that demonstrates all the features:
>>>>
>>>> https://github.com/ckraemer/gsoc2018-utils/blob/master/src/gpioc_intr_test.c
>>>>
>>>> Right now the code will let you block waiting for a pin-change event
>>>> using select(), poll() or kevents, or to be notified via SIGIO, but
>>>> after being notified that something happened, you still have to call
>>>> read() to see which pin changed. I think if the pin changes state
>>>> multiple times between calls to read(), you'll lose track of some
>>>> changes (I'm not positive of that, I don't understand the kevent stuff
>>>> well).
>>>>
>>>> I'd like to add some features so that you can configure it to track pin
>>>> changes in counting-mode and timestamping-mode. In counting mode, when
>>>> you do a read() you would get back a pair of values, the pin number and
>>>> how many times its interrupt fired since the last read. In
>>>> timestamping mode, every read would return a pin number and an
>>>> associated timespec giving the exact time the interrupt happened (there
>>>> would need to be a way to configure how many events it could buffer,
>>>> but I think even allowing a thousand buffered events would only use a
>>>> few kbytes of memory).
>>>
>>> I got it working as well, please see my other post from yesterday. I
>>> used gpioc_intr_test.c.
>>>
>>> I see hundreds of warning messages when I press the test button a few
>>> times. May these warnings be safely ignored. The kernel module of
>>> Oskar Holmund works quite nice as well (for what I need), and with
>>> that one, I don’t see warnings.
>>>
>>> The counting- and timestamping-mode for sure would be very useful.
>>> Perhaps by implementing this, there won’t be no unhandled interrupts
>>> anymore, and hence there won’t be any warnings either.
>>>
>>> Best regards
>>>
>>> Rolf
>>>
>>>
>> _______________________________________________
>> freebsd-arm at freebsd.org <mailto:freebsd-arm at freebsd.org> mailing list
>> https://lists.freebsd.org/mailman/listinfo/freebsd-arm
>> <https://lists.freebsd.org/mailman/listinfo/freebsd-arm>
>> To unsubscribe, send any mail to "freebsd-arm-unsubscribe at freebsd.org"
>
>
>
> Dr. Rolf Jansen
> - -
> Rua Reginaldo de Lima, 98
> Parque São Diogo
> 09732-550, São Bernardo do Campo
> São Paulo - Brazil
>
> Phone:0055-11/4317-0974
> Mobile:0055-11/9 8141-1465
> E-Mail:rj at obsigna.com <mailto:rj at obsigna.com>
> BLog:obsigna.com <http://obsigna.com/>
>
-------------- next part --------------
diff -ru ./dev/gpio/gpiobus.c /usr/home/buildroot/usr/src/sys/dev/gpio/gpiobus.c
--- ./dev/gpio/gpiobus.c 2020-10-23 03:02:05.000000000 +0300
+++ /usr/home/buildroot/usr/src/sys/dev/gpio/gpiobus.c 2020-11-25 12:17:30.428119000 +0300
@@ -143,7 +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);
}
Only in /usr/home/buildroot/usr/src/sys/dev/gpio: gpiobus.c.orig
diff -ru ./dev/gpio/gpioc.c /usr/home/buildroot/usr/src/sys/dev/gpio/gpioc.c
--- ./dev/gpio/gpioc.c 2020-10-23 03:02:05.000000000 +0300
+++ /usr/home/buildroot/usr/src/sys/dev/gpio/gpioc.c 2020-11-27 03:19:04.628872000 +0300
@@ -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>
@@ -44,40 +51,433 @@
#include "gpio_if.h"
#include "gpiobus_if.h"
-#undef GPIOC_DEBUG
+#define GPIOC_DEBUG 1
#ifdef GPIOC_DEBUG
#define dprintf printf
#else
#define dprintf(x, arg...)
#endif
+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_event {
+ uint32_t pin;
+ struct bintime bt;
+};
+
+struct gpioc_events {
+ struct gpioc_event event;
+ STAILQ_ENTRY(gpioc_events) next;
+};
+
+struct gpioc_cdevpriv {
+ struct gpioc_softc *sc;
+ uint32_t last_intr_pin;
+ struct selinfo selinfo;
+ bool async;
+ struct sigio *sigio;
+ struct mtx mtx;
+ STAILQ_HEAD(gpioc_event_list, gpioc_events) events;
+ 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;
+ SLIST_ENTRY(gpioc_pins) next;
+};
+
+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_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 int gpioc_kqread(struct knote*, long);
+static void gpioc_kqdetach(struct knote*);
+
+
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 int
+ 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)
+ return (ENXIO);
+
+ 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)
+ return (err);
+
+ intr_conf->pin->flags = flags;
+ mtx_lock(&intr_conf->mtx);
+ intr_conf->config_locked = false;
+ wakeup(&intr_conf->config_locked);
+
+ return (0);
+}
+
+ 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)
+ return (err);
+ 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)
+ return (err);
+ else {
+ intr_conf->intr_rid = 0;
+ intr_conf->intr_res = NULL;
+ }
+ }
+
+ intr_conf->pin->flags = 0;
+ mtx_lock(&intr_conf->mtx);
+ intr_conf->config_locked = false;
+ wakeup(&intr_conf->config_locked);
+
+ return (0);
+}
+
+ 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);
+ mtx_unlock(&intr_conf->mtx);
+ return (EEXIST);
+ }
+ priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC,
+ M_NOWAIT | M_ZERO);
+ if (priv_link == NULL)
+ {
+ mtx_unlock(&priv->mtx);
+ mtx_unlock(&intr_conf->mtx);
+ return (ENOMEM);
+ }
+ pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC,
+ M_NOWAIT | M_ZERO);
+ if (pin_link == NULL) {
+ mtx_unlock(&priv->mtx);
+ mtx_unlock(&intr_conf->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;
+ struct gpioc_events *evs_link, *evs_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++;
+ }
+ }
+
+ STAILQ_FOREACH_SAFE(evs_link, &priv->events, next, evs_link_temp) {
+ STAILQ_REMOVE(&priv->events,evs_link, gpioc_events, next);
+ free(evs_link, M_GPIOC);
+ printf("deleting unhandled event\n");
+ }
+ 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) {
+ 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);
+
+ SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
+ if (priv_link->priv == priv) {
+ flags |= GPIO_INTR_ATTACHED;
+ break;
+ }
+ }
+
+ 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 = arg;
+ struct gpioc_softc *sc = intr_conf->sc;
+ struct gpioc_privs *privs;
+
+ mtx_lock(&intr_conf->mtx);
+
+ if (intr_conf->config_locked == true) {
+ device_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)) {
+ device_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) {
+ mtx_lock(&privs->priv->mtx);
+ struct gpioc_events *event=malloc(sizeof(struct gpioc_events), M_GPIOC, M_NOWAIT | M_ZERO);
+ if(event){
+ event->event.pin=intr_conf->pin->pin;
+ getbintime(&event->event.bt);
+ STAILQ_INSERT_TAIL(&privs->priv->events,event,next);
+ privs->priv->last_intr_pin = intr_conf->pin->pin;
+ }else{
+ if (privs->priv->last_intr_pin != -1)
+ device_printf(sc->sc_dev, "Unhandled interrupt on pin "
+ "%d.\n", intr_conf->pin->pin);
+ privs->priv->last_intr_pin = intr_conf->pin->pin;
+
+ }
+ wakeup(privs->priv);
+ selwakeup(&privs->priv->selinfo);
+ KNOTE_LOCKED(&privs->priv->selinfo.si_note, 0);
+ if (privs->priv->async == true && privs->priv->sigio != NULL)
+ pgsigio(&privs->priv->sigio, SIGIO, 0);
+ mtx_unlock(&privs->priv->mtx);
+ }
+
+ mtx_unlock(&intr_conf->mtx);
+}
+
+ static int
gpioc_probe(device_t dev)
{
device_set_desc(dev, "GPIO controller");
return (0);
}
-static int
+ static int
gpioc_attach(device_t dev)
{
int err;
@@ -88,6 +488,22 @@
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);
+ 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;
@@ -103,7 +519,7 @@
return (0);
}
-static int
+ static int
gpioc_detach(device_t dev)
{
struct gpioc_softc *sc = device_get_softc(dev);
@@ -112,19 +528,148 @@
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 int
+ 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(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);
+ err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor);
+ if (err != 0) {
+ gpioc_cdevpriv_dtor(priv);
+ return (err);
+ }
+ priv->sc = dev->si_drv1;
+ priv->last_intr_pin = -1;
+ STAILQ_INIT(&priv->events);
+ mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF);
+ knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx);
+
+ return (0);
+}
+
+ static int
+gpioc_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct gpioc_cdevpriv *priv;
+ uint32_t last_intr_pin;
+ int err;
+
+ if (uio->uio_resid < sizeof(priv->last_intr_pin))
+ return EINVAL;
+
+ err = devfs_get_cdevpriv((void **)&priv);
+ if (err != 0)
+ return err;
+
+ mtx_lock(&priv->mtx);
+
+ if(!STAILQ_EMPTY(&priv->events)){
+ struct gpioc_events *event=STAILQ_FIRST(&priv->events);
+
+ STAILQ_REMOVE_HEAD(&priv->events, next);
+ if(STAILQ_EMPTY(&priv->events)){
+ priv->last_intr_pin = -1;
+ }
+
+ mtx_unlock(&priv->mtx);
+
+ err = uiomove(&event->event, sizeof(struct gpioc_event), uio);
+ free(event, M_GPIOC);
+ }else{
+ while (priv->last_intr_pin == -1) {
+ 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;
+ }
+ }
+
+ if (err == 0 && priv->last_intr_pin != -1) {
+ last_intr_pin = priv->last_intr_pin;
+ priv->last_intr_pin = -1;
+ mtx_unlock(&priv->mtx);
+ err = uiomove(&last_intr_pin, sizeof(last_intr_pin), uio);
+ } else {
+ mtx_unlock(&priv->mtx);
+ }
+ }
+
+ return (err);
+}
+
+ static int
gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag,
- struct thread *td)
+ struct thread *td)
{
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;
@@ -144,10 +689,15 @@
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);
+ &pin.gp_flags);
/* Fail early */
if (res)
break;
+ res = devfs_get_cdevpriv((void **)&priv);
+ if (res)
+ break;
+ 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));
@@ -155,56 +705,173 @@
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);
+ res = devfs_get_cdevpriv((void **)&priv);
if (res == 0)
+ 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 & ~GPIO_INTR_MASK));
+ if (res == 0)
+ res = gpioc_set_intr_config(sc, priv,
+ pin.gp_pin,
+ (pin.gp_flags & GPIO_INTR_MASK));
break;
case GPIOGET:
bcopy(arg, &req, sizeof(req));
res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin,
- &req.gp_value);
+ &req.gp_value);
dprintf("read pin %d -> %d\n",
- req.gp_pin, req.gp_value);
+ 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);
+ req.gp_value);
dprintf("write pin %d -> %d\n",
- req.gp_pin, req.gp_value);
+ req.gp_pin, req.gp_value);
break;
case GPIOTOGGLE:
bcopy(arg, &req, sizeof(req));
dprintf("toggle pin %d\n",
- req.gp_pin);
+ 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);
+ 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);
+ a32->clear_pins, a32->orig_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);
+ c32->num_pins, c32->pin_flags);
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->last_intr_pin != -1)
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(td, &priv->selinfo);
+ }
+
+ return (revents);
+}
+
+ static int
+gpioc_kqfilter(struct cdev *dev, struct knote *kn)
+{//attach to kqueue
+ 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;
+ if (SLIST_EMPTY(&priv->pins)) {
+ kn->kn_flags |= EV_EOF;
+ return (1);
+ } else {
+ if (priv->last_intr_pin != -1) {
+ kn->kn_data = sizeof(priv->last_intr_pin);
+ 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[] = {
Only in /usr/home/buildroot/usr/src/sys/dev/gpio: gpioc.c.orig
Only in .: diff.core
Only in .: gpio.diff
diff -ru ./sys/gpio.h /usr/home/buildroot/usr/src/sys/sys/gpio.h
--- ./sys/gpio.h 2020-10-23 03:02:18.000000000 +0300
+++ /usr/home/buildroot/usr/src/sys/sys/gpio.h 2020-11-25 12:22:41.460941000 +0300
@@ -84,6 +84,7 @@
#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 */
struct gpio_pin {
uint32_t gp_pin; /* pin number */
Only in /usr/home/buildroot/usr/src/sys/sys: gpio.h.orig
More information about the freebsd-arm
mailing list