git: c2cd78d9446e - stable/14 - GPIO: Add ACPI _AEI support

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Sun, 03 Nov 2024 16:02:28 UTC
The branch stable/14 has been updated by cperciva:

URL: https://cgit.FreeBSD.org/src/commit/?id=c2cd78d9446ebe79accf6c1862230dfbe7276525

commit c2cd78d9446ebe79accf6c1862230dfbe7276525
Author:     Colin Percival <cperciva@FreeBSD.org>
AuthorDate: 2024-10-22 18:53:55 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2024-11-03 16:01:28 +0000

    GPIO: Add ACPI _AEI support
    
    Changes to acpi_gpiobus.c handle discovering and parsing the _AEI
    objects and storing necessary data in device ivars.  A new gpioaei.c
    file implements the device, which simply requests an interrupt when
    the pin is triggered and invokes the appropriate _Exx or _Lxx ACPI
    method.
    
    This makes the GPIO "power button" work on arm64 Graviton systems,
    allowing EC2 "Stop"/"Reboot" instance calls to be handled cleanly.
    (Prior to this change, those requests would time out after 4 minutes
    and the instance would be forcibly killed.)
    
    Reviwed by:     imp, andrew, Ahmad Khalifa
    MFC after:      3 days
    Sponsored by:   Amazon
    Differential Revision:  https://reviews.freebsd.org/D47253
    Co-authored-by: Andrew Turner <andrew@FreeBSD.org>
    
    (cherry picked from commit 9709bda03cd0f20eba0ba4276fc3c2e06354a54f)
---
 sys/conf/files                    |   1 +
 sys/dev/gpio/acpi_gpiobus.c       | 121 +++++++++++++++++++++++++++++++++++
 sys/dev/gpio/acpi_gpiobusvar.h    |  49 ++++++++++++++
 sys/dev/gpio/gpioaei.c            | 131 ++++++++++++++++++++++++++++++++++++++
 sys/modules/gpio/Makefile         |   2 +-
 sys/modules/gpio/gpioaei/Makefile |  14 ++++
 6 files changed, 317 insertions(+), 1 deletion(-)

diff --git a/sys/conf/files b/sys/conf/files
index 5239f38d5154..3810032f7094 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1745,6 +1745,7 @@ dev/gpio/acpi_gpiobus.c		optional acpi gpio
 dev/gpio/dwgpio/dwgpio.c	optional gpio dwgpio fdt
 dev/gpio/dwgpio/dwgpio_bus.c	optional gpio dwgpio fdt
 dev/gpio/dwgpio/dwgpio_if.m	optional gpio dwgpio fdt
+dev/gpio/gpioaei.c		optional acpi gpio
 dev/gpio/gpiobacklight.c	optional gpiobacklight fdt
 dev/gpio/gpiokeys.c		optional gpiokeys fdt
 dev/gpio/gpiokeys_codes.c	optional gpiokeys fdt
diff --git a/sys/dev/gpio/acpi_gpiobus.c b/sys/dev/gpio/acpi_gpiobus.c
index 9828170daeca..14ded4539a5e 100644
--- a/sys/dev/gpio/acpi_gpiobus.c
+++ b/sys/dev/gpio/acpi_gpiobus.c
@@ -35,6 +35,9 @@
 #include <dev/acpica/acpivar.h>
 
 #include <dev/gpio/gpiobusvar.h>
