aarch64: arm the ARM Generic Timer -- CNTP/CNTHP TVAL+CTL, per-tick re-arm

Punch list §25 item 0.7 complete. This is what finally makes items 0.5 and
0.6 provably work end to end.

apic_timer_start(): writes CNTP_TVAL_EL0 (or CNTHP_TVAL_EL2 at EL2 --
aarch64_current_el(), same EL-aware discipline as 0.4-0.6) to
s_timer_period_tsc, then CTL.ENABLE=1/IMASK=0, followed by an ISB. The ISB
is not decorative: confirmed against Linux's own arch_timer_reg_write_cp15()
(arch/arm64/include/asm/arch_timer.h) that only the *control* register write
needs synchronising before the enable/mask state is guaranteed visible to
the interrupt pipeline -- TVAL/CVAL writes do not carry the same
requirement, which is why apic_timer_rearm() omits it.

TVAL is architecturally 32-bit but MSR-to-system-register is always a
64-bit instruction form -- passing a uint32_t operand directly failed to
build (-Wasm-operand-widths). Fixed by truncating to 32 bits ourselves then
zero-extending back to 64 for the operand, which supplies explicit,
provably-correct zeros in the RES0 upper field rather than depending on
unverified hardware behaviour -- Linux's own driver never exercises this
path (it always uses the 64-bit CVAL form instead), so there was no local
source to confirm the alternative against.

apic_timer_rearm() (new): re-writes TVAL only, no ISB needed. TVAL is
relative to "now," not an absolute deadline like riscv64's SBI interface
(item 0.3), so there is no drift-correction bookkeeping -- each write means
"N ticks from this instant." Wired into aarch64_irq_handler() and called
*first*, before heartbeat_tick(), matching riscv64_timer_rearm()'s ordering
discipline exactly: the ARM Generic Timer does not auto-reload, so a return
path that skips this leaves the interrupt condition latched, which the GIC
would redeliver the instant it's EOI'd -- a real storm, the same class of
failure item 0.6's verification investigated (and that time found absent,
because nothing was armed yet).

Verified: builds clean; every generated instruction checked against
disassembly, not just reviewed by eye (both EL branches, correct TVAL/CTL
register names, single shared ISB in apic_timer_start(), no ISB in
apic_timer_rearm()). Boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged.

