svn commit: r368702 - in stable/12: sys/dev/gpio sys/sys tools/test tools/test/gpioevents usr.sbin/gpioctl

Ian Lepore ian at FreeBSD.org
Wed Dec 16 17:09:39 UTC 2020


Author: ian
Date: Wed Dec 16 17:09:38 2020
New Revision: 368702
URL: https://svnweb.freebsd.org/changeset/base/368702

Log:
  MFC 368585:
  
  Provide userland notification of gpio pin changes ("userland gpio interrupts").
  
  This is an import of the Google Summer of Code 2018 project completed by
  Christian Kramer (and, sadly, ignored by us for two years now).  The goals
  stated for that project were:
  
      FreeBSD already has support for interrupts implemented in the GPIO
      controller drivers of several SoCs, but there are no interfaces to take
      advantage of them out of user space yet. The goal of this work is to
      implement such an interface by providing descriptors which integrate
      with the common I/O system calls and multiplexing mechanisms.
  
  The initial imported code supports the following functionality:
  
   -  A kernel driver that provides an interface to the user space; the
      existing gpioc(4) driver was enhanced with this functionality.
   -  Implement support for the most common I/O system calls / multiplexing
      mechanisms:
       -  read() Places the pin number on which the interrupt occurred in the
          buffer. Blocking and non-blocking behaviour supported.
       -  poll()/select()
       -  kqueue()
       -  signal driven I/O. Posting SIGIO when the O_ASYNC was set.
   -  Many-to-many relationship between pins and file descriptors.
       -  A file descriptor can monitor several GPIO pins.
       -  A GPIO pin can be monitored by multiple file descriptors.
   -  Integration with gpioctl and libgpio.
  
  I added some fixes (mostly to locking) and feature enhancements on top of
  the original gsoc code.  The feature ehancements allow the user to choose
  between detailed and summary event reporting.  Detailed reporting provides
  a record describing each pin change event.  Summary reporting provides the
  time of the first and last change of each pin, and a count of how many times
  it changed state since the last read(2) call.  Another enhancement allows
  the recording of multiple state change events on multiple pins between each
  call to read(2) (the original code would track only a single event at a time).
  
  The phabricator review for these changes timed out without approval, but I
  cite it below anyway, because the review contains a series of diffs that
  show how I evolved the code from its original state in Christian's github
  repo for the gsoc project to what is being commited here.  (In effect,
  the phab review extends the VC history back to the original code.)
  
  Submitted by: Christian Kramer
  Obtained from:        https://github.com/ckraemer/freebsd/tree/gsoc2018
  Differential Revision:        https://reviews.freebsd.org/D27398

Added:
  stable/12/tools/test/gpioevents/
     - copied from r368585, head/tools/test/gpioevents/
Modified:
  stable/12/sys/dev/gpio/gpiobus.c
  stable/12/sys/dev/gpio/gpioc.c
  stable/12/sys/sys/gpio.h
  stable/12/tools/test/README
  stable/12/usr.sbin/gpioctl/gpioctl.c
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/dev/gpio/gpiobus.c
==============================================================================
--- stable/12/sys/dev/gpio/gpiobus.c	Wed Dec 16 16:59:52 2020	(r368701)
+++ stable/12/sys/dev/gpio/gpiobus.c	Wed Dec 16 17:09:38 2020	(r368702)
@@ -143,6 +143,15 @@ gpio_check_flags(uint32_t caps, uint32_t flags)
 	/* Cannot mix pull-up/pull-down together. */
 	if (flags & GPIO_PIN_PULLUP && flags & GPIO_PIN_PULLDOWN)
 		return (EINVAL);
+	/* Cannot mix output and interrupt flags together */
+	if (flags & GPIO_PIN_OUTPUT && flags & GPIO_INTR_MASK)
+		return (EINVAL);
+	/* Only one interrupt flag can be defined at once */
+	if ((flags & GPIO_INTR_MASK) & ((flags & GPIO_INTR_MASK) - 1))
+		return (EINVAL);
+	/* The interrupt attached flag cannot be set */
+	if (flags & GPIO_INTR_ATTACHED)
+		return (EINVAL);
 
 	return (0);
 }

Modified: stable/12/sys/dev/gpio/gpioc.c
==============================================================================
--- stable/12/sys/dev/gpio/gpioc.c	Wed Dec 16 16:59:52 2020	(r368701)
+++ stable/12/sys/dev/gpio/gpioc.c	Wed Dec 16 17:09:38 2020	(r368702)
@@ -35,8 +35,15 @@ __FBSDID("$FreeBSD$");
 #include <sys/conf.h>
 #include <sys/gpio.h>
 #include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/fcntl.h>
+#include <sys/sigio.h>
+#include <sys/signalvar.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+#include <sys/selinfo.h>
 #include <sys/module.h>
 
 #include <dev/gpio/gpiobusvar.h>
@@ -47,30 +54,510 @@ __FBSDID("$FreeBSD$");
 #undef GPIOC_DEBUG
 #ifdef GPIOC_DEBUG
 #define dprintf printf
