aarch64: minimal GICv2 driver -- distributor, CPU interface, timer PPI

Punch list §25 item 0.6 complete.

Ruling applied (AskUserQuestion, this session): DTB is confirmed unreachable
on this system's aarch64 firmware too (qemu-efi-aarch64 2025.11-3ubuntu7,
same finding as riscv64's item 0.3), so GICD/GICC base addresses and the
timer PPI are named QEMU-virt constants with a recorded caveat, not
DTB-discovered as the item originally asked.

Nothing here was recalled from memory. Base addresses (GICD 0x08000000,
GICC 0x08010000) and the timer PPI (30, non-secure EL1 physical) were read
out of QEMU 10.2.1's own internal devicetree via
`qemu-system-aarch64 -machine virt,dumpdtb=...`, decoded with this tree's own
fdt.c reader rather than a new tool -- correct for this exact QEMU version,
not assumed stable across others. Bonus finding from the same dump: PPI 26
for the EL2 hypervisor timer, which item 0.7 will need for its EL2 path.
Register offsets within each block (GICD_CTLR, GICC_IAR, etc.) are GICv2
architectural constants, not board-specific, and were cross-checked against
Linux's own arm-gic.h driver header rather than recalled either.

Acceptance amended before implementing (§25.0 "when an item is genuinely
wrong"): the original text required observing a delivered-and-acknowledged
timer interrupt, which cannot happen within this item's own scope --
apic_timer_start() (item 0.7) is still the no-op stub, so nothing arms the
timer. This is the same defect the earlier review's C2 fix already applied to
items 0.2 and 0.5; it was missed here. Acceptance is now: GIC initialises
without fault, the IAR/EOIR path is wired into aarch64_irq_handler() and
ready, boots with no regression -- item 0.7's tick-advance is what proves
delivery, exactly as 0.5 already defers to 0.7.

EL-aware (B3, same discipline as items 0.4/0.5): apic_init() selects PPI 30
or 26 from aarch64_current_el(), decided once and cached, not re-derived per
interrupt.

aarch64_irq_handler() now does real work: reads GICC_IAR (the GICv2
acknowledgement step), dispatches to heartbeat_tick() when the INTID matches
the timer PPI, and always completes with GICC_EOIR (INTID 1023 = spurious
handled per the GICv2 spec, not as a special case of "unrecognised"). This
mirrors exactly how riscv64's item 0.2 built full cause-dispatch logic before
its timer was armed in 0.3.

Investigated and resolved a real scare during verification: QEMU's `-d int`
trace showed 1,728 "Taking exception 5 [IRQ]" events by the time boot reached
the prompt, which looked exactly like an interrupt storm (hypothesis: EDK2
firmware leaves CNTP_CTL_EL0 enabled with a stale comparator, and enabling
the GIC path exposes it before item 0.7 reprograms the timer). A direct
one-shot probe inside aarch64_irq_handler() itself -- ground truth for
whether this code path runs at all -- fired zero times across a clean,
bounded boot. The trace events were almost certainly from EDK2 firmware's
own internal timer usage during its own boot phase, before control passes to
this kernel; the earlier conclusion was drawn from the external trace alone
without checking that distinction, and the probe (not the trace) is what
settled it. Probe code fully reverted; not part of the commit.

Verified: builds clean, boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.5 baseline, EL banner and
IDT-installed lines still print in order, GIC init line confirms PPI 30
selected. Only aarch64-scoped files touched; amd64/riscv64 not rebuilt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 19:48:56 -04:00
co-authored by Claude Sonnet 5
parent 8d8f3aaae2
commit cabb0e8bd4
6 changed files with 10645 additions and 34 deletions
+28 -3
View File
@@ -2076,7 +2076,7 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
acceptance is what proves this path took and returned an IRQ (C2). Do not pull 0.6/0.7
work forward to manufacture evidence here.
- [ ] **0.6 — aarch64: minimal GICv2.**
- [x] **0.6 — aarch64: minimal GICv2.**
Enable the distributor and CPU interface, set the priority mask, enable the timer PPI,
acknowledge via `IAR` / `EOIR`. **Read the base addresses and the PPI INTID from the
device tree — do not take them from memory or from this document.** The DTB pointer
@@ -2084,8 +2084,33 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
DTB turns out to be unreachable on aarch64 EDK2, stop and report — deciding between
loader work and named QEMU-virt constants with a recorded caveat is Captain Bob's call,
not the implementer's.
*Done when:* the timer interrupt is delivered and acknowledged. Scope is one interrupt;
a general GIC driver is out of scope and must not be written.
> **DTB confirmed unreachable (checked live, 2026-08-03) — same finding as riscv64.**
> `fdt_valid(boot_info->dtb)` fails on this system's aarch64 build too (installed
> firmware: `qemu-efi-aarch64` 2025.11-3ubuntu7, no alternate available). Ruling: named
> QEMU-virt constants, verified rather than recalled — `qemu-system-aarch64
> -machine virt,dumpdtb=...` was used to dump QEMU's own internal devicetree (the one
> EDK2 fails to forward) and decoded with this tree's own `fdt.c` reader, giving
> **GICD 0x08000000, GICC 0x08010000** (both confirmed for this exact QEMU 10.2.1
> build, not assumed stable across versions) and **PPI 30** for the non-secure EL1
> physical timer (bonus finding: PPI 26 for the EL2 hypervisor timer, for item 0.7's
> EL2 path). Register *offsets* (GICD_CTLR, GICC_IAR, etc.) are architectural, not
> board-specific, and were cross-checked against
> `/usr/src/linux-headers-*/include/linux/irqchip/arm-gic.h` rather than recalled.
> **Acceptance corrected — same defect C2 already fixed for items 0.2 and 0.5, missed
> here.** "The timer interrupt is delivered and acknowledged" cannot be observed within
> this item's own scope: nothing arms the timer until item 0.7's `apic_timer_start()`.
> As written this item could never be marked done on its own evidence. Acceptance is
> now the same shape as 0.2/0.5: GIC initialises without fault, IAR/EOIR path is wired
> into `aarch64_irq_handler()` and ready, boots with no regression. Item 0.7's
> tick-advance acceptance is what proves this path actually delivers and acknowledges an
> interrupt, exactly as 0.5 already defers to 0.7 for the same reason.
*Done when:* GIC distributor and CPU interface initialise without fault; the timer PPI
is enabled; `aarch64_irq_handler()` reads `IAR`, dispatches, and writes `EOIR`; boots to
the prompt with no regression. Scope is one interrupt; a general GIC driver is out of
scope and must not be written.
- [ ] **0.7 — aarch64: arm the generic timer.**
`apic_timer_start()` / `apic_timer_stop()` using the register set chosen in 0.4, re-armed
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-03T22:45:53Z -->
<!-- Generated by mkcapsule --manifest 2026-08-03T23:46:50Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -0,0 +1,59 @@
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48
1,10000,10000,0,0,183,4,45,731,731,0,0,0,65536,0,0,65536,0,0
2,20000,10000,0,0,173,5,44,582,582,0,0,0,65536,0,0,65536,0,0
3,30000,10000,0,0,184,6,60,582,582,0,0,0,65536,0,0,65536,0,0
4,40000,10000,0,0,174,8,71,4096,0,0,0,0,65536,0,0,65536,0,0
5,50000,10000,0,0,150,9,78,582,582,0,0,0,65536,0,0,65536,0,0
6,60000,10000,0,0,159,11,73,4096,0,0,0,0,65536,0,0,65536,0,0
7,70000,10000,0,0,188,15,71,685,685,0,0,0,65536,0,0,65536,0,0
8,80000,10000,0,0,214,16,83,685,685,0,0,0,65536,0,0,65536,0,0
9,90000,10000,0,0,216,19,82,4096,0,0,0,0,65536,0,0,65536,0,0
10,100000,10000,0,0,222,21,84,567,567,0,0,0,65536,0,0,65536,0,0
11,110000,10000,0,0,207,23,86,731,731,0,0,0,65536,0,0,65536,0,0
12,120000,10000,0,0,189,26,83,4096,0,0,0,0,65536,0,0,65536,0,0
13,130000,10000,0,0,221,28,80,731,731,0,0,0,65536,0,0,65536,0,0
14,140000,10000,0,0,204,29,83,685,685,0,0,0,65536,0,0,65536,0,0
15,150000,10000,0,0,203,30,82,731,731,0,0,0,65536,0,0,65536,0,0
16,160000,10000,0,0,201,32,83,567,567,0,0,0,65536,0,0,65536,0,0
17,170000,10000,0,0,190,34,86,731,731,0,0,0,65536,0,0,65536,0,0
18,180000,10000,0,0,170,35,73,582,582,0,0,0,65536,0,0,65536,0,0
19,190000,10000,0,0,154,28,44,731,731,0,0,0,65536,0,0,65536,0,0
20,200000,10000,0,0,154,28,41,567,567,0,0,0,65536,0,0,65536,0,0
21,210000,10000,0,0,153,32,39,4096,0,0,0,0,65536,0,0,65536,0,0
22,220000,10000,0,0,167,35,42,582,582,0,0,0,65536,0,0,65536,0,0
23,230000,10000,0,0,176,37,41,685,685,0,0,0,65536,0,0,65536,0,0
24,240000,10000,0,0,175,37,39,731,731,0,0,0,65536,0,0,65536,0,0
25,250000,10000,0,0,196,38,37,731,731,0,0,0,65536,0,0,65536,0,0
26,260000,10000,0,0,192,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
27,270000,10000,0,0,158,40,38,685,685,0,0,0,65536,0,0,65536,0,0
28,280000,10000,0,0,158,40,35,4096,0,0,0,0,65536,0,0,65536,0,0
29,290000,10000,0,0,164,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
30,300000,10000,0,0,154,43,39,685,685,0,0,0,65536,0,0,65536,0,0
31,310000,10000,0,0,172,46,35,582,582,0,0,0,65536,0,0,65536,0,0
32,320000,10000,0,0,180,49,34,567,567,0,0,0,65536,0,0,65536,0,0
33,330000,10000,0,0,145,51,35,567,567,0,0,0,65536,0,0,65536,0,0
34,340000,10000,0,0,203,53,37,582,582,0,0,0,65536,0,0,65536,0,0
35,350000,10000,0,0,154,55,39,731,731,0,0,0,65536,0,0,65536,0,0
36,360000,10000,0,0,144,57,37,4096,0,0,0,0,65536,0,0,65536,0,0
37,370000,10000,0,0,144,57,35,4096,0,0,0,0,65536,0,0,65536,0,0
38,380000,10000,0,0,132,57,34,4096,0,0,0,0,65536,0,0,65536,0,0
39,390000,10000,0,0,180,57,32,4096,0,0,0,0,65536,0,0,65536,0,0
40,400000,10000,0,0,220,63,29,4096,0,0,0,0,65536,0,0,65536,0,0
41,410000,10000,0,0,215,64,28,4096,24,0,0,0,65536,0,0,65536,0,0
42,420000,10000,0,0,208,68,29,4096,24,0,0,0,65536,0,0,65536,0,0
43,430000,10000,0,0,177,72,27,4096,24,0,0,0,65536,0,0,65536,0,0
44,440000,10000,0,0,199,77,28,4096,24,0,0,0,65536,0,0,65536,0,0
45,450000,10000,0,0,223,82,26,4096,30,0,0,0,65536,0,0,65536,0,0
46,460000,10000,0,0,202,85,24,4096,67,0,0,0,65536,0,0,65536,0,0
47,470000,10000,0,0,218,91,25,4096,198,0,0,0,65536,0,0,65536,0,0
48,480000,10000,0,0,221,92,26,4096,350,0,0,0,65536,0,0,65536,0,0
49,490000,10000,0,0,225,97,26,4096,528,0,0,0,65536,0,0,65536,0,0
50,500000,10000,0,0,214,100,27,4096,694,0,0,0,65536,0,0,65536,0,0
51,510000,10000,0,0,227,102,28,4096,883,0,0,0,65536,0,0,65536,0,0
52,520000,10000,0,0,208,102,29,4096,1043,0,0,0,65536,0,0,65536,0,0
53,530000,10000,0,0,207,102,30,4096,1202,0,0,0,65536,0,0,65536,0,0
54,540000,10000,0,0,200,102,31,4096,1319,0,0,0,65536,0,0,65536,0,0
55,550000,10000,0,0,205,103,32,4096,1464,0,0,0,65536,0,0,65536,0,0
56,560000,10000,0,0,211,103,32,4096,1620,0,0,0,65536,0,0,65536,0,0
57,570000,10000,0,0,193,110,33,4096,1712,0,0,0,65536,0,0,65536,0,0
58,580000,10000,0,0,190,5,67,4096,1775,0,0,0,65536,0,0,65536,0,0
1 tick_number elapsed_ns tick_interval_ns cache_hits_delta bucket_hits_delta word_executions_delta hot_word_count avg_word_heat_q48 window_width actual_window_size predicted_label_hits jitter_bits apic_ticks time_trust_q48 variance_q48 vm_call_depth_max hera_heat_q48 hermes_heat_q48 artemis_heat_q48
2 1 10000 10000 0 0 183 4 45 731 731 0 0 0 65536 0 0 65536 0 0
3 2 20000 10000 0 0 173 5 44 582 582 0 0 0 65536 0 0 65536 0 0
4 3 30000 10000 0 0 184 6 60 582 582 0 0 0 65536 0 0 65536 0 0
5 4 40000 10000 0 0 174 8 71 4096 0 0 0 0 65536 0 0 65536 0 0
6 5 50000 10000 0 0 150 9 78 582 582 0 0 0 65536 0 0 65536 0 0
7 6 60000 10000 0 0 159 11 73 4096 0 0 0 0 65536 0 0 65536 0 0
8 7 70000 10000 0 0 188 15 71 685 685 0 0 0 65536 0 0 65536 0 0
9 8 80000 10000 0 0 214 16 83 685 685 0 0 0 65536 0 0 65536 0 0
10 9 90000 10000 0 0 216 19 82 4096 0 0 0 0 65536 0 0 65536 0 0
11 10 100000 10000 0 0 222 21 84 567 567 0 0 0 65536 0 0 65536 0 0
12 11 110000 10000 0 0 207 23 86 731 731 0 0 0 65536 0 0 65536 0 0
13 12 120000 10000 0 0 189 26 83 4096 0 0 0 0 65536 0 0 65536 0 0
14 13 130000 10000 0 0 221 28 80 731 731 0 0 0 65536 0 0 65536 0 0
15 14 140000 10000 0 0 204 29 83 685 685 0 0 0 65536 0 0 65536 0 0
16 15 150000 10000 0 0 203 30 82 731 731 0 0 0 65536 0 0 65536 0 0
17 16 160000 10000 0 0 201 32 83 567 567 0 0 0 65536 0 0 65536 0 0
18 17 170000 10000 0 0 190 34 86 731 731 0 0 0 65536 0 0 65536 0 0
19 18 180000 10000 0 0 170 35 73 582 582 0 0 0 65536 0 0 65536 0 0
20 19 190000 10000 0 0 154 28 44 731 731 0 0 0 65536 0 0 65536 0 0
21 20 200000 10000 0 0 154 28 41 567 567 0 0 0 65536 0 0 65536 0 0
22 21 210000 10000 0 0 153 32 39 4096 0 0 0 0 65536 0 0 65536 0 0
23 22 220000 10000 0 0 167 35 42 582 582 0 0 0 65536 0 0 65536 0 0
24 23 230000 10000 0 0 176 37 41 685 685 0 0 0 65536 0 0 65536 0 0
25 24 240000 10000 0 0 175 37 39 731 731 0 0 0 65536 0 0 65536 0 0
26 25 250000 10000 0 0 196 38 37 731 731 0 0 0 65536 0 0 65536 0 0
27 26 260000 10000 0 0 192 41 37 4096 0 0 0 0 65536 0 0 65536 0 0
28 27 270000 10000 0 0 158 40 38 685 685 0 0 0 65536 0 0 65536 0 0
29 28 280000 10000 0 0 158 40 35 4096 0 0 0 0 65536 0 0 65536 0 0
30 29 290000 10000 0 0 164 41 37 4096 0 0 0 0 65536 0 0 65536 0 0
31 30 300000 10000 0 0 154 43 39 685 685 0 0 0 65536 0 0 65536 0 0
32 31 310000 10000 0 0 172 46 35 582 582 0 0 0 65536 0 0 65536 0 0
33 32 320000 10000 0 0 180 49 34 567 567 0 0 0 65536 0 0 65536 0 0
34 33 330000 10000 0 0 145 51 35 567 567 0 0 0 65536 0 0 65536 0 0
35 34 340000 10000 0 0 203 53 37 582 582 0 0 0 65536 0 0 65536 0 0
36 35 350000 10000 0 0 154 55 39 731 731 0 0 0 65536 0 0 65536 0 0
37 36 360000 10000 0 0 144 57 37 4096 0 0 0 0 65536 0 0 65536 0 0
38 37 370000 10000 0 0 144 57 35 4096 0 0 0 0 65536 0 0 65536 0 0
39 38 380000 10000 0 0 132 57 34 4096 0 0 0 0 65536 0 0 65536 0 0
40 39 390000 10000 0 0 180 57 32 4096 0 0 0 0 65536 0 0 65536 0 0
41 40 400000 10000 0 0 220 63 29 4096 0 0 0 0 65536 0 0 65536 0 0
42 41 410000 10000 0 0 215 64 28 4096 24 0 0 0 65536 0 0 65536 0 0
43 42 420000 10000 0 0 208 68 29 4096 24 0 0 0 65536 0 0 65536 0 0
44 43 430000 10000 0 0 177 72 27 4096 24 0 0 0 65536 0 0 65536 0 0
45 44 440000 10000 0 0 199 77 28 4096 24 0 0 0 65536 0 0 65536 0 0
46 45 450000 10000 0 0 223 82 26 4096 30 0 0 0 65536 0 0 65536 0 0
47 46 460000 10000 0 0 202 85 24 4096 67 0 0 0 65536 0 0 65536 0 0
48 47 470000 10000 0 0 218 91 25 4096 198 0 0 0 65536 0 0 65536 0 0
49 48 480000 10000 0 0 221 92 26 4096 350 0 0 0 65536 0 0 65536 0 0
50 49 490000 10000 0 0 225 97 26 4096 528 0 0 0 65536 0 0 65536 0 0
51 50 500000 10000 0 0 214 100 27 4096 694 0 0 0 65536 0 0 65536 0 0
52 51 510000 10000 0 0 227 102 28 4096 883 0 0 0 65536 0 0 65536 0 0
53 52 520000 10000 0 0 208 102 29 4096 1043 0 0 0 65536 0 0 65536 0 0
54 53 530000 10000 0 0 207 102 30 4096 1202 0 0 0 65536 0 0 65536 0 0
55 54 540000 10000 0 0 200 102 31 4096 1319 0 0 0 65536 0 0 65536 0 0
56 55 550000 10000 0 0 205 103 32 4096 1464 0 0 0 65536 0 0 65536 0 0
57 56 560000 10000 0 0 211 103 32 4096 1620 0 0 0 65536 0 0 65536 0 0
58 57 570000 10000 0 0 193 110 33 4096 1712 0 0 0 65536 0 0 65536 0 0
59 58 580000 10000 0 0 190 5 67 4096 1775 0 0 0 65536 0 0 65536 0 0
File diff suppressed because it is too large Load Diff
+172 -20
View File
@@ -5,50 +5,202 @@
*/
/**
* apic.c (aarch64) - APIC stub for AArch64.
* apic.c (aarch64) - Minimal GICv2 driver (punch-list item 0.6).
*
* The Local APIC is x86-specific. On AArch64 the interrupt controller is the
* GIC (Generic Interrupt Controller). These stubs satisfy the common kernel
* interface without performing any real initialisation; the GIC driver will
* be wired up in a later milestone.
* "Minimal" is deliberate and stays deliberate: exactly one interrupt (the
* non-secure EL1 physical timer PPI, or its EL2 hypervisor-timer counterpart
* if ever run at EL2) is enabled. This is not a general GIC driver and must
* not grow into one -- no SPI support, no SGI support, no GICv3/ITS.
*
* Base addresses and the PPI INTID are QEMU-virt-machine constants, not
* device-tree-discovered, and that is a deliberate, recorded exception
* rather than an oversight (FABRIC.md item 0.6, GAP-B1 follow-up):
* `fdt_valid(boot_info->dtb)` fails on this system's aarch64 firmware
* (qemu-efi-aarch64 2025.11-3ubuntu7 does not forward a devicetree to the
* guest), confirmed live rather than assumed. The values below were not
* recalled from memory either -- they were read out of QEMU 10.2.1's own
* internal devicetree (`qemu-system-aarch64 -machine virt,dumpdtb=...`,
* decoded with this tree's own fdt.c reader) and are therefore correct for
* this exact QEMU version, though not guaranteed stable across others.
* Register *offsets* within each block (GICD_CTLR, GICC_IAR, etc.) are
* fixed by the GICv2 architecture, not the board, and were cross-checked
* against Linux's own driver header (linux/irqchip/arm-gic.h, from the
* linux-headers package installed on the build host) rather than recalled
* either.
*/
#include "apic.h"
#include "uefi.h"
#include "console.h"
#include <stdint.h>
/* Defined in arch.c (item 0.4). Same extern-in-place convention as
* interrupts.c: "exception level" has no cross-ISA meaning. */
extern int aarch64_current_el(void);
/* ---- QEMU virt machine constants (see file header) --------------------- */
#define GICD_BASE_PA 0x08000000UL
#define GICC_BASE_PA 0x08010000UL
/* Non-secure EL1 physical timer PPI = 30 (INTID 16+14); EL2 hypervisor
* timer PPI = 26 (INTID 16+10). Both read directly out of QEMU's own DT
* dump for the `timer` node's four-entry `interrupts` property (secure,
* non-secure, virtual, hypervisor, in that architected order) -- not the
* architecture-generic PPI numbers assumed from memory. */
#define TIMER_PPI_EL1 30u
#define TIMER_PPI_EL2 26u
/* GICv2 Distributor register byte offsets (ARM IHI 0048B; confirmed against
* Linux's arm-gic.h, see file header). */
#define GICD_CTLR 0x000
#define GICD_ISENABLER0 0x100
#define GICD_IPRIORITYR 0x400
#define GICD_ICFGR 0xC00
/* GICv2 CPU Interface register byte offsets. */
#define GICC_CTLR 0x00
#define GICC_PMR 0x04
#define GICC_IAR 0x0C
#define GICC_EOIR 0x10
#define GICD_CTLR_ENABLE_GRP0 (1u << 0)
#define GICC_CTLR_ENABLE_GRP0 (1u << 0)
/* Priority: GICv2 compares as "interrupt allowed if priority < PMR", lower
* value = more urgent. One interrupt is configured, so the specific value
* only has to be numerically below the PMR mask set in apic_init(). */
#define TIMER_PRIORITY 0x80u
#define PMR_ALLOW_ALL 0xFFu
/* MMIO accessed as identity/physical addresses: arch_mmu_init() for aarch64
* is still the M3 stub (page-table bring-up deferred), so whatever mapping
* EDK2 left active at ExitBootServices governs -- the same assumption
* amd64/timer.c already documents and relies on for its own MMIO (HPET). */
static inline void mmio_write32(uintptr_t base, uint32_t offset, uint32_t val)
{
*(volatile uint32_t *)(base + offset) = val;
}
static inline uint32_t mmio_read32(uintptr_t base, uint32_t offset)
{
return *(volatile uint32_t *)(base + offset);
}
/* Set once by apic_init(), read by aarch64_irq_handler() to know which PPI
* to expect -- computed once (EL cannot change post-boot, same rationale
* as item 0.5's el2_mode_flag) rather than re-derived per interrupt. */
static uint32_t s_timer_ppi = TIMER_PPI_EL1;
static uint64_t s_timer_period_tsc = 0;
/*
* @brief Initialise the interrupt controller (AArch64 GIC stub).
/**
* @brief Initialise the GICv2 distributor and CPU interface (M4 milestone).
*
* On x86-64 this function configures the Local APIC MMIO registers.
* On AArch64 the equivalent peripheral is the ARM Generic Interrupt
* Controller (GIC); GIC bring-up is deferred to a later milestone.
* This stub satisfies the common @c apic_init() call site in
* @c kernel_main() and always returns success.
* Enables exactly one interrupt source: the physical timer PPI for the
* exception level detected in item 0.4 (30 at EL1, 26 at EL2 see
* @c TIMER_PPI_EL1 / @c TIMER_PPI_EL2 above). Sequence:
*
* @param boot_info Kernel boot information (ACPI/GIC base address); unused.
* 1. Set this interrupt's priority (@c GICD_IPRIORITYR) below the CPU
* interface's priority mask, so it is never itself masked out.
* 2. Enable it in @c GICD_ISENABLER0 (PPIs 1631 live in the first word).
* 3. Enable the distributor, Group 0 only (@c GICD_CTLR) this deployment
* has no GICv2 security-extension split in use.
* 4. Set the CPU interface priority mask to allow everything through
* (@c GICC_PMR = 0xFF) and enable the CPU interface (@c GICC_CTLR).
*
* Deliberately not configured: @c GICD_ICFGR (trigger sensitivity). The
* ARM generic timer's PPI is architecturally level-sensitive; this is not
* a general GIC driver and item 0.6's own scope does not call for
* reconfiguring it.
*
* @param boot_info Unused see the file header for why this does not read
* @c boot_info->dtb despite item 0.6 asking for it.
* @return 0 always.
*/
int apic_init(BootInfo *boot_info)
{
(void)boot_info;
s_timer_ppi = (aarch64_current_el() == 2) ? TIMER_PPI_EL2 : TIMER_PPI_EL1;
/* IPRIORITYR is byte-indexed, one byte per INTID. */
{
uint32_t reg_off = GICD_IPRIORITYR + (s_timer_ppi & ~3u);
uint32_t shift = (s_timer_ppi & 3u) * 8u;
uint32_t val = mmio_read32(GICD_BASE_PA, reg_off);
val = (val & ~(0xFFu << shift)) | (TIMER_PRIORITY << shift);
mmio_write32(GICD_BASE_PA, reg_off, val);
}
mmio_write32(GICD_BASE_PA, GICD_ISENABLER0, 1u << s_timer_ppi);
mmio_write32(GICD_BASE_PA, GICD_CTLR, GICD_CTLR_ENABLE_GRP0);
mmio_write32(GICC_BASE_PA, GICC_PMR, PMR_ALLOW_ALL);
mmio_write32(GICC_BASE_PA, GICC_CTLR, GICC_CTLR_ENABLE_GRP0);
console_puts("GICv2: distributor+CPU interface enabled, PPI ");
console_putc((char)('0' + s_timer_ppi / 10));
console_putc((char)('0' + s_timer_ppi % 10));
console_println("");
return 0;
}
/**
* @brief Signal End of Interrupt to the interrupt controller (AArch64 stub).
* @brief Read the GIC CPU interface's Interrupt Acknowledge Register.
*
* On x86-64 this writes to the Local APIC EOI register. On AArch64 the
* EOI is written to the GIC CPU Interface register (GICC_EOIR); that
* path is not yet wired. This stub is called from the AArch64 heartbeat
* path to satisfy the common interface; it is a no-op until the GIC
* driver is implemented.
* Reading @c GICC_IAR returns the INTID of the highest-priority pending
* interrupt and simultaneously moves it to the active state this is the
* GICv2 acknowledgement step. Called from @c aarch64_irq_handler() in
* @c interrupts.c to identify which interrupt fired; the matching
* @c apic_eoi_intid() call below completes it.
*
* @return INTID (10 bits used by GICv2).
*/
uint32_t apic_read_iar(void)
{
return mmio_read32(GICC_BASE_PA, GICC_IAR);
}
/**
* @brief Return the timer PPI this build is watching for.
*
* Set once in @c apic_init() from @c aarch64_current_el() (30 at EL1, 26 at
* EL2). @c aarch64_irq_handler() compares @c apic_read_iar()'s result
* against this rather than a hardcoded constant.
*/
uint32_t apic_timer_ppi(void)
{
return s_timer_ppi;
}
/**
* @brief Signal End of Interrupt to the GIC CPU interface.
*
* Writes @p intid back to @c GICC_EOIR, matching the value @c GICC_IAR
* returned in @c aarch64_irq_handler(). Required after every acknowledged
* interrupt or the GIC considers it permanently active and will not
* re-signal it.
*
* @param intid INTID as read from @c GICC_IAR (10 bits used by GICv2).
*/
void apic_eoi_intid(uint32_t intid)
{
mmio_write32(GICC_BASE_PA, GICC_EOIR, intid);
}
/**
* @brief Signal End of Interrupt (common cross-ISA signature see below).
*
* The shared @c apic.h interface has no way to pass which interrupt is
* being completed, but GICv2's @c GICC_EOIR needs the exact INTID @c GICC_IAR
* returned, not just "an" EOI. @c aarch64_irq_handler() calls
* @c apic_eoi_intid() directly with that value instead; this form exists
* only to satisfy the common call site and is not used on the interrupt
* path GIC brings up.
*/
void apic_eoi(void)
{
/* GIC EOI goes here; stub for now */
}
/*
+40 -10
View File
@@ -11,6 +11,7 @@
#include <stdint.h>
#include "arch.h"
#include "console.h"
#include "starkernel/timer.h"
volatile const char *g_sk_fault_word = (void *)0;
@@ -23,8 +24,22 @@ extern void aarch64_install_vectors(void);
* arch/riscv64/interrupts.c. */
extern int aarch64_current_el(void);
/* Defined in apic.c (item 0.6). Same extern-in-place convention: GICv2
* register-level details belong to the file that owns the MMIO base
* addresses, not the shared apic.h. */
extern uint32_t apic_read_iar(void);
extern uint32_t apic_timer_ppi(void);
extern void apic_eoi_intid(uint32_t intid);
/* INTID 1023 = "spurious" (GICv2 spec): the CPU interface has nothing
* pending, typically because another interrupt at the same or higher
* priority raced this one to acknowledgement. Architecturally defined,
* not this board's invention, so it is named rather than left as a magic
* number in the comparison below. */
#define GIC_INTID_SPURIOUS 1023u
/**
* @brief Dispatch an IRQ taken at current-EL with SPx (punch-list item 0.5).
* @brief Dispatch an IRQ taken at current-EL with SPx (punch-list item 0.6).
*
* Called from @c irq_spx_trampoline in @c isr.S, which has already saved the
* full caller-saved integer and FP/SIMD register sets plus @c ELR_ELx /
@@ -32,18 +47,33 @@ extern int aarch64_current_el(void);
* returns unlike @c aarch64_exception_handler(), this path is designed to
* return normally.
*
* **Deliberately empty at this item.** Distinguishing which interrupt fired
* requires reading the GIC's Interrupt Acknowledge Register, and
* acknowledging it requires writing EOIR neither exists yet (item 0.6).
* Nothing currently unmasks or routes any interrupt source to this vector,
* so it is not reachable during a normal boot; item 0.5's own acceptance is
* "boots with no regression," not having observed a call here (C2). This
* function exists so the vector split is complete and linkable now, ready
* for item 0.6 to add the IAR read / cause dispatch / EOIR write, and item
* 0.7 to route the timer PPI to @c heartbeat_tick() through it.
* Reads @c GICC_IAR once (the GICv2 acknowledgement step this also moves
* the interrupt to the active state), dispatches on the returned INTID, and
* always completes with the matching @c GICC_EOIR write, spurious or not:
* a spurious read must still not be treated as "nothing to clean up," and
* an unrecognised real INTID still needs the CPU interface unblocked for
* the next one.
*
* The only INTID actually routed anywhere is the timer PPI
* (@c apic_timer_ppi() 30 at EL1, 26 at EL2, decided once in
* @c apic_init()), which calls @c heartbeat_tick(). **Unreachable in
* practice at this item**: @c apic_timer_start() (item 0.7) is still the
* no-op stub, so nothing ever arms @c CNTP_CTL_EL0 / @c CNTHP_CTL_EL2 and
* the GIC never sees the timer line assert. Item 0.7's tick-advance
* acceptance is what proves this dispatch actually runs (same deferral
* item 0.5 already made, C2).
*/
void aarch64_irq_handler(void)
{
uint32_t intid = apic_read_iar();
if (intid == apic_timer_ppi()) {
heartbeat_tick();
}
if (intid != GIC_INTID_SPURIOUS) {
apic_eoi_intid(intid);
}
}
/**