Module Name:    src
Committed By:   brad
Date:           Mon Nov  6 00:35:06 UTC 2023

Modified Files:
        src/doc: CHANGES
        src/etc: MAKEDEV.tmpl
        src/share/man/man4: gpioirq.4
        src/sys/conf: majors
        src/sys/dev/gpio: gpio.c gpioirq.c gpiovar.h

Log Message:
gpioirq(4) version 2

This update makes this driver more than just an example and allows for:

o More than one pin to be attached to a gpioirq instance.  That is,
  the mask parameter can be greater than 0x01 now.

o A /dev/gpioirqN device that allows GPIO pin interrupts to be
  transported into userland.  This is a device that can be opened for
  reading with a simple fixed output indicating the device unit, pin
  number and current pin state.

This update was used as part of a physical intrusion detection system
where multiple switches (i.e. window magnetic reed switches and etc.)
are tied to a bunch of GPIO inputs with userland software that reacts
to the pins changing state.


To generate a diff of this commit:
cvs rdiff -u -r1.3015 -r1.3016 src/doc/CHANGES
cvs rdiff -u -r1.233 -r1.234 src/etc/MAKEDEV.tmpl
cvs rdiff -u -r1.3 -r1.4 src/share/man/man4/gpioirq.4
cvs rdiff -u -r1.102 -r1.103 src/sys/conf/majors
cvs rdiff -u -r1.72 -r1.73 src/sys/dev/gpio/gpio.c
cvs rdiff -u -r1.1 -r1.2 src/sys/dev/gpio/gpioirq.c
cvs rdiff -u -r1.18 -r1.19 src/sys/dev/gpio/gpiovar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/doc/CHANGES
diff -u src/doc/CHANGES:1.3015 src/doc/CHANGES:1.3016
--- src/doc/CHANGES:1.3015	Sun Nov  5 18:32:01 2023
+++ src/doc/CHANGES	Mon Nov  6 00:35:06 2023
@@ -1,4 +1,4 @@
-# LIST OF CHANGES FROM LAST RELEASE:			<$Revision: 1.3015 $>
+# LIST OF CHANGES FROM LAST RELEASE:			<$Revision: 1.3016 $>
 #
 #
 # [Note: This file does not mention every change made to the NetBSD source tree.
@@ -258,3 +258,6 @@ Changes from NetBSD 10.0 to NetBSD 11.0:
 		machines. [tsutsui 20231104]
 	ena(4): MP-enable always, add RSS support, and reliability fixes.
 		[jdolecek 20231105]
+	gpioirq(4): allow multiple pins per gpioirq instance, add the ability
+	to use a /dev/gpioirqN device to get pin interrupts into userland.
+		[brad 20231105]

Index: src/etc/MAKEDEV.tmpl
diff -u src/etc/MAKEDEV.tmpl:1.233 src/etc/MAKEDEV.tmpl:1.234
--- src/etc/MAKEDEV.tmpl:1.233	Wed Dec 28 19:23:02 2022
+++ src/etc/MAKEDEV.tmpl	Mon Nov  6 00:35:05 2023
@@ -1,5 +1,5 @@
 #!/bin/sh -
-#	$NetBSD: MAKEDEV.tmpl,v 1.233 2022/12/28 19:23:02 jakllsch Exp $
+#	$NetBSD: MAKEDEV.tmpl,v 1.234 2023/11/06 00:35:05 brad Exp $
 #
 # Copyright (c) 2003,2007,2008 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -232,6 +232,7 @@
 #	dtv*	Digital TV interface
 #	fb*	PMAX generic framebuffer pseudo-device
 #	fd	file descriptors
+#	gpioirq* Interrupts on GPIO pins
 #	gpiopps* 1PPS signals on GPIO pins
 #	grf*	graphics frame buffer device
 #	hdaudio* High Definition audio control device
@@ -830,7 +831,7 @@ all)
 	makedev srt0 srt1 srt2 srt3
 	makedev tap tap0 tap1 tap2 tap3
 	makedev gpio gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7
-	makedev gpiopps0
+	makedev gpioirq0 gpiopps0
 	makedev pad pad0 pad1 pad2 pad3
 	makedev bthub
 	makedev putter