+#define ddevice_printf device_printf
 #else
 #define dprintf(x, arg...)
+#define ddevice_printf(dev, x, arg...)
 #endif
 
-static int gpioc_probe(device_t dev);
-static int gpioc_attach(device_t dev);
-static int gpioc_detach(device_t dev);
+struct gpioc_softc {
+	device_t		sc_dev;		/* gpiocX dev */
+	device_t		sc_pdev;	/* gpioX dev */
+	struct cdev		*sc_ctl_dev;	/* controller device */
+	int			sc_unit;
+	int			sc_npins;
+	struct gpioc_pin_intr	*sc_pin_intr;
+};
 
+struct gpioc_pin_intr {
+	struct gpioc_softc				*sc;
+	gpio_pin_t					pin;
+	bool						config_locked;
+	int						intr_rid;
+	struct resource					*intr_res;
+	void						*intr_cookie;
+	struct mtx					mtx;
+	SLIST_HEAD(gpioc_privs_list, gpioc_privs)	privs;
+};
+
+
+struct gpioc_cdevpriv {
+	struct gpioc_softc			*sc;
+	struct selinfo				selinfo;
+	bool					async;
+	uint8_t					report_option;
+	struct sigio				*sigio;
+	struct mtx				mtx;
+	struct gpioc_pin_event			*events;
+	int					numevents;
+	int					evidx_head;
+	int					evidx_tail;
+	SLIST_HEAD(gpioc_pins_list, gpioc_pins)	pins;
+};
+
+struct gpioc_privs {
+	struct gpioc_cdevpriv		*priv;
+	SLIST_ENTRY(gpioc_privs)	next;
+};
+
+struct gpioc_pins {
+	struct gpioc_pin_intr	*pin;
+	int			eventcount;
+	int			firstevent;
+	SLIST_ENTRY(gpioc_pins)	next;
+};
+
+struct gpioc_pin_event {
+	struct gpioc_pins	*privpin;
+	sbintime_t		event_time;
+	bool			event_pin_state;
+};
+
+static MALLOC_DEFINE(M_GPIOC, "gpioc", "gpioc device data");
+
+static int	gpioc_allocate_pin_intr(struct gpioc_pin_intr*, uint32_t);
+static int	gpioc_release_pin_intr(struct gpioc_pin_intr*);
+static int	gpioc_attach_priv_pin(struct gpioc_cdevpriv*,
+		    struct gpioc_pin_intr*);
+static int	gpioc_detach_priv_pin(struct gpioc_cdevpriv*,
+		    struct gpioc_pin_intr*);
+static bool	gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv*,
+		    struct gpioc_pin_intr *intr_conf);
+static uint32_t	gpioc_get_intr_config(struct gpioc_softc*,
+		    struct gpioc_cdevpriv*, uint32_t pin);
+static int	gpioc_set_intr_config(struct gpioc_softc*,
+		    struct gpioc_cdevpriv*, uint32_t, uint32_t);
+static void	gpioc_interrupt_handler(void*);
+
+static int	gpioc_kqread(struct knote*, long);
+static void	gpioc_kqdetach(struct knote*);
+
+static int	gpioc_probe(device_t dev);
+static int	gpioc_attach(device_t dev);
+static int	gpioc_detach(device_t dev);
+
+static void	gpioc_cdevpriv_dtor(void*);
+
+static d_open_t		gpioc_open;
+static d_read_t		gpioc_read;
 static d_ioctl_t	gpioc_ioctl;
+static d_poll_t		gpioc_poll;
+static d_kqfilter_t	gpioc_kqfilter;
 
 static struct cdevsw gpioc_cdevsw = {
 	.d_version	= D_VERSION,
+	.d_open		= gpioc_open,
+	.d_read		= gpioc_read,
 	.d_ioctl	= gpioc_ioctl,
+	.d_poll		= gpioc_poll,
+	.d_kqfilter	= gpioc_kqfilter,
 	.d_name		= "gpioc",
 };
 
