feat: Local/IO APIC, PS/2 keyboard & mouse
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
@@ -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") << ")";
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Pic.hpp
|
||||
* Legacy 8259 PIC disable
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Hal {
|
||||
void DisableLegacyPic();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user