sys: Implement LAPIC and IOAPIC support, add PS/2 keyboard driver
- Added LAPIC implementation in `apic.c` and `apic.h`, including initialization and basic operations. - Introduced IOAPIC (I/O Advanced Programmable Interrupt Controller) support in `ioapic.c` and `ioapic.h`, parsing the ACPI MADT to locate IOAPICs and manage redirection entries. - Implemented PS/2 keyboard driver in `ps2.c` and `ps2.h`, handling keyboard interrupts and translating scancodes to ASCII. - Attempted to make usermode work again (it failed, coming soon) - Cleaned up comments in `time.c` and `render.c` for clarity. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
#include "apic.h"
|
||||
#include "mm/vmm.h"
|
||||
#include "mm/memory.h"
|
||||
#include "stdio.h"
|
||||
|
||||
/* ── Internal state ───────────────────────────────────────────────────────── */
|
||||
static volatile uint32_t *g_lapic = NULL; /* Virtual address of LAPIC MMIO */
|
||||
|
||||
/* ── Low-level helpers ────────────────────────────────────────────────────── */
|
||||
|
||||
static inline uint32_t lapic_read(uint32_t reg) {
|
||||
return g_lapic[reg >> 2];
|
||||
}
|
||||
|
||||
static inline void lapic_write(uint32_t reg, uint32_t val) {
|
||||
g_lapic[reg >> 2] = val;
|
||||
/* Serialise: read back a read-only register to ensure the write landed
|
||||
* before we continue (required by the APIC spec). */
|
||||
(void)g_lapic[LAPIC_ID >> 2];
|
||||
}
|
||||
|
||||
static inline void rdmsr(uint32_t msr, uint32_t *lo, uint32_t *hi) {
|
||||
asm volatile("rdmsr" : "=a"(*lo), "=d"(*hi) : "c"(msr));
|
||||
}
|
||||
|
||||
static inline void wrmsr(uint32_t msr, uint32_t lo, uint32_t hi) {
|
||||
asm volatile("wrmsr" : : "a"(lo), "d"(hi), "c"(msr));
|
||||
}
|
||||
|
||||
/* ── Public API ───────────────────────────────────────────────────────────── */
|
||||
|
||||
uint64_t lapic_get_phys_base(void) {
|
||||
uint32_t lo, hi;
|
||||
rdmsr(IA32_APIC_BASE_MSR, &lo, &hi);
|
||||
/* Physical base is in bits [35:12] of the 64-bit MSR */
|
||||
return ((uint64_t)(hi & 0xFu) << 32) | (lo & 0xFFFFF000u);
|
||||
}
|
||||
|
||||
void lapic_eoi(void) {
|
||||
lapic_write(LAPIC_EOI, 0);
|
||||
}
|
||||
|
||||
uint32_t lapic_id(void) {
|
||||
/* On xAPIC the LAPIC ID sits in bits [31:24] of the ID register */
|
||||
return lapic_read(LAPIC_ID) >> 24;
|
||||
}
|
||||
|
||||
void lapic_init(void) {
|
||||
/*
|
||||
* ── Step 1: Re-enable the APIC global enable bit ─────────────────────
|
||||
*
|
||||
* Earlier in kmain (before pmm_init) we cleared bit 11 of IA32_APIC_BASE
|
||||
* so that the i8259 PIC would raise interrupts properly under Limine.
|
||||
* Now that the LAPIC is being brought online we must restore that bit;
|
||||
* without it the LAPIC is completely off and nothing below will work.
|
||||
*/
|
||||
uint32_t lo, hi;
|
||||
rdmsr(IA32_APIC_BASE_MSR, &lo, &hi);
|
||||
lo |= IA32_APIC_BASE_ENABLE;
|
||||
wrmsr(IA32_APIC_BASE_MSR, lo, hi);
|
||||
|
||||
/*
|
||||
* ── Step 2: Locate the LAPIC MMIO window ─────────────────────────────
|
||||
*
|
||||
* The physical address is almost always 0xFEE00000 but we read it from
|
||||
* the MSR to be correct. vmm_init() already identity-maps all physical
|
||||
* memory through the HHDM (up to 256 GiB), so the MMIO page is reachable
|
||||
* at phys + MEM_PHYS_OFFSET with no extra mapping needed.
|
||||
*
|
||||
* TODO: For strict correctness the LAPIC page should be mapped as
|
||||
* uncacheable (PAT / PCD). In practice, QEMU / BOCHS work fine
|
||||
* because MTRRs mark the 0xFEE00000 range as UC by default.
|
||||
*/
|
||||
uint64_t phys_base = ((uint64_t)(hi & 0xFu) << 32) | (lo & 0xFFFFF000u);
|
||||
printf("[LAPIC] Physical base: 0x%08x%08x\n",
|
||||
(uint32_t)(phys_base >> 32), (uint32_t)phys_base);
|
||||
|
||||
g_lapic = (volatile uint32_t *)(phys_base + MEM_PHYS_OFFSET);
|
||||
|
||||
/*
|
||||
* ── Step 3: Software-enable the LAPIC via the SVR ────────────────────
|
||||
*
|
||||
* Bit 8 = Software Enable. The lower 8 bits are the "spurious interrupt
|
||||
* vector" delivered if the CPU acknowledges an interrupt that was
|
||||
* retracted by the LAPIC; 0xFF is the conventional choice.
|
||||
*/
|
||||
lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE | 0xFF);
|
||||
|
||||
/*
|
||||
* ── Step 4: Configure LINT0 as ExtINT (i8259 pass-through) ──────────
|
||||
*
|
||||
* On a standard PC the i8259 INTR pin is wired to the BSP's LINT0.
|
||||
* Setting LINT0 to ExtINT delivery mode makes the LAPIC act as a
|
||||
* transparent relay: the i8259 interrupt acknowledge cycle goes through
|
||||
* normally, the full 8-bit vector comes from the i8259, and the CPU's
|
||||
* existing IDT entries + irq.c EOI code all keep working unchanged.
|
||||
*
|
||||
* This is the "Virtual Wire" mode described in the Intel MP Spec.
|
||||
*/
|
||||
lapic_write(LAPIC_LVT_LINT0, LAPIC_LVT_DM_EXTINT);
|
||||
|
||||
/*
|
||||
* ── Step 5: Configure LINT1 as NMI ───────────────────────────────────
|
||||
*
|
||||
* LINT1 is the NMI pin on standard PCs. Delivery mode = 4 (NMI),
|
||||
* unmasked, edge-triggered (the default when LAPIC_LVT_LEVEL is clear).
|
||||
*/
|
||||
lapic_write(LAPIC_LVT_LINT1, LAPIC_LVT_DM_NMI);
|
||||
|
||||
/*
|
||||
* ── Step 6: Mask LVT entries we are not yet using ────────────────────
|
||||
*
|
||||
* Timer, error, and (if present) thermal / perf entries all start masked.
|
||||
* Assign distinct vectors in the 0xF0-0xFE range so that if one fires
|
||||
* spuriously the IDT handler can identify it and send EOI.
|
||||
*/
|
||||
lapic_write(LAPIC_LVT_TIMER, LAPIC_LVT_MASKED | 0xFD);
|
||||
lapic_write(LAPIC_LVT_ERROR, LAPIC_LVT_MASKED | 0xFE);
|
||||
|
||||
/* Version register: bits [23:16] = max LVT entry index */
|
||||
uint32_t ver = lapic_read(LAPIC_VER);
|
||||
uint32_t max_lvt = (ver >> 16) & 0xFF;
|
||||
if (max_lvt >= 4) lapic_write(LAPIC_LVT_THERMAL, LAPIC_LVT_MASKED | 0xFC);
|
||||
if (max_lvt >= 5) lapic_write(LAPIC_LVT_PERF, LAPIC_LVT_MASKED | 0xFB);
|
||||
|
||||
/*
|
||||
* ── Step 7: Clear the Error Status Register ───────────────────────────
|
||||
*
|
||||
* The APIC spec requires writing ESR twice to clear stale error bits.
|
||||
*/
|
||||
lapic_write(LAPIC_ESR, 0);
|
||||
lapic_write(LAPIC_ESR, 0);
|
||||
|
||||
/* Dismiss any stale in-service interrupt */
|
||||
lapic_write(LAPIC_EOI, 0);
|
||||
|
||||
/*
|
||||
* ── Step 8: Set Task Priority to 0 ───────────────────────────────────
|
||||
*
|
||||
* TPR = 0 means the CPU will accept all interrupt priorities.
|
||||
* Raise this later if you need to block lower-priority interrupts.
|
||||
*/
|
||||
lapic_write(LAPIC_TPR, 0);
|
||||
|
||||
printf("[LAPIC] Online. ID=%u version=0x%02x max_lvt=%u\n",
|
||||
lapic_id(), ver & 0xFF, max_lvt);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* Local APIC (xAPIC mode, MMIO access)
|
||||
* Register offsets are byte offsets; we shift right by 2 for uint32_t indexing.
|
||||
*/
|
||||
|
||||
/* ── Register offsets ─────────────────────────────────────────────────────── */
|
||||
#define LAPIC_ID 0x020 /* LAPIC ID (bits [27:24] on P4, [31:24] on modern) */
|
||||
#define LAPIC_VER 0x030 /* Version register */
|
||||
#define LAPIC_TPR 0x080 /* Task Priority Register */
|
||||
#define LAPIC_APR 0x090 /* Arbitration Priority (RO) */
|
||||
#define LAPIC_PPR 0x0A0 /* Processor Priority (RO) */
|
||||
#define LAPIC_EOI 0x0B0 /* End-Of-Interrupt (write 0 to ack) */
|
||||
#define LAPIC_RRD 0x0C0 /* Remote Read (RO) */
|
||||
#define LAPIC_LDR 0x0D0 /* Logical Destination */
|
||||
#define LAPIC_DFR 0x0E0 /* Destination Format */
|
||||
#define LAPIC_SVR 0x0F0 /* Spurious Interrupt Vector */
|
||||
#define LAPIC_ESR 0x280 /* Error Status Register */
|
||||
#define LAPIC_ICR_LO 0x300 /* Interrupt Command (low 32 bits) */
|
||||
#define LAPIC_ICR_HI 0x310 /* Interrupt Command (high 32 bits) */
|
||||
#define LAPIC_LVT_TIMER 0x320 /* LVT: APIC timer */
|
||||
#define LAPIC_LVT_THERMAL 0x330 /* LVT: Thermal sensor */
|
||||
#define LAPIC_LVT_PERF 0x340 /* LVT: Performance monitoring */
|
||||
#define LAPIC_LVT_LINT0 0x350 /* LVT: Local interrupt pin 0 */
|
||||
#define LAPIC_LVT_LINT1 0x360 /* LVT: Local interrupt pin 1 */
|
||||
#define LAPIC_LVT_ERROR 0x370 /* LVT: Error */
|
||||
#define LAPIC_TIMER_ICR 0x380 /* Timer Initial Count */
|
||||
#define LAPIC_TIMER_CCR 0x390 /* Timer Current Count (RO) */
|
||||
#define LAPIC_TIMER_DCR 0x3E0 /* Timer Divide Configuration */
|
||||
|
||||
/* ── SVR flags ────────────────────────────────────────────────────────────── */
|
||||
#define LAPIC_SVR_ENABLE (1u << 8) /* Software-enable the LAPIC */
|
||||
|
||||
/* ── LVT delivery modes (bits [10:8]) ────────────────────────────────────── */
|
||||
#define LAPIC_LVT_DM_FIXED (0u << 8) /* Fixed delivery */
|
||||
#define LAPIC_LVT_DM_SMI (2u << 8)
|
||||
#define LAPIC_LVT_DM_NMI (4u << 8) /* NMI */
|
||||
#define LAPIC_LVT_DM_EXTINT (7u << 8) /* ExtINT: pass-through from i8259 */
|
||||
|
||||
/* ── LVT misc flags ───────────────────────────────────────────────────────── */
|
||||
#define LAPIC_LVT_MASKED (1u << 16) /* Mask this LVT entry */
|
||||
#define LAPIC_LVT_LEVEL (1u << 15) /* Level-triggered (vs edge) */
|
||||
#define LAPIC_LVT_ACTIVE_LOW (1u << 13) /* Active-low polarity */
|
||||
|
||||
/* ── IA32_APIC_BASE MSR ───────────────────────────────────────────────────── */
|
||||
#define IA32_APIC_BASE_MSR 0x1B
|
||||
#define IA32_APIC_BASE_ENABLE (1u << 11) /* APIC global enable bit */
|
||||
#define IA32_APIC_BASE_BSP (1u << 8) /* Bootstrap processor flag */
|
||||
|
||||
/* ── Public API ───────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* lapic_init - Map the LAPIC MMIO, re-enable the APIC global bit in the MSR
|
||||
* (which was cleared earlier to let the i8259 work), then bring
|
||||
* the LAPIC online with LINT0=ExtINT so the i8259/PIT path is
|
||||
* completely preserved.
|
||||
*
|
||||
* Call AFTER uacpi_initialize() so that paging and the heap are live.
|
||||
*/
|
||||
void lapic_init(void);
|
||||
|
||||
/**
|
||||
* lapic_eoi - Signal end-of-interrupt to the LAPIC.
|
||||
* Must be called from interrupt handlers that go through the LAPIC
|
||||
* (i.e. IOAPIC-routed interrupts). ExtINT (i8259) interrupts only
|
||||
* need the i8259 EOI, which your existing irq.c already sends.
|
||||
*/
|
||||
void lapic_eoi(void);
|
||||
|
||||
/** lapic_id - Return the LAPIC ID of the current CPU. */
|
||||
uint32_t lapic_id(void);
|
||||
|
||||
/** lapic_get_phys_base - Return the physical base address from the MSR. */
|
||||
uint64_t lapic_get_phys_base(void);
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "ata.h"
|
||||
#include "io.h" // your existing inb/outb/insw
|
||||
#include "io.h"
|
||||
#include "stdio.h"
|
||||
#include "memory.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
+31
-10
@@ -47,19 +47,40 @@ extern void tss_reload(void);
|
||||
void x86_64_GDT_Initialize(void) {
|
||||
uint64_t flags;
|
||||
spinlock_acquire_irqsave(&s_gdt_lock, &flags);
|
||||
// Kernel code
|
||||
gdt.entries[1].access = 0b10011010;
|
||||
gdt.entries[1].granularity = 0b00100000;
|
||||
|
||||
// Kernel data
|
||||
gdt.entries[2].access = 0b10010010;
|
||||
// null desc
|
||||
memset(&gdt.entries[0], 0, sizeof(struct gdt_desc));
|
||||
// Kernel code 0x08
|
||||
gdt.entries[1].limit = 0xFFFF;
|
||||
gdt.entries[1].base_low = 0;
|
||||
gdt.entries[1].base_mid = 0;
|
||||
gdt.entries[1].base_hi = 0;
|
||||
gdt.entries[1].access = 0b10011010;
|
||||
gdt.entries[1].granularity = 0b00100000;
|
||||
|
||||
// User data
|
||||
gdt.entries[3].access = 0b11110010;
|
||||
// Kernel data 0x10
|
||||
gdt.entries[2].limit = 0xFFFF;
|
||||
gdt.entries[2].base_low = 0;
|
||||
gdt.entries[2].base_mid = 0;
|
||||
gdt.entries[2].base_hi = 0;
|
||||
gdt.entries[2].access = 0b10010010;
|
||||
gdt.entries[2].granularity = 0b00100000;
|
||||
|
||||
// User code
|
||||
gdt.entries[4].access = 0b11111010;
|
||||
gdt.entries[4].granularity = 0b00100000;
|
||||
// User data 0x1B
|
||||
gdt.entries[3].limit = 0xFFFF;
|
||||
gdt.entries[3].base_low = 0;
|
||||
gdt.entries[3].base_mid = 0;
|
||||
gdt.entries[3].base_hi = 0;
|
||||
gdt.entries[3].access = 0b11110010;
|
||||
gdt.entries[3].granularity = 0b00100000;
|
||||
|
||||
// User code 0x23
|
||||
gdt.entries[4].limit = 0xFFFF;
|
||||
gdt.entries[4].base_low = 0;
|
||||
gdt.entries[4].base_mid = 0;
|
||||
gdt.entries[4].base_hi = 0;
|
||||
gdt.entries[4].access = 0b11111010;
|
||||
gdt.entries[4].granularity = 0b00100000;
|
||||
|
||||
// TSS
|
||||
gdt.tss.length = sizeof(struct tss);
|
||||
|
||||
@@ -6,4 +6,5 @@ const PICDriver* i8259_GetDriver();
|
||||
uint16_t i8259_GetMask();
|
||||
void i8259_SetMask(uint16_t newMask);
|
||||
|
||||
void i8259_Configure(uint8_t offsetPic1, uint8_t offsetPic2, bool autoEoi);
|
||||
void i8259_Configure(uint8_t offsetPic1, uint8_t offsetPic2, bool autoEoi);
|
||||
void i8259_Disable();
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "ioapic.h"
|
||||
#include "apic.h"
|
||||
#include "mm/vmm.h"
|
||||
#include "mm/memory.h"
|
||||
#include "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 your 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); // from your irq.c / i8259
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* I/O APIC driver
|
||||
*
|
||||
* The IOAPIC is found by parsing the ACPI MADT table via uACPI.
|
||||
* All redirection entries are masked at init time; use ioapic_redirect() /
|
||||
* ioapic_unmask_irq() to enable individual GSIs for PCI devices later.
|
||||
*
|
||||
* Legacy ISA IRQs (0-15) are intentionally left masked here because they are
|
||||
* delivered through the i8259 → LAPIC LINT0 ExtINT path instead.
|
||||
*/
|
||||
|
||||
/* Maximum IOAPICs supported (1 is typical for QEMU/BOCHS) */
|
||||
#define IOAPIC_MAX 4
|
||||
|
||||
/* ── IOAPIC MMIO register indices (written to IOREGSEL) ─────────────────── */
|
||||
#define IOAPIC_REG_ID 0x00 /* APIC ID */
|
||||
#define IOAPIC_REG_VER 0x01 /* Version / max redirection entries */
|
||||
#define IOAPIC_REG_ARB 0x02 /* Arbitration ID */
|
||||
#define IOAPIC_REDTBL_LO(n) (0x10 + (n) * 2) /* Redirection entry n, low */
|
||||
#define IOAPIC_REDTBL_HI(n) (0x10 + (n) * 2 + 1) /* Redirection entry n, high */
|
||||
|
||||
/* ── Redirection table entry flags (low 32-bit word) ────────────────────── */
|
||||
#define IOAPIC_RTE_MASKED (1u << 16) /* 1 = masked (disabled) */
|
||||
#define IOAPIC_RTE_LEVEL (1u << 15) /* 1 = level-triggered, 0 = edge */
|
||||
#define IOAPIC_RTE_ACTIVE_LOW (1u << 13) /* 1 = active low, 0 = active high */
|
||||
#define IOAPIC_RTE_LOGICAL (1u << 11) /* 1 = logical destination mode */
|
||||
#define IOAPIC_RTE_DM_FIXED (0u << 8) /* Fixed delivery */
|
||||
#define IOAPIC_RTE_DM_NMI (4u << 8) /* NMI */
|
||||
/* bits [7:0] hold the IDT vector */
|
||||
|
||||
/* ── Public API ──────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* ioapic_init - Parse the ACPI MADT, locate all I/O APICs and their GSI
|
||||
* bases, record any ISA interrupt source overrides, then mask
|
||||
* every redirection entry.
|
||||
*
|
||||
* Must be called after uacpi_initialize() (needs MADT accessible).
|
||||
*/
|
||||
void ioapic_init(void);
|
||||
|
||||
/**
|
||||
* ioapic_redirect - Programme a single redirection table entry.
|
||||
*
|
||||
* @gsi Global System Interrupt number (from MADT or known fixed GSI).
|
||||
* @vector IDT vector the CPU will see (e.g. 0x20 + irq_number).
|
||||
* @dest_lapic Physical LAPIC ID of the target CPU.
|
||||
* @active_low true if device uses active-low polarity.
|
||||
* @level true if device uses level-triggered signalling.
|
||||
* @masked true to programme the entry but leave it masked.
|
||||
*/
|
||||
void ioapic_redirect(uint32_t gsi, uint8_t vector, uint8_t dest_lapic,
|
||||
bool active_low, bool level, bool masked);
|
||||
|
||||
/**
|
||||
* ioapic_mask_gsi - Mask (disable) a GSI redirection entry.
|
||||
* ioapic_unmask_gsi - Unmask (enable) a GSI redirection entry.
|
||||
*/
|
||||
void ioapic_mask_gsi(uint32_t gsi);
|
||||
void ioapic_unmask_gsi(uint32_t gsi);
|
||||
|
||||
/**
|
||||
* ioapic_gsi_for_isa_irq - Look up the GSI that corresponds to an ISA IRQ
|
||||
* number, accounting for any MADT interrupt source
|
||||
* overrides (e.g. ISA IRQ 0 → GSI 2 on BOCHS).
|
||||
*
|
||||
* Returns the GSI, or the IRQ number itself if no override is defined.
|
||||
*/
|
||||
uint32_t ioapic_gsi_for_isa_irq(uint8_t isa_irq);
|
||||
|
||||
void irq_redirect_to_apic(uint8_t isa_irq, uint8_t vector, uint8_t dest_lapic, bool masked);
|
||||
|
||||
+40
-2
@@ -5,12 +5,19 @@
|
||||
#include <util/arrays.h>
|
||||
#include "stdio.h"
|
||||
#include <debug.h>
|
||||
#include "apic.h"
|
||||
#include "ioapic.h"
|
||||
|
||||
#define PIC_REMAP_OFFSET 0x20
|
||||
#define MODULE "PIC"
|
||||
|
||||
IRQHandler g_IRQHandlers[16];
|
||||
static const PICDriver* g_Driver = NULL;
|
||||
static IRQHandler g_APICHandlers[256];
|
||||
const PICDriver* g_Driver = NULL;
|
||||
|
||||
extern bool g_IOAPIC;
|
||||
|
||||
|
||||
|
||||
void x86_64_IRQ_Handler(Registers *regs)
|
||||
{
|
||||
@@ -26,11 +33,25 @@ void x86_64_IRQ_Handler(Registers *regs)
|
||||
log_warn(MODULE, "Unhandled IRQ %d...", irq);
|
||||
}
|
||||
|
||||
// send EOI
|
||||
g_Driver->SendEndOfInterrupt(irq);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void x86_64_APIC_IRQ_Handler(Registers* regs)
|
||||
{
|
||||
uint8_t vector = regs->interrupt;
|
||||
|
||||
if (g_APICHandlers[vector] != NULL) {
|
||||
g_APICHandlers[vector](regs);
|
||||
} else {
|
||||
log_warn("APIC", "Unhandled vector 0x%02x", vector);
|
||||
}
|
||||
|
||||
lapic_eoi(); // ← This is the key difference from PIC!
|
||||
}
|
||||
|
||||
|
||||
void x86_64_IRQ_Initialize(void)
|
||||
{
|
||||
@@ -65,6 +86,23 @@ void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler)
|
||||
g_IRQHandlers[irq] = handler;
|
||||
}
|
||||
|
||||
void x86_64_APIC_IRQ_RegisterHandler(uint8_t vector, IRQHandler handler)
|
||||
{
|
||||
if (vector < 32) {
|
||||
printf("[APIC] Warning: vector %u is in reserved range!\n", vector);
|
||||
}
|
||||
g_APICHandlers[vector] = handler;
|
||||
x86_64_ISR_RegisterHandler(vector, x86_64_APIC_IRQ_Handler);
|
||||
}
|
||||
|
||||
void x86_64_APIC_IRQ_RedirectAndRegister(uint8_t isa_irq,
|
||||
uint8_t vector,
|
||||
IRQHandler handler)
|
||||
{
|
||||
x86_64_APIC_IRQ_RegisterHandler(vector, handler);
|
||||
irq_redirect_to_apic(isa_irq, vector, lapic_id(), false);
|
||||
}
|
||||
|
||||
void x86_64_IRQ_Unmask(int irq)
|
||||
{
|
||||
if (g_Driver) g_Driver->Unmask(irq);
|
||||
|
||||
@@ -6,4 +6,8 @@ typedef void (*IRQHandler)(Registers* regs);
|
||||
|
||||
void x86_64_IRQ_Initialize();
|
||||
void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler);
|
||||
void x86_64_IRQ_Unmask(int irq);
|
||||
void x86_64_IRQ_Unmask(int irq);
|
||||
void x86_64_APIC_IRQ_RegisterHandler(uint8_t vector, IRQHandler handler);
|
||||
void x86_64_APIC_IRQ_RedirectAndRegister(uint8_t isa_irq,
|
||||
uint8_t vector,
|
||||
IRQHandler handler);
|
||||
@@ -22,7 +22,7 @@ typedef struct {
|
||||
#define PCI_CLASS_AUDIO 0x04
|
||||
#define PCI_SUBCLASS_HDA 0x03
|
||||
|
||||
// Read/write config space (re-uses your existing uACPI PCI code)
|
||||
// Read/write config space
|
||||
uint8_t pci_read8 (pci_address_t addr, uint16_t offset);
|
||||
uint16_t pci_read16(pci_address_t addr, uint16_t offset);
|
||||
uint32_t pci_read32(pci_address_t addr, uint16_t offset);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "stdio.h"
|
||||
#include "e9.h"
|
||||
#include "limine.h"
|
||||
#include "apic.h"
|
||||
|
||||
__attribute__((used, section(".limine_requests")))
|
||||
volatile struct limine_date_at_boot_request boot_request = {
|
||||
@@ -20,7 +21,7 @@ volatile struct limine_date_at_boot_request boot_request = {
|
||||
volatile uint64_t g_Ticks = 0;
|
||||
volatile uint64_t ticks = 0;
|
||||
uint64_t g_Unixseconds = 0;
|
||||
|
||||
extern bool g_IOAPIC;
|
||||
/* ========================= */
|
||||
/* IRQ0 Handler (Timer Tick) */
|
||||
/* ========================= */
|
||||
@@ -34,7 +35,10 @@ void PIT_IRQ_Handler(Registers* regs)
|
||||
ticks = 0;
|
||||
}
|
||||
|
||||
|
||||
if (g_IOAPIC == true) {
|
||||
lapic_eoi();
|
||||
}
|
||||
|
||||
// You can add scheduler / time logic here later
|
||||
}
|
||||
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
#include "ps2.h"
|
||||
#include "io.h"
|
||||
#include "irq.h"
|
||||
#include "stdio.h"
|
||||
#include "mp/spinlock.h"
|
||||
#include <stddef.h>
|
||||
#include "input/input.h"
|
||||
|
||||
#define PS2_DATA 0x60
|
||||
#define PS2_COMMAND 0x64
|
||||
|
||||
/* ====================== CONTROLLER HELPERS ====================== */
|
||||
static inline bool ps2_wait_write(void) {
|
||||
uint32_t timeout = 1000000;
|
||||
while (x86_64_inb(PS2_COMMAND) & 2) {
|
||||
if (--timeout == 0) {
|
||||
printf("PS/2: write timeout!\n");
|
||||
return false;
|
||||
}
|
||||
asm volatile("pause");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool ps2_wait_read(void) {
|
||||
uint32_t timeout = 1000000;
|
||||
while (!(x86_64_inb(PS2_COMMAND) & 1)) {
|
||||
if (--timeout == 0) {
|
||||
printf("PS/2: read timeout!\n");
|
||||
return false;
|
||||
}
|
||||
asm volatile("pause");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline uint8_t ps2_read(void) {
|
||||
if (!ps2_wait_read()) return 0xFF; // error value
|
||||
return x86_64_inb(PS2_DATA);
|
||||
}
|
||||
|
||||
static inline void ps2_write(uint8_t val) {
|
||||
if (!ps2_wait_write()) return;
|
||||
x86_64_outb(PS2_DATA, val);
|
||||
}
|
||||
|
||||
static inline void ps2_command(uint8_t cmd) {
|
||||
if (!ps2_wait_write()) return;
|
||||
x86_64_outb(PS2_COMMAND, cmd);
|
||||
}
|
||||
|
||||
/* Send command to AUX (mouse) port */
|
||||
static void ps2_write_mouse(uint8_t val) {
|
||||
ps2_command(0xD4); // tell controller next byte goes to mouse
|
||||
ps2_write(val);
|
||||
}
|
||||
|
||||
/* ====================== KEYBOARD RING BUFFER ====================== */
|
||||
#define KB_BUFFER_SIZE 256
|
||||
static uint8_t kb_buffer[KB_BUFFER_SIZE];
|
||||
static size_t kb_read_idx = 0;
|
||||
static size_t kb_write_idx = 0;
|
||||
static spinlock_t kb_lock = SPINLOCK_INIT;
|
||||
|
||||
/* US QWERTY scancode set 2 -> ASCII (unshifted) */
|
||||
static const char kb_map[128] = {
|
||||
/*00*/ 0, 0, '1', '2', '3', '4', '5', '6',
|
||||
/*08*/ '7', '8', '9', '0', '-', '=', '\b', '\t',
|
||||
/*10*/ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
|
||||
/*18*/ 'o', 'p', '[', ']', '\n', 0, 'a', 's',
|
||||
/*20*/ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
|
||||
/*28*/ '\'', '`', 0, '\\', 'z', 'x', 'c', 'v',
|
||||
/*30*/ 'b', 'n', 'm', ',', '.', '/', 0, '*',
|
||||
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
/*40*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*48*/ 0, 0, '-', 0, 0, 0, '+', 0,
|
||||
/*50*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*58*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*60*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*68*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*70*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*78*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
/* Shifted version */
|
||||
static const char kb_map_shift[128] = {
|
||||
/*00*/ 0, 0, '!', '@', '#', '$', '%', '^',
|
||||
/*08*/ '&', '*', '(', ')', '_', '+', '\b', '\t',
|
||||
/*10*/ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
|
||||
/*18*/ 'O', 'P', '{', '}', '\n', 0, 'A', 'S',
|
||||
/*20*/ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
|
||||
/*28*/ '"', '~', 0, '|', 'Z', 'X', 'C', 'V',
|
||||
/*30*/ 'B', 'N', 'M', '<', '>', '?', 0, '*',
|
||||
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
};
|
||||
|
||||
static bool shift = false;
|
||||
static bool caps = false;
|
||||
|
||||
static void kb_enqueue(uint8_t sc) {
|
||||
spinlock_acquire_or_wait(&kb_lock);
|
||||
size_t next = (kb_write_idx + 1) % KB_BUFFER_SIZE;
|
||||
if (next != kb_read_idx) {
|
||||
kb_buffer[kb_write_idx] = sc;
|
||||
kb_write_idx = next;
|
||||
}
|
||||
spinlock_drop(&kb_lock);
|
||||
}
|
||||
|
||||
bool ps2_keyboard_has_data(void) {
|
||||
spinlock_acquire_or_wait(&kb_lock);
|
||||
bool has = (kb_read_idx != kb_write_idx);
|
||||
spinlock_drop(&kb_lock);
|
||||
return has;
|
||||
}
|
||||
|
||||
|
||||
/* ====================== MOUSE RING BUFFER ====================== */
|
||||
#define MOUSE_BUFFER_SIZE 32
|
||||
static mouse_packet_t mouse_buffer[MOUSE_BUFFER_SIZE];
|
||||
static size_t mouse_read_idx = 0;
|
||||
static size_t mouse_write_idx = 0;
|
||||
static spinlock_t mouse_lock = SPINLOCK_INIT;
|
||||
|
||||
static uint8_t mouse_packet[4];
|
||||
static int mouse_idx = 0;
|
||||
static int mouse_packet_size = 3; // 3 or 4 for wheel
|
||||
|
||||
static void mouse_enqueue(const mouse_packet_t* pkt) {
|
||||
spinlock_acquire_or_wait(&mouse_lock);
|
||||
size_t next = (mouse_write_idx + 1) % MOUSE_BUFFER_SIZE;
|
||||
if (next != mouse_read_idx) {
|
||||
mouse_buffer[mouse_write_idx] = *pkt;
|
||||
mouse_write_idx = next;
|
||||
}
|
||||
spinlock_drop(&mouse_lock);
|
||||
}
|
||||
|
||||
bool ps2_mouse_get_packet(mouse_packet_t* out) {
|
||||
spinlock_acquire_or_wait(&mouse_lock);
|
||||
if (mouse_read_idx == mouse_write_idx) {
|
||||
spinlock_drop(&mouse_lock);
|
||||
return false;
|
||||
}
|
||||
*out = mouse_buffer[mouse_read_idx];
|
||||
mouse_read_idx = (mouse_read_idx + 1) % MOUSE_BUFFER_SIZE;
|
||||
spinlock_drop(&mouse_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ps2_mouse_has_packet(void) {
|
||||
spinlock_acquire_or_wait(&mouse_lock);
|
||||
bool has = (mouse_read_idx != mouse_write_idx);
|
||||
spinlock_drop(&mouse_lock);
|
||||
return has;
|
||||
}
|
||||
|
||||
/* ====================== IRQ HANDLERS ====================== */
|
||||
void keyboard_irq_handler(Registers* regs) {
|
||||
(void)regs;
|
||||
uint8_t sc = x86_64_inb(PS2_DATA);
|
||||
|
||||
// Optional: keep the old buffer if you still use the polling API somewhere
|
||||
kb_enqueue(sc);
|
||||
|
||||
// === NEW: process and push to the input subsystem ===
|
||||
bool released = (sc & 0x80) != 0;
|
||||
sc &= 0x7F;
|
||||
|
||||
if (sc == 0x2A || sc == 0x36) { // L/R Shift
|
||||
shift = !released;
|
||||
return;
|
||||
}
|
||||
if (sc == 0x3A && !released) { // Caps Lock
|
||||
caps = !caps;
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_alpha = (sc >= 0x10 && sc <= 0x19) ||
|
||||
(sc >= 0x1E && sc <= 0x26) ||
|
||||
(sc >= 0x2C && sc <= 0x32);
|
||||
|
||||
bool use_upper = shift ^ (caps && is_alpha);
|
||||
char ascii = use_upper ? kb_map_shift[sc] : kb_map[sc];
|
||||
|
||||
input_push_key(sc, released ? 0 : ascii, !released);
|
||||
}
|
||||
|
||||
void mouse_irq_handler(Registers* regs)
|
||||
{
|
||||
(void)regs;
|
||||
uint8_t data = x86_64_inb(PS2_DATA);
|
||||
|
||||
if (mouse_idx == 0 && !(data & 0x08)) return;
|
||||
|
||||
mouse_packet[mouse_idx++] = data;
|
||||
|
||||
if (mouse_idx == mouse_packet_size) {
|
||||
mouse_packet_t pkt = {0};
|
||||
uint8_t status = mouse_packet[0];
|
||||
|
||||
pkt.overflow_x = !!(status & (1 << 6));
|
||||
pkt.overflow_y = !!(status & (1 << 7));
|
||||
|
||||
if (pkt.overflow_x || pkt.overflow_y) {
|
||||
mouse_idx = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
pkt.buttons = status & 0x07;
|
||||
pkt.x_delta = (int8_t)mouse_packet[1];
|
||||
pkt.y_delta = -(int8_t)mouse_packet[2];
|
||||
pkt.z_delta = (mouse_packet_size == 4) ? (int8_t)mouse_packet[3] : 0;
|
||||
|
||||
mouse_enqueue(&pkt);
|
||||
|
||||
if (pkt.x_delta || pkt.y_delta)
|
||||
input_push_mouse_rel(pkt.x_delta, pkt.y_delta);
|
||||
if (pkt.z_delta)
|
||||
input_push_mouse_wheel(pkt.z_delta);
|
||||
|
||||
static uint8_t prev_buttons = 0;
|
||||
uint8_t changed = pkt.buttons ^ prev_buttons;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (changed & (1 << i))
|
||||
input_push_mouse_btn(i, !!(pkt.buttons & (1 << i)));
|
||||
}
|
||||
prev_buttons = pkt.buttons;
|
||||
|
||||
mouse_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ====================== DEVICE INITIALIZATION ====================== */
|
||||
static bool ps2_controller_init(void) {
|
||||
ps2_command(0xAD);
|
||||
ps2_command(0xA7);
|
||||
|
||||
while (x86_64_inb(PS2_COMMAND) & 1)
|
||||
x86_64_inb(PS2_DATA);
|
||||
|
||||
ps2_command(0xAA);
|
||||
if (ps2_read() != 0x55) {
|
||||
printf("PS/2: Controller self-test failed!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ps2_command(0xAB);
|
||||
if (ps2_read() != 0x00) {
|
||||
printf("PS/2: Keyboard interface test failed!\n");
|
||||
return false;
|
||||
}
|
||||
ps2_command(0xA9);
|
||||
if (ps2_read() != 0x00) {
|
||||
printf("PS/2: Mouse interface test failed (no mouse?)\n");
|
||||
}
|
||||
|
||||
ps2_command(0x20);
|
||||
uint8_t cfg = ps2_read();
|
||||
cfg |= (1 << 0) | (1 << 1) | (1 << 6);
|
||||
cfg &= ~((1 << 4) | (1 << 5));
|
||||
ps2_command(0x60);
|
||||
ps2_write(cfg);
|
||||
|
||||
ps2_command(0xAE);
|
||||
ps2_command(0xA8);
|
||||
|
||||
printf("PS/2: Controller ready (IRQs enabled, translation on)\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ps2_keyboard_init(void) {
|
||||
ps2_write(0xFF);
|
||||
ps2_read(); // ACK
|
||||
ps2_read(); ps2_read(); // AA 00
|
||||
|
||||
ps2_write(0xF4);
|
||||
if (ps2_read() != 0xFA)
|
||||
printf("PS/2: Keyboard enable failed\n");
|
||||
|
||||
printf("PS/2: Keyboard initialized (scancode set 2)\n");
|
||||
}
|
||||
|
||||
static void ps2_mouse_init(void) {
|
||||
ps2_write_mouse(0xFF);
|
||||
ps2_read(); ps2_read(); ps2_read(); // ACK, AA, 00
|
||||
|
||||
ps2_write_mouse(0xF3); ps2_write_mouse(200);
|
||||
ps2_write_mouse(0xF3); ps2_write_mouse(100);
|
||||
ps2_write_mouse(0xF3); ps2_write_mouse(80);
|
||||
ps2_write_mouse(0xF2);
|
||||
uint8_t id = ps2_read();
|
||||
if (id == 0x03) {
|
||||
mouse_packet_size = 4;
|
||||
printf("PS/2: IntelliMouse (wheel) detected\n");
|
||||
} else {
|
||||
mouse_packet_size = 3;
|
||||
printf("PS/2: Standard mouse detected\n");
|
||||
}
|
||||
|
||||
ps2_write_mouse(0xF6);
|
||||
ps2_write_mouse(0xF4);
|
||||
|
||||
printf("PS/2: Mouse initialized (packet size %d)\n", mouse_packet_size);
|
||||
}
|
||||
|
||||
/* ====================== MAIN INIT ====================== */
|
||||
void ps2_init(void) {
|
||||
if (!ps2_controller_init()) {
|
||||
printf("PS/2: Controller initialization failed - input disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
input_init();
|
||||
|
||||
ps2_keyboard_init();
|
||||
ps2_mouse_init();
|
||||
|
||||
|
||||
x86_64_IRQ_RegisterHandler(1, keyboard_irq_handler);
|
||||
x86_64_IRQ_RegisterHandler(12, mouse_irq_handler);
|
||||
|
||||
|
||||
x86_64_IRQ_Unmask(1);
|
||||
x86_64_IRQ_Unmask(12);
|
||||
|
||||
printf("PS/2 subsystem initialized successfully!\n");
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "mp/spinlock.h"
|
||||
|
||||
/* ====================== PUBLIC API ====================== */
|
||||
|
||||
/* Keyboard */
|
||||
uint8_t ps2_keyboard_get_scancode(void); // non-blocking, returns 0 if empty
|
||||
bool ps2_keyboard_has_data(void);
|
||||
char ps2_keyboard_get_char(void); // processed ASCII (handles shift/caps)
|
||||
|
||||
/* Mouse */
|
||||
typedef struct {
|
||||
int8_t x_delta;
|
||||
int8_t y_delta;
|
||||
int8_t z_delta; // wheel (0 if not supported)
|
||||
uint8_t buttons; // bit 0 = left, bit 1 = right, bit 2 = middle
|
||||
bool overflow_x;
|
||||
bool overflow_y;
|
||||
} mouse_packet_t;
|
||||
|
||||
bool ps2_mouse_get_packet(mouse_packet_t* out_packet); // non-blocking, returns false if empty
|
||||
bool ps2_mouse_has_packet(void);
|
||||
|
||||
/* Subsystem */
|
||||
void ps2_init(void);
|
||||
+32
-36
@@ -31,32 +31,21 @@ static void setup_user_stack()
|
||||
for (;;);
|
||||
}
|
||||
|
||||
// Map each page individually into the current pagemap
|
||||
// Stack grows downward, so start from the bottom
|
||||
uintptr_t virt = USER_STACK_TOP - USER_STACK_SIZE;
|
||||
uintptr_t stack_bottom = USER_STACK_TOP - USER_STACK_SIZE;
|
||||
uintptr_t virt = stack_bottom;
|
||||
|
||||
for (int i = 0; i < USER_STACK_PAGES; i++) {
|
||||
uint64_t phys = user_stack_phys_base + (i * PAGE_SIZE);
|
||||
uint64_t phys = user_stack_phys_base + i * PAGE_SIZE;
|
||||
|
||||
bool success = vmm_map_page(
|
||||
kernel_pagemap, // Use kernel_pagemap for now (later: process->process_pagemap)
|
||||
virt,
|
||||
phys,
|
||||
PAGE_READ | PAGE_WRITE | PAGE_USER, // RW + User
|
||||
Size4KiB
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
printf("Failed to map user stack page at 0x%lx\n", virt);
|
||||
// TODO: cleanup previous pages if partial failure
|
||||
if (!vmm_map_page(kernel_pagemap, virt, phys,
|
||||
PAGE_READ | PAGE_WRITE | PAGE_USER, Size4KiB)) {
|
||||
printf("Failed to map user stack at 0x%lx\n", virt);
|
||||
for (;;);
|
||||
}
|
||||
|
||||
virt += PAGE_SIZE;
|
||||
}
|
||||
|
||||
// Optional: zero the stack (good practice)
|
||||
// memset((void*)(USER_STACK_TOP - USER_STACK_SIZE), 0, USER_STACK_SIZE);
|
||||
// Zero the stack
|
||||
memset((void*)stack_bottom, 0, USER_STACK_SIZE);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,24 +54,26 @@ static void setup_user_stack()
|
||||
__attribute__((naked))
|
||||
void enter_user_mode(uint64_t rip, uint64_t rsp)
|
||||
{
|
||||
asm volatile(
|
||||
"cli\n"
|
||||
"mov $0x1B, %ax\n" // Use User Data Selector (0x1B)
|
||||
"mov %ax, %ds\n"
|
||||
"mov %ax, %es\n"
|
||||
"mov %ax, %fs\n"
|
||||
"mov %ax, %gs\n"
|
||||
asm volatile (
|
||||
"cli\n\t"
|
||||
"mov $0x1B, %%ax\n\t" // User data segment
|
||||
"mov %%ax, %%ds\n\t"
|
||||
"mov %%ax, %%es\n\t"
|
||||
"mov %%ax, %%fs\n\t"
|
||||
"mov %%ax, %%gs\n\t"
|
||||
|
||||
"pushq $0x1B\n" // SS (User Data)
|
||||
"pushq %rsi\n" // RSP
|
||||
"pushfq\n"
|
||||
// Optional: manually set IF bit in pushed RFLAGS if you want interrupts enabled
|
||||
"pop %rax\n"
|
||||
"or $0x200, %rax\n"
|
||||
"push %rax\n"
|
||||
"pushq $0x23\n" // CS (User Code)
|
||||
"pushq %rdi\n" // RIP
|
||||
"iretq\n"
|
||||
"push $0x1B\n\t" // SS (user data)
|
||||
"push %1\n\t" // RSP
|
||||
"pushfq\n\t"
|
||||
"pop %%rax\n\t"
|
||||
"or $0x200, %%rax\n\t" // Enable interrupts
|
||||
"push %%rax\n\t" // RFLAGS
|
||||
"push $0x23\n\t" // CS (user code)
|
||||
"push %0\n\t" // RIP
|
||||
"iretq\n\t"
|
||||
:
|
||||
: "r"(rip), "r"(rsp)
|
||||
: "rax", "memory"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,6 +86,11 @@ void start_userspace(void)
|
||||
for(;;);
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
printf("ELF has no entry point\n");
|
||||
for(;;);
|
||||
}
|
||||
|
||||
setup_user_stack();
|
||||
|
||||
printf("Jumping to user entry: 0x%lx with stack top 0x%lx\n",
|
||||
|
||||
Reference in New Issue
Block a user