+#include <dev/gpio/acpi_gpiobusvar.h>
+
+#include "gpiobus_if.h"
 
 struct acpi_gpiobus_softc {
 	struct gpiobus_softc	super_sc;
@@ -46,6 +49,13 @@ struct acpi_gpiobus_ctx {
 	ACPI_HANDLE		dev_handle;
 };
 
+struct acpi_gpiobus_ivar
+{
+	struct gpiobus_ivar	gpiobus;	/* Must come first */
+	ACPI_HANDLE		dev_handle;	/* ACPI handle for bus */
+	uint32_t		flags;
+};
+
 static uint32_t
 acpi_gpiobus_convflags(ACPI_RESOURCE_GPIO *gpio_res)
 {
@@ -136,6 +146,74 @@ acpi_gpiobus_enumerate_res(ACPI_RESOURCE *res, void *context)
 	return (AE_OK);
 }
 
+static struct acpi_gpiobus_ivar *
+acpi_gpiobus_setup_devinfo(device_t bus, device_t child,
+    ACPI_RESOURCE_GPIO *gpio_res)
+{
+	struct acpi_gpiobus_ivar *devi;
+
+	devi = malloc(sizeof(*devi), M_DEVBUF, M_NOWAIT | M_ZERO);
+	if (devi == NULL)
+		return (NULL);
+	resource_list_init(&devi->gpiobus.rl);
+
+	devi->flags = acpi_gpiobus_convflags(gpio_res);
+	if (acpi_quirks & ACPI_Q_AEI_NOPULL)
+		devi->flags &= ~GPIO_PIN_PULLUP;
+
+	devi->gpiobus.npins = 1;
+	if (gpiobus_alloc_ivars(&devi->gpiobus) != 0) {
+		free(devi, M_DEVBUF);
+		return (NULL);
+	}
+
+	for (int i = 0; i < devi->gpiobus.npins; i++)
+		devi->gpiobus.pins[i] = gpio_res->PinTable[i];
+
+	return (devi);
+}
+
+static ACPI_STATUS
+acpi_gpiobus_enumerate_aei(ACPI_RESOURCE *res, void *context)
+{
+	ACPI_RESOURCE_GPIO *gpio_res = &res->Data.Gpio;
+	struct acpi_gpiobus_ctx *ctx = context;
+	device_t bus = ctx->sc->sc_busdev;
+	device_t child;
+	struct acpi_gpiobus_ivar *devi;
+
+	/* Check that we have a GpioInt object. */
+	if (res->Type != ACPI_RESOURCE_TYPE_GPIO)
+		return (AE_OK);
+	if (gpio_res->ConnectionType != ACPI_RESOURCE_GPIO_TYPE_INT)
+		return (AE_OK);
+
+	/* Add a child. */
+	child = device_add_child_ordered(bus, 0, "gpio_aei", -1);
+	if (child == NULL)
+		return (AE_OK);
+	devi = acpi_gpiobus_setup_devinfo(bus, child, gpio_res);
+	if (devi == NULL) {
+		device_delete_child(bus, child);
+		return (AE_OK);
+	}
+	device_set_ivars(child, devi);
+
+	for (int i = 0; i < devi->gpiobus.npins; i++) {
+		if (GPIOBUS_PIN_SETFLAGS(bus, child, 0, devi->flags)) {
+			gpiobus_free_ivars(&devi->gpiobus);
+			free(devi, M_DEVBUF);
+			device_delete_child(bus, child);
+			return (AE_OK);
+		}
+	}
+
+	/* Pass ACPI information to children. */
+	devi->dev_handle = ctx->dev_handle;
+
+	return (AE_OK);
+}
+
 static ACPI_STATUS
 acpi_gpiobus_enumerate(ACPI_HANDLE handle, UINT32 depth, void *context,
     void **result)
@@ -272,6 +350,13 @@ acpi_gpiobus_attach(device_t dev)
 	if (ACPI_FAILURE(status))
 		device_printf(dev, "Failed to enumerate GPIO resources\n");
 
+	/* Look for AEI children */
+	status = AcpiWalkResources(handle, "_AEI", acpi_gpiobus_enumerate_aei,
+	    &ctx);
+
+	if (ACPI_FAILURE(status))
+		device_printf(dev, "Failed to enumerate GPIO resources\n");
+
 	return (0);
 }
 
