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