-struct gpioc_softc {
-	device_t	sc_dev;		/* gpiocX dev */
-	device_t	sc_pdev;	/* gpioX dev */
-	struct cdev	*sc_ctl_dev;	/* controller device */
-	int		sc_unit;
+static struct filterops gpioc_read_filterops = {
+	.f_isfd =	true,
+	.f_attach =	NULL,
+	.f_detach =	gpioc_kqdetach,
+	.f_event =	gpioc_kqread,
+	.f_touch =	NULL
 };
 
+static struct gpioc_pin_event *
+next_head_event(struct gpioc_cdevpriv *priv)
+{
+	struct gpioc_pin_event *rv;
+
+	rv = &priv->events[priv->evidx_head++];
+	if (priv->evidx_head == priv->numevents)
+		priv->evidx_head = 0;
+	return (rv);
+}
+
+static struct gpioc_pin_event *
+next_tail_event(struct gpioc_cdevpriv *priv)
+{
+	struct gpioc_pin_event *rv;
+
+	rv = &priv->events[priv->evidx_tail++];
+	if (priv->evidx_tail == priv->numevents)
+		priv->evidx_tail = 0;
+	return (rv);
+}
+
+static size_t
+number_of_events(struct gpioc_cdevpriv *priv)
+{
+	if (priv->evidx_head >= priv->evidx_tail)
+		return (priv->evidx_head - priv->evidx_tail);
+	else
+		return (priv->numevents + priv->evidx_head - priv->evidx_tail);
+}
+
 static int
+gpioc_allocate_pin_intr(struct gpioc_pin_intr *intr_conf, uint32_t flags)
+{
+	int err;
+
+	intr_conf->config_locked = true;
+	mtx_unlock(&intr_conf->mtx);
+
+	intr_conf->intr_res = gpio_alloc_intr_resource(intr_conf->pin->dev,
+	    &intr_conf->intr_rid, RF_ACTIVE, intr_conf->pin, flags);
+	if (intr_conf->intr_res == NULL) {
+		err = ENXIO;
+		goto error_exit;
+	}
+
+	err = bus_setup_intr(intr_conf->pin->dev, intr_conf->intr_res,
+	    INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpioc_interrupt_handler,
+	    intr_conf, &intr_conf->intr_cookie);
+	if (err != 0)
+		goto error_exit;
+
+	intr_conf->pin->flags = flags;
+
+error_exit:
+	mtx_lock(&intr_conf->mtx);
+	intr_conf->config_locked = false;
+	wakeup(&intr_conf->config_locked);
+
+	return (err);
+}
+
+static int
+gpioc_release_pin_intr(struct gpioc_pin_intr *intr_conf)
+{
+	int err;
+
+	intr_conf->config_locked = true;
+	mtx_unlock(&intr_conf->mtx);
+
+	if (intr_conf->intr_cookie != NULL) {
+		err = bus_teardown_intr(intr_conf->pin->dev,
+		    intr_conf->intr_res, intr_conf->intr_cookie);
+		if (err != 0)
+			goto error_exit;
+		else
+			intr_conf->intr_cookie = NULL;
+	}
+
+	if (intr_conf->intr_res != NULL) {
+		err = bus_release_resource(intr_conf->pin->dev, SYS_RES_IRQ,
+		    intr_conf->intr_rid, intr_conf->intr_res);
+		if (err != 0)
+			goto error_exit;
+		else {
+			intr_conf->intr_rid = 0;
+			intr_conf->intr_res = NULL;
+		}
+	}
+
+	intr_conf->pin->flags = 0;
+	err = 0;
+
+error_exit:
+	mtx_lock(&intr_conf->mtx);
+	intr_conf->config_locked = false;
+	wakeup(&intr_conf->config_locked);
+
+	return (err);
+}
+
+static int
+gpioc_attach_priv_pin(struct gpioc_cdevpriv *priv,
+    struct gpioc_pin_intr *intr_conf)
+{
+	struct gpioc_privs	*priv_link;
+	struct gpioc_pins	*pin_link;
+	unsigned int		consistency_a, consistency_b;
+
+	consistency_a = 0;
+	consistency_b = 0;
+	mtx_assert(&intr_conf->mtx, MA_OWNED);
+	mtx_lock(&priv->mtx);
+	SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
+		if (priv_link->priv == priv)
+			consistency_a++;
+	}
+	KASSERT(consistency_a <= 1,
+	    ("inconsistent links between pin config and cdevpriv"));
+	SLIST_FOREACH(pin_link, &priv->pins, next) {
+		if (pin_link->pin == intr_conf)
+			consistency_b++;
+	}
+	KASSERT(consistency_a == consistency_b,
+	    ("inconsistent links between pin config and cdevpriv"));
+	if (consistency_a == 1 && consistency_b == 1) {
+		mtx_unlock(&priv->mtx);
+		return (EEXIST);
+	}
+	priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC,
+	    M_NOWAIT | M_ZERO);
+	if (priv_link == NULL)
+	{
+		mtx_unlock(&priv->mtx);
+		return (ENOMEM);
+	}
+	pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC,
+	    M_NOWAIT | M_ZERO);
+	if (pin_link == NULL) {
+		mtx_unlock(&priv->mtx);
+		return (ENOMEM);
+	}
+	priv_link->priv = priv;
+	pin_link->pin = intr_conf;
+	SLIST_INSERT_HEAD(&intr_conf->privs, priv_link, next);
+	SLIST_INSERT_HEAD(&priv->pins, pin_link, next);
+	mtx_unlock(&priv->mtx);
+
+	return (0);
+}
+
+static int
+gpioc_detach_priv_pin(struct gpioc_cdevpriv *priv,
+    struct gpioc_pin_intr *intr_conf)
+{
+	struct gpioc_privs	*priv_link, *priv_link_temp;
+	struct gpioc_pins	*pin_link, *pin_link_temp;
+	unsigned int		consistency_a, consistency_b;
+
+	consistency_a = 0;
+	consistency_b = 0;
+	mtx_assert(&intr_conf->mtx, MA_OWNED);
+	mtx_lock(&priv->mtx);
+	SLIST_FOREACH_SAFE(priv_link, &intr_conf->privs, next, priv_link_temp) {
+		if (priv_link->priv == priv) {
+			SLIST_REMOVE(&intr_conf->privs, priv_link, gpioc_privs,
+			    next);
+			free(priv_link, M_GPIOC);
+			consistency_a++;
+		}
+	}
+	KASSERT(consistency_a <= 1,
+	    ("inconsistent links between pin config and cdevpriv"));
+	SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) {
+		if (pin_link->pin == intr_conf) {
+			/*
+			 * If the pin we're removing has events in the priv's
+			 * event fifo, we can't leave dangling pointers from
+			 * those events to the gpioc_pins struct we're about to
+			 * free.  We also can't remove random items and leave
+			 * holes in the events fifo, so just empty it out.
+			 */
+			if (pin_link->eventcount > 0) {
+				priv->evidx_head = priv->evidx_tail = 0;
+			}
+			SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next);
+			free(pin_link, M_GPIOC);
+			consistency_b++;
+		}
+	}
+	KASSERT(consistency_a == consistency_b,
+	    ("inconsistent links between pin config and cdevpriv"));
+	mtx_unlock(&priv->mtx);
+
+	return (0);
+}
+
+static bool
+gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv *priv,
+    struct gpioc_pin_intr *intr_conf)
+{
+	struct gpioc_privs	*priv_link;
+
+	mtx_assert(&intr_conf->mtx, MA_OWNED);
+
+	if (SLIST_EMPTY(&intr_conf->privs))
+		return (true);
+
+	SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
+		if (priv_link->priv != priv)
+			return (false);
+	}
+
+	return (true);
+}
+
+
+static uint32_t
+gpioc_get_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv,
+    uint32_t pin)
+{
+	struct gpioc_pin_intr	*intr_conf = &sc->sc_pin_intr[pin];
+	struct gpioc_privs	*priv_link;
+	uint32_t		flags;
+
+	flags = intr_conf->pin->flags;
+
+	if (flags == 0)
+		return (0);
+
+	mtx_lock(&intr_conf->mtx);
+	SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
+		if (priv_link->priv == priv) {
+			flags |= GPIO_INTR_ATTACHED;
+			break;
+		}
+	}
+	mtx_unlock(&intr_conf->mtx);
+
+	return (flags);
+}
+
+static int
+gpioc_set_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv,
+    uint32_t pin, uint32_t flags)
+{
+	struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin];
+	int res;
+
+	res = 0;
+	if (intr_conf->pin->flags == 0 && flags == 0) {
+		/* No interrupt configured and none requested: Do nothing. */
+		return (0);
+	}
+	mtx_lock(&intr_conf->mtx);
+	while (intr_conf->config_locked == true)
+		mtx_sleep(&intr_conf->config_locked, &intr_conf->mtx, 0,
+		    "gpicfg", 0);
+	if (intr_conf->pin->flags == 0 && flags != 0) {
+		/*
+		 * No interrupt is configured, but one is requested: Allocate
+		 * and setup interrupt on the according pin.
+		 */
+		res = gpioc_allocate_pin_intr(intr_conf, flags);
+		if (res == 0)
+			res = gpioc_attach_priv_pin(priv, intr_conf);
+		if (res == EEXIST)
+			res = 0;
+	} else if (intr_conf->pin->flags == flags) {
+		/*
+		 * Same interrupt requested as already configured: Attach the
+		 * cdevpriv to the corresponding pin.
+		 */
+		res = gpioc_attach_priv_pin(priv, intr_conf);
+		if (res == EEXIST)
+			res = 0;
+	} else if (intr_conf->pin->flags != 0 && flags == 0) {
+		/*
+		 * Interrupt configured, but none requested: Teardown and
+		 * release the pin when no other cdevpriv is attached. Otherwise
+		 * just detach pin and cdevpriv from each other.
+		 */
+		if (gpioc_intr_reconfig_allowed(priv, intr_conf)) {
+			res = gpioc_release_pin_intr(intr_conf);
+		}
+		if (res == 0)
+			res = gpioc_detach_priv_pin(priv, intr_conf);
+	} else {
+		/*
+		 * Other flag requested than configured: Reconfigure when no
+		 * other cdevpriv is are attached to the pin.
+		 */
+		if (!gpioc_intr_reconfig_allowed(priv, intr_conf))
+			res = EBUSY;
+		else {
+			res = gpioc_release_pin_intr(intr_conf);
+			if (res == 0)
+				res = gpioc_allocate_pin_intr(intr_conf, flags);
+			if (res == 0)
+				res = gpioc_attach_priv_pin(priv, intr_conf);
+			if (res == EEXIST)
+				res = 0;
+		}
+	}
+	mtx_unlock(&intr_conf->mtx);
+
+	return (res);
+}
+
+static void
+gpioc_interrupt_handler(void *arg)
+{
+	struct gpioc_pin_intr *intr_conf;
+	struct gpioc_privs *privs;
+	struct gpioc_softc *sc;
+	sbintime_t evtime;
+	uint32_t pin_state;
+
+	intr_conf = arg;
+	sc = intr_conf->sc;
+
+	/* Capture time and pin state first. */
+	evtime = sbinuptime();
+	if (intr_conf->pin->flags & GPIO_INTR_EDGE_BOTH)
+		GPIO_PIN_GET(sc->sc_pdev, intr_conf->pin->pin, &pin_state);
+	else if (intr_conf->pin->flags & GPIO_INTR_EDGE_RISING)
+		pin_state = true;
+	else
+		pin_state = false;
+
+	mtx_lock(&intr_conf->mtx);
+
+	if (intr_conf->config_locked == true) {
+		ddevice_printf(sc->sc_dev, "Interrupt configuration in "
+		    "progress. Discarding interrupt on pin %d.\n",
+		    intr_conf->pin->pin);
+		mtx_unlock(&intr_conf->mtx);
+		return;
+	}
+
+	if (SLIST_EMPTY(&intr_conf->privs)) {
+		ddevice_printf(sc->sc_dev, "No file descriptor associated with "
+		    "occurred interrupt on pin %d.\n", intr_conf->pin->pin);
+		mtx_unlock(&intr_conf->mtx);
+		return;
+	}
+
+	SLIST_FOREACH(privs, &intr_conf->privs, next) {
+		struct gpioc_cdevpriv *priv = privs->priv;
+		struct gpioc_pins *privpin;
+		struct gpioc_pin_event *event;
+		mtx_lock(&priv->mtx);
+		SLIST_FOREACH(privpin, &priv->pins, next) {
+			if (privpin->pin == intr_conf)
+				break;
+		}
+		if (privpin == NULL) {
+			/* Should be impossible. */
+			ddevice_printf(sc->sc_dev, "Cannot find privpin\n");
+			mtx_unlock(&priv->mtx);
+			continue;
+		}
+
+		if (priv->report_option == GPIO_EVENT_REPORT_DETAIL) {
+			event = next_head_event(priv);
+			/* If head is overtaking tail, advance tail. */
+			if (priv->evidx_head == priv->evidx_tail)
+				next_tail_event(priv);
+		} else {
+			if (privpin->eventcount > 0)
+				event = &priv->events[privpin->firstevent + 1];
+			else {
+				privpin->firstevent = priv->evidx_head;
+				event = next_head_event(priv);
+				event->privpin = privpin;
+				event->event_time = evtime;
+				event->event_pin_state = pin_state;
+				event = next_head_event(priv);
+			}
+			++privpin->eventcount;
+		}
+		event->privpin = privpin;
+		event->event_time = evtime;
+		event->event_pin_state = pin_state;
+		wakeup(priv);
+		selwakeup(&priv->selinfo);
+		KNOTE_LOCKED(&priv->selinfo.si_note, 0);
+		if (priv->async == true && priv->sigio != NULL)
+			pgsigio(&priv->sigio, SIGIO, 0);
+		mtx_unlock(&priv->mtx);
+	}
+
+	mtx_unlock(&intr_conf->mtx);
+}
+
+static int
 gpioc_probe(device_t dev)
 {
 	device_set_desc(dev, "GPIO controller");
@@ -88,6 +575,23 @@ gpioc_attach(device_t dev)
 	sc->sc_dev = dev;
 	sc->sc_pdev = device_get_parent(dev);
 	sc->sc_unit = device_get_unit(dev);
+
+	err = GPIO_PIN_MAX(sc->sc_pdev, &sc->sc_npins);
+	sc->sc_npins++; /* Number of pins is one more than max pin number. */
+	if (err != 0)
+		return (err);
+	sc->sc_pin_intr = malloc(sizeof(struct gpioc_pin_intr) * sc->sc_npins,
+	    M_GPIOC, M_WAITOK | M_ZERO);
+	for (int i = 0; i <= sc->sc_npins; i++) {
+		sc->sc_pin_intr[i].pin = malloc(sizeof(struct gpiobus_pin),
+		    M_GPIOC, M_WAITOK | M_ZERO);
+		sc->sc_pin_intr[i].sc = sc;
+		sc->sc_pin_intr[i].pin->pin = i;
+		sc->sc_pin_intr[i].pin->dev = sc->sc_pdev;
+		mtx_init(&sc->sc_pin_intr[i].mtx, "gpioc pin", NULL, MTX_DEF);
+		SLIST_INIT(&sc->sc_pin_intr[i].privs);
+	}
+
 	make_dev_args_init(&devargs);
 	devargs.mda_devsw = &gpioc_cdevsw;
 	devargs.mda_uid = UID_ROOT;
@@ -96,7 +600,7 @@ gpioc_attach(device_t dev)
 	devargs.mda_si_drv1 = sc;
 	err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit);
 	if (err != 0) {
-		printf("Failed to create gpioc%d", sc->sc_unit);
+		device_printf(dev, "Failed to create gpioc%d", sc->sc_unit);
 		return (ENXIO);
 	}
 
