banan-os/kernel/kernel/Input/PS2Controller.cpp

329 lines
8.7 KiB
C++

#include <BAN/ScopeGuard.h>
#include <kernel/ACPI.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/IDT.h>
#include <kernel/Input/PS2Controller.h>
#include <kernel/Input/PS2Keyboard.h>
#include <kernel/InterruptController.h>
#include <kernel/IO.h>
#include <kernel/Timer/Timer.h>
namespace Kernel::Input
{
namespace PS2
{
enum IOPort : uint8_t
{
DATA = 0x60,
STATUS = 0x64,
COMMAND = 0x64,
};
enum Status : uint8_t
{
OUTPUT_FULL = (1 << 0),
INPUT_FULL = (1 << 1),
SYSTEM = (1 << 2),
DEVICE_OR_CONTROLLER = (1 << 3),
TIMEOUT_ERROR = (1 << 6),
PARITY_ERROR = (1 << 7),
};
enum Config : uint8_t
{
INTERRUPT_FIRST_PORT = (1 << 0),
INTERRUPT_SECOND_PORT = (1 << 1),
SYSTEM_FLAG = (1 << 2),
ZERO1 = (1 << 3),
CLOCK_FIRST_PORT = (1 << 4),
CLOCK_SECOND_PORT = (1 << 5),
TRANSLATION_FIRST_PORT = (1 << 6),
ZERO2 = (1 << 7),
};
enum Command : uint8_t
{
READ_CONFIG = 0x20,
WRITE_CONFIG = 0x60,
DISABLE_SECOND_PORT = 0xA7,
ENABLE_SECOND_PORT = 0xA8,
TEST_SECOND_PORT = 0xA9,
TEST_CONTROLLER = 0xAA,
TEST_FIRST_PORT = 0xAB,
DISABLE_FIRST_PORT = 0xAD,
ENABLE_FIRST_PORT = 0xAE,
WRITE_TO_SECOND_PORT = 0xD4,
};
enum Response
{
TEST_FIRST_PORT_PASS = 0x00,
TEST_SECOND_PORT_PASS = 0x00,
TEST_CONTROLLER_PASS = 0x55,
SELF_TEST_PASS = 0xAA,
ACK = 0xFA,
};
enum DeviceCommand
{
ENABLE_SCANNING = 0xF4,
DISABLE_SCANNING = 0xF5,
IDENTIFY = 0xF2,
RESET = 0xFF,
};
enum IRQ
{
DEVICE0 = 1,
DEVICE1 = 12,
};
}
static constexpr uint64_t s_device_timeout_ms = 100;
static void controller_send_command(PS2::Command command)
{
IO::outb(PS2::IOPort::COMMAND, command);
}
static void controller_send_command(PS2::Command command, uint8_t data)
{
IO::outb(PS2::IOPort::COMMAND, command);
while (IO::inb(PS2::IOPort::STATUS) & PS2::Status::INPUT_FULL)
continue;
IO::outb(PS2::IOPort::DATA, data);
}
static uint8_t wait_and_read()
{
while (!(IO::inb(PS2::IOPort::STATUS) & PS2::Status::OUTPUT_FULL))
continue;
return IO::inb(PS2::IOPort::DATA);
}
static BAN::ErrorOr<void> device_send_byte(uint8_t device, uint8_t byte)
{
if (device == 1)
IO::outb(PS2::IOPort::COMMAND, PS2::Command::WRITE_TO_SECOND_PORT);
uint64_t timeout = SystemTimer::get().ms_since_boot() + s_device_timeout_ms;
while (SystemTimer::get().ms_since_boot() < timeout)
{
if (!(IO::inb(PS2::IOPort::STATUS) & PS2::Status::INPUT_FULL))
{
IO::outb(PS2::IOPort::DATA, byte);
return {};
}
}
return BAN::Error::from_error_code(ErrorCode::PS2_Timeout);
}
static BAN::ErrorOr<uint8_t> device_read_byte()
{
uint64_t timeout = SystemTimer::get().ms_since_boot() + s_device_timeout_ms;
while (SystemTimer::get().ms_since_boot() < timeout)
if (IO::inb(PS2::IOPort::STATUS) & PS2::Status::OUTPUT_FULL)
return IO::inb(PS2::IOPort::DATA);
return BAN::Error::from_error_code(ErrorCode::PS2_Timeout);
}
static BAN::ErrorOr<void> device_wait_ack()
{
while (TRY(device_read_byte()) != PS2::Response::ACK)
continue;;
return {};
}
void PS2Controller::device0_irq()
{
auto& controller = PS2Controller::get();
ASSERT(controller.m_devices[0] != nullptr);
controller.m_devices[0]->on_byte(IO::inb(PS2::IOPort::DATA));
}
void PS2Controller::device1_irq()
{
auto& controller = PS2Controller::get();
ASSERT(controller.m_devices[1] != nullptr);
controller.m_devices[1]->on_byte(IO::inb(PS2::IOPort::DATA));
}
static PS2Controller* s_instance = nullptr;
BAN::ErrorOr<void> PS2Controller::initialize()
{
ASSERT(s_instance == nullptr);
s_instance = new PS2Controller;
if (s_instance == nullptr)
return BAN::Error::from_errno(ENOMEM);
BAN::ScopeGuard guard([] { delete s_instance; });
TRY(s_instance->initialize_impl());
guard.disable();
return {};
}
PS2Controller& PS2Controller::get()
{
ASSERT(s_instance != nullptr);
return *s_instance;
}
BAN::ErrorOr<void> PS2Controller::initialize_impl()
{
// Step 1: Initialise USB Controllers
// FIXME
// Step 2: Determine if the PS/2 Controller Exists
// FIXME
// Step 3: Disable Devices
controller_send_command(PS2::Command::DISABLE_FIRST_PORT);
controller_send_command(PS2::Command::DISABLE_SECOND_PORT);
// Step 4: Flush The Output Buffer
IO::inb(PS2::IOPort::DATA);
// Step 5: Set the Controller Configuration Byte
controller_send_command(PS2::Command::READ_CONFIG);
uint8_t config = wait_and_read();
config &= ~PS2::Config::INTERRUPT_FIRST_PORT;
config &= ~PS2::Config::INTERRUPT_SECOND_PORT;
config &= ~PS2::Config::TRANSLATION_FIRST_PORT;
controller_send_command(PS2::Command::WRITE_CONFIG, config);
// Step 6: Perform Controller Self Test
controller_send_command(PS2::Command::TEST_CONTROLLER);
if (wait_and_read() != PS2::Response::TEST_CONTROLLER_PASS)
return BAN::Error::from_error_code(ErrorCode::PS2_SelfTest);
// NOTE: self test might reset the device so we set the config byte again
controller_send_command(PS2::Command::WRITE_CONFIG, config);
// Step 7: Determine If There Are 2 Channels
bool valid_ports[2] { true, false };
if (config & PS2::Config::CLOCK_SECOND_PORT)
{
controller_send_command(PS2::Command::ENABLE_SECOND_PORT);
controller_send_command(PS2::Command::READ_CONFIG);
if (!(wait_and_read() & PS2::Config::CLOCK_SECOND_PORT))
valid_ports[1] = true;
controller_send_command(PS2::Command::DISABLE_SECOND_PORT);
}
// Step 8: Perform Interface Tests
controller_send_command(PS2::Command::TEST_FIRST_PORT);
if (wait_and_read() != PS2::Response::TEST_FIRST_PORT_PASS)
valid_ports[0] = false;
if (valid_ports[1])
{
controller_send_command(PS2::Command::TEST_SECOND_PORT);
if (wait_and_read() != PS2::Response::TEST_SECOND_PORT_PASS)
valid_ports[1] = false;
}
if (!valid_ports[0] && !valid_ports[1])
return {};
// Step 9: Enable Devices (and disable scanning)
for (uint8_t device = 0; device < 2; device++)
{
if (!valid_ports[device])
continue;
controller_send_command(device == 0 ? PS2::Command::ENABLE_FIRST_PORT : PS2::Command::ENABLE_SECOND_PORT);
if (set_scanning(device, false).is_error())
valid_ports[device] = false;
}
// Step 10: Reset Devices
for (uint8_t device = 0; device < 2; device++)
{
if (!valid_ports[device])
continue;
if (reset_device(device).is_error())
valid_ports[device] = false;
if (set_scanning(device, false).is_error())
valid_ports[device] = false;
}
// Step 11: Initialize Device Drivers
for (uint8_t device = 0; device < 2; device++)
{
if (!valid_ports[device])
continue;
if (auto res = initialize_device(device); res.is_error())
dprintln("{}", res.error());
}
if (m_devices[0])
{
IDT::register_irq_handler(PS2::IRQ::DEVICE0, device0_irq);
InterruptController::get().enable_irq(PS2::IRQ::DEVICE0);
config |= PS2::Config::INTERRUPT_FIRST_PORT;
DevFileSystem::get().add_device("input0", m_devices[0]);
}
if (m_devices[1])
{
IDT::register_irq_handler(PS2::IRQ::DEVICE1, device1_irq);
InterruptController::get().enable_irq(PS2::IRQ::DEVICE1);
config |= PS2::Config::INTERRUPT_SECOND_PORT;
DevFileSystem::get().add_device("input1", m_devices[1]);
}
controller_send_command(PS2::Command::WRITE_CONFIG, config);
return {};
}
BAN::ErrorOr<void> PS2Controller::initialize_device(uint8_t device)
{
TRY(device_send_byte(device, PS2::DeviceCommand::IDENTIFY));
TRY(device_wait_ack());
uint8_t bytes[2] {};
uint8_t index = 0;
for (uint8_t i = 0; i < 2; i++)
{
auto res = device_read_byte();
if (res.is_error())
break;
bytes[index++] = res.value();
}
// Standard PS/2 Mouse
if (index == 1 && (bytes[0] == 0x00))
return BAN::Error::from_error_code(ErrorCode::PS2_UnsupportedDevice);
// MF2 Keyboard
if (index == 2 && (bytes[0] == 0xAB && bytes[1] == 0x83))
m_devices[device] = TRY(PS2Keyboard::create(*this));
if (m_devices[device])
return {};
return BAN::Error::from_error_code(ErrorCode::PS2_UnsupportedDevice);
}
void PS2Controller::send_byte(const PS2Device* device, uint8_t byte)
{
ASSERT(device != nullptr && (device == m_devices[0] || device == m_devices[1]));
uint8_t device_index = (device == m_devices[0]) ? 0 : 1;
MUST(device_send_byte(device_index, byte));
}
BAN::ErrorOr<void> PS2Controller::reset_device(uint8_t device)
{
TRY(device_send_byte(device, PS2::DeviceCommand::RESET));
TRY(device_wait_ack());
if (TRY(device_read_byte()) != PS2::Response::SELF_TEST_PASS)
return BAN::Error::from_error_code(ErrorCode::PS2_Reset);
return {};
}
BAN::ErrorOr<void> PS2Controller::set_scanning(uint8_t device, bool enabled)
{
TRY(device_send_byte(device, enabled ? PS2::DeviceCommand::ENABLE_SCANNING : PS2::DeviceCommand::DISABLE_SCANNING));
TRY(device_wait_ack());
return {};
}
}