Skip to content

Commit

Permalink
Aarch64 powerdown: add support for GPIO power key
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
francescolavra committed Nov 15, 2024
1 parent c345e6f commit 338d1d1
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 0 deletions.
1 change: 1 addition & 0 deletions platform/virt/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions platform/virt/kernel_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 29 additions & 0 deletions platform/virt/service.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <management.h>
#include <virtio/virtio.h>
#include <devicetree.h>
#include <gic.h>
#include <gpio.h>
#include <hyperv_platform.h>
#include "serial.h"

Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
}
}
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions src/aarch64/gpio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <kernel.h>
#include <gpio.h>

#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);
}
2 changes: 2 additions & 0 deletions src/aarch64/gpio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
void gpio_irq_enable(u64 mask);
void gpio_irq_clear(u64 mask);
33 changes: 33 additions & 0 deletions src/devicetree/devicetree.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
4 changes: 4 additions & 0 deletions src/devicetree/devicetree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))

0 comments on commit 338d1d1

Please sign in to comment.