Rate measured directly against real wall-clock time via QEMU's own -d int
trap trace (same method as riscv64's item 0.3), two independent windows:
1,090 interrupts over 11.05 s (98.679 Hz) and 4,031 over 40.88 s (98.614 Hz)
-- consistent across both, so this is a real, small, systematic bias
(~1.3-1.4% slow), not measurement noise from polling granularity, which
would have shrunk with the longer window and did not. Attributed to genuine
per-interrupt service latency: TVAL is rewritten mid-ISR, so the trampoline
save/restore, GICC_IAR read, EL branch and GICC_EOIR write all lengthen the
effective period slightly versus the nominal 10 ms, inherent to any
relative-countdown re-arm scheme. Reported as measured, not smoothed over.
The interrupt sustained continuously across both windows with no stall and
no storm, which is the primary evidence re-arm-every-tick is correct;
the small rate bias is overhead, not a defect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 22:14:52 -04:00
co-authored by Claude Sonnet 5
parent cabb0e8bd4
commit 43aa0a2a36
6 changed files with 10520 additions and 35 deletions
+1 -1
View File
@@ -2112,7 +2112,7 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
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.**
- [x] **0.7 — aarch64: arm the generic timer.**
`apic_timer_start()` / `apic_timer_stop()` using the register set chosen in 0.4, re-armed
each tick.
*Done when:* `heartbeat_ticks()` advances on aarch64 at the configured rate.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-03T23:46:50Z -->
<!-- Generated by mkcapsule --manifest 2026-08-04T02:14:10Z -->
<!-- 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
+99 -21
View File
@@ -5,7 +5,7 @@
*/
/**
* apic.c (aarch64) - Minimal GICv2 driver (punch-list item 0.6).
* apic.c (aarch64) - Minimal GICv2 driver + ARM Generic Timer (items 0.6/0.7).
*
* "Minimal" is deliberate and stays deliberate: exactly one interrupt (the
* non-secure EL1 physical timer PPI, or its EL2 hypervisor-timer counterpart
@@ -203,15 +203,14 @@ void apic_eoi(void)
{
}
/*
* @brief Configure the periodic timer (AArch64 stub).
/**
* @brief Compute the expected system-counter ticks per heartbeat period.
*
* On x86-64 this calibrates and programs the APIC timer. On AArch64 the
* equivalent is the ARM Generic Timer's EL1 Physical Timer (CNTP_CTL_EL0 /
* CNTP_TVAL_EL0); that driver is deferred. This stub computes
* @c s_timer_period_tsc (the expected number of @c CNTPCT_EL0 ticks
* between heartbeats) which is used by @c heartbeat_init() to seed the
* rolling-window expected-delta value.
* Computes @c s_timer_period_tsc (the expected number of @c CNTPCT_EL0
* ticks between heartbeats), used both by @c heartbeat_init() to seed the
* rolling-window @c expected_delta and by @c apic_timer_start() /
* @c apic_timer_rearm() (item 0.7) as the @c TVAL step written on every
* arm and re-arm.
*
* If either @p tsc_hz or @p tick_hz is zero, a fallback of 10,000,000
* ticks is stored (approximately 160 ms at 62.5 MHz, a safe non-zero
@@ -232,29 +231,108 @@ int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz)
}
/**
* @brief Start periodic timer delivery (AArch64 stub).
* @brief Arm the ARM Generic Timer and enable its interrupt (item 0.7).
*
* On x86-64 this unmasks the APIC timer LVT entry and re-arms the
* initial count register. On AArch64 the ARM Generic Timer's EL1
* Physical Timer would be enabled here; the driver is deferred.
* This stub is a no-op that satisfies the common call site in
* @c kernel_main() without fault.
* Writes @c CNTP_TVAL_EL0 (or @c CNTHP_TVAL_EL2 at EL2 — @c
* aarch64_current_el(), same EL-aware discipline as items 0.40.6) to
* @c s_timer_period_tsc, then sets @c CTL.ENABLE=1, @c CTL.IMASK=0.
*
* @c TVAL is a signed 32-bit *relative* countdown — writing it sets
* "fire @c s_timer_period_tsc ticks from this instant," unlike riscv64's
* SBI interface (item 0.3), which takes an absolute deadline the caller
* must compute. No "now" reading is needed here at all.
*
* The @c ISB after the @c CTL write is required, not decorative: per the
* architecture, and confirmed against Linux's own
* @c arch_timer_reg_write_cp15() (arch/arm64/include/asm/arch_timer.h),
* only the *control* register write needs synchronising before the
* enable/mask state is guaranteed visible to the interrupt pipeline —
* @c TVAL/@c CVAL writes do not carry the same requirement, which is why
* @c apic_timer_rearm() below omits it.
*
* @c s_timer_period_tsc must already be set by @c apic_timer_init(). If
* it is still 0, @c TVAL=0 fires immediately and continuously until
* @c apic_timer_rearm() runs — a correctly-functioning if oddly-paced
* timer, not a hang.
*/
void apic_timer_start(void)
{
/* ARM generic timer EL1 physical timer — stub */
/* CNTx_TVAL_EL is architecturally 32-bit (bits[63:32] RES0 on write),
* but MSR to a system register is always a 64-bit (Xn) instruction
* form -- there is no 32-bit encoding to match a narrower operand.
* Truncate to 32 bits ourselves, then zero-extend back to 64 for the
* operand: this supplies explicit, provably-correct zeros in the RES0
* field rather than depending on how the hardware treats whatever
* upper bits a narrower operand happened to leave there -- a
* distinction we could not confirm against a local authoritative
* source (unlike the GICv2 offsets and CTL bit layout above,
* cross-checked against Linux headers on this system; TVAL is not,
* since Linux's own driver only ever uses the 64-bit CVAL form and
* never exercises this path). */
uint64_t tval = (uint64_t)(uint32_t)s_timer_period_tsc;
if (aarch64_current_el() == 2) {
__asm__ volatile ("msr cnthp_tval_el2, %0" :: "r"(tval));
__asm__ volatile ("msr cnthp_ctl_el2, %0" :: "r"((uint64_t)1) : "memory");
} else {
__asm__ volatile ("msr cntp_tval_el0, %0" :: "r"(tval));
__asm__ volatile ("msr cntp_ctl_el0, %0" :: "r"((uint64_t)1) : "memory");
}
__asm__ volatile ("isb" ::: "memory");
}
/**
* @brief Stop periodic timer delivery (AArch64 stub).
* @brief Re-arm the ARM Generic Timer for the next period (item 0.7).
*
* On x86-64 this sets the APIC timer LVT mask bit. On AArch64 the
* ARM Generic Timer would be disabled here. Stub — no-op until the
* GIC / Generic Timer driver is implemented.
* Writes a fresh @c TVAL — the entirety of a periodic re-arm, since @c TVAL
* is relative to "now" rather than an absolute deadline. Unlike riscv64's
* SBI re-arm (item 0.3), there is no drift-correction bookkeeping: each
* write means "@c s_timer_period_tsc ticks from this instant," so time
* spent servicing the interrupt before this call simply lengthens that one
* period rather than accumulating error across ticks.
*
* **This is the one call that must never be skipped.** The ARM Generic
* Timer does not auto-reload: once @c TVAL crosses zero, @c CTL.ISTATUS
* latches and stays asserted — a level condition — until software writes
* a new @c TVAL or clears @c CTL.ENABLE. A return path that skips this
* leaves the interrupt permanently pending, which the GIC would deliver
* again the instant it is EOI'd: a real interrupt storm, not a
* hypothetical one (this is exactly the failure this driver was checked
* against during item 0.6's own verification, and confirmed absent there
* only because nothing armed the timer yet).
*
* Called from @c aarch64_irq_handler() (interrupts.c) before
* @c heartbeat_tick() — same ordering discipline as
* @c riscv64_timer_rearm() (item 0.3): re-arm first, so a fault in the
* rest of the handler cannot also cost the next tick.
*/
void apic_timer_rearm(void)
{
/* uint64_t, zero-extended -- see apic_timer_start() for why. */
uint64_t tval = (uint64_t)(uint32_t)s_timer_period_tsc;
if (aarch64_current_el() == 2) {
__asm__ volatile ("msr cnthp_tval_el2, %0" :: "r"(tval));
} else {
__asm__ volatile ("msr cntp_tval_el0, %0" :: "r"(tval));
}
}
/**
* @brief Stop timer delivery by clearing CTL.ENABLE (item 0.7).
*
* Per the architecture, @c ENABLE=0 unconditionally stops the count and
* de-asserts the interrupt condition regardless of @c IMASK — a single
* write is sufficient, no separate mask step needed.
*/
void apic_timer_stop(void)
{
/* Stub */
if (aarch64_current_el() == 2) {
__asm__ volatile ("msr cnthp_ctl_el2, %0" :: "r"((uint64_t)0) : "memory");
} else {
__asm__ volatile ("msr cntp_ctl_el0, %0" :: "r"((uint64_t)0) : "memory");
}
__asm__ volatile ("isb" ::: "memory");
}
/**
+15 -12
View File
@@ -24,12 +24,14 @@ 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. */
/* Defined in apic.c (items 0.6/0.7). Same extern-in-place convention: GICv2
* and generic-timer register-level details belong to the file that owns
* the MMIO base addresses / EL-aware register selection, 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);
extern void apic_timer_rearm(void);
/* INTID 1023 = "spurious" (GICv2 spec): the CPU interface has nothing
* pending, typically because another interrupt at the same or higher
@@ -39,7 +41,7 @@ extern void apic_eoi_intid(uint32_t intid);
#define GIC_INTID_SPURIOUS 1023u
/**
* @brief Dispatch an IRQ taken at current-EL with SPx (punch-list item 0.6).
* @brief Dispatch an IRQ taken at current-EL with SPx (punch-list item 0.7).
*
* 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 /
@@ -54,20 +56,21 @@ extern void apic_eoi_intid(uint32_t intid);
* 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).
* For the timer PPI (@c apic_timer_ppi() — 30 at EL1, 26 at EL2, decided
* once in @c apic_init()): @c apic_timer_rearm() runs **first**, before
* @c heartbeat_tick() — the ARM Generic Timer does not auto-reload, so
* skipping this leaves the interrupt condition permanently latched (see
* @c apic_timer_rearm()'s own doc comment). Ordering the re-arm ahead of
* the rest of the dispatch means a fault in @c heartbeat_tick() cannot
* also cost the next tick — same discipline as @c riscv64_timer_rearm()
* (item 0.3).
*/
void aarch64_irq_handler(void)
{
uint32_t intid = apic_read_iar();
if (intid == apic_timer_ppi()) {
apic_timer_rearm();
heartbeat_tick();
}