@@ -294,12 +379,48 @@ acpi_gpiobus_detach(device_t dev)
 	return (gpiobus_detach(dev));
 }
 
+int
+gpio_pin_get_by_acpi_index(device_t consumer, uint32_t idx,
+    gpio_pin_t *out_pin)
+{
+	struct acpi_gpiobus_ivar *devi;
+	int rv;
+
+	rv = gpio_pin_get_by_child_index(consumer, idx, out_pin);
+	if (rv != 0)
+		return (rv);
+
+	devi = device_get_ivars(consumer);
+	(*out_pin)->flags = devi->flags;
+
+	return (0);
+}
+
+static int
+acpi_gpiobus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result)
+{
+	struct acpi_gpiobus_ivar *devi = device_get_ivars(child);
+
+	switch (which) {
+	case ACPI_GPIOBUS_IVAR_HANDLE:
+		*result = (uintptr_t)devi->dev_handle;
+		break;
+	default:
+		return (gpiobus_read_ivar(dev, child, which, result));
+	}
+
+	return (0);
+}
+
 static device_method_t acpi_gpiobus_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe,		acpi_gpiobus_probe),
 	DEVMETHOD(device_attach,	acpi_gpiobus_attach),
 	DEVMETHOD(device_detach,	acpi_gpiobus_detach),
 
+	/* Bus interface */
+	DEVMETHOD(bus_read_ivar,	acpi_gpiobus_read_ivar),
+
 	DEVMETHOD_END
 };
 
