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
+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();
};