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:
Robert Allan James
2026-08-08 00:18:48 -04:00
co-authored by Claude Sonnet 5
parent 78ff335b97
commit 88eb73cfe8
14 changed files with 914 additions and 11 deletions
+53 -7
View File
@@ -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
* ============================================================================ */
+150
View File
@@ -0,0 +1,150 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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;
}
+23
View File
@@ -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 ===");
+319
View File
@@ -0,0 +1,319 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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;
}
+19 -3
View File
@@ -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