Kernel: Add basic PS/2 Mouse driver

This commit is contained in:
Bananymous 2022-12-30 19:38:21 +02:00
parent 3c92aa45fb
commit ef0b2010e0
10 changed files with 318 additions and 137 deletions

View File

@ -32,8 +32,8 @@ BUILDDIR=$(abspath build)
KERNEL_OBJS= \
$(KERNEL_ARCH_OBJS) \
kernel/build_libc.o \
kernel/Input.o \
kernel/kernel.o \
kernel/Keyboard.o \
kernel/kmalloc.o \
kernel/PIC.o \
kernel/PIT.o \

View File

@ -406,10 +406,10 @@ namespace APIC
}
}
void EOI()
void EOI(uint8_t irq)
{
if (s_using_fallback_pic)
return PIC::EOI(0);
return PIC::EOI(irq);
WriteLocalAPIC(0xB0, 0);
}

View File

@ -144,7 +144,7 @@ found:
else
Kernel::panic("no handler for irq 0x{2H}\n", irq);
APIC::EOI();
APIC::EOI(irq);
}
extern "C" void handle_irq_common();

View File

@ -6,7 +6,7 @@ namespace APIC
{
void Initialize(bool force_pic = false);
void EOI();
void EOI(uint8_t irq);
void GetISR(uint32_t[8]);
void EnableIRQ(uint8_t irq);

View File

@ -2,7 +2,7 @@
#include <stdint.h>
namespace Keyboard
namespace Input
{
enum class Key : uint8_t
@ -37,13 +37,30 @@ namespace Keyboard
bool pressed;
};
enum class MouseButton
{
Left, Right, Middle,
};
struct MouseButtonEvent
{
MouseButton button;
};
struct MouseMoveEvent
{
int16_t dx;
int16_t dy;
};
bool initialize();
void update_keyboard();
void update();
bool is_key_down(Key);
void register_key_event_callback(void (*callback)(KeyEvent));
void register_mouse_button_event_callback(void (*callback)(MouseButtonEvent));
void register_mouse_move_event_callback(void (*callback)(MouseMoveEvent));
const char* key_event_to_utf8(KeyEvent);
void led_disco();
}

View File

