//! I/O APIC — routes external device interrupts (a device's line) to a LAPIC //! vector on a chosen CPU. Its address and the ISA-IRQ-to-GSI remappings come from //! ACPI's MADT (via discovery), never assumed. //! //! `init` maps the I/O APIC and **masks every input** — the correct quiescent state //! on a legacy-free machine. Lines are then unmasked one at a time, as user-space //! drivers bind them (`routeGsi`/`unmaskGsi`, driven by system/kernel/irq.zig). //! //! Two entry points, for two kinds of caller. `routeIrq` takes a legacy **ISA IRQ** //! and resolves it through the MADT overrides — for in-kernel use, and still without //! a caller. `routeGsi` takes a **GSI** directly, which is what a device's own //! routing capability names (e.g. the HPET's `Tn_INT_ROUTE_CAP`), and is the path a //! bound driver interrupt takes. const paging = @import("paging.zig"); /// A MADT Interrupt Source Override: an ISA IRQ that appears at a different global /// system interrupt, with its own polarity/trigger (MPS INTI `flags`). pub const IsoEntry = struct { source: u8, gsi: u32, flags: u16 }; var base: u64 = 0; // 0 = no I/O APIC discovered var gsi_base: u32 = 0; var maximum_entries: u32 = 0; var overrides: [16]IsoEntry = undefined; var override_count: usize = 0; // The I/O APIC exposes an index register (IOREGSEL) and a data window (IOWIN). const register_ioregsel = 0x00; const register_iowin = 0x10; const register_version = 0x01; const redir_base = 0x10; // redirection table: two 32-bit regs per entry const redir_mask = 1 << 16; // mask bit in the low dword /// Supply the discovered I/O APIC location + the MADT IRQ overrides. Call before `init`. pub fn configure(ioapic_base: u64, ioapic_gsi_base: u32, isos: []const IsoEntry) void { base = ioapic_base; gsi_base = ioapic_gsi_base; override_count = @min(isos.len, overrides.len); for (isos[0..override_count], 0..) |iso, i| overrides[i] = iso; } fn registerRead(index: u32) u32 { @as(*volatile u32, @ptrFromInt(base + register_ioregsel)).* = index; return @as(*volatile u32, @ptrFromInt(base + register_iowin)).*; } fn registerWrite(index: u32, value: u32) void { @as(*volatile u32, @ptrFromInt(base + register_ioregsel)).* = index; @as(*volatile u32, @ptrFromInt(base + register_iowin)).* = value; } fn writeEntry(n: u32, low: u32, high: u32) void { registerWrite(redir_base + 2 * n, low); registerWrite(redir_base + 2 * n + 1, high); } /// Map the I/O APIC and mask every redirection entry — the safe quiescent state. pub fn init() void { if (base == 0) return; // Reach the I/O APIC through the physmap; switch `base` to that virtual // address so the register accessors work without the identity map. base = paging.mapMmio(base, 0x1000, true); maximum_entries = ((registerRead(register_version) >> 16) & 0xFF) + 1; var n: u32 = 0; while (n < maximum_entries) : (n += 1) writeEntry(n, redir_mask, 0); } /// Route ISA `irq` to `vector` on the LAPIC `apic_id`, honouring a MADT override /// for its GSI/polarity/trigger, and unmask it. No caller yet — groundwork for the /// first device driver. pub fn routeIrq(irq: u8, vector: u8, apic_id: u8) void { if (base == 0) return; var gsi: u32 = irq; var flags: u16 = 0; for (overrides[0..override_count]) |o| { if (o.source == irq) { gsi = o.gsi; flags = o.flags; } } if (gsi < gsi_base) return; const n = gsi - gsi_base; if (n >= maximum_entries) return; // Low dword: vector + delivery mode fixed(0) + physical dest(0), unmasked. // MPS INTI flags: bits [1:0] polarity (3 = active low), [3:2] trigger (3 = level). var low: u32 = vector; if (flags & 0x3 == 3) low |= (1 << 13); if ((flags >> 2) & 0x3 == 3) low |= (1 << 15); const high: u32 = @as(u32, apic_id) << 24; // destination APIC ID writeEntry(n, low, high); } // --- GSI-level control (the user-space driver path) -------------------------- // // `routeIrq` above takes an *ISA IRQ* and resolves it through the MADT overrides. // A driver-bound interrupt is already a **GSI** (the device told us so, e.g. the // HPET's `Tn_INT_ROUTE_CAP`), so it needs no override lookup — just the redirection // entry. These three are what `system/kernel/irq.zig` drives. // // Callers must serialise: the I/O APIC is reached through an index/data register // pair, so two cores interleaving `registerWrite` would corrupt each other. The kernel // holds the big lock across these. /// Redirection-entry index for `gsi`, or null if this I/O APIC doesn't own it. fn entryFor(gsi: u32) ?u32 { if (base == 0 or gsi < gsi_base) return null; const n = gsi - gsi_base; return if (n < maximum_entries) n else null; } /// True if `gsi` lands on this I/O APIC — the kernel's validity check before binding. pub fn ownsGsi(gsi: u32) bool { return entryFor(gsi) != null; } /// Point `gsi` at `vector` on the LAPIC `apic_id`, with explicit polarity/trigger, /// and leave it **masked**. The caller unmasks once a handler is bound — otherwise a /// device asserting between route and bind would fire into a null handler. pub fn routeGsi(gsi: u32, vector: u8, apic_id: u8, level: bool, active_low: bool) void { const n = entryFor(gsi) orelse return; var low: u32 = @as(u32, vector) | redir_mask; // masked until bound if (active_low) low |= (1 << 13); if (level) low |= (1 << 15); writeEntry(n, low, @as(u32, apic_id) << 24); } /// Stop `gsi` reaching any CPU. Called from the ISR *before* the LAPIC EOI: a /// level-triggered line is still asserted at that point, so an unmasked entry would /// redeliver immediately and storm before the user-space driver ever runs. pub fn maskGsi(gsi: u32) void { const n = entryFor(gsi) orelse return; registerWrite(redir_base + 2 * n, registerRead(redir_base + 2 * n) | redir_mask); } /// Let `gsi` through again — the tail of `irq_ack`, once the driver has quieted the /// device (so the line is deasserted and this can't immediately refire). pub fn unmaskGsi(gsi: u32) void { const n = entryFor(gsi) orelse return; registerWrite(redir_base + 2 * n, registerRead(redir_base + 2 * n) & ~@as(u32, redir_mask)); } /// Number of redirection entries the I/O APIC advertises (0 until `init`). pub fn entryCount() u32 { return maximum_entries; } /// The low dword of redirection entry `n` — for diagnostics/read-back. pub fn entryLow(n: u32) u32 { if (base == 0) return 0; return registerRead(redir_base + 2 * n); }