feat: Local/IO APIC, PS/2 keyboard & mouse
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user