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:
kaguya
2026-04-24 01:59:33 -04:00
parent 551eb00708
commit 3b6e68bc16
27 changed files with 1009 additions and 504 deletions
Binary file not shown.
+147
View File
@@ -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);
}
+77
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
#include "ata.h" #include "ata.h"
#include "io.h" // your existing inb/outb/insw #include "io.h"
#include "stdio.h" #include "stdio.h"
#include "memory.h" #include "memory.h"
#include <stdbool.h> #include <stdbool.h>
+25 -4
View File
@@ -47,17 +47,38 @@ extern void tss_reload(void);
void x86_64_GDT_Initialize(void) { void x86_64_GDT_Initialize(void) {
uint64_t flags; uint64_t flags;
spinlock_acquire_irqsave(&s_gdt_lock, &flags); spinlock_acquire_irqsave(&s_gdt_lock, &flags);
// Kernel code
// 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].access = 0b10011010;
gdt.entries[1].granularity = 0b00100000; gdt.entries[1].granularity = 0b00100000;
// Kernel data // 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].access = 0b10010010;
gdt.entries[2].granularity = 0b00100000;
// User data // 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].access = 0b11110010;
gdt.entries[3].granularity = 0b00100000;
// User code // 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].access = 0b11111010;
gdt.entries[4].granularity = 0b00100000; gdt.entries[4].granularity = 0b00100000;
+1
View File
@@ -7,3 +7,4 @@ uint16_t i8259_GetMask();
void i8259_SetMask(uint16_t newMask); 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();
+359
View File
@@ -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;
}
+76
View File
@@ -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
View File
@@ -5,12 +5,19 @@
#include <util/arrays.h> #include <util/arrays.h>
#include "stdio.h" #include "stdio.h"
#include <debug.h> #include <debug.h>
#include "apic.h"
#include "ioapic.h"
#define PIC_REMAP_OFFSET 0x20 #define PIC_REMAP_OFFSET 0x20
#define MODULE "PIC" #define MODULE "PIC"
IRQHandler g_IRQHandlers[16]; 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) void x86_64_IRQ_Handler(Registers *regs)
{ {
@@ -26,9 +33,23 @@ void x86_64_IRQ_Handler(Registers *regs)
log_warn(MODULE, "Unhandled IRQ %d...", irq); log_warn(MODULE, "Unhandled IRQ %d...", irq);
} }
// send EOI
g_Driver->SendEndOfInterrupt(irq); 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!
} }
@@ -65,6 +86,23 @@ void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler)
g_IRQHandlers[irq] = 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) void x86_64_IRQ_Unmask(int irq)
{ {
if (g_Driver) g_Driver->Unmask(irq); if (g_Driver) g_Driver->Unmask(irq);
+4
View File
@@ -7,3 +7,7 @@ typedef void (*IRQHandler)(Registers* regs);
void x86_64_IRQ_Initialize(); void x86_64_IRQ_Initialize();
void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler); 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);
+1 -1
View File
@@ -22,7 +22,7 @@ typedef struct {
#define PCI_CLASS_AUDIO 0x04 #define PCI_CLASS_AUDIO 0x04
#define PCI_SUBCLASS_HDA 0x03 #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); uint8_t pci_read8 (pci_address_t addr, uint16_t offset);
uint16_t pci_read16(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); uint32_t pci_read32(pci_address_t addr, uint16_t offset);
+5 -1
View File
@@ -5,6 +5,7 @@
#include "stdio.h" #include "stdio.h"
#include "e9.h" #include "e9.h"
#include "limine.h" #include "limine.h"
#include "apic.h"
__attribute__((used, section(".limine_requests"))) __attribute__((used, section(".limine_requests")))
volatile struct limine_date_at_boot_request boot_request = { 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 g_Ticks = 0;
volatile uint64_t ticks = 0; volatile uint64_t ticks = 0;
uint64_t g_Unixseconds = 0; uint64_t g_Unixseconds = 0;
extern bool g_IOAPIC;
/* ========================= */ /* ========================= */
/* IRQ0 Handler (Timer Tick) */ /* IRQ0 Handler (Timer Tick) */
/* ========================= */ /* ========================= */
@@ -34,6 +35,9 @@ void PIT_IRQ_Handler(Registers* regs)
ticks = 0; ticks = 0;
} }
if (g_IOAPIC == true) {
lapic_eoi();
}
// You can add scheduler / time logic here later // You can add scheduler / time logic here later
} }
-332
View File
@@ -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");
}
-29
View File
@@ -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
View File
@@ -31,32 +31,21 @@ static void setup_user_stack()
for (;;); for (;;);
} }
// Map each page individually into the current pagemap uintptr_t stack_bottom = USER_STACK_TOP - USER_STACK_SIZE;
// Stack grows downward, so start from the bottom uintptr_t virt = stack_bottom;
uintptr_t virt = USER_STACK_TOP - USER_STACK_SIZE;
for (int i = 0; i < USER_STACK_PAGES; i++) { 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( if (!vmm_map_page(kernel_pagemap, virt, phys,
kernel_pagemap, // Use kernel_pagemap for now (later: process->process_pagemap) PAGE_READ | PAGE_WRITE | PAGE_USER, Size4KiB)) {
virt, printf("Failed to map user stack at 0x%lx\n", 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
for (;;); for (;;);
} }
virt += PAGE_SIZE; virt += PAGE_SIZE;
} }
// Zero the stack
// Optional: zero the stack (good practice) memset((void*)stack_bottom, 0, USER_STACK_SIZE);
// memset((void*)(USER_STACK_TOP - USER_STACK_SIZE), 0, USER_STACK_SIZE);
} }
@@ -65,24 +54,26 @@ static void setup_user_stack()
__attribute__((naked)) __attribute__((naked))
void enter_user_mode(uint64_t rip, uint64_t rsp) void enter_user_mode(uint64_t rip, uint64_t rsp)
{ {
asm volatile( asm volatile (
"cli\n" "cli\n\t"
"mov $0x1B, %ax\n" // Use User Data Selector (0x1B) "mov $0x1B, %%ax\n\t" // User data segment
"mov %ax, %ds\n" "mov %%ax, %%ds\n\t"
"mov %ax, %es\n" "mov %%ax, %%es\n\t"
"mov %ax, %fs\n" "mov %%ax, %%fs\n\t"
"mov %ax, %gs\n" "mov %%ax, %%gs\n\t"
"pushq $0x1B\n" // SS (User Data) "push $0x1B\n\t" // SS (user data)
"pushq %rsi\n" // RSP "push %1\n\t" // RSP
"pushfq\n" "pushfq\n\t"
// Optional: manually set IF bit in pushed RFLAGS if you want interrupts enabled "pop %%rax\n\t"
"pop %rax\n" "or $0x200, %%rax\n\t" // Enable interrupts
"or $0x200, %rax\n" "push %%rax\n\t" // RFLAGS
"push %rax\n" "push $0x23\n\t" // CS (user code)
"pushq $0x23\n" // CS (User Code) "push %0\n\t" // RIP
"pushq %rdi\n" // RIP "iretq\n\t"
"iretq\n" :
: "r"(rip), "r"(rsp)
: "rax", "memory"
); );
} }
@@ -95,6 +86,11 @@ void start_userspace(void)
for(;;); for(;;);
} }
if (!entry) {
printf("ELF has no entry point\n");
for(;;);
}
setup_user_stack(); setup_user_stack();
printf("Jumping to user entry: 0x%lx with stack top 0x%lx\n", printf("Jumping to user entry: 0x%lx with stack top 0x%lx\n",
+1 -6
View File
@@ -81,12 +81,7 @@ void input_push_mouse_wheel(int8_t delta)
push_event(&ev); push_event(&ev);
} }
/* ------------------------------------------------------------------ *
* /dev/input/event0 VFS device node
*
* Adjust VFS_RegisterDevice() to match whatever your VFS layer exposes
* for character-device registration.
* ------------------------------------------------------------------ */
static int dev_input_read(void *buf, size_t len) static int dev_input_read(void *buf, size_t len)
{ {
size_t count = 0; size_t count = 0;
+193
View File
@@ -0,0 +1,193 @@
#include "ps2.h"
#include "input/input.h"
#include "arch/x86_64/irq.h"
#include "arch/x86_64/isr.h"
#include "arch/x86_64/io.h"
#include "arch/x86_64/ioapic.h"
#include "arch/x86_64/apic.h"
#include "stdio.h"
/* ── PS/2 I/O ports ───────────────────────────────────────────────────────── */
#define PS2_DATA_PORT 0x60 /* Read: scancode / Write: command data */
#define PS2_STATUS_PORT 0x64 /* Read: controller status */
#define PS2_CMD_PORT 0x64 /* Write: controller command */
/* Status register bits */
#define PS2_STATUS_OBF (1 << 0) /* Output buffer full (data ready to read) */
#define PS2_STATUS_IBF (1 << 1) /* Input buffer full (don't write yet) */
/* ── IRQ number for the keyboard ─────────────────────────────────────────── */
#define KBD_IRQ 1
#define KBD_IDT_VECTOR (0x20 + KBD_IRQ) /* 0x21 with PIC_REMAP_OFFSET=0x20 */
/* ── Modifier state ───────────────────────────────────────────────────────── */
static bool s_shift = false;
static bool s_ctrl = false;
static bool s_alt = false;
static bool s_caps_lock = false;
static bool s_extended = false; /* true after receiving 0xE0 prefix */
/* ══════════════════════════════════════════════════════════════════════════
* Set-1 keycode → ASCII translation tables
*
* Index = Set-1 make code (0x000x58 covers the full AT-101 layout).
* 0x00 = unmapped / non-printable.
* ══════════════════════════════════════════════════════════════════════════ */
static const char s_normal[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, '7',
/*48*/ '8', '9', '-', '4', '5', '6', '+', '1',
/*50*/ '2', '3', '0', '.', 0, 0, 0, 0,
/*58*/ 0
};
static const char s_shifted[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,
/*40*/ 0, 0, 0, 0, 0, 0, 0, '7',
/*48*/ '8', '9', '-', '4', '5', '6', '+', '1',
/*50*/ '2', '3', '0', '.', 0, 0, 0, 0,
/*58*/ 0
};
/* ══════════════════════════════════════════════════════════════════════════
* Extended (0xE0-prefixed) make codes we care about
* ══════════════════════════════════════════════════════════════════════════ */
typedef enum {
/* Regular Set-1 make codes (non-extended) */
SC_LSHIFT = 0x2A,
SC_RSHIFT = 0x36,
SC_LCTRL = 0x1D,
SC_LALT = 0x38,
SC_CAPSLOCK = 0x3A,
SC_BACKSPACE= 0x0E,
SC_ENTER = 0x1C,
SC_TAB = 0x0F,
SC_ESC = 0x01,
/* Function keys */
SC_F1 = 0x3B, SC_F2 = 0x3C, SC_F3 = 0x3D, SC_F4 = 0x3E,
SC_F5 = 0x3F, SC_F6 = 0x40, SC_F7 = 0x41, SC_F8 = 0x42,
SC_F9 = 0x43, SC_F10 = 0x44, SC_F11 = 0x57, SC_F12 = 0x58,
} scancode_t;
/* ══════════════════════════════════════════════════════════════════════════
* IRQ1 handler
* ══════════════════════════════════════════════════════════════════════════ */
void ps2_kbd_handler(Registers *regs)
{
(void)regs;
/*
* Always drain the output buffer even if we don't handle the byte,
* otherwise the PS/2 controller stalls and stops sending interrupts.
*/
uint8_t status = x86_64_inb(PS2_STATUS_PORT);
if (!(status & PS2_STATUS_OBF)) {
/* Spurious interrupt with no data — just return */
return;
}
uint8_t byte = x86_64_inb(PS2_DATA_PORT);
/* ── Handle 0xE0 extended-key prefix ─────────────────────────────── */
if (byte == 0xE0) {
s_extended = true;
return;
}
/* ── Decode make / break ─────────────────────────────────────────── */
bool pressed = !(byte & 0x80); /* bit 7 = 0 → make, 1 → break */
uint8_t scancode = byte & 0x7F; /* strip the break bit */
bool extended = s_extended;
s_extended = false;
/* ── Update modifier keys ────────────────────────────────────────── */
if (!extended) {
switch (scancode) {
case SC_LSHIFT: case SC_RSHIFT:
s_shift = pressed;
return; /* don't push a key event for bare modifiers */
case SC_LCTRL:
s_ctrl = pressed;
return;
case SC_LALT:
s_alt = pressed;
return;
case SC_CAPSLOCK:
if (pressed) s_caps_lock = !s_caps_lock;
return;
}
}
/* ── Translate to ASCII ──────────────────────────────────────────── */
char ascii = 0;
if (!extended && scancode < 128) {
bool use_upper = s_shift ^ s_caps_lock; /* XOR: caps inverts shift */
ascii = use_upper ? s_shifted[scancode] : s_normal[scancode];
}
/* Extended keys (arrows, home, end, etc.) leave ascii=0 */
/* ── Push event ──────────────────────────────────────────────────── */
input_push_key(scancode, ascii, pressed);
/* Optionally echo to debug port for development */
if (pressed && ascii) {
/* e9_putc(ascii); ← uncomment if you have e9 debug output */
}
}
/* ══════════════════════════════════════════════════════════════════════════
* Public helpers
* ══════════════════════════════════════════════════════════════════════════ */
uint8_t ps2_kbd_read_scancode(void)
{
/* Spin until data is available (for early/polled use only) */
while (!(x86_64_inb(PS2_STATUS_PORT) & PS2_STATUS_OBF))
asm volatile("pause");
return x86_64_inb(PS2_DATA_PORT);
}
/* ══════════════════════════════════════════════════════════════════════════
* Initialisation
* ══════════════════════════════════════════════════════════════════════════ */
void ps2_kbd_init(void)
{
/*
* ── Step 1: Flush the PS/2 output buffer ─────────────────────────
*
* Any leftover bytes from before boot will cause the controller to
* withhold interrupts. Drain them now.
*/
int attempts = 16;
while (attempts-- && (x86_64_inb(PS2_STATUS_PORT) & PS2_STATUS_OBF))
(void)x86_64_inb(PS2_DATA_PORT);
x86_64_APIC_IRQ_RedirectAndRegister(1, 0x21, ps2_kbd_handler);
printf("[PS2] Keyboard driver initialised (IRQ%d vector 0x%02x)\n",
KBD_IRQ, KBD_IDT_VECTOR);
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
void ps2_kbd_init(void);
uint8_t ps2_kbd_read_scancode(void);
+15 -70
View File
@@ -27,8 +27,10 @@
#include "arch/x86_64/pci.h" #include "arch/x86_64/pci.h"
#include "sound/hda.h" #include "sound/hda.h"
#include "sound/pcm.h" #include "sound/pcm.h"
#include "arch/x86_64/ps2.h"
#include "input/input.h" #include "input/input.h"
#include "arch/x86_64/apic.h"
#include "arch/x86_64/ioapic.h"
#include "input/ps2.h"
uintptr_t g_hhdm_offset; uintptr_t g_hhdm_offset;
@@ -85,11 +87,6 @@ static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_
__attribute__((used, section(".limine_requests_end"))) __attribute__((used, section(".limine_requests_end")))
static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER; static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER;
// GCC and Clang reserve the right to generate calls to the following
// 4 functions even if they are not directly called.
// Implement them as the C specification mandates.
// DO NOT remove or rename these functions, or stuff will eventually break!
// They CAN be moved to a different .c file.
@@ -203,7 +200,6 @@ void kmain(void) {
x86_64_ISR_Initialize(); x86_64_ISR_Initialize();
x86_64_IRQ_Initialize(); x86_64_IRQ_Initialize();
x86_64_PIT_Initialize(1000); x86_64_PIT_Initialize(1000);
ps2_init();
asm volatile("sti"); asm volatile("sti");
calibrate_tsc(); calibrate_tsc();
@@ -429,6 +425,7 @@ void kmain(void) {
} }
/*pci_init(); /*pci_init();
pci_device_t hda; pci_device_t hda;
@@ -445,72 +442,20 @@ void kmain(void) {
}*/ }*/
//syscall_init();
//start_userspace();
lapic_init();
ioapic_init();
irq_redirect_to_apic(0, 0x20, lapic_id(), false);
printf("tst"); printf("tst");
printf("\n=== Input Subsystem Test ===\n"); input_init();
printf("Opening /dev/input/event0...\n"); ps2_kbd_init();
fd_t input_fd = VFS_Open("/dev/input/event0"); //syscall_init();
if (input_fd < 0) { //start_userspace();
printf("Failed to open /dev/input/event0!\n");
} else {
printf("Successfully opened /dev/input/event0 (fd=%d)\n", input_fd);
printf("Press keys or move mouse... (press ESC to exit test)\n\n");
input_event_t ev;
bool running = true;
while (running) {
// Non-blocking read from the input device
int bytes = VFS_Read(input_fd, (uint8_t*)&ev, sizeof(input_event_t));
if (bytes == sizeof(input_event_t)) {
switch (ev.type) {
case INPUT_EV_KEY:
printf("KEY: scancode=0x%02X ascii='%c' (%d) pressed=%s\n",
ev.key.scancode,
ev.key.ascii ? ev.key.ascii : '?',
ev.key.ascii,
ev.key.pressed ? "YES" : "NO");
// Exit test on ESC key release
if (ev.key.scancode == 0x01 && !ev.key.pressed) {
printf("ESC pressed - exiting input test.\n");
running = false;
}
break;
case INPUT_EV_MOUSE_REL:
if (ev.rel.dx || ev.rel.dy) {
printf("MOUSE MOVE: dx=%d dy=%d\n", ev.rel.dx, ev.rel.dy);
}
break;
case INPUT_EV_MOUSE_BTN:
printf("MOUSE BTN: button=%d pressed=%s\n",
ev.btn.button,
ev.btn.pressed ? "YES" : "NO");
break;
case INPUT_EV_MOUSE_WHEEL:
printf("MOUSE WHEEL: delta=%d\n", ev.wheel.delta);
break;
default:
printf("Unknown event type %d\n", ev.type);
break;
}
}
// Small yield so we don't spin the CPU too hard
// (you can replace with a proper sleep later)
asm volatile("pause");
}
VFS_Close(input_fd);
printf("Input test finished.\n");
}
// We're done, just hang... // We're done, just hang...
hcf(); hcf();
+1 -1
View File
@@ -57,7 +57,7 @@ void syscall_init(void)
extern void syscall_entry(void); extern void syscall_entry(void);
uint64_t kernel_cs = 0x08; uint64_t kernel_cs = 0x08;
uint64_t user_cs = 0x1B; uint64_t user_cs = 0x23;
uint64_t star = uint64_t star =
((uint64_t)kernel_cs << 32) | ((uint64_t)kernel_cs << 32) |
+4 -2
View File
@@ -3,8 +3,10 @@
syscall_entry: syscall_entry:
swapgs swapgs
push %rcx mov %rsp, %gs:0x10 # save user rsp for per cpu
push %r11
push %rcx # save user rip
push %r11 # save rflags
# Save original args before we clobber them # Save original args before we clobber them
push %rdi # orig rdi (arg1) push %rdi # orig rdi (arg1)
-1
View File
@@ -41,7 +41,6 @@ void calibrate_tsc(void)
// Compute elapsed time // Compute elapsed time
uint64_t tsc_delta = end_tsc - start_tsc; uint64_t tsc_delta = end_tsc - start_tsc;
// 100 ticks at 100 Hz = 1 second = 1,000,000 microseconds
uint64_t elapsed_us = 1000000ULL; uint64_t elapsed_us = 1000000ULL;
tsc_cycles_per_us = tsc_delta / elapsed_us; tsc_cycles_per_us = tsc_delta / elapsed_us;
+1 -1
View File
@@ -263,7 +263,7 @@ void draw_image(uint32_t *img, int x0, int y0)
uint32_t color = pixels[y * w + x]; uint32_t color = pixels[y * w + x];
// alpha handling (TGA is ARGB in your parser) // alpha handling
uint8_t a = color >> 24; uint8_t a = color >> 24;
// fully transparent → skip // fully transparent → skip
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+8 -8
View File
@@ -1,12 +1,12 @@
.global _start
.section .text .section .text
.global _start
_start: _start:
call main mov $1, %rax
mov $'H', %rdi
mov $60, %rax # exit syscall (you define this later)
xor %rdi, %rdi
syscall syscall
hang: mov $1, %rax
jmp hang mov $'i', %rdi
syscall
1: jmp 1b