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