diff --git a/sys/dev/gpio/acpi_gpiobusvar.h b/sys/dev/gpio/acpi_gpiobusvar.h
new file mode 100644
index 000000000000..77cb91110cb5
--- /dev/null
+++ b/sys/dev/gpio/acpi_gpiobusvar.h
@@ -0,0 +1,49 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Colin Percival
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef	__ACPI_GPIOBUS_H__
+#define	__ACPI_GPIOBUS_H__
+
+#include <sys/bus.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+
+enum acpi_gpiobus_ivars {
+	ACPI_GPIOBUS_IVAR_HANDLE	= 10600,
+};
+
+#define ACPI_GPIOBUS_ACCESSOR(var, ivar, type)			\
+	__BUS_ACCESSOR(acpi_gpiobus, var, ACPI_GPIOBUS, ivar, type)
+
+ACPI_GPIOBUS_ACCESSOR(handle,	HANDLE,		ACPI_HANDLE)
+
+#undef ACPI_GPIOBUS_ACCESSOR
+
+int gpio_pin_get_by_acpi_index(device_t consumer, uint32_t idx,
+    gpio_pin_t *out_pin);
+
+#endif	/* __ACPI_GPIOBUS_H__ */
diff --git a/sys/dev/gpio/gpioaei.c b/sys/dev/gpio/gpioaei.c
new file mode 100644
index 000000000000..050f259a2127
--- /dev/null
+++ b/sys/dev/gpio/gpioaei.c
@@ -0,0 +1,131 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Colin Percival
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include "gpiobus_if.h"
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/gpio/acpi_gpiobusvar.h>
+
+struct gpio_aei_softc {
+	ACPI_HANDLE handle;
+	char objname[5];	/* "_EXX" or "_LXX" */
+	struct resource * intr_res;
+	int intr_rid;
+	void * intr_cookie;
+};
+
+static int
+gpio_aei_probe(device_t dev)
+{
+
+	/* We only match when gpiobus explicitly requested gpio_aei. */
+	return (BUS_PROBE_NOWILDCARD);
+}
+
+static void
+gpio_aei_intr(void * arg)
+{
+	struct gpio_aei_softc * sc = arg;
+
+	/* Ask ACPI to run the appropriate _Exx or _Lxx method. */
+	AcpiEvaluateObject(sc->handle, sc->objname, NULL, NULL);
+}
+
+static int
+gpio_aei_attach(device_t dev)
+{
+	struct gpio_aei_softc * sc = device_get_softc(dev);
+	gpio_pin_t pin;
+	int err;
+
+	/* This is us. */
+	device_set_desc(dev, "ACPI Event Information Device");
+
+	/* Store parameters needed by gpio_aei_intr. */
+	sc->handle = acpi_gpiobus_get_handle(dev);
+	if (gpio_pin_get_by_acpi_index(dev, 0, &pin) != 0) {
+		device_printf(dev, "Unable to get the input pin\n");
+		return (ENXIO);
+	}
+	sprintf(sc->objname, "_%c%02X",
+	    (pin->flags & GPIO_INTR_EDGE_MASK) ? 'E' : 'L', pin->pin);
+
+	/* Support for GPIO pins > 255 is not implemented. */
+	if (pin->pin > 255) {
+		device_printf(dev, "ACPI Event Information Device does not support pins > 255");
+		return (ENOTSUP);
+	}
+
+	/* Set up the interrupt. */
+	if ((sc->intr_res = gpio_alloc_intr_resource(dev, &sc->intr_rid,
+	    RF_ACTIVE, pin, pin->flags & GPIO_INTR_MASK)) == NULL) {
+		device_printf(dev, "Cannot allocate an IRQ\n");
+		return (ENOTSUP);
+	}
+	err = bus_setup_intr(dev, sc->intr_res, INTR_TYPE_MISC | INTR_MPSAFE,
+	    NULL, gpio_aei_intr, sc, &sc->intr_cookie);
+	if (err != 0) {
+		device_printf(dev, "Cannot set up IRQ\n");
+		bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid,
+		    sc->intr_res);
+		return (err);
+	}
+
+	return (0);
+}
+
+static int
+gpio_aei_detach(device_t dev)
+{
+	struct gpio_aei_softc * sc = device_get_softc(dev);
+
+	bus_teardown_intr(dev, sc->intr_res, sc->intr_cookie);
+	bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res);
+	return (0);
+}
+
+static device_method_t gpio_aei_methods[] = {
+	/* Device interface. */
+	DEVMETHOD(device_probe,		gpio_aei_probe),
+	DEVMETHOD(device_attach,	gpio_aei_attach),
+	DEVMETHOD(device_detach,	gpio_aei_detach),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(gpio_aei, gpio_aei_driver, gpio_aei_methods, sizeof(struct gpio_aei_softc));
+DRIVER_MODULE(gpio_aei, gpiobus, gpio_aei_driver, NULL, NULL);
+MODULE_DEPEND(gpio_aei, acpi_gpiobus, 1, 1, 1);
diff --git a/sys/modules/gpio/Makefile b/sys/modules/gpio/Makefile
index ffb3581d1f01..1d7f69f1836d 100644
--- a/sys/modules/gpio/Makefile
+++ b/sys/modules/gpio/Makefile
@@ -24,7 +24,7 @@
 # SUCH DAMAGE.
 #
 
-SUBDIR = gpiobus gpioiic gpioled gpiospi gpioths
+SUBDIR = gpioaei gpiobus gpioiic gpioled gpiospi gpioths
 
 .if !empty(OPT_FDT)
 SUBDIR += gpiokeys gpiopps
diff --git a/sys/modules/gpio/gpioaei/Makefile b/sys/modules/gpio/gpioaei/Makefile
new file mode 100644
index 000000000000..514dcd80a35e
--- /dev/null
+++ b/sys/modules/gpio/gpioaei/Makefile
@@ -0,0 +1,14 @@
+.PATH:	${SRCTOP}/sys/dev/gpio/
+
+KMOD=	gpioaei
+SRCS=	gpioaei.c
+
+SRCS+=	\
+	bus_if.h \
+	device_if.h \
+	gpio_if.h \
+	gpiobus_if.h
+
+CFLAGS+=  -I. -I${SRCTOP}/sys/dev/gpio/
+
+.include <bsd.kmod.mk>