From 338d1d14b934f68eaf6eea18e3343db839444953 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 1 Nov 2024 13:31:23 +0100 Subject: [PATCH] Aarch64 powerdown: add support for GPIO power key If ACPI tables are not present in a given Qemu aarch64 instance (e.g. when running an instance with the "virt" machine type and without a UEFI firmware), upon reception of the "system_powerdown" command (which is used to gracefully shut down the instance), Qemu toggles the "poweroff" GPIO instead of generating an ACPI event. Add parsing of GPIO key information from the device tree, and if such information can be found, enable interrupt generation on the PL061 GPIO controller when the poweroff GPIO is toggled, so that the kernel can be shut down by the interrupt handler. This allows a graceful shutdown to be performed when an on-prem instance launched with `ops run --arch=arm64` is terminated with CTRL-C. --- platform/virt/Makefile | 1 + platform/virt/kernel_platform.h | 1 + platform/virt/service.c | 29 +++++++++++++++++++++++++++++ src/aarch64/gpio.c | 17 +++++++++++++++++ src/aarch64/gpio.h | 2 ++ src/devicetree/devicetree.c | 33 +++++++++++++++++++++++++++++++++ src/devicetree/devicetree.h | 4 ++++ 7 files changed, 87 insertions(+) create mode 100644 src/aarch64/gpio.c create mode 100644 src/aarch64/gpio.h diff --git a/platform/virt/Makefile b/platform/virt/Makefile index c9dc2fa4d..3b40dcca6 100644 --- a/platform/virt/Makefile +++ b/platform/virt/Makefile @@ -17,6 +17,7 @@ SRCS-kernel.elf= \ $(SRCDIR)/aarch64/crt0.S \ $(SRCDIR)/aarch64/elf64.c \ $(SRCDIR)/aarch64/gic.c \ + $(SRCDIR)/aarch64/gpio.c \ $(SRCDIR)/aarch64/hyperv.c \ $(SRCDIR)/aarch64/interrupt.c \ $(SRCDIR)/aarch64/kernel_machine.c \ diff --git a/platform/virt/kernel_platform.h b/platform/virt/kernel_platform.h index d80385e31..3109bb065 100644 --- a/platform/virt/kernel_platform.h +++ b/platform/virt/kernel_platform.h @@ -32,6 +32,7 @@ #define VIRT_PCIE_IRQ_BASE 3 #define VIRT_PCIE_IRQ_NUM 4 +#define VIRT_GPIO_IRQ 7 #define VIRT_MMIO_IRQ_BASE 16 #define VIRT_MMIO_IRQ_NUM 32 diff --git a/platform/virt/service.c b/platform/virt/service.c index a2bcc35f5..c11a939db 100644 --- a/platform/virt/service.c +++ b/platform/virt/service.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include "serial.h" @@ -32,6 +34,8 @@ #define init_dump(p, len) #endif +static RO_AFTER_INIT u8 gpio_key_power = -1; + BSS_RO_AFTER_INIT struct uefi_boot_params boot_params; u64 machine_random_seed(void) @@ -396,6 +400,12 @@ void __attribute__((noreturn)) start(u64 x0, u64 x1) while (1); } +closure_func_basic(thunk, void, gpio_key_handler) +{ + kernel_powerdown(); + gpio_irq_clear(U64_FROM_BIT(gpio_key_power)); +} + static void platform_dtb_parse(kernel_heaps kh, vector cpu_ids) { struct fdt fdt; @@ -441,6 +451,18 @@ static void platform_dtb_parse(kernel_heaps kh, vector cpu_ids) } } } + } else if (!runtime_strcmp(name, ss("gpio-keys"))) { + fdt_foreach_node(&fdt, node) { + if (!runtime_strcmp(fdt_node_name(&fdt, node), ss("poweroff"))) { + dt_prop gpio_prop = fdt_get_prop(&fdt, ss("gpios")); + if ((gpio_prop != INVALID_ADDRESS) && + (dt_prop_cell_count(gpio_prop) >= 2)) + /* cell #0: phandle of GPIO controller + * cell #1: GPIO number + */ + gpio_key_power = dt_prop_get_cell(gpio_prop, 1); + } + } } } } @@ -491,6 +513,13 @@ void detect_hypervisor(kernel_heaps kh) void detect_devices(kernel_heaps kh, storage_attach sa) { + if (gpio_key_power != (typeof(gpio_key_power))-1) { + thunk handler = closure_func(heap_locked(kh), thunk, gpio_key_handler); + assert(handler != INVALID_ADDRESS); + irq_register_handler(GIC_SPI_INTS_START + VIRT_GPIO_IRQ, handler, ss("gpio-keys"), + irange(0, 0)); + gpio_irq_enable(U64_FROM_BIT(gpio_key_power)); + } init_acpi(kh); if (hyperv_detected()) { boolean hv_storvsc_attached = false; diff --git a/src/aarch64/gpio.c b/src/aarch64/gpio.c new file mode 100644 index 000000000..779e9c8a1 --- /dev/null +++ b/src/aarch64/gpio.c @@ -0,0 +1,17 @@ +#include +#include + +#define PL061_GPIOIEV 0x40c +#define PL061_GPIOIE 0x410 +#define PL061_GPIOIC 0x41c + +void gpio_irq_enable(u64 mask) +{ + mmio_write_32(mmio_base_addr(GPIO) + PL061_GPIOIEV, mask); + mmio_write_32(mmio_base_addr(GPIO) + PL061_GPIOIE, mask); +} + +void gpio_irq_clear(u64 mask) +{ + mmio_write_32(mmio_base_addr(GPIO) + PL061_GPIOIC, mask); +} diff --git a/src/aarch64/gpio.h b/src/aarch64/gpio.h new file mode 100644 index 000000000..38c78f124 --- /dev/null +++ b/src/aarch64/gpio.h @@ -0,0 +1,2 @@ +void gpio_irq_enable(u64 mask); +void gpio_irq_clear(u64 mask); diff --git a/src/devicetree/devicetree.c b/src/devicetree/devicetree.c index 7c746e0c7..455a5b237 100644 --- a/src/devicetree/devicetree.c +++ b/src/devicetree/devicetree.c @@ -87,6 +87,17 @@ static struct prop_value_map { { ss_static_init("cpu"), DT_VALUE_PHANDLE }, }; +unsigned int dt_prop_cell_count(dt_prop prop) +{ + return dt_u32(prop->data_length) / sizeof(u32); +} + +u32 dt_prop_get_cell(dt_prop prop, unsigned int index) +{ + void *ptr = prop->data + index * sizeof(u32); + return dt_u32(*((u32 *)ptr)); +} + sstring dtb_string(void *dtb, u64 off) { dt_header fdt = dtb; @@ -665,3 +676,25 @@ boolean fdt_get_reg(fdt fdt, u32 acells, u32 scells, dt_reg_iterator *iter) fdt->ptr = ptr; return found; } + +/* Consumes all the properties of the current node (i.e. only children of the current node can be + * parsed after this function is called). */ +dt_prop fdt_get_prop(fdt fdt, sstring name) +{ + void *ptr = fdt->ptr; + void *end = fdt->end; + dt_prop prop = INVALID_ADDRESS; + while (ptr < end) { + u32 token = dt_u32(*(u32 *)ptr); + if (token != FDT_PROP) + break; + ptr += sizeof(token); + dt_prop p = ptr; + u32 prop_len = dt_u32(p->data_length); + ptr += sizeof(*p) + pad(prop_len, 4); + if (!runtime_strcmp(fdt_prop_name(fdt, p), name)) + prop = p; + } + fdt->ptr = ptr; + return prop; +} diff --git a/src/devicetree/devicetree.h b/src/devicetree/devicetree.h index bbec457e4..07eba6400 100644 --- a/src/devicetree/devicetree.h +++ b/src/devicetree/devicetree.h @@ -42,6 +42,9 @@ typedef struct dt_value { closure_type(dt_node_handler, boolean, dt_node n, sstring name); +unsigned int dt_prop_cell_count(dt_prop prop); +u32 dt_prop_get_cell(dt_prop prop, unsigned int index); + typedef struct fdt { void *ptr, *end; char *strings_start, *strings_end; @@ -72,6 +75,7 @@ dt_node fdt_next_node(fdt fdt); sstring fdt_node_name(fdt fdt, dt_node node); void fdt_get_cells(fdt fdt, u32 *acells, u32 *scells); boolean fdt_get_reg(fdt fdt, u32 acells, u32 scells, dt_reg_iterator *iter); +dt_prop fdt_get_prop(fdt fdt, sstring name); #define fdt_foreach_node(fdt, node) \ for (dt_node node = fdt_get_node(fdt); node; node = fdt_next_node(fdt))