diff options
| author | Bradley Morgan <brads@mainlining.org> | 2026-10-04 02:24:47 +0000 |
|---|---|---|
| committer | Bradley Morgan <brads@mainlining.org> | 2026-10-04 02:24:47 +0000 |
| commit | be4565f1fe3de7e16a48688ab8da39c433f0fee3 (patch) | |
| tree | e8e8a4bc2ea0861f6e1d6c5e8011ea11b4abaea4 /arch | |
| parent | 871e4d1551ac8c5be4339e4a3129c45014e613b5 (diff) | |
tashaboot: gic init and the devicetree reg walker
The interrupt controller state the kernel inherits is the
bootloader's to define. On real hardware the secure world
owns which interrupts the non-secure kernel will ever see,
and a distributor left with random enables or secure group
bits can fire before the kernel's irqchip driver is up. The
gic goes into the defined state here, distributor off, every
line in the non-secure group, per interrupt enables, pending
and active cleared, cpu interface off. The kernel programs
everything it runs with itself, it starts from zero instead
of from whatever the last stage left.
The controller is found in the devicetree, no hardcoded
address. The new walker locates a node by name at any depth
and decodes the first reg pair with the root cell counts,
the same parse the kernel does. The node's own begin token
starts the walk at depth zero, starting at one skips every
prop in the node, the first version matched nothing.
receipt: gic 8000000 off, root irq handler gic_handle_irq,
smp brought up 1 node 4 cpus, run /init, busybox shell, the
uart console driven by irq 14 through the gic the kernel
reprogrammed over our off state.
Diffstat (limited to 'arch')
| -rw-r--r-- | arch/arm64/lib/gic.c | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/arch/arm64/lib/gic.c b/arch/arm64/lib/gic.c new file mode 100644 index 0000000..11bb9cd --- /dev/null +++ b/arch/arm64/lib/gic.c @@ -0,0 +1,105 @@ +/* + * gic.c - the interrupt controller state a bootloader owns. the + * kernel programs the gic itself for the running system, but it + * trusts the state it inherits: on real hardware the secure + * world configures which interrupts are visible to non-secure, + * and a bootloader that leaves random enables or secure group + * bits set hands the kernel a half-configured distributor that + * can fire before the kernel's irqchip driver is up. + * + * this is the gicv2 sequence from the TRM, the same shape + * u-boot leaves the machine in: distributor off, every + * interrupt in the non-secure group, all per interrupt enables + * cleared, pending state cleared, cpu interfaces off. defined + * state, nothing firing, the kernel starts from zero. + * + * GICv1 shows the same register map minus the security + * extension registers, the writes below are harmless there. + * + * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org> + */ + +#include <string.h> +#include <stdint.h> +#include <boot.h> +#include <reg.h> + +/* distributor registers, offsets from the GICD base */ +#define GICD_CTLR 0x000 +#define GICD_TYPER 0x004 +#define GICD_IGROUPR(n) (0x080 + (n) * 4) +#define GICD_ISENABLER(n) (0x100 + (n) * 4) +#define GICD_ICENABLER(n) (0x180 + (n) * 4) +#define GICD_ICPENDR(n) (0x280 + (n) * 4) +#define GICD_ICACTIVER(n) (0x380 + (n) * 4) + +/* cpu interface registers, offsets from the GICC base */ +#define GICC_CTLR 0x000 +#define GICC_PMR 0x004 + +/* GICD_CTLR bits */ +#define GICD_CTLR_ENABLE_GRP1 (1 << 0) +#define GICD_CTLR_ENABLE_GRP0 (1 << 1) + +/* GICC_CTLR bits */ +#define GICC_CTLR_ENABLE (1 << 0) + +#define GICD_TYPER_ITLINES_MASK 0x1f + +/* + * how many 32-irq lines the distributor carries, TYPER.ITLines + * holds count of (irqs / 32) - 1, clamped per the spec because + * the field is 5 bits and caps at 1020 irqs. + */ +static int gicd_irq_lines(uintptr_t gicd) +{ + uint32_t typer = readl(REG32(gicd + GICD_TYPER)); + + return ((typer & GICD_TYPER_ITLINES_MASK) + 1); +} + +/* + * leave the gic in the defined state the kernel expects. the + * addresses come from the devicetree the caller walked, qemu + * virt carries a gicv2 at 0x08000000 with the cpu interface at + * +0x10000. + */ +int tb_gic_init(uintptr_t gicd, uintptr_t gicc) +{ + int lines; + int n; + + if (!gicd || !gicc) + return -1; + + /* the distributor is off while it is reconfigured */ + writel(0, REG32(gicd + GICD_CTLR)); + writel(0, REG32(gicc + GICC_CTLR)); + + lines = gicd_irq_lines(gicd); + + /* + * every interrupt in group 1, the non-secure group. the + * kernel does not see group 0 interrupts on non-secure + * hardware, and a bootloader that leaves any line in the + * secure group strands it. + */ + for (n = 0; n < lines; n++) + writel(0xffffffff, REG32(gicd + GICD_IGROUPR(n))); + + /* no per interrupt enables, nothing pending */ + for (n = 0; n < lines; n++) { + writel(0xffffffff, REG32(gicd + GICD_ICENABLER(n))); + writel(0xffffffff, REG32(gicd + GICD_ICPENDR(n))); + } + + /* + * the cpu interface stays off with the priority mask at + * the lowest priority, the kernel raises it when it + * brings its own irq handling up. off is the defined + * state, the enable is the kernel's decision to make. + */ + writel(0, REG32(gicc + GICC_PMR)); + + return 0; +} |