@@ -112,12 +616,160 @@ gpioc_detach(device_t dev)
 	if (sc->sc_ctl_dev)
 		destroy_dev(sc->sc_ctl_dev);
 
+	for (int i = 0; i <= sc->sc_npins; i++) {
+		mtx_destroy(&sc->sc_pin_intr[i].mtx);
+		free(&sc->sc_pin_intr[i].pin, M_GPIOC);
+	}
+	free(sc->sc_pin_intr, M_GPIOC);
+
 	if ((err = bus_generic_detach(dev)) != 0)
 		return (err);
 
 	return (0);
 }
 
+static void
+gpioc_cdevpriv_dtor(void *data)
+{
+	struct gpioc_cdevpriv	*priv;
+	struct gpioc_privs	*priv_link, *priv_link_temp;
+	struct gpioc_pins	*pin_link, *pin_link_temp;
+	unsigned int		consistency;
+
+	priv = data;
+
+	SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) {
+		consistency = 0;
+		mtx_lock(&pin_link->pin->mtx);
+		while (pin_link->pin->config_locked == true)
+			mtx_sleep(&pin_link->pin->config_locked,
+			    &pin_link->pin->mtx, 0, "gpicfg", 0);
+		SLIST_FOREACH_SAFE(priv_link, &pin_link->pin->privs, next,
+		    priv_link_temp) {
+			if (priv_link->priv == priv) {
+				SLIST_REMOVE(&pin_link->pin->privs, priv_link,
+				    gpioc_privs, next);
+				free(priv_link, M_GPIOC);
+				consistency++;
+			}
+		}
+		KASSERT(consistency == 1,
+		    ("inconsistent links between pin config and cdevpriv"));
+		if (gpioc_intr_reconfig_allowed(priv, pin_link->pin)) {
+			gpioc_release_pin_intr(pin_link->pin);
+		}
+		mtx_unlock(&pin_link->pin->mtx);
+		SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next);
+		free(pin_link, M_GPIOC);
+	}
+
+	wakeup(&priv);
+	knlist_clear(&priv->selinfo.si_note, 0);
+	seldrain(&priv->selinfo);
+	knlist_destroy(&priv->selinfo.si_note);
+	funsetown(&priv->sigio);
+
+	mtx_destroy(&priv->mtx);
+	free(priv->events, M_GPIOC);
+	free(data, M_GPIOC);
+}
+
+static int
+gpioc_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+	struct gpioc_cdevpriv *priv;
+	int err;
+
+	priv = malloc(sizeof(*priv), M_GPIOC, M_WAITOK | M_ZERO);
+	priv->sc = dev->si_drv1;
+	priv->report_option = GPIO_EVENT_REPORT_DETAIL;
+	err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor);
+	if (err != 0) {
+		gpioc_cdevpriv_dtor(priv);
+		return (err);
+	}
+	mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF);
+	knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx);
+
+	/*
+	 * Allocate a circular buffer for events.  The scheme we use for summary
+	 * reporting assumes there will always be a pair of events available to
+	 * record the first/last events on any pin, so we allocate 2 * npins.
+	 * Even though we actually default to detailed event reporting, 2 *
+	 * npins isn't a horrible fifo size for that either.
+	 */
+	priv->numevents = priv->sc->sc_npins * 2;
+	priv->events = malloc(priv->numevents * sizeof(struct gpio_event_detail),
+	    M_GPIOC, M_WAITOK | M_ZERO);
+
+	return (0);
+}
+
+static int
+gpioc_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+	struct gpioc_cdevpriv *priv;
+	struct gpioc_pin_event *event;
+	union {
+		struct gpio_event_summary sum;
+		struct gpio_event_detail  evt;
+		uint8_t 		  data[1];
+	} recbuf;
+	size_t recsize;
+	int err;
+
+	if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
+		return (err);
+
+	if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY)
+		recsize = sizeof(struct gpio_event_summary);
+	else
+		recsize = sizeof(struct gpio_event_detail);
+
+	if (uio->uio_resid < recsize)
+		return (EINVAL);
+
+	mtx_lock(&priv->mtx);
+	while (priv->evidx_head == priv->evidx_tail) {
+		if (SLIST_EMPTY(&priv->pins)) {
+			err = ENXIO;
+			break;
+		} else if (ioflag & O_NONBLOCK) {
+			err = EWOULDBLOCK;
+			break;
+		} else {
+			err = mtx_sleep(priv, &priv->mtx, PCATCH, "gpintr", 0);
+			if (err != 0)
+				break;
+		}
+	}
+
+	while (err == 0 && uio->uio_resid >= recsize &&
+           priv->evidx_tail != priv->evidx_head) {
+		event = next_tail_event(priv);
+		if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) {
+			recbuf.sum.gp_first_time = event->event_time;
+			recbuf.sum.gp_pin = event->privpin->pin->pin->pin;
+			recbuf.sum.gp_count = event->privpin->eventcount;
+			recbuf.sum.gp_first_state = event->event_pin_state;
+			event = next_tail_event(priv);
+			recbuf.sum.gp_last_time = event->event_time;
+			recbuf.sum.gp_last_state = event->event_pin_state;
+			event->privpin->eventcount = 0;
+			event->privpin->firstevent = 0;
+		} else {
+			recbuf.evt.gp_time = event->event_time;
+			recbuf.evt.gp_pin = event->privpin->pin->pin->pin;
+			recbuf.evt.gp_pinstate = event->event_pin_state;
+		}
+		mtx_unlock(&priv->mtx);
+		err = uiomove(recbuf.data, recsize, uio);
+		mtx_lock(&priv->mtx);
+	}
+	mtx_unlock(&priv->mtx);
+	return (err);
+}
+
 static int 
 gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, 
     struct thread *td)
