starkernel: item 4.3.5 -- amd64 I/O APIC + i8042 keyboard, interrupt-driven
Punch list §25 item 4.3.5 complete. New ioapic.c/i8042.c drivers (MADT-derived I/O APIC base, no hardcoded constants) plus a KBD-SCAN/KBD-DEBUG diagnostic word pair. Three real bugs found and fixed en route, all blocking this item's own acceptance: a fatal LAPIC spurious-vector crash (nothing had driven a real external interrupt through the I/O APIC before), OVMF leaving the keyboard device itself scanning-disabled (0xF4 fix), and isr.S's stub table only having individually-numbered stubs through vector 32 -- everything above that, including our IRQ1 vector 33, silently reported as vector 255 regardless of which IDT slot actually fired. Verified live via QEMU sendkey against KBD-SCAN: correct XT Set-1 make/break codes for two different keys. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
78ff335b97
commit
88eb73cfe8
@@ -273,14 +273,46 @@ int apic_init(BootInfo *boot_info) {
|
||||
lapic_write(APIC_REG_TPR, 0);
|
||||
|
||||
/*
|
||||
* Issue a spurious EOI to clear any stale in-service interrupt left by
|
||||
* UEFI firmware. UEFI's last APIC timer tick may have fired just before
|
||||
* ExitBootServices with no subsequent EOI; the APIC's ISR then shows
|
||||
* vector 0x20 still "in service", which suppresses all future 0x20
|
||||
* delivery until an EOI is written. Issuing EOI here is always safe —
|
||||
* it is a no-op if the ISR is already clear.
|
||||
* Drain ALL stale in-service bits left by UEFI firmware, not just one.
|
||||
* UEFI's own interrupt handling commonly uses the same 0x20-based
|
||||
* legacy-IRQ vector remap convention this kernel does, and can leave
|
||||
* MORE THAN ONE vector "in service" when ExitBootServices() cuts it
|
||||
* off mid-handling (its own timer tick AND, per item 4.3.5's finding,
|
||||
* its own keyboard IRQ1 handling -- both land in the same 0x20-0x2F
|
||||
* range). A single EOI clears only the highest-priority stale bit;
|
||||
* firmware's other stale bit then sits permanently in-service and
|
||||
* silently demotes every future same-or-lower-class interrupt to the
|
||||
* spurious vector instead of its real one. EOI always clears whichever
|
||||
* ISR bit is currently highest-priority, so looping while any of the
|
||||
* 8 ISR registers (0x100-0x170, vectors 0-255) is nonzero drains all
|
||||
* of them. Bounded to 8 iterations -- one firmware is not expected to
|
||||
* leave more stale bits than that; if it does, stop rather than spin
|
||||
* forever on real hardware.
|
||||
*/
|
||||
lapic_write(APIC_REG_EOI, 0);
|
||||
{
|
||||
#define APIC_REG_ISR_BASE 0x100
|
||||
int drained;
|
||||
for (drained = 0; drained < 8; drained++) {
|
||||
int any_set = 0;
|
||||
for (int r = 0; r < 8; r++) {
|
||||
if (lapic_read(APIC_REG_ISR_BASE + (uint32_t)r * 0x10) != 0) {
|
||||
any_set = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any_set) break;
|
||||
lapic_write(APIC_REG_EOI, 0);
|
||||
}
|
||||
console_puts("APIC: stale-ISR drain: ");
|
||||
{
|
||||
char buf[8]; int i = 0; uint32_t v = (uint32_t)drained;
|
||||
if (v == 0) buf[i++] = '0';
|
||||
else { char tmp[8]; int j = 0; while (v > 0) { tmp[j++] = (char)('0' + (v % 10)); v /= 10; } while (j > 0) buf[i++] = tmp[--j]; }
|
||||
buf[i] = '\0';
|
||||
console_puts(buf);
|
||||
}
|
||||
console_puts(" EOI(s) issued\n");
|
||||
}
|
||||
|
||||
console_puts("APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear)\n");
|
||||
return 0;
|
||||
@@ -302,6 +334,20 @@ void apic_eoi(void) {
|
||||
lapic_write(APIC_REG_EOI, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return this CPU's xAPIC ID.
|
||||
*
|
||||
* Reads @c APIC_REG_ID and extracts bits 31:24, the xAPIC ID field. Used by
|
||||
* the I/O APIC driver (item 4.3.5) as the destination field when programming
|
||||
* a redirection entry — interrupts routed to this ID are delivered to the
|
||||
* current CPU's Local APIC.
|
||||
*
|
||||
* @return This CPU's 8-bit xAPIC ID.
|
||||
*/
|
||||
uint8_t apic_id(void) {
|
||||
return (uint8_t)(lapic_read(APIC_REG_ID) >> 24);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* APIC Timer
|
||||
* ============================================================================ */
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* i8042.c - PS/2 keyboard controller driver (amd64)
|
||||
*
|
||||
* Item 4.3.5 (FABRIC.md §27.5).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "i8042.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/i8042.h"
|
||||
#include "console.h"
|
||||
|
||||
#define I8042_DATA_PORT 0x60
|
||||
#define I8042_STATUS_PORT 0x64
|
||||
#define I8042_CMD_PORT 0x64
|
||||
|
||||
#define I8042_STATUS_OUTPUT_FULL (1u << 0)
|
||||
#define I8042_STATUS_INPUT_FULL (1u << 1)
|
||||
|
||||
#define I8042_CMD_READ_CONFIG 0x20
|
||||
#define I8042_CMD_WRITE_CONFIG 0x60
|
||||
#define I8042_CONFIG_IRQ1_ENABLE (1u << 0)
|
||||
|
||||
#define I8042_DEV_CMD_ENABLE_SCANNING 0xF4
|
||||
#define I8042_DEV_ACK 0xFA
|
||||
|
||||
/* Bounded wait -- command handshakes during one-time init only, never in
|
||||
* the interrupt path or a per-keystroke loop. */
|
||||
#define I8042_INIT_WAIT_ITERS 100000
|
||||
|
||||
static inline void outb(uint16_t port, uint8_t val) {
|
||||
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
|
||||
}
|
||||
static inline uint8_t inb(uint16_t port) {
|
||||
uint8_t val;
|
||||
__asm__ volatile ("inb %1, %0" : "=a"(val) : "Nd"(port));
|
||||
return val;
|
||||
}
|
||||
|
||||
void i8042_init(void) {
|
||||
/* Drain any stale output byte left by firmware before enabling IRQ1 —
|
||||
* otherwise the first "interrupt" would really be this leftover byte
|
||||
* read via polling, not a real IRQ1 delivery. */
|
||||
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
|
||||
(void)inb(I8042_DATA_PORT);
|
||||
}
|
||||
|
||||
outb(I8042_CMD_PORT, I8042_CMD_READ_CONFIG);
|
||||
uint8_t config = inb(I8042_DATA_PORT);
|
||||
config |= I8042_CONFIG_IRQ1_ENABLE;
|
||||
outb(I8042_CMD_PORT, I8042_CMD_WRITE_CONFIG);
|
||||
outb(I8042_DATA_PORT, config);
|
||||
|
||||
/* Tell the keyboard device itself to (re)enable scanning. OVMF's own
|
||||
* PS/2 driver commonly sends 0xF5 (disable scanning) to the device as
|
||||
* part of its own teardown before handoff, the same way it stops
|
||||
* driving the PIT/PIC -- the controller's IRQ1-enable bit above only
|
||||
* governs the controller, not whether the device generates scancodes
|
||||
* at all. Bounded waits: one-time init handshake, not steady-state
|
||||
* polling. */
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < I8042_INIT_WAIT_ITERS; i++) {
|
||||
if (!(inb(I8042_STATUS_PORT) & I8042_STATUS_INPUT_FULL)) break;
|
||||
}
|
||||
outb(I8042_DATA_PORT, I8042_DEV_CMD_ENABLE_SCANNING);
|
||||
for (i = 0; i < I8042_INIT_WAIT_ITERS; i++) {
|
||||
if (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
|
||||
uint8_t resp = inb(I8042_DATA_PORT);
|
||||
if (resp == I8042_DEV_ACK) {
|
||||
console_println("i8042: keyboard ACKed enable-scanning");
|
||||
} else {
|
||||
console_println("i8042: keyboard responded (not ACK) to enable-scanning");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console_println("i8042: IRQ1 enabled in controller config byte");
|
||||
}
|
||||
|
||||
void i8042_drain_stale(void) {
|
||||
/* Edge-triggered IRQ1 + OBF already set at unmask time == no rising
|
||||
* edge ever again (the textbook "fires once, then never" pattern).
|
||||
* i8042_init()'s drain happens at M4; VM bootstrap runs for real wall
|
||||
* time between then and unmask, so re-drain immediately before. */
|
||||
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
|
||||
(void)inb(I8042_DATA_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ring buffer: single producer (the ISR), single reader (i8042_pop_scancode(),
|
||||
* called from ordinary non-interrupt context, e.g. a diagnostic FORTH word).
|
||||
* 32 entries is far more than one QEMU test keypress burst can fill. */
|
||||
#define RING_SIZE 32
|
||||
static volatile uint8_t ring[RING_SIZE];
|
||||
static volatile uint8_t ring_head = 0; /* next slot the ISR writes */
|
||||
static volatile uint8_t ring_tail = 0; /* next slot the reader takes */
|
||||
|
||||
/* Count of real keyboard IRQs serviced since boot -- a plain counter, no
|
||||
* I/O, is cheap enough to keep in the ISR itself and doubles as the
|
||||
* quickest way to confirm the interrupt path is alive without touching
|
||||
* the ring buffer at all. */
|
||||
volatile uint32_t g_i8042_isr_count = 0;
|
||||
|
||||
void i8042_handle_irq(void) {
|
||||
/* Must always read 0x60, even to discard — otherwise OBF never clears
|
||||
* and the controller stops delivering further interrupts. */
|
||||
uint8_t scancode = inb(I8042_DATA_PORT);
|
||||
g_i8042_isr_count++;
|
||||
|
||||
uint8_t next_head = (uint8_t)((ring_head + 1) % RING_SIZE);
|
||||
if (next_head != ring_tail) { /* drop the byte if the ring is full */
|
||||
ring[ring_head] = scancode;
|
||||
ring_head = next_head;
|
||||
}
|
||||
}
|
||||
|
||||
int i8042_pop_scancode(uint8_t *out) {
|
||||
if (ring_tail == ring_head) return 0; /* empty */
|
||||
*out = ring[ring_tail];
|
||||
ring_tail = (uint8_t)((ring_tail + 1) % RING_SIZE);
|
||||
return 1;
|
||||
}
|
||||
@@ -49,11 +49,15 @@
|
||||
#include "console.h"
|
||||
#include "apic.h"
|
||||
#include "timer.h"
|
||||
#include "starkernel/i8042.h"
|
||||
|
||||
/* Set by the FORTH dispatcher just before calling entry->func(vm).
|
||||
* Printed on fault to identify which word was executing. */
|
||||
volatile const char *g_sk_fault_word = (void *)0;
|
||||
|
||||
/* Count of LAPIC spurious-vector interrupts since boot (item 4.3.5). */
|
||||
volatile uint32_t g_spurious_count = 0;
|
||||
|
||||
#define IDT_ENTRIES 256
|
||||
#define INTERRUPT_GATE 0x8E
|
||||
|
||||
@@ -342,6 +346,25 @@ void isr_common_handler(uint64_t vector,
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle i8042 keyboard interrupt (item 4.3.5) */
|
||||
if (vector == I8042_KEYBOARD_VECTOR) {
|
||||
i8042_handle_irq();
|
||||
apic_eoi();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Spurious interrupt: normal occasional race per Intel SDM Vol.3 §10.9,
|
||||
* not a fault. No EOI -- the SDM is explicit that spurious-vector
|
||||
* interrupts must not be acknowledged. g_spurious_count is a cheap,
|
||||
* permanent diagnostic (item 4.3.5 found a real keyboard IRQ silently
|
||||
* misreported as spurious due to a missing dedicated ISR stub -- see
|
||||
* isr.S -- so a way to notice "spurious firing when it shouldn't be"
|
||||
* is worth keeping, not just scaffolding for that one investigation). */
|
||||
if (vector == APIC_SPURIOUS_VECTOR) {
|
||||
g_spurious_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
/* All other vectors are exceptions - print diagnostic and halt */
|
||||
console_println("\n=== INTERRUPT/EXCEPTION ===");
|
||||
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* ioapic.c - I/O APIC driver (amd64)
|
||||
*
|
||||
* Item 4.3.5 (FABRIC.md §27.5). MADT parsing mirrors pci.c's RSDP -> XSDT ->
|
||||
* table-by-signature walk (the two files don't share a header for this —
|
||||
* same duplication pci.c already has relative to a hypothetical shared
|
||||
* acpi.c, not introduced fresh here).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "ioapic.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "starkernel/ioapic.h"
|
||||
#include "console.h"
|
||||
#include "vmm.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* ACPI structures for MADT discovery
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint8_t signature[8];
|
||||
uint8_t checksum;
|
||||
uint8_t oem_id[6];
|
||||
uint8_t revision;
|
||||
uint32_t rsdt_address;
|
||||
uint32_t length;
|
||||
uint64_t xsdt_address;
|
||||
uint8_t extended_checksum;
|
||||
uint8_t reserved[3];
|
||||
} __attribute__((packed)) Rsdp2;
|
||||
|
||||
typedef struct {
|
||||
uint8_t signature[4];
|
||||
uint32_t length;
|
||||
uint8_t revision;
|
||||
uint8_t checksum;
|
||||
uint8_t oem_id[6];
|
||||
uint8_t oem_table_id[8];
|
||||
uint32_t oem_revision;
|
||||
uint32_t creator_id;
|
||||
uint32_t creator_revision;
|
||||
} __attribute__((packed)) AcpiHeader;
|
||||
|
||||
typedef struct {
|
||||
AcpiHeader hdr;
|
||||
uint32_t local_apic_address;
|
||||
uint32_t flags;
|
||||
/* variable-length entries follow */
|
||||
} __attribute__((packed)) MadtHeader;
|
||||
|
||||
typedef struct {
|
||||
uint8_t type; /* 1 = I/O APIC */
|
||||
uint8_t length; /* 12 */
|
||||
uint8_t io_apic_id;
|
||||
uint8_t reserved;
|
||||
uint32_t io_apic_address;
|
||||
uint32_t gsi_base;
|
||||
} __attribute__((packed)) MadtIoApic;
|
||||
|
||||
typedef struct {
|
||||
uint8_t type; /* 2 = Interrupt Source Override */
|
||||
uint8_t length; /* 10 */
|
||||
uint8_t bus;
|
||||
uint8_t source; /* legacy ISA IRQ */
|
||||
uint32_t gsi;
|
||||
uint16_t flags; /* bits[1:0] polarity, bits[3:2] trigger mode */
|
||||
} __attribute__((packed)) MadtIntSrcOverride;
|
||||
|
||||
#define MADT_TYPE_IOAPIC 1
|
||||
#define MADT_TYPE_INT_SRC 2
|
||||
|
||||
#define ISO_POLARITY_MASK 0x3u
|
||||
#define ISO_POLARITY_HIGH 0x1u
|
||||
#define ISO_POLARITY_LOW 0x3u
|
||||
#define ISO_TRIGGER_MASK 0xCu
|
||||
#define ISO_TRIGGER_EDGE 0x4u
|
||||
#define ISO_TRIGGER_LEVEL 0xCu
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Module state
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define MAX_OVERRIDES 16
|
||||
|
||||
static uint64_t g_ioapic_base = 0;
|
||||
static uint32_t g_ioapic_gsi_base = 0;
|
||||
static int g_ioapic_ready = 0;
|
||||
|
||||
static MadtIntSrcOverride g_overrides[MAX_OVERRIDES];
|
||||
static int g_override_count = 0;
|
||||
|
||||
/* Cached low-dword redirection values, indexed by GSI - g_ioapic_gsi_base,
|
||||
* so ioapic_unmask_legacy_irq() can clear just the mask bit without having
|
||||
* to re-derive polarity/trigger from the override table again. */
|
||||
#define MAX_REDIR_ENTRIES 24
|
||||
static uint32_t g_redir_low[MAX_REDIR_ENTRIES];
|
||||
static int g_redir_valid[MAX_REDIR_ENTRIES];
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* I/O APIC MMIO access (IOREGSEL @ +0x00, IOWIN @ +0x10)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void ioapic_write(uint8_t reg, uint32_t val) {
|
||||
volatile uint32_t *regsel = (volatile uint32_t *)(uintptr_t)(g_ioapic_base + 0x00);
|
||||
volatile uint32_t *iowin = (volatile uint32_t *)(uintptr_t)(g_ioapic_base + 0x10);
|
||||
*regsel = reg;
|
||||
*iowin = val;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* ACPI MADT parsing
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int sig4_eq(const uint8_t *p, const char *s) {
|
||||
return p[0] == (uint8_t)s[0] && p[1] == (uint8_t)s[1] &&
|
||||
p[2] == (uint8_t)s[2] && p[3] == (uint8_t)s[3];
|
||||
}
|
||||
static int sig8_eq(const uint8_t *p, const char *s) {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++)
|
||||
if (p[i] != (uint8_t)s[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_madt(void *rsdp) {
|
||||
if (!rsdp) return -1;
|
||||
|
||||
Rsdp2 *r = (Rsdp2 *)rsdp;
|
||||
if (!sig8_eq(r->signature, "RSD PTR ")) return -1;
|
||||
if (r->revision < 2 || r->xsdt_address == 0) return -1;
|
||||
|
||||
AcpiHeader *xsdt = (AcpiHeader *)(uintptr_t)r->xsdt_address;
|
||||
if (!sig4_eq(xsdt->signature, "XSDT")) return -1;
|
||||
|
||||
uint32_t hdr_len = xsdt->length;
|
||||
if (hdr_len <= 36u) return -1;
|
||||
uint32_t n_entries = (hdr_len - 36u) / 8u;
|
||||
uint64_t *entries = (uint64_t *)((uint8_t *)xsdt + 36u);
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < n_entries; i++) {
|
||||
AcpiHeader *sdt = (AcpiHeader *)(uintptr_t)entries[i];
|
||||
if (!sig4_eq(sdt->signature, "APIC")) continue;
|
||||
|
||||
MadtHeader *madt = (MadtHeader *)sdt;
|
||||
uint8_t *p = (uint8_t *)madt + sizeof(MadtHeader);
|
||||
uint8_t *end = (uint8_t *)madt + madt->hdr.length;
|
||||
int found_ioapic = 0;
|
||||
|
||||
while (p + 2 <= end) {
|
||||
uint8_t type = p[0];
|
||||
uint8_t len = p[1];
|
||||
if (len == 0 || p + len > end) break;
|
||||
|
||||
if (type == MADT_TYPE_IOAPIC && !found_ioapic) {
|
||||
MadtIoApic *io = (MadtIoApic *)p;
|
||||
g_ioapic_base = io->io_apic_address;
|
||||
g_ioapic_gsi_base = io->gsi_base;
|
||||
found_ioapic = 1;
|
||||
} else if (type == MADT_TYPE_INT_SRC && g_override_count < MAX_OVERRIDES) {
|
||||
MadtIntSrcOverride *iso = (MadtIntSrcOverride *)p;
|
||||
g_overrides[g_override_count++] = *iso;
|
||||
}
|
||||
|
||||
p += len;
|
||||
}
|
||||
|
||||
return found_ioapic ? 0 : -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public API
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
int ioapic_init(void *acpi_rsdp) {
|
||||
if (parse_madt(acpi_rsdp) != 0) {
|
||||
console_println("I/O APIC: no MADT I/O APIC entry found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vmm_map_range(g_ioapic_base, g_ioapic_base, 0x1000u,
|
||||
VMM_FLAG_WRITABLE | VMM_FLAG_CACHE_DISABLE) != 0) {
|
||||
console_println("I/O APIC: failed to map MMIO page");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_ioapic_ready = 1;
|
||||
|
||||
console_puts("I/O APIC: base=0x");
|
||||
for (int s = 28; s >= 0; s -= 4)
|
||||
console_putc("0123456789abcdef"[((uint32_t)g_ioapic_base >> s) & 0xF]);
|
||||
console_puts(", gsi_base=");
|
||||
{
|
||||
char buf[16]; int i = 0; uint32_t v = g_ioapic_gsi_base;
|
||||
if (v == 0) buf[i++] = '0';
|
||||
else { char tmp[16]; int j = 0; while (v > 0) { tmp[j++] = (char)('0' + (v % 10)); v /= 10; } while (j > 0) buf[i++] = tmp[--j]; }
|
||||
buf[i] = '\0';
|
||||
console_puts(buf);
|
||||
}
|
||||
console_puts(", overrides=");
|
||||
{
|
||||
char buf[8]; int i = 0; uint32_t v = (uint32_t)g_override_count;
|
||||
if (v == 0) buf[i++] = '0';
|
||||
else { char tmp[8]; int j = 0; while (v > 0) { tmp[j++] = (char)('0' + (v % 10)); v /= 10; } while (j > 0) buf[i++] = tmp[--j]; }
|
||||
buf[i] = '\0';
|
||||
console_puts(buf);
|
||||
}
|
||||
console_println("");
|
||||
|
||||
/* TEMP DEBUG (item 4.3.5 investigation) */
|
||||
for (int oi = 0; oi < g_override_count; oi++) {
|
||||
console_puts(" override: source=");
|
||||
console_putc("0123456789abcdef"[(g_overrides[oi].source >> 4) & 0xF]);
|
||||
console_putc("0123456789abcdef"[g_overrides[oi].source & 0xF]);
|
||||
console_puts(" gsi=");
|
||||
{
|
||||
char buf[16]; int i = 0; uint32_t v = g_overrides[oi].gsi;
|
||||
if (v == 0) buf[i++] = '0';
|
||||
else { char tmp[16]; int j = 0; while (v > 0) { tmp[j++] = (char)('0' + (v % 10)); v /= 10; } while (j > 0) buf[i++] = tmp[--j]; }
|
||||
buf[i] = '\0';
|
||||
console_puts(buf);
|
||||
}
|
||||
console_puts(" flags=0x");
|
||||
console_putc("0123456789abcdef"[(g_overrides[oi].flags >> 12) & 0xF]);
|
||||
console_putc("0123456789abcdef"[(g_overrides[oi].flags >> 8) & 0xF]);
|
||||
console_putc("0123456789abcdef"[(g_overrides[oi].flags >> 4) & 0xF]);
|
||||
console_putc("0123456789abcdef"[g_overrides[oi].flags & 0xF]);
|
||||
console_println("");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ioapic_route_legacy_irq(uint8_t isa_irq, uint8_t vector, uint8_t dest_apic_id) {
|
||||
if (!g_ioapic_ready) return -1;
|
||||
|
||||
uint32_t gsi = isa_irq;
|
||||
uint32_t polarity = 0; /* active-high (ISA default) */
|
||||
uint32_t trigger = 0; /* edge (ISA default) */
|
||||
|
||||
int i;
|
||||
for (i = 0; i < g_override_count; i++) {
|
||||
if (g_overrides[i].source == isa_irq) {
|
||||
gsi = g_overrides[i].gsi;
|
||||
uint16_t flags = g_overrides[i].flags;
|
||||
if ((flags & ISO_POLARITY_MASK) == ISO_POLARITY_LOW) polarity = 1;
|
||||
if ((flags & ISO_TRIGGER_MASK) == ISO_TRIGGER_LEVEL) trigger = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gsi < g_ioapic_gsi_base) return -1;
|
||||
uint32_t redir_index = gsi - g_ioapic_gsi_base;
|
||||
if (redir_index >= MAX_REDIR_ENTRIES) return -1;
|
||||
|
||||
uint32_t low = (uint32_t)vector
|
||||
| (polarity << 13)
|
||||
| (trigger << 15)
|
||||
| (1u << 16); /* masked until ioapic_unmask_legacy_irq() */
|
||||
uint32_t high = (uint32_t)dest_apic_id << 24;
|
||||
|
||||
ioapic_write((uint8_t)(0x10 + 2 * redir_index + 1), high);
|
||||
ioapic_write((uint8_t)(0x10 + 2 * redir_index), low);
|
||||
|
||||
g_redir_low[redir_index] = low;
|
||||
g_redir_valid[redir_index] = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ioapic_unmask_legacy_irq(uint8_t isa_irq) {
|
||||
if (!g_ioapic_ready) return;
|
||||
|
||||
uint32_t gsi = isa_irq;
|
||||
int i;
|
||||
for (i = 0; i < g_override_count; i++) {
|
||||
if (g_overrides[i].source == isa_irq) {
|
||||
gsi = g_overrides[i].gsi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gsi < g_ioapic_gsi_base) return;
|
||||
uint32_t redir_index = gsi - g_ioapic_gsi_base;
|
||||
if (redir_index >= MAX_REDIR_ENTRIES || !g_redir_valid[redir_index]) return;
|
||||
|
||||
uint32_t low = g_redir_low[redir_index] & ~(1u << 16);
|
||||
ioapic_write((uint8_t)(0x10 + 2 * redir_index), low);
|
||||
g_redir_low[redir_index] = low;
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
.hidden isr_stub_table
|
||||
.hidden isr_stub0
|
||||
.hidden isr_stub32
|
||||
.hidden isr_stub33
|
||||
.hidden isr_stub_default
|
||||
.hidden isr_common_entry
|
||||
|
||||
@@ -155,7 +156,20 @@ isr_stub32:
|
||||
push 0x20
|
||||
jmp isr_common_entry
|
||||
|
||||
/* Default stub for other vectors (33-255) */
|
||||
/* Stub for i8042 keyboard IRQ1, routed via I/O APIC to vector 0x21 = 33
|
||||
* (item 4.3.5, FABRIC.md §27.5). Before this stub existed, vector 33 fell
|
||||
* through to isr_stub_default below, which unconditionally reports "255"
|
||||
* regardless of which IDT slot actually fired -- the CPU legitimately took
|
||||
* IDT[33] (confirmed via the LAPIC's own ISR register), but isr_common_handler
|
||||
* only ever saw vector 255 and treated a real keyboard IRQ as spurious,
|
||||
* silently skipping EOI and leaving vector 33 permanently stuck in-service. */
|
||||
.global isr_stub33
|
||||
isr_stub33:
|
||||
push 0
|
||||
push 33
|
||||
jmp isr_common_entry
|
||||
|
||||
/* Default stub for other vectors (34-255) */
|
||||
.global isr_stub_default
|
||||
isr_stub_default:
|
||||
push 0 /* fake error */
|
||||
@@ -177,7 +191,9 @@ isr_stub_table:
|
||||
.quad isr_stub28, isr_stub29, isr_stub30, isr_stub31
|
||||
/* Vector 32 = APIC Timer */
|
||||
.quad isr_stub32
|
||||
/* Vectors 33-255 default to unknown stub */
|
||||
.rept 223
|
||||
/* Vector 33 = i8042 keyboard IRQ1 (item 4.3.5) */
|
||||
.quad isr_stub33
|
||||
/* Vectors 34-255 default to unknown stub */
|
||||
.rept 222
|
||||
.quad isr_stub_default
|
||||
.endr
|
||||
|
||||
Reference in New Issue
Block a user