From 641ec4681b0b16b559ffc899be9b793355282eb3 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 17 Feb 2026 12:44:31 +0100 Subject: [PATCH] feat: Local/IO APIC, PS/2 keyboard & mouse --- kernel/src/ACPI/ACPI.cpp | 3 +- kernel/src/ACPI/ACPI.hpp | 5 + kernel/src/ACPI/MADT.cpp | 135 ++++++++++++ kernel/src/ACPI/MADT.hpp | 82 +++++++ kernel/src/Drivers/PS2/Keyboard.cpp | 267 +++++++++++++++++++++++ kernel/src/Drivers/PS2/Keyboard.hpp | 52 +++++ kernel/src/Drivers/PS2/Mouse.cpp | 199 +++++++++++++++++ kernel/src/Drivers/PS2/Mouse.hpp | 37 ++++ kernel/src/Drivers/PS2/PS2Controller.cpp | 167 ++++++++++++++ kernel/src/Drivers/PS2/PS2Controller.hpp | 57 +++++ kernel/src/Hal/Apic/Apic.cpp | 75 +++++++ kernel/src/Hal/Apic/Apic.hpp | 41 ++++ kernel/src/Hal/Apic/ApicInit.cpp | 72 ++++++ kernel/src/Hal/Apic/ApicInit.hpp | 22 ++ kernel/src/Hal/Apic/Interrupts.cpp | 51 +++++ kernel/src/Hal/Apic/Interrupts.hpp | 37 ++++ kernel/src/Hal/Apic/IoApic.cpp | 137 ++++++++++++ kernel/src/Hal/Apic/IoApic.hpp | 44 ++++ kernel/src/Hal/Apic/IsrStubs.asm | 127 +++++++++++ kernel/src/Hal/Apic/Pic.cpp | 59 +++++ kernel/src/Hal/Apic/Pic.hpp | 11 + kernel/src/Hal/IDT.cpp | 6 +- kernel/src/Hal/IDT.hpp | 3 + kernel/src/Main.cpp | 16 ++ kernel/src/Memory/Paging.cpp | 25 ++- kernel/src/Memory/Paging.hpp | 3 + 26 files changed, 1730 insertions(+), 3 deletions(-) create mode 100644 kernel/src/ACPI/MADT.cpp create mode 100644 kernel/src/ACPI/MADT.hpp create mode 100644 kernel/src/Drivers/PS2/Keyboard.cpp create mode 100644 kernel/src/Drivers/PS2/Keyboard.hpp create mode 100644 kernel/src/Drivers/PS2/Mouse.cpp create mode 100644 kernel/src/Drivers/PS2/Mouse.hpp create mode 100644 kernel/src/Drivers/PS2/PS2Controller.cpp create mode 100644 kernel/src/Drivers/PS2/PS2Controller.hpp create mode 100644 kernel/src/Hal/Apic/Apic.cpp create mode 100644 kernel/src/Hal/Apic/Apic.hpp create mode 100644 kernel/src/Hal/Apic/ApicInit.cpp create mode 100644 kernel/src/Hal/Apic/ApicInit.hpp create mode 100644 kernel/src/Hal/Apic/Interrupts.cpp create mode 100644 kernel/src/Hal/Apic/Interrupts.hpp create mode 100644 kernel/src/Hal/Apic/IoApic.cpp create mode 100644 kernel/src/Hal/Apic/IoApic.hpp create mode 100644 kernel/src/Hal/Apic/IsrStubs.asm create mode 100644 kernel/src/Hal/Apic/Pic.cpp create mode 100644 kernel/src/Hal/Apic/Pic.hpp diff --git a/kernel/src/ACPI/ACPI.cpp b/kernel/src/ACPI/ACPI.cpp index 918f8d6..3316c02 100644 --- a/kernel/src/ACPI/ACPI.cpp +++ b/kernel/src/ACPI/ACPI.cpp @@ -71,7 +71,8 @@ namespace Hal { if (xsdp->Revision >= 2) { nextTableAddress = xsdp->XSDTAddress; - HandleXSDT((CommonSDTHeader*)Memory::HHDM(nextTableAddress)); + m_xsdt = (CommonSDTHeader*)Memory::HHDM(nextTableAddress); + HandleXSDT(m_xsdt); } else { diff --git a/kernel/src/ACPI/ACPI.hpp b/kernel/src/ACPI/ACPI.hpp index ad61e1c..120b2b7 100644 --- a/kernel/src/ACPI/ACPI.hpp +++ b/kernel/src/ACPI/ACPI.hpp @@ -42,5 +42,10 @@ namespace Hal { static bool TestChecksum(CommonSDTHeader* header); CommonSDTHeader* FindNextTable(CommonSDTHeader* table); + + CommonSDTHeader* GetXSDT() { return m_xsdt; } + + private: + CommonSDTHeader* m_xsdt = nullptr; }; }; \ No newline at end of file diff --git a/kernel/src/ACPI/MADT.cpp b/kernel/src/ACPI/MADT.cpp new file mode 100644 index 0000000..4dc1f12 --- /dev/null +++ b/kernel/src/ACPI/MADT.cpp @@ -0,0 +1,135 @@ +/* + * MADT.cpp + * Multiple APIC Description Table parsing + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "MADT.hpp" +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace MADT { + + static bool SignatureMatch(const char* sig, const char* target, int len) { + for (int i = 0; i < len; i++) { + if (sig[i] != target[i]) return false; + } + return true; + } + + static ACPI::CommonSDTHeader* FindMADTInXSDT(ACPI::CommonSDTHeader* xsdt) { + uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8; + uint64_t* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader)); + + for (uint32_t i = 0; i < entryCount; i++) { + auto* header = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]); + if (SignatureMatch(header->Signature, "APIC", 4)) { + return header; + } + } + + return nullptr; + } + + bool Parse(ACPI::CommonSDTHeader* xsdt, ParsedMADT& result) { + auto* madtHeader = FindMADTInXSDT(xsdt); + if (madtHeader == nullptr) { + KernelLogStream(ERROR, "MADT") << "MADT table not found in XSDT"; + return false; + } + + if (!ACPI::TestChecksum(madtHeader)) { + KernelLogStream(ERROR, "MADT") << "MADT checksum failed"; + return false; + } + + KernelLogStream(OK, "MADT") << "Found MADT table"; + + auto* madt = (Header*)madtHeader; + result.LocalApicAddress = madt->LocalApicAddress; + result.OverrideCount = 0; + result.LocalApicCount = 0; + result.IoApicAddress = 0; + result.IoApicId = 0; + result.IoApicGsiBase = 0; + + KernelLogStream(INFO, "MADT") << "Local APIC address: " << base::hex << (uint64_t)result.LocalApicAddress; + + uint32_t offset = sizeof(Header); + uint32_t length = madt->SDTHeader.Length; + + while (offset < length) { + auto* entry = (EntryHeader*)((uint64_t)madt + offset); + + switch (entry->Type) { + case 0: { // Processor Local APIC + auto* lapic = (LocalApicEntry*)entry; + if (result.LocalApicCount < ParsedMADT::MaxLocalApics) { + result.LocalApics[result.LocalApicCount] = *lapic; + result.LocalApicCount++; + } + KernelLogStream(DEBUG, "MADT") << "Local APIC: processor=" << (uint64_t)lapic->ProcessorId + << " id=" << (uint64_t)lapic->ApicId + << " flags=" << base::hex << (uint64_t)lapic->Flags; + break; + } + + case 1: { // I/O APIC + auto* ioapic = (IoApicEntry*)entry; + result.IoApicAddress = ioapic->IoApicAddress; + result.IoApicId = ioapic->IoApicId; + result.IoApicGsiBase = ioapic->GlobalSystemInterruptBase; + KernelLogStream(DEBUG, "MADT") << "IOAPIC: id=" << (uint64_t)ioapic->IoApicId + << " address=" << base::hex << (uint64_t)ioapic->IoApicAddress + << " GSI base=" << base::dec << (uint64_t)ioapic->GlobalSystemInterruptBase; + break; + } + + case 2: { // Interrupt Source Override + auto* iso = (InterruptSourceOverride*)entry; + if (result.OverrideCount < ParsedMADT::MaxOverrides) { + result.Overrides[result.OverrideCount] = *iso; + result.OverrideCount++; + } + KernelLogStream(DEBUG, "MADT") << "IRQ Override: bus=" << (uint64_t)iso->BusSource + << " irq=" << (uint64_t)iso->IrqSource + << " -> GSI " << (uint64_t)iso->GlobalSystemInterrupt + << " flags=" << base::hex << (uint64_t)iso->Flags; + break; + } + + case 4: { // NMI + auto* nmi = (NmiEntry*)entry; + KernelLogStream(DEBUG, "MADT") << "NMI: processor=" << (uint64_t)nmi->ProcessorId + << " lint=" << (uint64_t)nmi->Lint; + break; + } + + case 5: { // Local APIC Address Override + auto* override = (LocalApicAddressOverride*)entry; + result.LocalApicAddress = override->LocalApicAddress; + KernelLogStream(DEBUG, "MADT") << "Local APIC address override: " << base::hex << result.LocalApicAddress; + break; + } + + default: + KernelLogStream(DEBUG, "MADT") << "Unknown MADT entry type: " << (uint64_t)entry->Type; + break; + } + + offset += entry->Length; + } + + KernelLogStream(OK, "MADT") << "Parsed " << base::dec + << (uint64_t)result.LocalApicCount << " local APICs, " + << (uint64_t)result.OverrideCount << " overrides"; + + return true; + } + }; +}; diff --git a/kernel/src/ACPI/MADT.hpp b/kernel/src/ACPI/MADT.hpp new file mode 100644 index 0000000..a64de37 --- /dev/null +++ b/kernel/src/ACPI/MADT.hpp @@ -0,0 +1,82 @@ +/* + * MADT.hpp + * Multiple APIC Description Table parsing + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace MADT { + struct Header { + Hal::ACPI::CommonSDTHeader SDTHeader; + uint32_t LocalApicAddress; + uint32_t Flags; + }__attribute__((packed)); + + struct EntryHeader { + uint8_t Type; + uint8_t Length; + }__attribute__((packed)); + + // Type 0: Processor Local APIC + struct LocalApicEntry { + EntryHeader Header; + uint8_t ProcessorId; + uint8_t ApicId; + uint32_t Flags; + }__attribute__((packed)); + + // Type 1: I/O APIC + struct IoApicEntry { + EntryHeader Header; + uint8_t IoApicId; + uint8_t Reserved; + uint32_t IoApicAddress; + uint32_t GlobalSystemInterruptBase; + }__attribute__((packed)); + + // Type 2: Interrupt Source Override + struct InterruptSourceOverride { + EntryHeader Header; + uint8_t BusSource; + uint8_t IrqSource; + uint32_t GlobalSystemInterrupt; + uint16_t Flags; + }__attribute__((packed)); + + // Type 4: Non-Maskable Interrupt + struct NmiEntry { + EntryHeader Header; + uint8_t ProcessorId; + uint16_t Flags; + uint8_t Lint; + }__attribute__((packed)); + + // Type 5: Local APIC Address Override + struct LocalApicAddressOverride { + EntryHeader Header; + uint16_t Reserved; + uint64_t LocalApicAddress; + }__attribute__((packed)); + + struct ParsedMADT { + uint64_t LocalApicAddress; + uint64_t IoApicAddress; + uint8_t IoApicId; + uint32_t IoApicGsiBase; + + static constexpr int MaxOverrides = 16; + InterruptSourceOverride Overrides[MaxOverrides]; + int OverrideCount; + + static constexpr int MaxLocalApics = 16; + LocalApicEntry LocalApics[MaxLocalApics]; + int LocalApicCount; + }; + + bool Parse(ACPI::CommonSDTHeader* xsdt, ParsedMADT& result); + }; +}; diff --git a/kernel/src/Drivers/PS2/Keyboard.cpp b/kernel/src/Drivers/PS2/Keyboard.cpp new file mode 100644 index 0000000..e597d3a --- /dev/null +++ b/kernel/src/Drivers/PS2/Keyboard.cpp @@ -0,0 +1,267 @@ +/* + * Keyboard.cpp + * PS/2 Keyboard driver -- Scancode Set 1 + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Keyboard.hpp" +#include "PS2Controller.hpp" + +#include +#include +#include +#include +#include +#include + +namespace Drivers::PS2::Keyboard { + + // Scancode Set 1 to ASCII lookup table (unshifted) + static const char g_ScancodeToAscii[128] = { + 0, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 - 0x07 + '7', '8', '9', '0', '-', '=', '\b', '\t', // 0x08 - 0x0F + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 - 0x17 + 'o', 'p', '[', ']', '\n', 0, 'a', 's', // 0x18 - 0x1F + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 - 0x27 + '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', // 0x28 - 0x2F + 'b', 'n', 'm', ',', '.', '/', 0, '*', // 0x30 - 0x37 + 0, ' ', 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F + 0, 0, 0, 0, 0, 0, 0, '7', // 0x40 - 0x47 + '8', '9', '-', '4', '5', '6', '+', '1', // 0x48 - 0x4F + '2', '3', '0', '.', 0, 0, 0, 0, // 0x50 - 0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77 + 0, 0, 0, 0, 0, 0, 0, 0 // 0x78 - 0x7F + }; + + // Scancode Set 1 to ASCII lookup table (shifted) + static const char g_ScancodeToAsciiShifted[128] = { + 0, 0x1B, '!', '@', '#', '$', '%', '^', // 0x00 - 0x07 + '&', '*', '(', ')', '_', '+', '\b', '\t', // 0x08 - 0x0F + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 - 0x17 + 'O', 'P', '{', '}', '\n', 0, 'A', 'S', // 0x18 - 0x1F + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 - 0x27 + '"', '~', 0, '|', 'Z', 'X', 'C', 'V', // 0x28 - 0x2F + 'B', 'N', 'M', '<', '>', '?', 0, '*', // 0x30 - 0x37 + 0, ' ', 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F + 0, 0, 0, 0, 0, 0, 0, '7', // 0x40 - 0x47 + '8', '9', '-', '4', '5', '6', '+', '1', // 0x48 - 0x4F + '2', '3', '0', '.', 0, 0, 0, 0, // 0x50 - 0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77 + 0, 0, 0, 0, 0, 0, 0, 0 // 0x78 - 0x7F + }; + + // Scancode constants for modifier keys (Set 1) + constexpr uint8_t ScLeftShift = 0x2A; + constexpr uint8_t ScRightShift = 0x36; + constexpr uint8_t ScLeftCtrl = 0x1D; + constexpr uint8_t ScLeftAlt = 0x38; + constexpr uint8_t ScCapsLock = 0x3A; + constexpr uint8_t ScNumLock = 0x45; + constexpr uint8_t ScScrollLock = 0x46; + + // Break code flag (bit 7 set means key release) + constexpr uint8_t BreakCodeBit = 0x80; + + // Ring buffer for key events + static KeyEvent g_KeyBuffer[KeyBufferSize]; + static volatile uint32_t g_BufferHead = 0; + static volatile uint32_t g_BufferTail = 0; + static kcp::Spinlock g_BufferLock; + + // Current modifier state + static ModifierState g_Modifiers = {}; + + // Track extended scancode prefix (0xE0) + static bool g_ExtendedScancode = false; + + static bool IsShiftActive() { + bool shift = g_Modifiers.LeftShift || g_Modifiers.RightShift; + bool caps = g_Modifiers.CapsLock; + return shift ^ caps; + } + + static void BufferPush(const KeyEvent& event) { + uint32_t nextHead = (g_BufferHead + 1) & (KeyBufferSize - 1); + if (nextHead == g_BufferTail) { + // Buffer full, drop the event + return; + } + g_KeyBuffer[g_BufferHead] = event; + g_BufferHead = nextHead; + } + + static bool BufferPop(KeyEvent& event) { + if (g_BufferTail == g_BufferHead) { + return false; + } + event = g_KeyBuffer[g_BufferTail]; + g_BufferTail = (g_BufferTail + 1) & (KeyBufferSize - 1); + return true; + } + + void Initialize() { + Kt::KernelLogStream(Kt::INFO, "PS2/KB") << "Initializing keyboard driver"; + + // Reset keyboard to default state + // Send 0xFF (Reset) to the keyboard + Drivers::PS2::SendData(0xFF); + // Expect 0xFA (ACK) then 0xAA (self-test passed) + uint8_t ack = Drivers::PS2::ReadData(); + if (ack != 0xFA) { + Kt::KernelLogStream(Kt::WARNING, "PS2/KB") << "Keyboard reset: unexpected ACK: " << base::hex << (uint64_t)ack; + } + + uint8_t selfTest = Drivers::PS2::ReadData(); + if (selfTest != 0xAA) { + Kt::KernelLogStream(Kt::WARNING, "PS2/KB") << "Keyboard self-test: unexpected result: " << base::hex << (uint64_t)selfTest; + } + + // Enable scanning (in case it was disabled) + Drivers::PS2::SendData(0xF4); + ack = Drivers::PS2::ReadData(); + + g_Modifiers = {}; + g_BufferHead = 0; + g_BufferTail = 0; + g_ExtendedScancode = false; + + // Register IRQ handler and unmask IRQ1 on the IOAPIC + Hal::RegisterIrqHandler(Hal::IRQ_KEYBOARD, HandleIRQ); + Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(Hal::IRQ_KEYBOARD)); + + Kt::KernelLogStream(Kt::OK, "PS2/KB") << "Keyboard driver initialized"; + } + + void HandleIRQ(uint8_t irq) { + (void)irq; + uint8_t scancode = Io::In8(DataPort); + + // Handle extended scancode prefix + if (scancode == 0xE0) { + g_ExtendedScancode = true; + return; + } + + bool released = (scancode & BreakCodeBit) != 0; + uint8_t keycode = scancode & ~BreakCodeBit; + + if (g_ExtendedScancode) { + g_ExtendedScancode = false; + // Extended scancodes: Right Ctrl (0x1D), Right Alt (0x38), etc. + if (keycode == ScLeftCtrl) { + g_Modifiers.RightCtrl = !released; + } else if (keycode == ScLeftAlt) { + g_Modifiers.RightAlt = !released; + } + // Extended keys don't produce ASCII characters for now + KeyEvent event = { + .Scancode = scancode, + .Ascii = 0, + .Pressed = !released, + .Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift, + .Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl, + .Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt, + .CapsLock = g_Modifiers.CapsLock + }; + g_BufferLock.Acquire(); + BufferPush(event); + g_BufferLock.Release(); + + return; + } + + // Handle modifier keys + switch (keycode) { + case ScLeftShift: + g_Modifiers.LeftShift = !released; + return; + case ScRightShift: + g_Modifiers.RightShift = !released; + return; + case ScLeftCtrl: + g_Modifiers.LeftCtrl = !released; + return; + case ScLeftAlt: + g_Modifiers.LeftAlt = !released; + return; + case ScCapsLock: + if (!released) { + g_Modifiers.CapsLock = !g_Modifiers.CapsLock; + } + return; + case ScNumLock: + if (!released) { + g_Modifiers.NumLock = !g_Modifiers.NumLock; + } + return; + case ScScrollLock: + if (!released) { + g_Modifiers.ScrollLock = !g_Modifiers.ScrollLock; + } + return; + default: + break; + } + + // Translate scancode to ASCII + char ascii = 0; + if (keycode < 128) { + if (IsShiftActive()) { + ascii = g_ScancodeToAsciiShifted[keycode]; + } else { + ascii = g_ScancodeToAscii[keycode]; + } + } + + KeyEvent event = { + .Scancode = scancode, + .Ascii = ascii, + .Pressed = !released, + .Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift, + .Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl, + .Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt, + .CapsLock = g_Modifiers.CapsLock + }; + + g_BufferLock.Acquire(); + BufferPush(event); + g_BufferLock.Release(); + } + + bool IsKeyAvailable() { + return g_BufferHead != g_BufferTail; + } + + KeyEvent GetKey() { + KeyEvent event = {}; + g_BufferLock.Acquire(); + BufferPop(event); + g_BufferLock.Release(); + return event; + } + + char GetChar() { + // Blocks until a printable key press is available + while (true) { + if (IsKeyAvailable()) { + KeyEvent event = GetKey(); + if (event.Pressed && event.Ascii != 0) { + return event.Ascii; + } + } + // Yield the CPU while waiting + asm volatile("hlt"); + } + } + + const ModifierState& GetModifiers() { + return g_Modifiers; + } + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Keyboard.hpp b/kernel/src/Drivers/PS2/Keyboard.hpp new file mode 100644 index 0000000..b9c34e9 --- /dev/null +++ b/kernel/src/Drivers/PS2/Keyboard.hpp @@ -0,0 +1,52 @@ +/* + * Keyboard.hpp + * PS/2 Keyboard driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::PS2::Keyboard { + + // Key event structure + struct KeyEvent { + uint8_t Scancode; + char Ascii; + bool Pressed; + bool Shift; + bool Ctrl; + bool Alt; + bool CapsLock; + }; + + // Modifier key state + struct ModifierState { + bool LeftShift; + bool RightShift; + bool LeftCtrl; + bool RightCtrl; + bool LeftAlt; + bool RightAlt; + bool CapsLock; + bool NumLock; + bool ScrollLock; + }; + + // Ring buffer size (must be power of 2) + constexpr uint32_t KeyBufferSize = 256; + + void Initialize(); + + // Interrupt handler -- called from IRQ dispatch (EOI is sent automatically) + void HandleIRQ(uint8_t irq); + + // Public interface for consuming key events + bool IsKeyAvailable(); + KeyEvent GetKey(); + char GetChar(); + + // Modifier state query + const ModifierState& GetModifiers(); + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Mouse.cpp b/kernel/src/Drivers/PS2/Mouse.cpp new file mode 100644 index 0000000..7f3acbb --- /dev/null +++ b/kernel/src/Drivers/PS2/Mouse.cpp @@ -0,0 +1,199 @@ +/* + * Mouse.cpp + * PS/2 Mouse driver with optional scroll wheel support + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Mouse.hpp" +#include "PS2Controller.hpp" + +#include +#include +#include +#include +#include +#include + +namespace Drivers::PS2::Mouse { + + // Mouse protocol commands + constexpr uint8_t CmdSetDefaults = 0xF6; + constexpr uint8_t CmdEnableReporting = 0xF4; + constexpr uint8_t CmdDisableReporting = 0xF5; + constexpr uint8_t CmdSetSampleRate = 0xF3; + constexpr uint8_t CmdGetDeviceId = 0xF2; + constexpr uint8_t CmdAck = 0xFA; + + // Mouse packet byte 0 bit fields + constexpr uint8_t PacketYOverflow = (1 << 7); + constexpr uint8_t PacketXOverflow = (1 << 6); + constexpr uint8_t PacketYSign = (1 << 5); + constexpr uint8_t PacketXSign = (1 << 4); + constexpr uint8_t PacketAlwaysOne = (1 << 3); + + // Mouse state + static MouseState g_State = {}; + static kcp::Spinlock g_StateLock; + + // Screen bounds + static int32_t g_MaxX = 1024; + static int32_t g_MaxY = 768; + + // Packet assembly state + static uint8_t g_PacketBuffer[4] = {}; + static uint8_t g_PacketIndex = 0; + static bool g_HasScrollWheel = false; + static uint8_t g_PacketSize = 3; + + static uint8_t SendMouseCommand(uint8_t command) { + Drivers::PS2::SendToPort2(command); + return Drivers::PS2::ReadData(); + } + + static void SetSampleRate(uint8_t rate) { + SendMouseCommand(CmdSetSampleRate); + SendMouseCommand(rate); + } + + static bool DetectScrollWheel() { + // Magic sequence to enable scroll wheel: set sample rate 200, 100, 80 + // then query device ID. If it returns 3, scroll wheel is present. + SetSampleRate(200); + SetSampleRate(100); + SetSampleRate(80); + + SendMouseCommand(CmdGetDeviceId); + uint8_t deviceId = Drivers::PS2::ReadData(); + + return (deviceId == 3); + } + + void Initialize() { + Kt::KernelLogStream(Kt::INFO, "PS2/Mouse") << "Initializing mouse driver"; + + if (!Drivers::PS2::IsDualChannel()) { + Kt::KernelLogStream(Kt::WARNING, "PS2/Mouse") << "PS/2 controller is not dual-channel, mouse unavailable"; + return; + } + + // Set defaults + uint8_t ack = SendMouseCommand(CmdSetDefaults); + if (ack != CmdAck) { + Kt::KernelLogStream(Kt::WARNING, "PS2/Mouse") << "Set defaults: unexpected response: " << base::hex << (uint64_t)ack; + } + + // Try to enable scroll wheel + g_HasScrollWheel = DetectScrollWheel(); + if (g_HasScrollWheel) { + g_PacketSize = 4; + Kt::KernelLogStream(Kt::OK, "PS2/Mouse") << "Scroll wheel detected"; + } else { + g_PacketSize = 3; + Kt::KernelLogStream(Kt::INFO, "PS2/Mouse") << "Standard 3-byte mouse protocol"; + } + + // Enable data reporting + ack = SendMouseCommand(CmdEnableReporting); + if (ack != CmdAck) { + Kt::KernelLogStream(Kt::WARNING, "PS2/Mouse") << "Enable reporting: unexpected response: " << base::hex << (uint64_t)ack; + } + + g_State = {}; + g_PacketIndex = 0; + + // Register IRQ handler and unmask IRQ12 on the IOAPIC + Hal::RegisterIrqHandler(Hal::IRQ_MOUSE, HandleIRQ); + Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(Hal::IRQ_MOUSE)); + + Kt::KernelLogStream(Kt::OK, "PS2/Mouse") << "Mouse driver initialized"; + } + + void HandleIRQ(uint8_t irq) { + (void)irq; + uint8_t data = Io::In8(DataPort); + + // Synchronization: byte 0 must always have bit 3 set + if (g_PacketIndex == 0 && !(data & PacketAlwaysOne)) { + // Out of sync, discard and wait for a valid start byte + return; + } + + g_PacketBuffer[g_PacketIndex] = data; + g_PacketIndex++; + + if (g_PacketIndex < g_PacketSize) { + return; + } + + // Full packet received, process it + g_PacketIndex = 0; + + uint8_t flags = g_PacketBuffer[0]; + uint8_t buttons = flags & 0x07; + + // Check for overflow -- discard the packet if overflow is set + if (flags & (PacketXOverflow | PacketYOverflow)) { + return; + } + + // Reconstruct signed X and Y deltas with sign extension + int32_t deltaX = (int32_t)g_PacketBuffer[1]; + int32_t deltaY = (int32_t)g_PacketBuffer[2]; + + if (flags & PacketXSign) { + deltaX |= (int32_t)0xFFFFFF00; + } + if (flags & PacketYSign) { + deltaY |= (int32_t)0xFFFFFF00; + } + + // PS/2 mouse Y axis is inverted (positive = up) + deltaY = -deltaY; + + // Scroll wheel delta (4th byte, signed) + int32_t scrollDelta = 0; + if (g_HasScrollWheel && g_PacketSize == 4) { + scrollDelta = (int8_t)g_PacketBuffer[3]; + } + + g_StateLock.Acquire(); + + g_State.X += deltaX; + g_State.Y += deltaY; + g_State.Buttons = buttons; + g_State.ScrollDelta = scrollDelta; + + // Clamp to screen bounds + if (g_State.X < 0) g_State.X = 0; + if (g_State.Y < 0) g_State.Y = 0; + if (g_State.X > g_MaxX) g_State.X = g_MaxX; + if (g_State.Y > g_MaxY) g_State.Y = g_MaxY; + + g_StateLock.Release(); + } + + MouseState GetMouseState() { + g_StateLock.Acquire(); + MouseState state = g_State; + g_StateLock.Release(); + return state; + } + + int32_t GetX() { + return g_State.X; + } + + int32_t GetY() { + return g_State.Y; + } + + uint8_t GetButtons() { + return g_State.Buttons; + } + + void SetBounds(int32_t maxX, int32_t maxY) { + g_MaxX = maxX; + g_MaxY = maxY; + } + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Mouse.hpp b/kernel/src/Drivers/PS2/Mouse.hpp new file mode 100644 index 0000000..964d339 --- /dev/null +++ b/kernel/src/Drivers/PS2/Mouse.hpp @@ -0,0 +1,37 @@ +/* + * Mouse.hpp + * PS/2 Mouse driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::PS2::Mouse { + + // Mouse button flags + constexpr uint8_t ButtonLeft = (1 << 0); + constexpr uint8_t ButtonRight = (1 << 1); + constexpr uint8_t ButtonMiddle = (1 << 2); + + struct MouseState { + int32_t X; + int32_t Y; + int32_t ScrollDelta; + uint8_t Buttons; + }; + + void Initialize(); + + // Interrupt handler -- called from IRQ dispatch (EOI is sent automatically) + void HandleIRQ(uint8_t irq); + + // Public interface + MouseState GetMouseState(); + int32_t GetX(); + int32_t GetY(); + uint8_t GetButtons(); + + void SetBounds(int32_t maxX, int32_t maxY); + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/PS2Controller.cpp b/kernel/src/Drivers/PS2/PS2Controller.cpp new file mode 100644 index 0000000..4ab014a --- /dev/null +++ b/kernel/src/Drivers/PS2/PS2Controller.cpp @@ -0,0 +1,167 @@ +/* + * PS2Controller.cpp + * PS/2 Controller (8042) initialization and utility functions + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "PS2Controller.hpp" + +#include +#include +#include + +namespace Drivers::PS2 { + + static bool g_DualChannel = false; + + void WaitForInput() { + // Wait until the input buffer is empty (bit 1 clear) + int timeout = 100000; + while ((Io::In8(StatusPort) & StatusInputFull) && --timeout) { + Io::IoPortWait(); + } + } + + void WaitForOutput() { + // Wait until the output buffer is full (bit 0 set) + int timeout = 100000; + while (!(Io::In8(StatusPort) & StatusOutputFull) && --timeout) { + Io::IoPortWait(); + } + } + + void SendCommand(uint8_t command) { + WaitForInput(); + Io::Out8(command, CommandPort); + } + + void SendData(uint8_t data) { + WaitForInput(); + Io::Out8(data, DataPort); + } + + uint8_t ReadData() { + WaitForOutput(); + return Io::In8(DataPort); + } + + void FlushOutputBuffer() { + // Read and discard any pending data in the output buffer + int maxReads = 32; + while ((Io::In8(StatusPort) & StatusOutputFull) && --maxReads) { + Io::In8(DataPort); + Io::IoPortWait(); + } + } + + void SendToPort2(uint8_t data) { + SendCommand(CmdWritePort2Input); + SendData(data); + } + + bool IsDualChannel() { + return g_DualChannel; + } + + void Initialize() { + Kt::KernelLogStream(Kt::INFO, "PS2") << "Initializing PS/2 controller"; + + // Step 1: Disable both PS/2 ports + SendCommand(CmdDisablePort1); + SendCommand(CmdDisablePort2); + + // Step 2: Flush the output buffer + FlushOutputBuffer(); + + // Step 3: Read the controller configuration byte + SendCommand(CmdReadConfig); + uint8_t config = ReadData(); + + Kt::KernelLogStream(Kt::DEBUG, "PS2") << "Controller config byte: " << base::hex << (uint64_t)config; + + // Disable interrupts and translation in the config byte for now + config &= ~(ConfigPort1Interrupt | ConfigPort2Interrupt | ConfigPort1Translation); + + // Check if this is a dual-channel controller + // If bit 5 (port 2 clock) was set, it might be dual-channel + g_DualChannel = (config & ConfigPort2Clock) != 0; + + // Step 4: Write the modified configuration byte + SendCommand(CmdWriteConfig); + SendData(config); + + // Step 5: Controller self-test + SendCommand(CmdSelfTest); + uint8_t selfTestResult = ReadData(); + + if (selfTestResult != SelfTestPass) { + Kt::KernelLogStream(Kt::ERROR, "PS2") << "Controller self-test failed: " << base::hex << (uint64_t)selfTestResult; + return; + } + + Kt::KernelLogStream(Kt::OK, "PS2") << "Controller self-test passed"; + + // Self-test may reset the controller, so restore config + SendCommand(CmdWriteConfig); + SendData(config); + + // Step 6: Test port 2 to confirm dual-channel + if (g_DualChannel) { + SendCommand(CmdEnablePort2); + + SendCommand(CmdReadConfig); + uint8_t config2 = ReadData(); + + if (config2 & ConfigPort2Clock) { + // Port 2 clock is still disabled after enabling -- not dual-channel + g_DualChannel = false; + } else { + // It is dual-channel; disable port 2 again for testing + SendCommand(CmdDisablePort2); + } + } + + // Step 7: Interface tests + SendCommand(CmdTestPort1); + uint8_t port1Test = ReadData(); + + if (port1Test != PortTestPass) { + Kt::KernelLogStream(Kt::ERROR, "PS2") << "Port 1 test failed: " << base::hex << (uint64_t)port1Test; + } else { + Kt::KernelLogStream(Kt::OK, "PS2") << "Port 1 (keyboard) test passed"; + } + + if (g_DualChannel) { + SendCommand(CmdTestPort2); + uint8_t port2Test = ReadData(); + + if (port2Test != PortTestPass) { + Kt::KernelLogStream(Kt::ERROR, "PS2") << "Port 2 test failed: " << base::hex << (uint64_t)port2Test; + g_DualChannel = false; + } else { + Kt::KernelLogStream(Kt::OK, "PS2") << "Port 2 (mouse) test passed"; + } + } + + // Step 8: Enable ports + SendCommand(CmdEnablePort1); + if (g_DualChannel) { + SendCommand(CmdEnablePort2); + } + + // Step 9: Enable interrupts in the configuration byte + SendCommand(CmdReadConfig); + config = ReadData(); + + config |= ConfigPort1Interrupt; + if (g_DualChannel) { + config |= ConfigPort2Interrupt; + } + + SendCommand(CmdWriteConfig); + SendData(config); + + Kt::KernelLogStream(Kt::OK, "PS2") << "Controller initialized (dual-channel: " << (g_DualChannel ? "yes" : "no") << ")"; + } + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/PS2Controller.hpp b/kernel/src/Drivers/PS2/PS2Controller.hpp new file mode 100644 index 0000000..3cf48fe --- /dev/null +++ b/kernel/src/Drivers/PS2/PS2Controller.hpp @@ -0,0 +1,57 @@ +/* + * PS2Controller.hpp + * PS/2 Controller (8042) initialization and utility functions + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::PS2 { + + // PS/2 controller I/O ports + constexpr uint16_t DataPort = 0x60; + constexpr uint16_t StatusPort = 0x64; + constexpr uint16_t CommandPort = 0x64; + + // PS/2 controller commands + constexpr uint8_t CmdReadConfig = 0x20; + constexpr uint8_t CmdWriteConfig = 0x60; + constexpr uint8_t CmdDisablePort2 = 0xA7; + constexpr uint8_t CmdEnablePort2 = 0xA8; + constexpr uint8_t CmdTestPort2 = 0xA9; + constexpr uint8_t CmdSelfTest = 0xAA; + constexpr uint8_t CmdTestPort1 = 0xAB; + constexpr uint8_t CmdDisablePort1 = 0xAD; + constexpr uint8_t CmdEnablePort1 = 0xAE; + constexpr uint8_t CmdWritePort2Input = 0xD4; + + // PS/2 controller status register bits + constexpr uint8_t StatusOutputFull = 0x01; + constexpr uint8_t StatusInputFull = 0x02; + + // PS/2 controller self-test result + constexpr uint8_t SelfTestPass = 0x55; + constexpr uint8_t PortTestPass = 0x00; + + // Configuration byte bits + constexpr uint8_t ConfigPort1Interrupt = (1 << 0); + constexpr uint8_t ConfigPort2Interrupt = (1 << 1); + constexpr uint8_t ConfigPort1Clock = (1 << 4); + constexpr uint8_t ConfigPort2Clock = (1 << 5); + constexpr uint8_t ConfigPort1Translation = (1 << 6); + + void Initialize(); + + void SendCommand(uint8_t command); + void SendData(uint8_t data); + uint8_t ReadData(); + void WaitForInput(); + void WaitForOutput(); + void FlushOutputBuffer(); + + void SendToPort2(uint8_t data); + + bool IsDualChannel(); + +}; \ No newline at end of file diff --git a/kernel/src/Hal/Apic/Apic.cpp b/kernel/src/Hal/Apic/Apic.cpp new file mode 100644 index 0000000..8b44033 --- /dev/null +++ b/kernel/src/Hal/Apic/Apic.cpp @@ -0,0 +1,75 @@ +/* + * Apic.cpp + * Local APIC (Advanced Programmable Interrupt Controller) + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Apic.hpp" +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace LocalApic { + static volatile uint32_t* g_apicBase = nullptr; + + static inline uint64_t ReadMSR(uint32_t msr) { + uint32_t lo, hi; + asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr)); + return ((uint64_t)hi << 32) | lo; + } + + static inline void WriteMSR(uint32_t msr, uint64_t value) { + uint32_t lo = (uint32_t)(value & 0xFFFFFFFF); + uint32_t hi = (uint32_t)(value >> 32); + asm volatile("wrmsr" : : "a"(lo), "d"(hi), "c"(msr)); + } + + uint32_t ReadRegister(uint32_t reg) { + return g_apicBase[reg / 4]; + } + + void WriteRegister(uint32_t reg, uint32_t value) { + g_apicBase[reg / 4] = value; + } + + void Initialize(uint64_t apicBasePhys) { + // Read the APIC base MSR to verify/confirm the base address + uint64_t msrValue = ReadMSR(MSR_APIC_BASE); + uint64_t msrBase = msrValue & 0xFFFFF000; + + KernelLogStream(DEBUG, "APIC") << "MSR APIC base: " << base::hex << msrBase; + KernelLogStream(DEBUG, "APIC") << "MADT APIC base: " << base::hex << apicBasePhys; + + // Use the MADT-provided address (it should match MSR) + g_apicBase = (volatile uint32_t*)Memory::HHDM(apicBasePhys); + + // Enable the APIC by setting bit 8 (APIC Software Enable) in SVR + // and set the spurious interrupt vector + uint32_t svr = ReadRegister(REG_SPURIOUS); + svr |= (1 << 8); // Enable APIC + svr = (svr & 0xFFFFFF00) | SPURIOUS_VECTOR; // Set spurious vector + WriteRegister(REG_SPURIOUS, svr); + + // Set Task Priority to 0 to accept all interrupts + WriteRegister(REG_TPR, 0); + + uint32_t version = ReadRegister(REG_VERSION); + uint32_t id = GetId(); + + KernelLogStream(OK, "APIC") << "Local APIC initialized: id=" << base::dec << (uint64_t)id + << " version=" << base::hex << (uint64_t)(version & 0xFF) + << " max LVT=" << base::dec << (uint64_t)((version >> 16) & 0xFF); + } + + void SendEOI() { + WriteRegister(REG_EOI, 0); + } + + uint32_t GetId() { + return (ReadRegister(REG_ID) >> 24) & 0xFF; + } + }; +}; diff --git a/kernel/src/Hal/Apic/Apic.hpp b/kernel/src/Hal/Apic/Apic.hpp new file mode 100644 index 0000000..72086eb --- /dev/null +++ b/kernel/src/Hal/Apic/Apic.hpp @@ -0,0 +1,41 @@ +/* + * Apic.hpp + * Local APIC (Advanced Programmable Interrupt Controller) + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Hal { + namespace LocalApic { + // Local APIC register offsets + constexpr uint32_t REG_ID = 0x020; + constexpr uint32_t REG_VERSION = 0x030; + constexpr uint32_t REG_TPR = 0x080; + constexpr uint32_t REG_EOI = 0x0B0; + constexpr uint32_t REG_SPURIOUS = 0x0F0; + constexpr uint32_t REG_ICR_LOW = 0x300; + constexpr uint32_t REG_ICR_HIGH = 0x310; + constexpr uint32_t REG_TIMER_LVT = 0x320; + constexpr uint32_t REG_LINT0_LVT = 0x350; + constexpr uint32_t REG_LINT1_LVT = 0x360; + constexpr uint32_t REG_ERROR_LVT = 0x370; + constexpr uint32_t REG_TIMER_INITIAL = 0x380; + constexpr uint32_t REG_TIMER_CURRENT = 0x390; + constexpr uint32_t REG_TIMER_DIVIDE = 0x3E0; + + // Spurious vector number + constexpr uint8_t SPURIOUS_VECTOR = 0xFF; + + // MSR for APIC base + constexpr uint32_t MSR_APIC_BASE = 0x1B; + + void Initialize(uint64_t apicBasePhys); + void SendEOI(); + uint32_t GetId(); + + uint32_t ReadRegister(uint32_t reg); + void WriteRegister(uint32_t reg, uint32_t value); + }; +}; diff --git a/kernel/src/Hal/Apic/ApicInit.cpp b/kernel/src/Hal/Apic/ApicInit.cpp new file mode 100644 index 0000000..f5d7ebe --- /dev/null +++ b/kernel/src/Hal/Apic/ApicInit.cpp @@ -0,0 +1,72 @@ +/* + * ApicInit.cpp + * APIC subsystem initialization + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "ApicInit.hpp" +#include "Pic.hpp" +#include "Apic.hpp" +#include "IoApic.hpp" +#include "Interrupts.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + void ApicInitialize(ACPI::CommonSDTHeader* xsdt) { + KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem"; + + // Step 1: Parse MADT + MADT::ParsedMADT madt{}; + if (!MADT::Parse(xsdt, madt)) { + KernelLogStream(ERROR, "APIC") << "Failed to parse MADT, cannot initialize APIC"; + return; + } + + if (madt.IoApicAddress == 0) { + KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT"; + return; + } + + // Step 2: Map APIC MMIO regions into kernel page tables + // The HHDM only covers physical RAM; MMIO regions need explicit mapping. + if (Memory::VMM::g_paging) { + Memory::VMM::g_paging->MapMMIO(madt.LocalApicAddress, Memory::HHDM(madt.LocalApicAddress)); + KernelLogStream(DEBUG, "APIC") << "Mapped Local APIC MMIO at phys " << base::hex << madt.LocalApicAddress; + + Memory::VMM::g_paging->MapMMIO(madt.IoApicAddress, Memory::HHDM(madt.IoApicAddress)); + KernelLogStream(DEBUG, "APIC") << "Mapped IOAPIC MMIO at phys " << base::hex << madt.IoApicAddress; + } + + // Step 3: Disable legacy 8259 PIC + DisableLegacyPic(); + + // Step 4: Install IRQ stubs into IDT + InitializeIrqHandlers(); + IDTReload(); + + // Step 5: Initialize Local APIC + LocalApic::Initialize(madt.LocalApicAddress); + + // Step 6: Initialize IOAPIC + IoApic::Initialize(madt.IoApicAddress, madt.IoApicGsiBase, + madt.Overrides, madt.OverrideCount); + + // Step 7: Route keyboard (IRQ1) and mouse (IRQ12) to BSP + uint8_t bspApicId = (uint8_t)LocalApic::GetId(); + + IoApic::RouteIrq(IRQ_KEYBOARD, IRQ_VECTOR_BASE + IRQ_KEYBOARD, bspApicId); + IoApic::RouteIrq(IRQ_MOUSE, IRQ_VECTOR_BASE + IRQ_MOUSE, bspApicId); + + // Step 8: Enable interrupts + asm volatile("sti"); + + KernelLogStream(OK, "APIC") << "APIC subsystem initialized, interrupts enabled"; + } +}; diff --git a/kernel/src/Hal/Apic/ApicInit.hpp b/kernel/src/Hal/Apic/ApicInit.hpp new file mode 100644 index 0000000..3d4ea38 --- /dev/null +++ b/kernel/src/Hal/Apic/ApicInit.hpp @@ -0,0 +1,22 @@ +/* + * ApicInit.hpp + * APIC subsystem initialization + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Hal { + // Initialize the full APIC subsystem: + // 1. Parse MADT from XSDT + // 2. Disable legacy PIC + // 3. Install IRQ stubs in IDT + // 4. Initialize Local APIC + // 5. Initialize IOAPIC + // 6. Route keyboard (IRQ1) and mouse (IRQ12) + // 7. Enable interrupts + // + // xsdt: pointer to the XSDT (already HHDM-mapped) + void ApicInitialize(ACPI::CommonSDTHeader* xsdt); +}; diff --git a/kernel/src/Hal/Apic/Interrupts.cpp b/kernel/src/Hal/Apic/Interrupts.cpp new file mode 100644 index 0000000..a1f4f90 --- /dev/null +++ b/kernel/src/Hal/Apic/Interrupts.cpp @@ -0,0 +1,51 @@ +/* + * Interrupts.cpp + * Hardware interrupt registration and dispatch + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Interrupts.hpp" +#include "Apic.hpp" +#include +#include +#include + +// Assembly-defined stub table and spurious handler +extern "C" void* IrqStubTable[Hal::IRQ_COUNT]; +extern "C" void IrqStubSpurious(); + +namespace Hal { + // Dispatch table: one handler per IRQ + static IrqHandler g_irqHandlers[IRQ_COUNT] = {}; + + void RegisterIrqHandler(uint8_t irq, IrqHandler handler) { + if (irq >= IRQ_COUNT) return; + g_irqHandlers[irq] = handler; + Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq + << " (vector " << (uint64_t)(IRQ_VECTOR_BASE + irq) << ")"; + } + + void InitializeIrqHandlers() { + // Install IRQ stubs into IDT vectors 32..55 + for (int i = 0; i < IRQ_COUNT; i++) { + IDTEncodeInterrupt(IRQ_VECTOR_BASE + i, IrqStubTable[i], 0x8E); + } + + // Install spurious interrupt handler at vector 0xFF + IDTEncodeInterrupt(0xFF, (void*)IrqStubSpurious, 0x8E); + + Kt::KernelLogStream(Kt::OK, "IRQ") << "Installed " << base::dec << (uint64_t)IRQ_COUNT + << " IRQ stubs (vectors " << (uint64_t)IRQ_VECTOR_BASE << "-" + << (uint64_t)(IRQ_VECTOR_BASE + IRQ_COUNT - 1) << ")"; + } +}; + +// C linkage dispatch function called from assembly stubs +extern "C" void HalIrqDispatch(uint64_t irqNumber) { + if (irqNumber < Hal::IRQ_COUNT && Hal::g_irqHandlers[irqNumber] != nullptr) { + Hal::g_irqHandlers[irqNumber]((uint8_t)irqNumber); + } + + // Send End of Interrupt to the Local APIC + Hal::LocalApic::SendEOI(); +} diff --git a/kernel/src/Hal/Apic/Interrupts.hpp b/kernel/src/Hal/Apic/Interrupts.hpp new file mode 100644 index 0000000..1e7f170 --- /dev/null +++ b/kernel/src/Hal/Apic/Interrupts.hpp @@ -0,0 +1,37 @@ +/* + * Interrupts.hpp + * Hardware interrupt registration and dispatch + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Hal { + // IRQ handler function type. The parameter is the IRQ number (0-23). + using IrqHandler = void(*)(uint8_t irq); + + // Number of IRQ lines supported (IOAPIC inputs) + constexpr int IRQ_COUNT = 24; + + // IRQ vector base: hardware IRQs start at IDT vector 32 + constexpr uint8_t IRQ_VECTOR_BASE = 32; + + // Well-known ISA IRQ assignments + constexpr uint8_t IRQ_TIMER = 0; + constexpr uint8_t IRQ_KEYBOARD = 1; + constexpr uint8_t IRQ_CASCADE = 2; + constexpr uint8_t IRQ_COM2 = 3; + constexpr uint8_t IRQ_COM1 = 4; + constexpr uint8_t IRQ_FLOPPY = 6; + constexpr uint8_t IRQ_RTC = 8; + constexpr uint8_t IRQ_MOUSE = 12; + constexpr uint8_t IRQ_ATA1 = 14; + constexpr uint8_t IRQ_ATA2 = 15; + + // Register a handler for the given IRQ number (0-23) + void RegisterIrqHandler(uint8_t irq, IrqHandler handler); + + // Install IRQ stubs into the IDT and set up the dispatch table + void InitializeIrqHandlers(); +}; diff --git a/kernel/src/Hal/Apic/IoApic.cpp b/kernel/src/Hal/Apic/IoApic.cpp new file mode 100644 index 0000000..a31dbc4 --- /dev/null +++ b/kernel/src/Hal/Apic/IoApic.cpp @@ -0,0 +1,137 @@ +/* + * IoApic.cpp + * I/O APIC (I/O Advanced Programmable Interrupt Controller) + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "IoApic.hpp" +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace IoApic { + static volatile uint32_t* g_ioApicBase = nullptr; + static uint32_t g_gsiBase = 0; + static uint32_t g_maxRedirEntries = 0; + + // Stored overrides for IRQ remapping + static MADT::InterruptSourceOverride g_overrides[MADT::ParsedMADT::MaxOverrides]; + static int g_overrideCount = 0; + + static uint32_t ReadRegister(uint32_t reg) { + // Write the register index to IOREGSEL (offset 0x00) + g_ioApicBase[0] = reg; + // Read the value from IOWIN (offset 0x10 = index 4 in uint32_t array) + return g_ioApicBase[4]; + } + + static void WriteRegister(uint32_t reg, uint32_t value) { + g_ioApicBase[0] = reg; + g_ioApicBase[4] = value; + } + + void SetRedirectionEntry(uint8_t index, uint64_t entry) { + uint32_t regLow = IOREDTBL_BASE + (index * 2); + uint32_t regHigh = IOREDTBL_BASE + (index * 2) + 1; + + WriteRegister(regHigh, (uint32_t)(entry >> 32)); + WriteRegister(regLow, (uint32_t)(entry & 0xFFFFFFFF)); + } + + uint64_t GetRedirectionEntry(uint8_t index) { + uint32_t regLow = IOREDTBL_BASE + (index * 2); + uint32_t regHigh = IOREDTBL_BASE + (index * 2) + 1; + + uint64_t low = ReadRegister(regLow); + uint64_t high = ReadRegister(regHigh); + + return (high << 32) | low; + } + + void MaskIrq(uint8_t irq) { + uint64_t entry = GetRedirectionEntry(irq); + entry |= REDIR_MASKED; + SetRedirectionEntry(irq, entry); + } + + void UnmaskIrq(uint8_t irq) { + uint64_t entry = GetRedirectionEntry(irq); + entry &= ~REDIR_MASKED; + SetRedirectionEntry(irq, entry); + } + + uint32_t GetGsiForIrq(uint8_t isaIrq) { + for (int i = 0; i < g_overrideCount; i++) { + if (g_overrides[i].IrqSource == isaIrq) { + return g_overrides[i].GlobalSystemInterrupt; + } + } + // No override found, identity map + return isaIrq; + } + + void RouteIrq(uint8_t isaIrq, uint8_t vector, uint8_t destinationApicId) { + uint32_t gsi = GetGsiForIrq(isaIrq); + uint32_t ioApicIndex = gsi - g_gsiBase; + + // Build redirection entry + uint64_t entry = 0; + entry |= vector; // Vector + entry |= ((uint64_t)destinationApicId << 56); // Destination + + // Check if there's an override with specific polarity/trigger settings + for (int i = 0; i < g_overrideCount; i++) { + if (g_overrides[i].IrqSource == isaIrq) { + uint16_t flags = g_overrides[i].Flags; + // Polarity: bits 0-1 (0b11 = active low) + if ((flags & 0x03) == 0x03) { + entry |= REDIR_ACTIVE_LOW; + } + // Trigger mode: bits 2-3 (0b11 = level triggered) + if ((flags & 0x0C) == 0x0C) { + entry |= REDIR_LEVEL_TRIGGER; + } + break; + } + } + + SetRedirectionEntry(ioApicIndex, entry); + + KernelLogStream(DEBUG, "IOAPIC") << "Routed ISA IRQ " << base::dec << (uint64_t)isaIrq + << " -> GSI " << (uint64_t)gsi + << " -> vector " << (uint64_t)vector + << " -> APIC " << (uint64_t)destinationApicId; + } + + void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase, + MADT::InterruptSourceOverride* overrides, int overrideCount) { + g_ioApicBase = (volatile uint32_t*)Memory::HHDM(ioApicBasePhys); + g_gsiBase = gsiBase; + + // Store overrides + g_overrideCount = overrideCount; + for (int i = 0; i < overrideCount; i++) { + g_overrides[i] = overrides[i]; + } + + // Read IOAPIC version and max redirection entries + uint32_t version = ReadRegister(IOAPICVER); + g_maxRedirEntries = ((version >> 16) & 0xFF) + 1; + uint32_t ioapicId = (ReadRegister(IOAPICID) >> 24) & 0x0F; + + KernelLogStream(OK, "IOAPIC") << "IOAPIC initialized: id=" << base::dec << (uint64_t)ioapicId + << " version=" << base::hex << (uint64_t)(version & 0xFF) + << " entries=" << base::dec << (uint64_t)g_maxRedirEntries; + + // Mask all redirection entries initially + for (uint32_t i = 0; i < g_maxRedirEntries; i++) { + SetRedirectionEntry(i, REDIR_MASKED | (IRQ_VECTOR_BASE + i)); + } + + KernelLogStream(OK, "IOAPIC") << "All redirection entries masked"; + } + }; +}; diff --git a/kernel/src/Hal/Apic/IoApic.hpp b/kernel/src/Hal/Apic/IoApic.hpp new file mode 100644 index 0000000..f6a96e5 --- /dev/null +++ b/kernel/src/Hal/Apic/IoApic.hpp @@ -0,0 +1,44 @@ +/* + * IoApic.hpp + * I/O APIC (I/O Advanced Programmable Interrupt Controller) + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace IoApic { + // IOAPIC register indices (written to IOREGSEL) + constexpr uint32_t IOAPICID = 0x00; + constexpr uint32_t IOAPICVER = 0x01; + constexpr uint32_t IOAPICARB = 0x02; + // Redirection table entries start at 0x10, each entry is 2 x 32-bit registers + constexpr uint32_t IOREDTBL_BASE = 0x10; + + // Redirection entry flags + constexpr uint64_t REDIR_MASKED = (1ULL << 16); + constexpr uint64_t REDIR_LEVEL_TRIGGER = (1ULL << 15); + constexpr uint64_t REDIR_ACTIVE_LOW = (1ULL << 13); + constexpr uint64_t REDIR_LOGICAL_DEST = (1ULL << 11); + + // IRQ vector base: hardware IRQs start at vector 32 + constexpr uint8_t IRQ_VECTOR_BASE = 32; + + void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase, + MADT::InterruptSourceOverride* overrides, int overrideCount); + + void SetRedirectionEntry(uint8_t irq, uint64_t entry); + uint64_t GetRedirectionEntry(uint8_t irq); + + void MaskIrq(uint8_t irq); + void UnmaskIrq(uint8_t irq); + + // Route an ISA IRQ to a specific vector, applying source overrides + void RouteIrq(uint8_t isaIrq, uint8_t vector, uint8_t destinationApicId); + + // Get the GSI for a given ISA IRQ (applies overrides) + uint32_t GetGsiForIrq(uint8_t isaIrq); + }; +}; diff --git a/kernel/src/Hal/Apic/IsrStubs.asm b/kernel/src/Hal/Apic/IsrStubs.asm new file mode 100644 index 0000000..47cf4a6 --- /dev/null +++ b/kernel/src/Hal/Apic/IsrStubs.asm @@ -0,0 +1,127 @@ +; +; IsrStubs.asm +; Hardware interrupt (IRQ) entry stubs for APIC +; Copyright (c) 2025 Daniel Hammer +; + +[bits 64] +section .text + +; External C++ handler function +extern HalIrqDispatch + +; Macro to define an IRQ stub +; Each stub pushes the IRQ number and jumps to the common handler +%macro IRQ_STUB 1 +global IrqStub%1 +IrqStub%1: + push rax ; Save rax (we use it for the IRQ number) + mov rax, %1 ; IRQ number + jmp IrqCommon +%endmacro + +; Common IRQ handler: saves all general-purpose registers, +; calls the C++ dispatch function, restores registers, and returns. +IrqCommon: + ; rax is already on the stack and holds the IRQ number + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Pass IRQ number as first argument (rdi) + mov rdi, rax + + ; Align stack to 16 bytes before call (we pushed 15 registers x 8 = 120 bytes + ; + return address 8 = 128, which is 16-byte aligned, so we're good) + cld + call HalIrqDispatch + + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + + iretq + +; Define stubs for IRQs 0..23 (vectors 32..55) +; This covers all standard ISA IRQs plus some extra IOAPIC inputs +IRQ_STUB 0 +IRQ_STUB 1 +IRQ_STUB 2 +IRQ_STUB 3 +IRQ_STUB 4 +IRQ_STUB 5 +IRQ_STUB 6 +IRQ_STUB 7 +IRQ_STUB 8 +IRQ_STUB 9 +IRQ_STUB 10 +IRQ_STUB 11 +IRQ_STUB 12 +IRQ_STUB 13 +IRQ_STUB 14 +IRQ_STUB 15 +IRQ_STUB 16 +IRQ_STUB 17 +IRQ_STUB 18 +IRQ_STUB 19 +IRQ_STUB 20 +IRQ_STUB 21 +IRQ_STUB 22 +IRQ_STUB 23 + +; Spurious interrupt handler (vector 0xFF) - do nothing, no EOI +global IrqStubSpurious +IrqStubSpurious: + iretq + +; Export the stub table for C++ to reference +section .data +global IrqStubTable +IrqStubTable: + dq IrqStub0 + dq IrqStub1 + dq IrqStub2 + dq IrqStub3 + dq IrqStub4 + dq IrqStub5 + dq IrqStub6 + dq IrqStub7 + dq IrqStub8 + dq IrqStub9 + dq IrqStub10 + dq IrqStub11 + dq IrqStub12 + dq IrqStub13 + dq IrqStub14 + dq IrqStub15 + dq IrqStub16 + dq IrqStub17 + dq IrqStub18 + dq IrqStub19 + dq IrqStub20 + dq IrqStub21 + dq IrqStub22 + dq IrqStub23 diff --git a/kernel/src/Hal/Apic/Pic.cpp b/kernel/src/Hal/Apic/Pic.cpp new file mode 100644 index 0000000..4b779c2 --- /dev/null +++ b/kernel/src/Hal/Apic/Pic.cpp @@ -0,0 +1,59 @@ +/* + * Pic.cpp + * Legacy 8259 PIC disable + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Pic.hpp" +#include +#include +#include + +namespace Hal { + // 8259 PIC ports + constexpr uint16_t PIC1_COMMAND = 0x20; + constexpr uint16_t PIC1_DATA = 0x21; + constexpr uint16_t PIC2_COMMAND = 0xA0; + constexpr uint16_t PIC2_DATA = 0xA1; + + // ICW1 flags + constexpr uint8_t ICW1_INIT = 0x11; + // ICW4 flags + constexpr uint8_t ICW4_8086 = 0x01; + + void DisableLegacyPic() { + // Remap the PIC to vectors 0xF0-0xF7 (master) and 0xF8-0xFF (slave) + // so they don't conflict with CPU exceptions or our APIC vectors. + // Then mask all IRQs. + + // ICW1: begin initialization sequence + Io::Out8(ICW1_INIT, PIC1_COMMAND); + Io::IoPortWait(); + Io::Out8(ICW1_INIT, PIC2_COMMAND); + Io::IoPortWait(); + + // ICW2: remap IRQ base offsets + Io::Out8(0xF0, PIC1_DATA); // Master offset + Io::IoPortWait(); + Io::Out8(0xF8, PIC2_DATA); // Slave offset + Io::IoPortWait(); + + // ICW3: cascade wiring + Io::Out8(0x04, PIC1_DATA); // Slave on IRQ2 + Io::IoPortWait(); + Io::Out8(0x02, PIC2_DATA); // Cascade identity + Io::IoPortWait(); + + // ICW4: 8086 mode + Io::Out8(ICW4_8086, PIC1_DATA); + Io::IoPortWait(); + Io::Out8(ICW4_8086, PIC2_DATA); + Io::IoPortWait(); + + // Mask all IRQs on both PICs + Io::Out8(0xFF, PIC1_DATA); + Io::Out8(0xFF, PIC2_DATA); + + Kt::KernelLogStream(Kt::OK, "PIC") << "Legacy 8259 PIC disabled"; + } +}; diff --git a/kernel/src/Hal/Apic/Pic.hpp b/kernel/src/Hal/Apic/Pic.hpp new file mode 100644 index 0000000..60b93dc --- /dev/null +++ b/kernel/src/Hal/Apic/Pic.hpp @@ -0,0 +1,11 @@ +/* + * Pic.hpp + * Legacy 8259 PIC disable + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once + +namespace Hal { + void DisableLegacyPic(); +}; diff --git a/kernel/src/Hal/IDT.cpp b/kernel/src/Hal/IDT.cpp index 6ee4666..dca179c 100644 --- a/kernel/src/Hal/IDT.cpp +++ b/kernel/src/Hal/IDT.cpp @@ -111,7 +111,7 @@ namespace Hal { void IDTInitialize() { IDT = (InterruptDescriptor*)Memory::g_pfa->Allocate(); Kt::KernelLogStream(Kt::DEBUG, "IDT") << "Allocated IDT at " << base::hex << (uint64_t)IDT; - IDTR.Limit = 0x0FF; + IDTR.Limit = (256 * sizeof(InterruptDescriptor)) - 1; IDTR.Base = (uint64_t)IDT; Kt::KernelLogStream(Kt::DEBUG, "IDT") << "Set IDTR Base to " << base::hex << IDTR.Base << " and Limit to " << base::hex << IDTR.Limit; @@ -123,4 +123,8 @@ namespace Hal { Kt::KernelLogStream(Kt::OK, "Hal") << "Loaded new IDT"; } + + void IDTReload() { + LoadIDT(IDTR); + } }; \ No newline at end of file diff --git a/kernel/src/Hal/IDT.hpp b/kernel/src/Hal/IDT.hpp index d54499a..6f2bbf2 100644 --- a/kernel/src/Hal/IDT.hpp +++ b/kernel/src/Hal/IDT.hpp @@ -6,6 +6,7 @@ #pragma once #include +#include namespace Hal { struct InterruptDescriptor { @@ -24,4 +25,6 @@ namespace Hal { }__attribute__((packed)); void IDTInitialize(); + void IDTEncodeInterrupt(std::size_t i, void* handler, uint8_t type_attr); + void IDTReload(); }; \ No newline at end of file diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index d64c255..7307060 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -27,6 +27,10 @@ #include #include #include +#include +#include +#include +#include #include using namespace Kt; @@ -107,10 +111,22 @@ extern "C" void kmain() { Hal::IDTInitialize(); Memory::VMM::Paging g_paging{}; + Memory::VMM::g_paging = &g_paging; g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), memmap_request.response); #endif Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(rsdp_request.response->address)); + +#if defined (__x86_64__) + if (g_acpi.GetXSDT() != nullptr) { + Hal::ApicInitialize(g_acpi.GetXSDT()); + + Drivers::PS2::Initialize(); + Drivers::PS2::Keyboard::Initialize(); + Drivers::PS2::Mouse::Initialize(); + } +#endif + Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address); Efi::Init(ST); diff --git a/kernel/src/Memory/Paging.cpp b/kernel/src/Memory/Paging.cpp index 8e03b84..5de9d77 100644 --- a/kernel/src/Memory/Paging.cpp +++ b/kernel/src/Memory/Paging.cpp @@ -4,8 +4,10 @@ #include namespace Memory::VMM { + Paging* g_paging = nullptr; + extern "C" uint64_t KernelStartSymbol; - + std::uint64_t GetPhysKernelAddress(std::uint64_t virtualAddress) { return Paging::GetPhysAddr(GetCR3(), (std::uint64_t)virtualAddress, true); } @@ -72,6 +74,27 @@ namespace Memory::VMM { pageEntry->Address = physicalAddress >> 12; } + void Paging::MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress) { + if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) { + Panic("Value that isn't page-aligned passed as address to Paging::MapMMIO!", nullptr); + } + + VirtualAddress virtualAddressObj(virtualAddress); + + auto PML3 = HandleLevel(virtualAddressObj, PML4, 4); + auto PML2 = HandleLevel(virtualAddressObj, PML3, 3); + auto PML1 = HandleLevel(virtualAddressObj, PML2, 2); + + PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]); + + pageEntry->Present = true; + pageEntry->Writable = true; + pageEntry->CacheDisabled = true; + pageEntry->WriteThrough = true; + + pageEntry->Address = physicalAddress >> 12; + } + std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) { VirtualAddress virtualAddressObj(virtualAddress); diff --git a/kernel/src/Memory/Paging.hpp b/kernel/src/Memory/Paging.hpp index 3547cd1..7b8a3d7 100644 --- a/kernel/src/Memory/Paging.hpp +++ b/kernel/src/Memory/Paging.hpp @@ -90,10 +90,13 @@ public: Paging(); void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap); void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress); + void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress); static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false); std::uint64_t GetPhysAddr(std::uint64_t virtualAddress); }; + extern Paging* g_paging; + extern "C" uint64_t GetCR3(); extern "C" void LoadCR3(PageTable* PML4); }; \ No newline at end of file