feat: Local/IO APIC, PS/2 keyboard & mouse

This commit is contained in:
2026-02-17 12:44:31 +01:00
parent e2f059bf90
commit 641ec4681b
26 changed files with 1730 additions and 3 deletions
+2 -1
View File
@@ -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
{
+5
View File
@@ -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;
};
};
+135
View File
@@ -0,0 +1,135 @@
/*
* MADT.cpp
* Multiple APIC Description Table parsing
* Copyright (c) 2025 Daniel Hammer
*/
#include "MADT.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Libraries/Memory.hpp>
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;
}
};
};
+82
View File
@@ -0,0 +1,82 @@
/*
* MADT.hpp
* Multiple APIC Description Table parsing
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <ACPI/ACPI.hpp>
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);
};
};
+267
View File
@@ -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 <Io/IoPort.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Terminal/Terminal.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
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;
}
};
+52
View File
@@ -0,0 +1,52 @@
/*
* Keyboard.hpp
* PS/2 Keyboard driver
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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();
};
+199
View File
@@ -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 <Io/IoPort.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Terminal/Terminal.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
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;
}
};
+37
View File
@@ -0,0 +1,37 @@
/*
* Mouse.hpp
* PS/2 Mouse driver
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
};
+167
View File
@@ -0,0 +1,167 @@
/*
* PS2Controller.cpp
* PS/2 Controller (8042) initialization and utility functions
* Copyright (c) 2025 Daniel Hammer
*/
#include "PS2Controller.hpp"
#include <Io/IoPort.hpp>
#include <CppLib/Stream.hpp>
#include <Terminal/Terminal.hpp>
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") << ")";
}
};
+57
View File
@@ -0,0 +1,57 @@
/*
* PS2Controller.hpp
* PS/2 Controller (8042) initialization and utility functions
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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();
};
+75
View File
@@ -0,0 +1,75 @@
/*
* Apic.cpp
* Local APIC (Advanced Programmable Interrupt Controller)
* Copyright (c) 2025 Daniel Hammer
*/
#include "Apic.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
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;
}
};
};
+41
View File
@@ -0,0 +1,41 @@
/*
* Apic.hpp
* Local APIC (Advanced Programmable Interrupt Controller)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
};
};
+72
View File
@@ -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 <ACPI/MADT.hpp>
#include <Hal/IDT.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
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";
}
};
+22
View File
@@ -0,0 +1,22 @@
/*
* ApicInit.hpp
* APIC subsystem initialization
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <ACPI/ACPI.hpp>
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);
};
+51
View File
@@ -0,0 +1,51 @@
/*
* Interrupts.cpp
* Hardware interrupt registration and dispatch
* Copyright (c) 2025 Daniel Hammer
*/
#include "Interrupts.hpp"
#include "Apic.hpp"
#include <Hal/IDT.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
// 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();
}
+37
View File
@@ -0,0 +1,37 @@
/*
* Interrupts.hpp
* Hardware interrupt registration and dispatch
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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();
};
+137
View File
@@ -0,0 +1,137 @@
/*
* IoApic.cpp
* I/O APIC (I/O Advanced Programmable Interrupt Controller)
* Copyright (c) 2025 Daniel Hammer
*/
#include "IoApic.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
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";
}
};
};
+44
View File
@@ -0,0 +1,44 @@
/*
* IoApic.hpp
* I/O APIC (I/O Advanced Programmable Interrupt Controller)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <ACPI/MADT.hpp>
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);
};
};
+127
View File
@@ -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
+59
View File
@@ -0,0 +1,59 @@
/*
* Pic.cpp
* Legacy 8259 PIC disable
* Copyright (c) 2025 Daniel Hammer
*/
#include "Pic.hpp"
#include <Io/IoPort.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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";
}
};
+11
View File
@@ -0,0 +1,11 @@
/*
* Pic.hpp
* Legacy 8259 PIC disable
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
namespace Hal {
void DisableLegacyPic();
};
+5 -1
View File
@@ -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);
}
};
+3
View File
@@ -6,6 +6,7 @@
#pragma once
#include <cstdint>
#include <cstddef>
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();
};
+16
View File
@@ -27,6 +27,10 @@
#include <Io/IoPort.hpp>
#include <Memory/Paging.hpp>
#include <ACPI/ACPI.hpp>
#include <Hal/Apic/ApicInit.hpp>
#include <Drivers/PS2/PS2Controller.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp>
#include <CppLib/BoxUI.hpp>
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);
+24 -1
View File
@@ -4,8 +4,10 @@
#include <Memory/HHDM.hpp>
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);
+3
View File
@@ -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);
};