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:
co-authored by
Claude Sonnet 5
parent
cabb0e8bd4
commit
43aa0a2a36
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user