aarch64: GIC SPI wiring for virtio-input (item 4.3.5d)

Punch list §25 item 4.3.5d complete.

New apic_spi_enable(intid) generalizes item 0.6's PPI-only sequence to one
explicit SPI (IPRIORITYR/ISENABLER/ITARGETSR, ICFGR read-checked not
written). Verified via a software-pended SPI (GICD_ISPENDR, no device
needed) through the existing generic IRQ dispatch, which needed no changes.
Found and fixed a real bug during verification: PSTATE.I is still set at
apic_init()'s point in boot, so the first self-test run correctly latched
but never took the interrupt. Self-test code reverted after capturing
evidence -- interrupts.c has zero net diff, only apic_spi_enable() remains,
unused until 4.3.5e. Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-08 12:15:23 -04:00
co-authored by Claude Sonnet 5
parent 8251aebcf8
commit 2a2d7c5e5e
17 changed files with 90693 additions and 80 deletions
+73 -5
View File
@@ -5,12 +5,14 @@
*/
/**
* apic.c (aarch64) - Minimal GICv2 driver + ARM Generic Timer (items 0.6/0.7).
* apic.c (aarch64) - Minimal GICv2 driver + ARM Generic Timer (items 0.6/0.7/
* 4.3.5d).
*
* "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.
* "Minimal" is deliberate and stays deliberate: item 0.6 enabled exactly one
* PPI (the timer); item 4.3.5d adds the ability to enable exactly one SPI
* (for virtio-keyboard-pci, item 4.3.5e), still by explicit per-INTID call,
* not a general GIC driver -- no SGI support, no GICv3/ITS, no SPI beyond
* the one 4.3.5e will actually enable.
*
* Base addresses and the PPI INTID are QEMU-virt-machine constants, not
* device-tree-discovered, and that is a deliberate, recorded exception
@@ -56,7 +58,9 @@ extern int aarch64_current_el(void);
#define GICD_CTLR 0x000
#define GICD_ISENABLER0 0x100
#define GICD_IPRIORITYR 0x400
#define GICD_ITARGETSR 0x800 /* item 4.3.5d -- SPI target-CPU byte array */
#define GICD_ICFGR 0xC00
#define GICD_ISPENDR 0x200 /* item 4.3.5d -- software set-pending, SPIs only */
/* GICv2 CPU Interface register byte offsets. */
#define GICC_CTLR 0x00
@@ -148,6 +152,70 @@ int apic_init(BootInfo *boot_info)
return 0;
}
/**
* @brief Enable and route one SPI (item 4.3.5d).
*
* Extends item 0.6's PPI-only init just far enough to bring up one Shared
* Peripheral Interrupt -- still not a general GIC driver, still one
* explicit INTID per call, same bounded-scope posture 0.6 used.
*
* Programs, for @p intid (expected range: SPIs, INTID >= 32):
* 1. @c GICD_IPRIORITYR (byte-indexed, same pattern @c apic_init() already
* uses for the timer PPI) -- @c TIMER_PRIORITY, below @c PMR_ALLOW_ALL.
* 2. @c GICD_ISENABLER at @c 0x100 + 4*(intid/32) -- PPIs/SGIs (0-31) live
* in word 0 (@c GICD_ISENABLER0); SPIs beyond use word @c intid/32, same
* architecture layout, not board-specific.
* 3. @c GICD_ITARGETSR (byte-indexed like IPRIORITYR) -- routes to CPU 0
* only; this target has no @c -smp, so no other bit is ever valid.
* 4. @c GICD_ICFGR is read back, not written, unless the readback disagrees
* with the expected level-triggered configuration -- FABRIC.md §27.5.1's
* decoded QEMU `interrupt-map` says PCI legacy INTx on this board already
* is level-triggered by default, so this keeps item 0.6's "don't touch
* ICFGR unless forced to" posture rather than writing it unconditionally.
*
* Does **not** touch @c GICD_CTLR or @c GICC_CTLR/@c PMR -- @c apic_init()
* already enabled the distributor and CPU interface for the timer PPI, and
* those are global, not per-INTID; nothing here needs to repeat that.
*
* @param intid Target SPI's INTID (32 + GIC SPI number).
*/
void apic_spi_enable(uint32_t intid)
{
{
uint32_t reg_off = GICD_IPRIORITYR + (intid & ~3u);
uint32_t shift = (intid & 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);
}
{
uint32_t reg_off = GICD_ISENABLER0 + 4u * (intid / 32u);
uint32_t bit = intid % 32u;
mmio_write32(GICD_BASE_PA, reg_off, 1u << bit);
}
{
uint32_t reg_off = GICD_ITARGETSR + (intid & ~3u);
uint32_t shift = (intid & 3u) * 8u;
uint32_t val = mmio_read32(GICD_BASE_PA, reg_off);
val = (val & ~(0xFFu << shift)) | (0x01u << shift); /* CPU 0 only */
mmio_write32(GICD_BASE_PA, reg_off, val);
}
{
uint32_t reg_off = GICD_ICFGR + 4u * (intid / 16u);
uint32_t bitpos = (intid % 16u) * 2u;
uint32_t val = mmio_read32(GICD_BASE_PA, reg_off);
uint32_t level_expected = 0u; /* level-triggered = the "trigger mode"
* bit (bit 1 of the 2-bit field) clear */
if (((val >> bitpos) & 0x2u) != level_expected) {
val = (val & ~(0x3u << bitpos)) | (level_expected << bitpos);
mmio_write32(GICD_BASE_PA, reg_off, val);
}
}
}
/**
* @brief Read the GIC CPU interface's Interrupt Acknowledge Register.
*