@@ -125,86 +777,268 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg
 	device_t bus;
 	int max_pin, res;
 	struct gpioc_softc *sc = cdev->si_drv1;
+	struct gpioc_cdevpriv *priv;
 	struct gpio_pin pin;
 	struct gpio_req req;
 	struct gpio_access_32 *a32;
 	struct gpio_config_32 *c32;
-	uint32_t caps;
+	struct gpio_event_config *evcfg;
+	uint32_t caps, intrflags;
 
 	bus = GPIO_GET_BUS(sc->sc_pdev);
 	if (bus == NULL)
 		return (EINVAL);
 	switch (cmd) {
-		case GPIOMAXPIN:
-			max_pin = -1;
-			res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin);
-			bcopy(&max_pin, arg, sizeof(max_pin));
+	case GPIOMAXPIN:
+		max_pin = -1;
+		res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin);
+		bcopy(&max_pin, arg, sizeof(max_pin));
+		break;
+	case GPIOGETCONFIG:
+		bcopy(arg, &pin, sizeof(pin));
+		dprintf("get config pin %d\n", pin.gp_pin);
+		res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin,
+		    &pin.gp_flags);
+		/* Fail early */
+		if (res)
 			break;
-		case GPIOGETCONFIG:
-			bcopy(arg, &pin, sizeof(pin));
-			dprintf("get config pin %d\n", pin.gp_pin);
-			res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin,
-			    &pin.gp_flags);
-			/* Fail early */
-			if (res)
-				break;
-			GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps);
-			GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name);
-			bcopy(&pin, arg, sizeof(pin));
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res)
 			break;
