summaryrefslogtreecommitdiff
path: root/arch/arm64/lib/gic.c
diff options
context:
space:
mode:
authorBradley Morgan <brads@mainlining.org>2026-10-04 10:42:10 +0000
committerBradley Morgan <brads@mainlining.org>2026-10-04 10:46:46 +0000
commit5ff54a875642962fc857cee00bde17f9a465f1fa (patch)
treeb265a12d538167c3bbb86b24118a7ad7a062fc74 /arch/arm64/lib/gic.c
tashaboot: arm64 bootloaderHEADmain
holy shit it's here, Tashaboot, based from arm arm, enjoy reading this masterpiece Signed-off-by: Bradley Morgan <brads@mainlining.org>
Diffstat (limited to 'arch/arm64/lib/gic.c')
-rw-r--r--arch/arm64/lib/gic.c105
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..647e987
--- /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_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);
+
+ /*
+ * the group routing is deliberately untouched. the group
+ * registers are the secure world's, a non-secure loader's
+ * writes are dropped on hardware that implements the
+ * security extension, and on emulators that accept them
+ * the timer's per cpu interrupts stop reaching the
+ * kernel. group config belongs to the EL3 monitor, this
+ * loader runs without one.
+ */
+
+ /* 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;
+}