aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Percival <cperciva@FreeBSD.org>2024-10-22 18:53:55 +0000
committerColin Percival <cperciva@FreeBSD.org>2024-11-03 16:15:39 +0000
commit1db6ffb2a482dee109bbb596f31e9d8d035153c4 (patch)
treea69c22ade48dd82ac171979eb9748aeb00060fd6
parent4b4c7b1924004e53f501612c7ad97b860644e583 (diff)
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.) Reviewed by: imp, andrew, Ahmad Khalifa Approved by: re (kib) 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) (cherry picked from commit c2cd78d9446ebe79accf6c1862230dfbe7276525)
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/gpio/acpi_gpiobus.c121
-rw-r--r--sys/dev/gpio/acpi_gpiobusvar.h49
-rw-r--r--sys/dev/gpio/gpioaei.c131
-rw-r--r--sys/modules/gpio/Makefile2
-rw-r--r--sys/modules/gpio/gpioaei/Makefile14
6 files changed, 317 insertions, 1 deletions
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>