-		case GPIOSETCONFIG:
-			bcopy(arg, &pin, sizeof(pin));
-			dprintf("set config pin %d\n", pin.gp_pin);
-			res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps);
-			if (res == 0)
-				res = gpio_check_flags(caps, pin.gp_flags);
-			if (res == 0)
-				res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin,
-				    pin.gp_flags);
+		pin.gp_flags |= gpioc_get_intr_config(sc, priv,
+		    pin.gp_pin);
+		GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps);
+		GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name);
+		bcopy(&pin, arg, sizeof(pin));
+		break;
+	case GPIOSETCONFIG:
+		bcopy(arg, &pin, sizeof(pin));
+		dprintf("set config pin %d\n", pin.gp_pin);
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res != 0)
 			break;
-		case GPIOGET:
-			bcopy(arg, &req, sizeof(req));
-			res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin,
-			    &req.gp_value);
-			dprintf("read pin %d -> %d\n", 
-			    req.gp_pin, req.gp_value);
-			bcopy(&req, arg, sizeof(req));
+		res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps);
+		if (res != 0)
 			break;
-		case GPIOSET:
-			bcopy(arg, &req, sizeof(req));
-			res = GPIO_PIN_SET(sc->sc_pdev, req.gp_pin, 
-			    req.gp_value);
-			dprintf("write pin %d -> %d\n", 
-			    req.gp_pin, req.gp_value);
+		res = gpio_check_flags(caps, pin.gp_flags);
+		if (res != 0)
 			break;