@@ -873,6 +874,10 @@ gpio)
 	lndev gpio0 gpio
 	;;
 
+gpioirq)
+	makedev gpioirq0
+	;;
+
 gpiopps)
 	makedev gpiopps0
 	lndev gpiopps0 gpiopps
@@ -1547,6 +1552,11 @@ gpio[0-9]*)
 	mkdev gpio$unit c %gpio_chr% $unit 664 $g_gpio
 	;;
 
+gpioirq[0-9]*)
+	unit=${i#gpioirq}
+	mkdev gpioirq$unit c %gpioirq_chr% $unit 444 $g_gpio
+	;;
+
 gpiopps[0-9]*)
 	unit=${i#gpiopps}
 	mkdev gpiopps$unit c %gpiopps_chr% $unit 664 $g_gpio

Index: src/share/man/man4/gpioirq.4
diff -u src/share/man/man4/gpioirq.4:1.3 src/share/man/man4/gpioirq.4:1.4
--- src/share/man/man4/gpioirq.4:1.3	Tue Aug  1 20:39:15 2023
+++ src/share/man/man4/gpioirq.4	Mon Nov  6 00:35:05 2023
@@ -1,6 +1,6 @@
-.\" $NetBSD: gpioirq.4,v 1.3 2023/08/01 20:39:15 andvar Exp $
+.\" $NetBSD: gpioirq.4,v 1.4 2023/11/06 00:35:05 brad Exp $
 .\"
-.\" Copyright (c) 2016 Brad Spencer <b...@anduin.eldar.org>
+.\" Copyright (c) 2016, 2023 Brad Spencer <b...@anduin.eldar.org>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -14,25 +14,26 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd May 11, 2018
+.Dd November 5, 2023
 .Dt GPIOIRQ 4
 .Os
 .Sh NAME
 .Nm gpioirq
-.Nd Install an interrupt handler on a GPIO pin
+.Nd Install an interrupt handler on GPIO pins
 .Sh SYNOPSIS
 .Cd "gpioirq* at gpio? offset 0 mask 0x1 flag 0x00"
 .Sh DESCRIPTION
 The
 .Nm
-driver attaches an interrupt handler to a single GPIO pin.
+driver attaches an interrupt handler to a one or more GPIO pins.
 .Pp
-The pin number is specified in the kernel configuration file with the
+The base pin number is specified in the kernel configuration file with the
 .Ar offset
 locator.
 The
 .Ar mask
-locator should always be 0x1.
+locator can be 0x01 or greater to indicate that more pins should have an
+interrupt handler attached to them.
 .Pp
 The
 .Ar flag
@@ -49,7 +50,7 @@ edge of the pin.
 .It Dv 0x04
 Interrupt on both edges of the pin.
 .It Dv 0x08
-Assert the interrupt as long as the pin is high.
+Assert the intrerrupt as long as the pin is high.
 .It Dv 0x10
 Assert the interrupt as long as the pin is low.
 .El
@@ -80,6 +81,36 @@ is attached at runtime using the
 on the
 .Xr gpio 4
 device.
+.Sh FILES
+.Bl -tag -width "/dev/gpioirqu" -compact
+.It /dev/gpioirq Ns Ar u
+GPIOIRQ device unit
+.Ar u
+file.
+The output from this device are three uint8_t bytes every time an interrupt fires.
+The bytes contain the device unit, pin number and the current state of the pin.
+.Sh EXAMPLES
+The following example will output the device unit, pin and
+the pins current state for pins 4, 5, 6, 7, 8, 9, 10, 11, 12 on gpio0:
+.Bd -literal -offset indent
+/etc/gpio.conf contains:
+gpio0 attach gpioirq 4 0x1ff 0x04
+
+or a kernel was compiled to have the same parameters.
+
+#!/usr/pkg/bin/perl
+
+$dev = "/dev/gpioirq0";
+
+sysopen(DEV,$dev,O_RDONLY) || die "sysopen: $!";
+
+while (sysread(DEV,$b,3)) {
+    @v = unpack("CCC",$b);
+
+    print join(',',@v);
+    print "\\n";
+}
+
 .Sh SEE ALSO
 .Xr gpio 4 ,
 .Xr drvctl 8 ,
@@ -95,3 +126,22 @@ The
 .Nm
 driver was written by
 .An Brad Spencer Aq Mt b...@anduin.eldar.org .
+.Sh BUGS
+When an interrupt fires in most devices there is not any information carried
+along in the interrupt as to whether or not the pin is high or low.  Hence the
+driver reads the current state of the pin after the interrupt has fired and it is
+possible that the state of the pin could have changed between the time the interrupt
+fired and the reading of the state.  As a practical matter the only time the pin state
+will be reported wrong is if there is a very large number of interrupts happening.  The
+driver could have made some assumptions if the interrupt was only for a rising edge or falling
+edge as in those cases it would be possible to know what the pin state would have been, but
+in the case of the double edge, there really will not be any way to be sure with most hardware
+and, in any case, the
+.Xr gpio 4
+infrastructure does not support getting at that information even if it did exist.
+.Pp
+It is important that if the
+.Xr gpioirq 4
+device is opened that it be read, as it may be possible
+to run the kernel out of memory if the device is opened but not read and interrupts
+occur on a pin tied to the driver.

Index: src/sys/conf/majors
diff -u src/sys/conf/majors:1.102 src/sys/conf/majors:1.103
--- src/sys/conf/majors:1.102	Fri Aug 12 11:15:42 2022
+++ src/sys/conf/majors	Mon Nov  6 00:35:05 2023
@@ -1,4 +1,4 @@
-# $NetBSD: majors,v 1.102 2022/08/12 11:15:42 riastradh Exp $
+# $NetBSD: majors,v 1.103 2023/11/06 00:35:05 brad Exp $
 #
 # Device majors for Machine-Independent drivers.
 #
@@ -96,3 +96,4 @@ device-major efi       char 361         
 device-major sht3xtemp char 362		   sht3xtemp
 device-major scmd      char 363		   scmd
 device-major viocon    char 364		   viocon
+device-major gpioirq   char 365		   gpioirq

Index: src/sys/dev/gpio/gpio.c
diff -u src/sys/dev/gpio/gpio.c:1.72 src/sys/dev/gpio/gpio.c:1.73
--- src/sys/dev/gpio/gpio.c:1.72	Tue Dec 13 21:50:43 2022
+++ src/sys/dev/gpio/gpio.c	Mon Nov  6 00:35:05 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: gpio.c,v 1.72 2022/12/13 21:50:43 jakllsch Exp $ */
+/* $NetBSD: gpio.c,v 1.73 2023/11/06 00:35:05 brad Exp $ */
 /*	$OpenBSD: gpio.c,v 1.6 2006/01/14 12:33:49 grange Exp $	*/
 
 /*
@@ -23,7 +23,7 @@
 #endif
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: gpio.c,v 1.72 2022/12/13 21:50:43 jakllsch Exp $");
+__KERNEL_RCSID(0, "$NetBSD: gpio.c,v 1.73 2023/11/06 00:35:05 brad Exp $");
 
 /*
  * General Purpose Input/Output framework.
@@ -619,6 +619,14 @@ gpio_intr_str(void *gpio, struct gpio_pi
 }
 
 int
+gpio_pin_to_pin_num(void *gpio, struct gpio_pinmap *map, int pin)
+{
+	struct gpio_softc *sc = gpio;
+
+	return sc->sc_pins[map->pm_map[pin]].pin_num;
+}
+
+int
 gpio_npins(uint32_t mask)
 {
 	int npins, i;

Index: src/sys/dev/gpio/gpioirq.c
diff -u src/sys/dev/gpio/gpioirq.c:1.1 src/sys/dev/gpio/gpioirq.c:1.2
--- src/sys/dev/gpio/gpioirq.c:1.1	Sat May 19 14:15:39 2018
+++ src/sys/dev/gpio/gpioirq.c	Mon Nov  6 00:35:05 2023
@@ -1,7 +1,7 @@
-/* $NetBSD: gpioirq.c,v 1.1 2018/05/19 14:15:39 thorpej Exp $ */
+/* $NetBSD: gpioirq.c,v 1.2 2023/11/06 00:35:05 brad Exp $ */
 
 /*
- * Copyright (c) 2016 Brad Spencer <b...@anduin.eldar.org>
+ * Copyright (c) 2016, 2023 Brad Spencer <b...@anduin.eldar.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,32 +17,64 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: gpioirq.c,v 1.1 2018/05/19 14:15:39 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: gpioirq.c,v 1.2 2023/11/06 00:35:05 brad Exp $");
 
 /*
- * Example GPIO driver that uses interrupts.
+ * GPIO driver that uses interrupts and can send that fact to userland.
  */
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/device.h>
+#include <sys/device_impl.h>
 #include <sys/gpio.h>
 #include <sys/module.h>
+#include <sys/conf.h>
+#include <sys/proc.h>
+#include <sys/pool.h>
+#include <sys/kmem.h>
+#include <sys/condvar.h>
 
 #include <dev/gpio/gpiovar.h>
 
-#define	GPIOIRQ_NPINS		1
+#define	GPIOIRQ_NPINS		64
+
+struct gpioirq_iv {
+	char			sc_intrstr[128];
+	void *			sc_ih;
+	int			i_thispin_index;
+	uint8_t			i_thispin_num;
+	uint8_t			i_parentunit;
+	struct gpioirq_softc    *sc;
+};
 
 struct gpioirq_softc {
 	device_t		sc_dev;
+	device_t		sc_parentdev;
 	void *			sc_gpio;
 	struct gpio_pinmap	sc_map;
 	int			_map[GPIOIRQ_NPINS];
-	char			sc_intrstr[128];
-	void *			sc_ih;
+	struct gpioirq_iv sc_intrs[GPIOIRQ_NPINS];
+	int			sc_npins;
 	kmutex_t		sc_lock;
+	kmutex_t		sc_read_mutex;
+	kmutex_t		sc_dying_mutex;
 	bool			sc_verbose;
 	bool			sc_functional;
+	bool			sc_opened;
+	bool			sc_dying;
+	kcondvar_t              sc_condreadready;
+	kcondvar_t		sc_cond_dying;
+	pool_cache_t            sc_readpool;
+	char                    *sc_readpoolname;
+	SIMPLEQ_HEAD(,gpioirq_read_q)  sc_read_queue;
+};
+
+struct gpioirq_read_q {
+	int	parentunit;
+	int	thepin;
+	int	theval;
+	SIMPLEQ_ENTRY(gpioirq_read_q) read_q;
 };
 
 #define	GPIOIRQ_FLAGS_IRQMODE	GPIO_INTR_MODE_MASK
@@ -52,8 +84,9 @@ static int	gpioirq_match(device_t, cfdat
 static void	gpioirq_attach(device_t, device_t, void *);
 static int	gpioirq_detach(device_t, int);
 static int	gpioirq_activate(device_t, enum devact);
-
 static int	gpioirq_intr(void *);
+static uint8_t	gpioirq_index_to_pin_num(struct gpioirq_softc *, int);
+static uint8_t	gpioirq_parent_unit(struct gpioirq_softc *);
 
 CFATTACH_DECL_NEW(gpioirq, sizeof(struct gpioirq_softc),
 		  gpioirq_match, gpioirq_attach,
@@ -61,20 +94,47 @@ CFATTACH_DECL_NEW(gpioirq, sizeof(struct
 
 extern struct cfdriver gpioirq_cd;
 
+static dev_type_open(gpioirq_open);
+static dev_type_read(gpioirq_read);
+static dev_type_close(gpioirq_close);
+const struct cdevsw gpioirq_cdevsw = {
+	.d_open = gpioirq_open,
+	.d_close = gpioirq_close,
+	.d_read = gpioirq_read,
+	.d_write = nowrite,
+	.d_ioctl = noioctl,
+	.d_stop = nostop,
+	.d_tty = notty,
+	.d_poll = nopoll,
+	.d_mmap = nommap,
+	.d_kqfilter = nokqfilter,
+	.d_discard = nodiscard,
+	.d_flag = D_OTHER
+};
+
+static uint8_t
+gpioirq_index_to_pin_num(struct gpioirq_softc *sc, int index)
+{
+	return (uint8_t)gpio_pin_to_pin_num(sc->sc_gpio, &sc->sc_map, index);
+}
+
+static uint8_t
+gpioirq_parent_unit(struct gpioirq_softc *sc)
+{
+	device_t parent = sc->sc_parentdev;
+
+	return (uint8_t)parent->dv_unit;
+}
+
 static int
 gpioirq_match(device_t parent, cfdata_t cf, void *aux)
 {
 	struct gpio_attach_args *ga = aux;
-	int npins;
 
 	if (strcmp(ga->ga_dvname, cf->cf_name))
 		return (0);
-	
-	if (ga->ga_offset == -1)
-		return (0);
 
-	npins = gpio_npins(ga->ga_mask);
-	if (npins > 1)
+	if (ga->ga_offset == -1)
 		return (0);
 
 	return (1);
@@ -85,18 +145,30 @@ gpioirq_attach(device_t parent, device_t
 {
 	struct gpioirq_softc *sc = device_private(self);
 	struct gpio_attach_args *ga = aux;
-	int npins = gpio_npins(ga->ga_mask);
+	int mask = ga->ga_mask;
 	int irqmode, flags;
 
 	sc->sc_dev = self;
+	sc->sc_parentdev = parent;
+	sc->sc_opened = false;
+	sc->sc_dying = false;
+	sc->sc_readpoolname = NULL;
 
 	/* Map pins */
 	sc->sc_gpio = ga->ga_gpio;
 	sc->sc_map.pm_map = sc->_map;
 
-	/* We always map just 1 pin. */
+	/* Determine our pin configuation. */
+	sc->sc_npins = gpio_npins(mask);
+	if (sc->sc_npins == 0) {
+		sc->sc_npins = 1;
+		mask = 0x1;
+	}
+
+	/* XXX - exit if more than allowed number of pins */
+
 	if (gpio_pin_map(sc->sc_gpio, ga->ga_offset,
-			 npins ? ga->ga_mask : 0x1, &sc->sc_map)) {
+			 mask, &sc->sc_map)) {
 		aprint_error(": can't map pins\n");
 		return;
 	}
@@ -109,74 +181,239 @@ gpioirq_attach(device_t parent, device_t
 	irqmode = ga->ga_flags & GPIOIRQ_FLAGS_IRQMODE;
 
 	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM);
-
-	if (!gpio_intr_str(sc->sc_gpio, &sc->sc_map, 0, irqmode,
-			   sc->sc_intrstr, sizeof(sc->sc_intrstr))) {
-		aprint_error_dev(self, "failed to decode interrupt\n");
-		return;
-	}
-
-	if (!gpio_pin_irqmode_issupported(sc->sc_gpio, &sc->sc_map, 0,
-					  irqmode)) {
-		aprint_error_dev(self,
-		    "irqmode not supported: %s\n", sc->sc_intrstr);
-		gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
-		return;
-	}
-
-	flags = gpio_pin_get_conf(sc->sc_gpio, &sc->sc_map, 0);
-	flags = (flags & ~(GPIO_PIN_OUTPUT|GPIO_PIN_INOUT)) |
-	    GPIO_PIN_INPUT;
-	if (!gpio_pin_set_conf(sc->sc_gpio, &sc->sc_map, 0, flags)) {
-		aprint_error_dev(sc->sc_dev, "pin not capable of input\n");
-		gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
-		return;
+	mutex_init(&sc->sc_dying_mutex, MUTEX_DEFAULT, IPL_VM);
+	mutex_init(&sc->sc_read_mutex, MUTEX_DEFAULT, IPL_VM);
+	cv_init(&sc->sc_cond_dying, "girqdie");
+	cv_init(&sc->sc_condreadready,"girqrr");
+	sc->sc_readpoolname = kmem_asprintf("girqread%d",device_unit(self));
+	sc->sc_readpool = pool_cache_init(sizeof(struct gpioirq_read_q),0,0,0,sc->sc_readpoolname,NULL,IPL_VM,NULL,NULL,NULL);
+	pool_cache_sethiwat(sc->sc_readpool,100);
+	SIMPLEQ_INIT(&sc->sc_read_queue);
+
+	for(int apin = 0; apin < sc->sc_npins; apin++) {
+		if (!gpio_intr_str(sc->sc_gpio, &sc->sc_map, apin, irqmode,
+		    sc->sc_intrs[apin].sc_intrstr, sizeof(sc->sc_intrs[apin].sc_intrstr))) {
+			aprint_error_dev(self, "failed to decode interrupt\n");
+			return;
+		}
+
+		if (!gpio_pin_irqmode_issupported(sc->sc_gpio, &sc->sc_map, apin,
+		    irqmode)) {
+			aprint_error_dev(self,
+			    "irqmode not supported: %s\n", sc->sc_intrs[apin].sc_intrstr);
+			gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
+			return;
+		}
+
+		flags = gpio_pin_get_conf(sc->sc_gpio, &sc->sc_map, apin);
+		flags = (flags & ~(GPIO_PIN_OUTPUT|GPIO_PIN_INOUT)) |
+		    GPIO_PIN_INPUT;
+		if (!gpio_pin_set_conf(sc->sc_gpio, &sc->sc_map, apin, flags)) {
+			aprint_error_dev(sc->sc_dev, "pin not capable of input\n");
+			gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
+			return;
+		}
+
+		/* These are static for each pin, so just stuff them in here,
+		 * so they don't need to be looked up again.
+		 */
+		sc->sc_intrs[apin].i_thispin_index = apin;
+		sc->sc_intrs[apin].i_thispin_num = gpioirq_index_to_pin_num(sc,apin);
+		sc->sc_intrs[apin].i_parentunit = gpioirq_parent_unit(sc);
+		sc->sc_intrs[apin].sc = sc;
+
+		sc->sc_intrs[apin].sc_ih = gpio_intr_establish(sc->sc_gpio, &sc->sc_map, apin, IPL_VM,
+		    irqmode | GPIO_INTR_MPSAFE,
+		    gpioirq_intr, &sc->sc_intrs[apin]);
+		if (sc->sc_intrs[apin].sc_ih == NULL) {
+			aprint_error_dev(self,
+			    "unable to establish interrupt on %s\n", sc->sc_intrs[apin].sc_intrstr);
+			gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
+			return;
+		}
+		aprint_normal_dev(self, "interrupting on %s\n", sc->sc_intrs[apin].sc_intrstr);
 	}
 
-	sc->sc_ih = gpio_intr_establish(sc->sc_gpio, &sc->sc_map, 0, IPL_VM,
-					irqmode | GPIO_INTR_MPSAFE,
-					gpioirq_intr, sc);
-	if (sc->sc_ih == NULL) {
-		aprint_error_dev(self,
-		    "unable to establish interrupt on %s\n", sc->sc_intrstr);
-		gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
-		return;
-	}
-	aprint_normal_dev(self, "interrupting on %s\n", sc->sc_intrstr);
-
 	sc->sc_functional = true;
 }
 
 int
 gpioirq_intr(void *arg)
 {
-	struct gpioirq_softc *sc = arg;
+	struct gpioirq_iv *is = arg;
+	struct gpioirq_softc *sc = is->sc;
+	struct gpioirq_read_q *q;
 	int val;
 
 	mutex_enter(&sc->sc_lock);
 
-	val = gpio_pin_read(sc->sc_gpio, &sc->sc_map, 0);
+	val = gpio_pin_read(sc->sc_gpio, &sc->sc_map, is->i_thispin_index);
 
 	if (sc->sc_verbose)
 		printf("%s: interrupt on %s --> %d\n",
-		       device_xname(sc->sc_dev), sc->sc_intrstr, val);
+		       device_xname(sc->sc_dev), sc->sc_intrs[is->i_thispin_index].sc_intrstr, val);
 
 	mutex_exit(&sc->sc_lock);
 
+	if (sc->sc_opened) {
+		mutex_enter(&sc->sc_read_mutex);
+		q = pool_cache_get(sc->sc_readpool,PR_NOWAIT);
+		if (q != NULL) {
+			q->thepin = is->i_thispin_num;
+			q->parentunit = is->i_parentunit;
+			q->theval = val;
+			SIMPLEQ_INSERT_TAIL(&sc->sc_read_queue,q,read_q);
+			cv_signal(&sc->sc_condreadready);
+		} else {
+			aprint_error("Could not allocate memory for read pool\n");
+		}
+		mutex_exit(&sc->sc_read_mutex);
+	}
+
 	return (1);
 }
 
+static int
+gpioirq_open(dev_t dev, int flags, int fmt, struct lwp *l)
+{
+	struct gpioirq_softc *sc;
+
+	sc = device_lookup_private(&gpioirq_cd, minor(dev));
+	if (!sc)
+		return (ENXIO);
+
+	if (sc->sc_opened)
+		return (EBUSY);
+
+	mutex_enter(&sc->sc_lock);
+	sc->sc_opened = true;
+	mutex_exit(&sc->sc_lock);
+
+	return (0);
+}
+
+static int
+gpioirq_read(dev_t dev, struct uio *uio, int flags)
+{
+	struct gpioirq_softc *sc;
+	struct gpioirq_read_q *chp;
+	int error = 0,any;
+	uint8_t obuf[3];
+
+	sc = device_lookup_private(&gpioirq_cd, minor(dev));
+	if (!sc)
+		return (ENXIO);
+
+	while (uio->uio_resid > 0) {
+		any = 0;
+		error = 0;
+		mutex_enter(&sc->sc_read_mutex);
+
+		while (any == 0) {
+			chp = SIMPLEQ_FIRST(&sc->sc_read_queue);
+			if (chp != NULL) {
+				SIMPLEQ_REMOVE_HEAD(&sc->sc_read_queue, read_q);
+				any = 1;
+				break;
+			} else {
+				error = cv_wait_sig(&sc->sc_condreadready,&sc->sc_read_mutex);
+				if (sc->sc_dying)
+					error = EIO;
+				if (error == 0)
+					continue;
+				break;
+			}
+		}
+
+		if (any == 1 && error == 0) {
+			obuf[0] = (uint8_t)chp->parentunit;
+			obuf[1] = (uint8_t)chp->thepin;
+			obuf[2] = (uint8_t)chp->theval;
+			pool_cache_put(sc->sc_readpool,chp);
+			mutex_exit(&sc->sc_read_mutex);
+			if ((error = uiomove(&obuf[0], 3, uio)) != 0) {
+				break;
+			}
+		} else {
+			mutex_exit(&sc->sc_read_mutex);
+			if (error) {
+				break;
+			}
+		}
+	}
+
+	if (sc->sc_dying) {
+		mutex_enter(&sc->sc_dying_mutex);
+		cv_signal(&sc->sc_cond_dying);
+		mutex_exit(&sc->sc_dying_mutex);
+	}
+	return error;
+}
+
+static int
+gpioirq_close(dev_t dev, int flags, int fmt, struct lwp *l)
+{
+	struct gpioirq_softc *sc;
+	struct gpioirq_read_q *q;
+
+	sc = device_lookup_private(&gpioirq_cd, minor(dev));
+
+	mutex_enter(&sc->sc_lock);
+	while ((q = SIMPLEQ_FIRST(&sc->sc_read_queue)) != NULL) {
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_read_queue, read_q);
+		pool_cache_put(sc->sc_readpool,q);
+	}
+	sc->sc_opened = false;
+	mutex_exit(&sc->sc_lock);
+
+	return(0);
+}
+
 int
 gpioirq_detach(device_t self, int flags)
 {
 	struct gpioirq_softc *sc = device_private(self);
+	struct gpioirq_read_q *q;
 
 	/* Clear the handler and disable the interrupt. */
-	gpio_intr_disestablish(sc->sc_gpio, sc->sc_ih);
+	for(int apin = 0;apin < sc->sc_npins;apin++) {
+		gpio_intr_disestablish(sc->sc_gpio, sc->sc_intrs[apin].sc_ih);
+	}
 
 	/* Release the pin. */
 	gpio_pin_unmap(sc->sc_gpio, &sc->sc_map);
 
+	sc->sc_dying = true;
+
+	if (sc->sc_opened) {
+		mutex_enter(&sc->sc_dying_mutex);
+		mutex_enter(&sc->sc_read_mutex);
+		cv_signal(&sc->sc_condreadready);
+		mutex_exit(&sc->sc_read_mutex);
+		/* In the worst case this will time out after 5 seconds.
+		 * It really should not take that long for the drain / whatever
+		 * to happen
+		 */
+		cv_timedwait_sig(&sc->sc_cond_dying,
+		    &sc->sc_dying_mutex, mstohz(5000));
+		mutex_exit(&sc->sc_dying_mutex);
+		cv_destroy(&sc->sc_condreadready);
+		cv_destroy(&sc->sc_cond_dying);
+	}
+
+	/* Drain any read pools */
+	while ((q = SIMPLEQ_FIRST(&sc->sc_read_queue)) != NULL) {
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_read_queue, read_q);
+		pool_cache_put(sc->sc_readpool,q);
+	}
+
+	if (sc->sc_readpoolname != NULL) {
+		kmem_free(sc->sc_readpoolname,strlen(sc->sc_readpoolname) + 1);
+	}
+
+	mutex_destroy(&sc->sc_read_mutex);
+	mutex_destroy(&sc->sc_lock);
+
 	return (0);
 }
 
@@ -184,9 +421,11 @@ int
 gpioirq_activate(device_t self, enum devact act)
 {
 
+	struct gpioirq_softc *sc = device_private(self);
+
 	switch (act) {
 	case DVACT_DEACTIVATE:
-		/* We don't really need to do anything. */
+		sc->sc_dying = true;
 		return (0);
 	default:
 		return (EOPNOTSUPP);
@@ -203,26 +442,39 @@ static int
 gpioirq_modcmd(modcmd_t cmd, void *opaque)
 {
 	int error = 0;
+#ifdef _MODULE
+	int bmaj = -1, cmaj = -1;
+#endif
 
 	switch (cmd) {
 	case MODULE_CMD_INIT:
 #ifdef _MODULE
 		error = config_init_component(cfdriver_ioconf_gpioirq,
 		    cfattach_ioconf_gpioirq, cfdata_ioconf_gpioirq);
-		if (error)
+		if (error) {
 			aprint_error("%s: unable to init component\n",
 			    gpioirq_cd.cd_name);
+			return (error);
+		}
+
+		error = devsw_attach("gpioirq", NULL, &bmaj,
+		    &gpioirq_cdevsw, &cmaj);
+		if (error) {
+			aprint_error("%s: unable to attach devsw\n",
+			    gpioirq_cd.cd_name);
+			config_fini_component(cfdriver_ioconf_gpioirq,
+			    cfattach_ioconf_gpioirq, cfdata_ioconf_gpioirq);
+		}
 #endif
-		break;
+		return (error);
 	case MODULE_CMD_FINI:
 #ifdef _MODULE
+		devsw_detach(NULL, &gpioirq_cdevsw);
 		config_fini_component(cfdriver_ioconf_gpioirq,
 		    cfattach_ioconf_gpioirq, cfdata_ioconf_gpioirq);
 #endif
-		break;
+		return (0);
 	default:
-		error = ENOTTY;
+		return (ENOTTY);
 	}
-
-	return (error);
 }

Index: src/sys/dev/gpio/gpiovar.h
diff -u src/sys/dev/gpio/gpiovar.h:1.18 src/sys/dev/gpio/gpiovar.h:1.19
--- src/sys/dev/gpio/gpiovar.h:1.18	Sat May 19 13:59:06 2018
+++ src/sys/dev/gpio/gpiovar.h	Mon Nov  6 00:35:05 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: gpiovar.h,v 1.18 2018/05/19 13:59:06 thorpej Exp $ */
+/* $NetBSD: gpiovar.h,v 1.19 2023/11/06 00:35:05 brad Exp $ */
 /*	$OpenBSD: gpiovar.h,v 1.3 2006/01/14 12:33:49 grange Exp $	*/
 
 /*
@@ -118,6 +118,7 @@ void *	gpio_intr_establish(void *, struc
 void	gpio_intr_disestablish(void *, void *);
 bool	gpio_intr_str(void *, struct gpio_pinmap *, int, int,
 		      char *, size_t);
+int	gpio_pin_to_pin_num(void *, struct gpio_pinmap *, int);
 
 int	gpio_lock(void *);
 void	gpio_unlock(void *);

Reply via email to