@ -1,8 +1,8 @@
#pragma once
#include <kernel/Keyboard.h>
#include <kernel/Input.h>
namespace Keyboard
namespace Input
{
constexpr Key scan_code_to_key_extended[0xFF]

View File

@ -1,7 +1,7 @@
#pragma once
#include <BAN/String.h>
#include <kernel/Keyboard.h>
#include <kernel/Input.h>
#include <kernel/TTY.h>
namespace Kernel
@ -22,7 +22,7 @@ namespace Kernel
Shell();
void PrintPrompt();
void ProcessCommand(const BAN::Vector<BAN::StringView>&);
void KeyEventCallback(Keyboard::KeyEvent);
void KeyEventCallback(Input::KeyEvent);
private:
TTY* m_tty;

View File

@ -1,8 +1,8 @@
#include <BAN/Queue.h>
#include <kernel/APIC.h>
#include <kernel/IDT.h>
#include <kernel/Input.h>
#include <kernel/IO.h>
#include <kernel/Keyboard.h>
#include <kernel/kprint.h>
#include <kernel/PIT.h>
#include <kernel/Serial.h>
@ -23,12 +23,13 @@
#define I8042_CONFING_IRQ_FIRST (1 << 0)
#define I8042_CONFING_IRQ_SECOND (1 << 1)
#define I8042_CONFING_DUAL_CHANNEL (1 << 5)
#define I8042_CONFING_TRANSLATION (1 << 6)
#define I8042_ENABLE_FIRST 0xAE
#define I8042_ENABLE_SECOND 0xA8
#define I8042_DISABLE_FIRST 0xAD
#define I8042_DISABLE_SECOND 0xA7
#define I8042_ENABLE_FIRST_PORT 0xAE
#define I8042_ENABLE_SECOND_PORT 0xA8
#define I8042_DISABLE_FIRST_PORT 0xAD
#define I8042_DISABLE_SECOND_PORT 0xA7
#define I8042_TEST_CONTROLLER 0xAA
#define I8042_TEST_CONTROLLER_PASS 0x55
@ -50,7 +51,17 @@
#define I8042_KB_LED_NUM_LOCK (1 << 1)
#define I8042_KB_LED_CAPS_LOCK (1 << 2)
#define I8042_MOUSE_ACK 0xFA
#define I8042_MOUSE_RESET 0xFF
#define I8042_MOUSE_SELF_TEST_PASS 0xAA
#define I8042_MOUSE_ENABLE 0xF4
#define I8042_MOUSE_DISABLE 0xF5
#define KEYBOARD_IRQ 0x01
#define MOUSE_IRQ 0x0C
#define TARGET_KEYBOARD 1
#define TARGET_MOUSE 2
#define MOD_ALT (1 << 0)
#define MOD_CTRL (1 << 1)
@ -58,32 +69,41 @@
#define MOD_ALTGR (1 << 3)
#define MOD_CAPS (1 << 4)
namespace Keyboard
namespace Input
{
static bool s_keyboard_state[0xFF] = {};
struct Command
{
uint8_t target = 0;
uint8_t command = 0;
uint8_t data = 0;
bool has_data = false;
bool extra = false;
uint8_t resp_cnt = 0;
uint8_t _sent = 0;
uint8_t _ack = 0;
bool _done = false;
};
static BAN::Queue<Command> s_keyboard_command_queue;
static uint8_t s_keyboard_command_extra = 0x00;
static BAN::Queue<Command> s_command_queue;
static uint8_t s_command_response[3] = {};
static uint8_t s_command_response_index = 0;
static BAN::Queue<KeyEvent> s_key_event_queue;
static uint8_t s_keyboard_key_buffer[10] = {};
static uint8_t s_keyboard_key_buffer_size = 0;
static BAN::Queue<MouseButtonEvent> s_mouse_button_event_queue;
static BAN::Queue<MouseMoveEvent> s_mouse_move_event_queue;
static uint8_t s_mouse_data_buffer[3] = {};
static uint8_t s_mouse_data_buffer_index = 0;
static uint8_t s_led_states = 0b000;
static uint8_t s_modifiers = 0x00;
static void (*s_key_event_callback)(KeyEvent) = nullptr;
static void (*s_mouse_button_event_callback)(MouseButtonEvent) = nullptr;
static void (*s_mouse_move_event_callback)(MouseMoveEvent) = nullptr;
static const char* s_key_to_utf8_lower[]
{
@ -107,7 +127,7 @@ namespace Keyboard
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
};
static_assert(sizeof(s_key_to_utf8_lower) == static_cast<int>(Key::Count) * sizeof(*s_key_to_utf8_lower));
static_assert(sizeof(s_key_to_utf8_lower) == (int)Key::Count * sizeof(*s_key_to_utf8_lower));
static const char* s_key_to_utf8_upper[]
{
@ -131,7 +151,7 @@ namespace Keyboard
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
};
static_assert(sizeof(s_key_to_utf8_upper) == static_cast<int>(Key::Count) * sizeof(*s_key_to_utf8_upper));
static_assert(sizeof(s_key_to_utf8_upper) == (int)Key::Count * sizeof(*s_key_to_utf8_upper));
static uint8_t wait_and_read()
{
@ -153,8 +173,11 @@ namespace Keyboard
IO::outb(I8042_DATA_PORT, data);
}
static bool i8042_keyboard_command(uint8_t command)
static bool i8042_command(uint8_t target, uint8_t command)
{
if (target == TARGET_MOUSE)
IO::outb(I8042_STATUS_REGISTER, 0xD4);
auto timeout = PIT::ms_since_boot() + I8042_KB_TIMEOUT_MS;
while (PIT::ms_since_boot() < timeout)
@ -169,40 +192,66 @@ namespace Keyboard
return false;
}
void update_keyboard()
void update()
{
if (!s_keyboard_command_queue.Empty())
if (!s_command_queue.Empty())
{
auto& command = s_keyboard_command_queue.Front();
auto& command = s_command_queue.Front();
if (command.target != TARGET_KEYBOARD && command.target != TARGET_MOUSE)
Kernel::panic("Undefined target for command 0x{2H}", command.command);
if (command._sent == 0 && command._ack == 0)
{
if (!i8042_keyboard_command(command.command))
Kernel::panic("oof 1");
if (!i8042_command(command.target, command.command))
Kernel::panic("PS/2 command oof {}, 0x{2H}", command.target, command.command);
command._sent++;
}
if (command._sent == 1 && command._ack == 1 && command.has_data)
{
if (!i8042_keyboard_command(command.data))
Kernel::panic("oof 2");
if (!i8042_command(command.target, command.data))
Kernel::panic("PS/2 data oof {}, 0x{2H}", command.target, command.data);
command._sent++;
}
if (command._done)
{
if (command.target == TARGET_KEYBOARD)
{
switch (command.command)
{
case I8042_KB_RESET:
if (s_keyboard_command_extra != I8042_KB_SELF_TEST_PASS)
if (s_command_response[0] != I8042_KB_SELF_TEST_PASS)
Kernel::panic("PS/2 Keyboard self test failed");
break;
case I8042_KB_SET_SCAN_CODE_SET:
break;
case I8042_KB_SET_LEDS:
break;
default:
Kernel::panic("PS/2 Keyboard unhandled command");
}
s_keyboard_command_queue.Pop();
}
else if (command.target == TARGET_MOUSE)
{
switch (command.command)
{
case I8042_MOUSE_RESET:
if (s_command_response[0] != I8042_MOUSE_SELF_TEST_PASS)
Kernel::panic("PS/2 Mouse self test failed");
if (s_command_response[1] != 0x00)
Kernel::panic("PS/2 Mouse invalid byte sent after self test");
break;
case I8042_MOUSE_ENABLE:
break;
case I8042_MOUSE_DISABLE:
break;
default:
Kernel::panic("PS/2 Mouse unhandled command");
}
}
s_command_response_index = 0;
s_command_queue.Pop();
}
}
@ -212,6 +261,25 @@ namespace Keyboard
s_key_event_callback(s_key_event_queue.Front());
s_key_event_queue.Pop();
}
while (!s_mouse_button_event_queue.Empty())
{
if (s_mouse_button_event_callback)
s_mouse_button_event_callback(s_mouse_button_event_queue.Front());
s_mouse_button_event_queue.Pop();
}
while (!s_mouse_move_event_queue.Empty())
{
if (s_mouse_move_event_callback)
s_mouse_move_event_callback(s_mouse_move_event_queue.Front());
s_mouse_move_event_queue.Pop();
}
}
bool is_key_down(Key key)
{
return s_keyboard_state[(int)key];
}
static void keyboard_new_key()
@ -265,7 +333,7 @@ namespace Keyboard
kprintln("{} {}", ch, extended ? 'E' : ' ');
#endif
s_keyboard_state[static_cast<int>(key)] = pressed;
s_keyboard_state[(int)key] = pressed;
bool update_leds = false;
switch (key)
@ -291,7 +359,7 @@ namespace Keyboard
if (update_leds)
{
s_keyboard_command_queue.Push({
s_command_queue.Push({
.command = I8042_KB_SET_LEDS,
.data = s_led_states,
.has_data = true,
@ -321,54 +389,52 @@ namespace Keyboard
memmove(s_keyboard_key_buffer, s_keyboard_key_buffer + index, s_keyboard_key_buffer_size);
}
void keyboard_irq_handler()
static void keyboard_irq_handler()
{
uint8_t raw = IO::inb(I8042_DATA_PORT);
bool command_waiting = false;
if (!s_keyboard_command_queue.Empty())
bool waiting_response = false;
if (!s_command_queue.Empty())
{
auto& command = s_keyboard_command_queue.Front();
command_waiting = (command._sent > 0 && !command._done);
auto& command = s_command_queue.Front();
if (command.target == TARGET_MOUSE)
Kernel::panic("Please no :( (PS/2 Keyboard got irq while Mouse was waiting)");
waiting_response = (command._sent > 0 && !command._done);
}
if (command_waiting)
if (waiting_response)
{
auto& command = s_keyboard_command_queue.Front();
auto& command = s_command_queue.Front();
if (raw == I8042_KB_RESEND)
{
dprintln("PS/2 Keyboard: Resend 0x{H}", command._sent == 2 ? command.data : command.command);
command._sent--;
return;
}
if (raw == I8042_KB_ACK)
else if (raw == I8042_KB_ACK)
{
command._ack++;
if (command.extra > 0)
return;
command._done = command.has_data ? (command._ack == 2) : true;
return;
if (command.resp_cnt == 0)
command._done = (command._ack >= (1 + command.has_data));
}
if (raw == 0x00)
else if (raw == 0x00)
{
dprintln("\e[33mKey detection error or internal buffer overrun\e[m");
command._sent = 0;
command._ack = 0;
command._done = false;
return;
s_command_response_index = 0;
}
if (raw == 0xEE && command.command == 0xEE)
else if (raw == 0xEE && command.command == 0xEE)
{
s_keyboard_command_queue.Pop();
return;
s_command_queue.Pop();
}
s_keyboard_command_extra = raw;
else
{
s_command_response[s_command_response_index++] = raw;
if (s_command_response_index >= command.resp_cnt)
command._done = true;
return;
}
}
else
{
@ -378,12 +444,118 @@ namespace Keyboard
}
}
static void mouse_irq_handler()
{
uint8_t raw = IO::inb(I8042_DATA_PORT);
bool waiting_response = false;
if (!s_command_queue.Empty())
{
auto& command = s_command_queue.Front();
if (command.target == TARGET_KEYBOARD)
Kernel::panic("Please no :( (PS/2 Mouse got irq while Keyboard was waiting)");
waiting_response = (command._sent > 0 && !command._done);
}
if (waiting_response)
{
auto& command = s_command_queue.Front();
if (raw == I8042_MOUSE_ACK)
{
command._ack++;
if (command.resp_cnt == 0)
command._done = (command._ack >= (1 + command.has_data));
}
else
{
s_command_response[s_command_response_index++] = raw;
if (s_command_response_index >= command.resp_cnt)
command._done = true;
}
}
else
{
s_mouse_data_buffer[s_mouse_data_buffer_index++] = raw;
if (s_mouse_data_buffer_index >= 3)
{
if (s_mouse_data_buffer[0] & 0x07)
{
bool left = s_mouse_data_buffer[0] & (1 << 0);
bool right = s_mouse_data_buffer[0] & (1 << 1);
bool middle = s_mouse_data_buffer[0] & (1 << 2);
if (left) s_mouse_button_event_queue.Push({ .button = MouseButton::Left });
if (right) s_mouse_button_event_queue.Push({ .button = MouseButton::Right });
if (middle) s_mouse_button_event_queue.Push({ .button = MouseButton::Middle });
}
if (s_mouse_data_buffer[1] || s_mouse_data_buffer[2])
{
int16_t rel_x = (int16_t)s_mouse_data_buffer[1] - ((s_mouse_data_buffer[0] << 4) & 0x100);
int16_t rel_y = (int16_t)s_mouse_data_buffer[2] - ((s_mouse_data_buffer[0] << 3) & 0x100);
s_mouse_move_event_queue.Push({ .dx = rel_x, .dy = rel_y });
}
s_mouse_data_buffer_index = 0;
}
}
}
static void initialize_keyboard()
{
MUST(s_command_queue.Push({
.target = TARGET_KEYBOARD,
.command = I8042_KB_RESET,
.resp_cnt = 1,
}));
// Set scan code set 2
MUST(s_command_queue.Push({
.target = TARGET_KEYBOARD,
.command = I8042_KB_SET_SCAN_CODE_SET,
.data = 0x02,
.has_data = true,
}));
// Turn LEDs off
MUST(s_command_queue.Push({
.target = TARGET_KEYBOARD,
.command = I8042_KB_SET_LEDS,
.data = s_led_states,
.has_data = true,
}));
// Register callback and IRQ
IDT::register_irq_handler(KEYBOARD_IRQ, keyboard_irq_handler);
APIC::EnableIRQ(KEYBOARD_IRQ);
}
static void initialize_mouse()
{
MUST(s_command_queue.Push({
.target = TARGET_MOUSE,
.command = I8042_MOUSE_RESET,
.resp_cnt = 2,
}));
// Mouse should be disabled when sending commands
MUST(s_command_queue.Push({
.target = TARGET_MOUSE,
.command = I8042_MOUSE_ENABLE,
}));
// Register callback and IRQ
IDT::register_irq_handler(MOUSE_IRQ, mouse_irq_handler);
APIC::EnableIRQ(MOUSE_IRQ);
}
bool initialize()
{
// https://wiki.osdev.org/%228042%22_PS/2_Controller
// TODO: support dual channel
// Step 1: Initialize USB Controllers
// TODO
@ -391,8 +563,8 @@ namespace Keyboard
// TODO
// Step 3: Disable Devices
i8042_controller_command(I8042_DISABLE_FIRST);
i8042_controller_command(I8042_DISABLE_SECOND);
i8042_controller_command(I8042_DISABLE_FIRST_PORT);
i8042_controller_command(I8042_DISABLE_SECOND_PORT);
// Step 4: Flush The Ouput Buffer
while ((IO::inb(I8042_STATUS_REGISTER) & I8042_STATUS_OUT_FULL) != 0)
@ -404,6 +576,7 @@ namespace Keyboard
config &= ~(I8042_CONFING_IRQ_FIRST | I8042_CONFING_IRQ_SECOND);
i8042_controller_command(I8042_WRITE_CONFIG, config);
// Step 6: Perform Controller Self Test
i8042_controller_command(I8042_TEST_CONTROLLER);
if (wait_and_read() != I8042_TEST_CONTROLLER_PASS)
@ -413,6 +586,16 @@ namespace Keyboard
}
// Step 7: Determine If There Are 2 Channels
bool dual_channel = config & I8042_CONFING_DUAL_CHANNEL;
if (dual_channel)
{
i8042_controller_command(I8042_ENABLE_SECOND_PORT);
i8042_controller_command(I8042_READ_CONFIG);
if (wait_and_read() & I8042_CONFING_DUAL_CHANNEL)
dual_channel = false;
else
i8042_controller_command(I8042_DISABLE_SECOND_PORT);
}
// Step 8: Perform Interface Tests
i8042_controller_command(I8042_TEST_FIRST_PORT);
@ -422,34 +605,28 @@ namespace Keyboard
return false;
}
if (dual_channel)
{
i8042_controller_command(I8042_TEST_SECOND_PORT);
if (wait_and_read() != I8042_TEST_SECOND_PORT_PASS)
{
dwarnln("PS/2 second port test failed. Mouse will be disabled");
dual_channel = false;
}
}
// Step 9: Enable Devices
config |= I8042_CONFING_IRQ_FIRST;
if (dual_channel)
config |= I8042_CONFING_IRQ_SECOND;
i8042_controller_command(I8042_WRITE_CONFIG, config);
i8042_controller_command(I8042_ENABLE_FIRST);
i8042_controller_command(I8042_ENABLE_FIRST_PORT);
i8042_controller_command(I8042_ENABLE_SECOND_PORT);
// Step 10: Reset Devices
MUST(s_keyboard_command_queue.Push({
.command = I8042_KB_RESET,
.extra = true,
}));
// Set scan code set 2
MUST(s_keyboard_command_queue.Push({
.command = I8042_KB_SET_SCAN_CODE_SET,
.data = 0x02,
.has_data = true,
}));
// Turn LEDs off
MUST(s_keyboard_command_queue.Push({
.command = I8042_KB_SET_LEDS,
.data = s_led_states,
.has_data = true,
}));
// Register callback and IRQ
IDT::register_irq_handler(KEYBOARD_IRQ, keyboard_irq_handler);
APIC::EnableIRQ(KEYBOARD_IRQ);
initialize_keyboard();
if (dual_channel)
initialize_mouse();
return true;
}
@ -459,36 +636,23 @@ namespace Keyboard
s_key_event_callback = callback;
}
void register_mouse_button_event_callback(void (*callback)(MouseButtonEvent))
{
s_mouse_button_event_callback = callback;
}
void register_mouse_move_event_callback(void (*callback)(MouseMoveEvent))
{
s_mouse_move_event_callback = callback;
}
const char* key_event_to_utf8(KeyEvent event)
{
bool shift = event.modifiers & MOD_SHIFT;
bool caps = event.modifiers & MOD_CAPS;
if (shift ^ caps)
return s_key_to_utf8_upper[static_cast<uint8_t>(event.key)];
return s_key_to_utf8_lower[static_cast<uint8_t>(event.key)];
}
void led_disco()
{
uint64_t time = PIT::ms_since_boot();
uint64_t freq = 100;
bool state = false;
for(;;)
{
if (PIT::ms_since_boot() > time + freq)
{
time += freq;
state = !state;
MUST(s_keyboard_command_queue.Push({
.command = I8042_KB_SET_LEDS,
.data = (uint8_t)(state ? 0b111 : 0b000),
.has_data = true,
}));
}
update_keyboard();
}
return s_key_to_utf8_upper[(int)event.key];
return s_key_to_utf8_lower[(int)event.key];
}
}

View File

@ -1,12 +1,12 @@
#include <BAN/StringView.h>
#include <BAN/Vector.h>
#include <kernel/CPUID.h>
#include <kernel/Input.h>
#include <kernel/IO.h>
#include <kernel/Keyboard.h>
#include <kernel/PIT.h>
#include <kernel/RTC.h>
#include <kernel/Shell.h>
#include <kernel/Serial.h>
#include <kernel/Shell.h>
#include <kernel/TTY.h>
#define TTY_PRINT(...) BAN::Formatter::print([this](char c) { m_tty->PutChar(c); }, __VA_ARGS__)
@ -26,7 +26,7 @@ namespace Kernel
Shell::Shell()
{
Keyboard::register_key_event_callback([](Keyboard::KeyEvent event) { Shell::Get().KeyEventCallback(event); });
Input::register_key_event_callback([](Input::KeyEvent event) { Shell::Get().KeyEventCallback(event); });
m_buffer.Reserve(128);
}
@ -46,7 +46,7 @@ namespace Kernel
for (;;)
{
asm volatile("hlt");
Keyboard::update_keyboard();
Input::update();
}
}
@ -213,14 +213,14 @@ namespace Kernel
return 1;
}
void Shell::KeyEventCallback(Keyboard::KeyEvent event)
void Shell::KeyEventCallback(Input::KeyEvent event)
{
if (!event.pressed)
return;
switch (event.key)
{
case Keyboard::Key::Backspace:
case Input::Key::Backspace:
{
if (!m_buffer.Empty())
{
@ -233,8 +233,8 @@ namespace Kernel
break;
}
case Keyboard::Key::Enter:
case Keyboard::Key::NumpadEnter:
case Input::Key::Enter:
case Input::Key::NumpadEnter:
{
TTY_PRINT("\n");
ProcessCommand(MUST(m_buffer.SV().Split(' ')));
@ -243,16 +243,16 @@ namespace Kernel
break;
}
case Keyboard::Key::Escape:
case Input::Key::Escape:
TTY_PRINTLN("time since boot {} ms", PIT::ms_since_boot());
break;
case Keyboard::Key::Tab:
case Input::Key::Tab:
break;
default:
{
const char* utf8 = Keyboard::key_event_to_utf8(event);
const char* utf8 = Input::key_event_to_utf8(event);
if (utf8)
{
TTY_PRINT("{}", utf8);

View File

@ -1,8 +1,8 @@
#include <kernel/APIC.h>
#include <kernel/GDT.h>
#include <kernel/IDT.h>
#include <kernel/Input.h>
#include <kernel/IO.h>
#include <kernel/Keyboard.h>
#include <kernel/kmalloc.h>
#include <kernel/kprint.h>
#include <kernel/multiboot.h>
@ -86,7 +86,7 @@ extern "C" void kernel_main(multiboot_info_t* mbi, uint32_t magic)
TTY* tty1 = new TTY;
tty1->SetCursorPosition(0, 2);
if (!Keyboard::initialize())
if (!Input::initialize())
return;
ENABLE_INTERRUPTS();