-		case GPIOTOGGLE:
-			bcopy(arg, &req, sizeof(req));
-			dprintf("toggle pin %d\n", 
-			    req.gp_pin);
-			res = GPIO_PIN_TOGGLE(sc->sc_pdev, req.gp_pin);
+		intrflags = pin.gp_flags & GPIO_INTR_MASK;
+		/*
+		 * We can do only edge interrupts, and only if the
+		 * hardware supports that interrupt type on that pin.
+		 */
+		switch (intrflags) {
+		case GPIO_INTR_NONE:
 			break;
-		case GPIOSETNAME:
-			bcopy(arg, &pin, sizeof(pin));
-			dprintf("set name on pin %d\n", pin.gp_pin);
-			res = GPIOBUS_PIN_SETNAME(bus, pin.gp_pin,
-			    pin.gp_name);
+		case GPIO_INTR_EDGE_RISING:
+		case GPIO_INTR_EDGE_FALLING:
+		case GPIO_INTR_EDGE_BOTH:
+			if ((intrflags & caps) == 0)
+				res = EOPNOTSUPP;
 			break;
-		case GPIOACCESS32:
-			a32 = (struct gpio_access_32 *)arg;
-			res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin,
-			    a32->clear_pins, a32->change_pins, &a32->orig_pins);
+		default:
+			res = EINVAL;
 			break;
