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)
This commit is contained in:
Colin Percival 2024-10-22 11:53:55 -07:00
parent c54bdf84d5
commit c2cd78d944
6 changed files with 317 additions and 1 deletions

View file

@ -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

View file

@ -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
};

View file

@ -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__ */

131
sys/dev/gpio/gpioaei.c Normal file
View file

@ -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);

View file

@ -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

View file

@ -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>