Files
KirkOS/src/arch/x86_64/sys/ioapic.c
T
kaguya 9a9b91c940 user: implement mlibc as the libc, finally.
It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
2026-05-02 03:31:49 -04:00

359 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ioapic.h"
#include "apic.h"
#include "mm/vmm.h"
#include "mm/memory.h"
#include "libk/stdio.h"
#include <uacpi/tables.h> /* uacpi_table_find_by_signature / uacpi_table_unref */
#include "pic.h"
/* ══════════════════════════════════════════════════════════════════════════
* ACPI MADT structures
* (Defined locally so we don't depend on uACPI's internal acpi.h layout.)
* ══════════════════════════════════════════════════════════════════════════ */
extern const PICDriver* g_Driver; // old pic driver
bool g_IOAPIC = false; // IOAPIC enabled
typedef struct {
uint8_t signature[4]; /* "APIC" */
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;
/* --- MADT-specific -------------------------------------------------- */
uint32_t local_apic_addr; /* Default LAPIC physical address (32-bit) */
uint32_t flags; /* bit 0 = dual 8259 PICs present */
/* followed by variable-length Interrupt Controller Structure entries */
} __attribute__((packed)) madt_t;
typedef struct {
uint8_t type;
uint8_t length;
} __attribute__((packed)) madt_entry_hdr_t;
/* Type 0: Processor Local APIC */
typedef struct {
madt_entry_hdr_t hdr;
uint8_t uid;
uint8_t apic_id;
uint32_t flags; /* bit 0 = enabled */
} __attribute__((packed)) madt_lapic_t;
/* Type 1: I/O APIC */
typedef struct {
madt_entry_hdr_t hdr;
uint8_t id;
uint8_t reserved;
uint32_t address; /* Physical MMIO base */
uint32_t gsi_base; /* First GSI handled by this IOAPIC */
} __attribute__((packed)) madt_ioapic_t;
/* Type 2: Interrupt Source Override */
typedef struct {
madt_entry_hdr_t hdr;
uint8_t bus; /* 0 = ISA */
uint8_t source; /* ISA IRQ number */
uint32_t gsi; /* Remapped GSI */
uint16_t flags; /* bits [1:0] = polarity, bits [3:2] = trigger mode */
} __attribute__((packed)) madt_iso_t;
/* Type 4: Local APIC NMI */
typedef struct {
madt_entry_hdr_t hdr;
uint8_t uid; /* 0xFF = all processors */
uint16_t flags;
uint8_t lint; /* 0 or 1 */
} __attribute__((packed)) madt_nmi_t;
/* ══════════════════════════════════════════════════════════════════════════
* Internal state
* ══════════════════════════════════════════════════════════════════════════ */
typedef struct {
volatile uint32_t *base; /* Virtual address of IOAPIC MMIO */
uint32_t gsi_base;
uint32_t gsi_count; /* Number of redirection entries */
} ioapic_t;
static ioapic_t g_ioapics[IOAPIC_MAX];
static int g_ioapic_count = 0;
/* ISA interrupt source overrides (max 16 ISA IRQs) */
typedef struct {
bool present;
uint32_t gsi;
bool active_low;
bool level;
} iso_t;
static iso_t g_iso[16]; /* indexed by ISA IRQ (source) */
/* ══════════════════════════════════════════════════════════════════════════
* Low-level IOAPIC MMIO access
*
* The IOAPIC has two MMIO registers:
* base+0x00 IOREGSEL index register (write which register to access)
* base+0x10 IOWIN data window (read/write the selected register)
* ══════════════════════════════════════════════════════════════════════════ */
static uint32_t ioapic_read(const ioapic_t *io, uint8_t reg) {
*((volatile uint32_t *)(io->base + (0x00 >> 2))) = reg;
return *((volatile uint32_t *)(io->base + (0x10 >> 2)));
}
static void ioapic_write(const ioapic_t *io, uint8_t reg, uint32_t val) {
*((volatile uint32_t *)(io->base + (0x00 >> 2))) = reg;
*((volatile uint32_t *)(io->base + (0x10 >> 2))) = val;
}
/* ── Find which IOAPIC owns a given GSI ─────────────────────────────────── */
static ioapic_t *ioapic_for_gsi(uint32_t gsi) {
for (int i = 0; i < g_ioapic_count; i++) {
ioapic_t *io = &g_ioapics[i];
if (gsi >= io->gsi_base && gsi < io->gsi_base + io->gsi_count)
return io;
}
return NULL;
}
/* ══════════════════════════════════════════════════════════════════════════
* Public API
* ══════════════════════════════════════════════════════════════════════════ */
uint32_t ioapic_gsi_for_isa_irq(uint8_t isa_irq) {
if (isa_irq < 16 && g_iso[isa_irq].present)
return g_iso[isa_irq].gsi;
return isa_irq;
}
void ioapic_redirect(uint32_t gsi, uint8_t vector, uint8_t dest_lapic,
bool active_low, bool level, bool masked) {
ioapic_t *io = ioapic_for_gsi(gsi);
if (!io) {
printf("[IOAPIC] No IOAPIC for GSI %u\n", gsi);
return;
}
uint8_t idx = (uint8_t)(gsi - io->gsi_base);
uint32_t lo = (uint32_t)vector
| IOAPIC_RTE_DM_FIXED
| (active_low ? IOAPIC_RTE_ACTIVE_LOW : 0)
| (level ? IOAPIC_RTE_LEVEL : 0)
| (masked ? IOAPIC_RTE_MASKED : 0);
uint32_t hi = (uint32_t)dest_lapic << 24;
/* Write high word first, then low (avoids momentary spurious delivery) */
ioapic_write(io, IOAPIC_REDTBL_HI(idx), hi);
ioapic_write(io, IOAPIC_REDTBL_LO(idx), lo);
}
void ioapic_mask_gsi(uint32_t gsi) {
ioapic_t *io = ioapic_for_gsi(gsi);
if (!io) return;
uint8_t idx = (uint8_t)(gsi - io->gsi_base);
uint32_t lo = ioapic_read(io, IOAPIC_REDTBL_LO(idx));
ioapic_write(io, IOAPIC_REDTBL_LO(idx), lo | IOAPIC_RTE_MASKED);
}
void ioapic_unmask_gsi(uint32_t gsi) {
ioapic_t *io = ioapic_for_gsi(gsi);
if (!io) return;
uint8_t idx = (uint8_t)(gsi - io->gsi_base);
uint32_t lo = ioapic_read(io, IOAPIC_REDTBL_LO(idx));
ioapic_write(io, IOAPIC_REDTBL_LO(idx), lo & ~IOAPIC_RTE_MASKED);
}
/* ══════════════════════════════════════════════════════════════════════════
* Initialisation
* ══════════════════════════════════════════════════════════════════════════ */
void ioapic_init(void) {
/*
* ── Step 1: Find the MADT via uACPI ──────────────────────────────────
*
* The MADT signature in ACPI is "APIC" (not "MADT").
* uacpi_table_find_by_signature returns a handle whose .ptr field points
* to the raw table data in (HHDM-mapped) physical memory.
*/
struct uacpi_table madt_table;
uacpi_status st = uacpi_table_find_by_signature("APIC", &madt_table);
if (uacpi_unlikely_error(st)) {
printf("[IOAPIC] Could not find MADT: %s\n", uacpi_status_to_string(st));
return;
}
madt_t *madt = (madt_t *)madt_table.ptr;
printf("[IOAPIC] MADT @ virt 0x%lx len=%u lapic_addr=0x%08x\n",
(uint64_t)madt, madt->length, madt->local_apic_addr);
/*
* ── Step 2: Walk the MADT entry list ─────────────────────────────────
*
* Entries start immediately after the fixed 44-byte MADT header and run
* until madt->length bytes from the table base.
*/
const uint8_t *entry_ptr = (const uint8_t *)madt + sizeof(madt_t);
const uint8_t *madt_end = (const uint8_t *)madt + madt->length;
while (entry_ptr < madt_end) {
const madt_entry_hdr_t *hdr = (const madt_entry_hdr_t *)entry_ptr;
if (hdr->length < 2) {
printf("[IOAPIC] MADT entry with length < 2, stopping parse\n");
break;
}
switch (hdr->type) {
/* ── Type 0: Processor Local APIC ──────────────────────────────── */
case 0: {
const madt_lapic_t *e = (const madt_lapic_t *)entry_ptr;
printf("[IOAPIC] MADT[0] Processor UID=%u LAPIC ID=%u flags=0x%x%s\n",
e->uid, e->apic_id, e->flags,
(e->flags & 1) ? "" : " (disabled)");
break;
}
/* ── Type 1: I/O APIC ───────────────────────────────────────────── */
case 1: {
const madt_ioapic_t *e = (const madt_ioapic_t *)entry_ptr;
if (g_ioapic_count >= IOAPIC_MAX) {
printf("[IOAPIC] Too many IOAPICs, skipping ID=%u\n", e->id);
break;
}
ioapic_t *io = &g_ioapics[g_ioapic_count];
io->gsi_base = e->gsi_base;
/*
* Map the IOAPIC MMIO page. Like the LAPIC, the HHDM covers the
* IOAPIC's physical address (typically 0xFEC00000) so we just add
* MEM_PHYS_OFFSET. Two MMIO registers are accessed (offsets 0 and
* 0x10) so one 4 KiB page is sufficient.
*
* TODO: Mark the page UC (cache-disable) in the PTE when VMM
* gains support for PAT / PCD flags.
*/
uint64_t phys = (uint64_t)e->address;
uintptr_t virt = (uintptr_t)phys + MEM_PHYS_OFFSET;
/* The HHDM loop in vmm_init already covered this range; if not,
* uncomment the explicit map call below: */
/* vmm_map_page(kernel_pagemap, virt, phys,
PAGE_READ | PAGE_WRITE | PAGE_NO_EXECUTE, Size4KiB); */
io->base = (volatile uint32_t *)virt;
/* Read version register to discover number of redirection entries */
uint32_t ver = ioapic_read(io, IOAPIC_REG_VER);
io->gsi_count = ((ver >> 16) & 0xFF) + 1; /* bits [23:16] = max entry index */
printf("[IOAPIC] MADT[1] ID=%u phys=0x%08x GSI base=%u entries=%u\n",
e->id, e->address, io->gsi_base, io->gsi_count);
/*
* ── Mask every redirection entry ─────────────────────────────
*
* We keep all entries masked at boot time. Legacy ISA IRQs are
* handled by the i8259 → LAPIC LINT0 ExtINT path, not via the
* IOAPIC. PCI devices can be connected later with ioapic_redirect().
*/
for (uint32_t n = 0; n < io->gsi_count; n++) {
ioapic_write(io, IOAPIC_REDTBL_HI(n), 0);
ioapic_write(io, IOAPIC_REDTBL_LO(n), IOAPIC_RTE_MASKED | 0xFF);
}
g_ioapic_count++;
break;
}
/* ── Type 2: Interrupt Source Override ──────────────────────────── */
case 2: {
const madt_iso_t *e = (const madt_iso_t *)entry_ptr;
/* flags bits [1:0]: 00/11 = conforms to bus (ISA = active high edge)
* 01 = active high, 11 = active low
* flags bits [3:2]: 00/11 = conforms to bus (ISA = edge)
* 01 = edge, 11 = level */
bool active_low = ((e->flags & 0x3) == 3);
bool level = ((e->flags >> 2) & 0x3) == 3;
printf("[IOAPIC] MADT[2] ISA IRQ %u -> GSI %u %s %s\n",
e->source, e->gsi,
active_low ? "active-low" : "active-high",
level ? "level" : "edge");
if (e->source < 16) {
g_iso[e->source].present = true;
g_iso[e->source].gsi = e->gsi;
g_iso[e->source].active_low = active_low;
g_iso[e->source].level = level;
}
break;
}
/* ── Type 4: Local APIC NMI ─────────────────────────────────────── */
case 4: {
const madt_nmi_t *e = (const madt_nmi_t *)entry_ptr;
printf("[IOAPIC] MADT[4] NMI UID=0x%02x LINT%u\n",
e->uid, e->lint);
break;
}
default:
printf("[IOAPIC] MADT entry type=%u length=%u (skipped)\n",
hdr->type, hdr->length);
break;
}
entry_ptr += hdr->length;
}
uacpi_table_unref(&madt_table);
printf("[IOAPIC] Init done: %d IOAPIC(s) found\n", g_ioapic_count);
}
void irq_redirect_to_apic(uint8_t isa_irq, uint8_t vector,
uint8_t dest_lapic, bool masked)
{
if (isa_irq >= 16) {
printf("[IRQ] irq_redirect_to_apic: ISA IRQ %u out of range\n", isa_irq);
return;
}
uint32_t gsi = ioapic_gsi_for_isa_irq(isa_irq);
/* Get polarity/trigger from MADT ISO or use ISA defaults */
bool active_low = false;
bool level = false;
if (isa_irq < 16 && g_iso[isa_irq].present) {
active_low = g_iso[isa_irq].active_low;
level = g_iso[isa_irq].level;
} else {
/* Standard ISA: active-high, edge-triggered (except some like IRQ0/8) */
if (isa_irq == 0 || isa_irq == 8) {
level = true; /* often level on real hardware */
}
}
/* Mask in the 8259 so it stops firing through LINT0 */
if (g_Driver) {
g_Driver->Mask(isa_irq);
}
/* Programme IOAPIC redirection entry */
ioapic_redirect(gsi, vector, dest_lapic,
active_low, level, masked);
printf("[IRQ] Redirected ISA IRQ %u -> GSI %u vector 0x%02x LAPIC %u %s %s\n",
isa_irq, gsi, vector, dest_lapic,
active_low ? "active-low" : "active-high",
level ? "level" : "edge");
g_IOAPIC = true;
}