-		case GPIOCONFIG32:
-			c32 = (struct gpio_config_32 *)arg;
-			res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin,
-			    c32->num_pins, c32->pin_flags);
+		}
+		if (res != 0)
 			break;
-		default:
-			return (ENOTTY);
+		res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin,
+		    (pin.gp_flags & ~GPIO_INTR_MASK));
+		if (res != 0)
 			break;
+		res = gpioc_set_intr_config(sc, priv, pin.gp_pin,
+		    intrflags);
+		break;
+	case GPIOGET:
+		bcopy(arg, &req, sizeof(req));
+		res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin,
+		    &req.gp_value);
+		dprintf("read pin %d -> %d\n", 
+		    req.gp_pin, req.gp_value);
+		bcopy(&req, arg, sizeof(req));
+		break;
+	case GPIOSET:
+		bcopy(arg, &req, sizeof(req));
+		res = GPIO_PIN_SET(sc->sc_pdev, req.gp_pin, 
+		    req.gp_value);
+		dprintf("write pin %d -> %d\n", 
+		    req.gp_pin, req.gp_value);
+		break;
+	case GPIOTOGGLE:
+		bcopy(arg, &req, sizeof(req));
+		dprintf("toggle pin %d\n", 
+		    req.gp_pin);
+		res = GPIO_PIN_TOGGLE(sc->sc_pdev, req.gp_pin);
+		break;
+	case GPIOSETNAME:
+		bcopy(arg, &pin, sizeof(pin));
+		dprintf("set name on pin %d\n", pin.gp_pin);
+		res = GPIOBUS_PIN_SETNAME(bus, pin.gp_pin,
+		    pin.gp_name);
+		break;
+	case GPIOACCESS32:
+		a32 = (struct gpio_access_32 *)arg;
+		res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin,
+		    a32->clear_pins, a32->change_pins, &a32->orig_pins);
+		break;
+	case GPIOCONFIG32:
+		c32 = (struct gpio_config_32 *)arg;
+		res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin,
+		    c32->num_pins, c32->pin_flags);
+		break;
+	case GPIOCONFIGEVENTS:
+		evcfg = (struct gpio_event_config *)arg;
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res != 0)
+			break;
+		/* If any pins have been configured, changes aren't allowed. */
+		if (!SLIST_EMPTY(&priv->pins)) {
+			res = EINVAL;
+			break;
+		}
+		if (evcfg->gp_report_type != GPIO_EVENT_REPORT_DETAIL &&
+		    evcfg->gp_report_type != GPIO_EVENT_REPORT_SUMMARY) {
+			res = EINVAL;
+			break;
+		}
+		priv->report_option = evcfg->gp_report_type;
+		/* Reallocate the events buffer if the user wants it bigger. */
+		if (priv->report_option == GPIO_EVENT_REPORT_DETAIL &&
+		    priv->numevents < evcfg->gp_fifo_size) {
+			free(priv->events, M_GPIOC);
+			priv->numevents = evcfg->gp_fifo_size;
+			priv->events = malloc(priv->numevents *
+			    sizeof(struct gpio_event_detail), M_GPIOC,
+			    M_WAITOK | M_ZERO);
+			priv->evidx_head = priv->evidx_tail = 0;
+		}
+		break;
+	case FIONBIO:
+		/*
+		 * This dummy handler is necessary to prevent fcntl()
+		 * from failing. The actual handling of non-blocking IO
+		 * is done using the O_NONBLOCK ioflag passed to the
+		 * read() syscall.
+		 */
+		res = 0;
+		break;
+	case FIOASYNC:
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res == 0) {
+			if (*(int *)arg == FASYNC)
+				priv->async = true;
+			else
+				priv->async = false;
+		}
+		break;
+	case FIOGETOWN:
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res == 0)
+			*(int *)arg = fgetown(&priv->sigio);
+		break;
+	case FIOSETOWN:
+		res = devfs_get_cdevpriv((void **)&priv);
+		if (res == 0)
+			res = fsetown(*(int *)arg, &priv->sigio);
+		break;
+	default:
+		return (ENOTTY);
+		break;
 	}
 
 	return (res);
+}
+
+static int
+gpioc_poll(struct cdev *dev, int events, struct thread *td)
+{
+	struct gpioc_cdevpriv *priv;
+	int err;
+	int revents;
+
+	revents = 0;
+
+	err = devfs_get_cdevpriv((void **)&priv);
+	if (err != 0) {
+		revents = POLLERR;
+		return (revents);
+	}
+
+	if (SLIST_EMPTY(&priv->pins)) {

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list