feat: partially implement PS/2 input subsystem with keyboard and mouse support

This implementation is incomplete, and doesn't work, but it will soon, to be determined, hopefully, uh, yknow.

Signed-off-by kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-04-17 17:34:46 -04:00
parent 016ee32987
commit 551eb00708
13 changed files with 686 additions and 8 deletions
+2 -2
View File
@@ -130,7 +130,7 @@ bool ata_read_sectors(uint64_t lba, uint8_t sector_count, void* buffer)
return true;
}
bool ata_write_sectors(uint64_t lba, uint8_t sector_count, const void* buffer) {
bool ata_write_sectors(uint64_t lba, uint8_t sector_count, void* buffer) {
if (sector_count == 0) return true;
@@ -143,7 +143,7 @@ bool ata_write_sectors(uint64_t lba, uint8_t sector_count, const void* buffer) {
x86_64_outb(ATA_PRIMARY_LBAHIGH, (uint8_t)(lba >> 16));
x86_64_outb(ATA_PRIMARY_COMMAND, 0x30); // write sectors
const uint16_t* buf = buffer;
uint16_t* buf = buffer;
for (uint8_t i = 0; i < sector_count; i++) {
if (!ata_poll()) {
+1 -1
View File
@@ -10,4 +10,4 @@ void ata_identify(void);
// Buffer must be large enough (sector_count * 512).
bool ata_read_sectors(uint64_t lba, uint8_t sector_count, void* buffer);
bool ata_write_sectors(uint64_t lba, uint8_t sector_count, const void* buffer);
bool ata_write_sectors(uint64_t lba, uint8_t sector_count, void* buffer);
+5
View File
@@ -63,4 +63,9 @@ void x86_64_IRQ_Initialize(void)
void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler)
{
g_IRQHandlers[irq] = handler;
}
void x86_64_IRQ_Unmask(int irq)
{
if (g_Driver) g_Driver->Unmask(irq);
}
+2 -1
View File
@@ -5,4 +5,5 @@
typedef void (*IRQHandler)(Registers* regs);
void x86_64_IRQ_Initialize();
void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler);
void x86_64_IRQ_RegisterHandler(int irq, IRQHandler handler);
void x86_64_IRQ_Unmask(int irq);
+332
View File
@@ -0,0 +1,332 @@
#include "ps2.h"
#include "io.h"
#include "irq.h"
#include "stdio.h"
#include "mp/spinlock.h"
#include <stddef.h>
#include "input/input.h"
#define PS2_DATA 0x60
#define PS2_COMMAND 0x64
/* ====================== CONTROLLER HELPERS ====================== */
static inline bool ps2_wait_write(void) {
uint32_t timeout = 1000000;
while (x86_64_inb(PS2_COMMAND) & 2) {
if (--timeout == 0) {
printf("PS/2: write timeout!\n");
return false;
}
asm volatile("pause");
}
return true;
}
static inline bool ps2_wait_read(void) {
uint32_t timeout = 1000000;
while (!(x86_64_inb(PS2_COMMAND) & 1)) {
if (--timeout == 0) {
printf("PS/2: read timeout!\n");
return false;
}
asm volatile("pause");
}
return true;
}
static inline uint8_t ps2_read(void) {
if (!ps2_wait_read()) return 0xFF; // error value
return x86_64_inb(PS2_DATA);
}
static inline void ps2_write(uint8_t val) {
if (!ps2_wait_write()) return;
x86_64_outb(PS2_DATA, val);
}
static inline void ps2_command(uint8_t cmd) {
if (!ps2_wait_write()) return;
x86_64_outb(PS2_COMMAND, cmd);
}
/* Send command to AUX (mouse) port */
static void ps2_write_mouse(uint8_t val) {
ps2_command(0xD4); // tell controller next byte goes to mouse
ps2_write(val);
}
/* ====================== KEYBOARD RING BUFFER ====================== */
#define KB_BUFFER_SIZE 256
static uint8_t kb_buffer[KB_BUFFER_SIZE];
static size_t kb_read_idx = 0;
static size_t kb_write_idx = 0;
static spinlock_t kb_lock = SPINLOCK_INIT;
/* US QWERTY scancode set 2 -> ASCII (unshifted) */
static const char kb_map[128] = {
/*00*/ 0, 0, '1', '2', '3', '4', '5', '6',
/*08*/ '7', '8', '9', '0', '-', '=', '\b', '\t',
/*10*/ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
/*18*/ 'o', 'p', '[', ']', '\n', 0, 'a', 's',
/*20*/ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
/*28*/ '\'', '`', 0, '\\', 'z', 'x', 'c', 'v',
/*30*/ 'b', 'n', 'm', ',', '.', '/', 0, '*',
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
/*40*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*48*/ 0, 0, '-', 0, 0, 0, '+', 0,
/*50*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*58*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*60*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*68*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*70*/ 0, 0, 0, 0, 0, 0, 0, 0,
/*78*/ 0, 0, 0, 0, 0, 0, 0, 0,
};
/* Shifted version */
static const char kb_map_shift[128] = {
/*00*/ 0, 0, '!', '@', '#', '$', '%', '^',
/*08*/ '&', '*', '(', ')', '_', '+', '\b', '\t',
/*10*/ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
/*18*/ 'O', 'P', '{', '}', '\n', 0, 'A', 'S',
/*20*/ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
/*28*/ '"', '~', 0, '|', 'Z', 'X', 'C', 'V',
/*30*/ 'B', 'N', 'M', '<', '>', '?', 0, '*',
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
};
static bool shift = false;
static bool caps = false;
static void kb_enqueue(uint8_t sc) {
spinlock_acquire_or_wait(&kb_lock);
size_t next = (kb_write_idx + 1) % KB_BUFFER_SIZE;
if (next != kb_read_idx) {
kb_buffer[kb_write_idx] = sc;
kb_write_idx = next;
}
spinlock_drop(&kb_lock);
}
bool ps2_keyboard_has_data(void) {
spinlock_acquire_or_wait(&kb_lock);
bool has = (kb_read_idx != kb_write_idx);
spinlock_drop(&kb_lock);
return has;
}
/* ====================== MOUSE RING BUFFER ====================== */
#define MOUSE_BUFFER_SIZE 32
static mouse_packet_t mouse_buffer[MOUSE_BUFFER_SIZE];
static size_t mouse_read_idx = 0;
static size_t mouse_write_idx = 0;
static spinlock_t mouse_lock = SPINLOCK_INIT;
static uint8_t mouse_packet[4];
static int mouse_idx = 0;
static int mouse_packet_size = 3; // 3 or 4 for wheel
static void mouse_enqueue(const mouse_packet_t* pkt) {
spinlock_acquire_or_wait(&mouse_lock);
size_t next = (mouse_write_idx + 1) % MOUSE_BUFFER_SIZE;
if (next != mouse_read_idx) {
mouse_buffer[mouse_write_idx] = *pkt;
mouse_write_idx = next;
}
spinlock_drop(&mouse_lock);
}
bool ps2_mouse_get_packet(mouse_packet_t* out) {
spinlock_acquire_or_wait(&mouse_lock);
if (mouse_read_idx == mouse_write_idx) {
spinlock_drop(&mouse_lock);
return false;
}
*out = mouse_buffer[mouse_read_idx];
mouse_read_idx = (mouse_read_idx + 1) % MOUSE_BUFFER_SIZE;
spinlock_drop(&mouse_lock);
return true;
}
bool ps2_mouse_has_packet(void) {
spinlock_acquire_or_wait(&mouse_lock);
bool has = (mouse_read_idx != mouse_write_idx);
spinlock_drop(&mouse_lock);
return has;
}
/* ====================== IRQ HANDLERS ====================== */
void keyboard_irq_handler(Registers* regs) {
(void)regs;
uint8_t sc = x86_64_inb(PS2_DATA);
// Optional: keep the old buffer if you still use the polling API somewhere
kb_enqueue(sc);
// === NEW: process and push to the input subsystem ===
bool released = (sc & 0x80) != 0;
sc &= 0x7F;
if (sc == 0x2A || sc == 0x36) { // L/R Shift
shift = !released;
return;
}
if (sc == 0x3A && !released) { // Caps Lock
caps = !caps;
return;
}
bool is_alpha = (sc >= 0x10 && sc <= 0x19) ||
(sc >= 0x1E && sc <= 0x26) ||
(sc >= 0x2C && sc <= 0x32);
bool use_upper = shift ^ (caps && is_alpha);
char ascii = use_upper ? kb_map_shift[sc] : kb_map[sc];
input_push_key(sc, released ? 0 : ascii, !released);
}
void mouse_irq_handler(Registers* regs)
{
(void)regs;
uint8_t data = x86_64_inb(PS2_DATA);
if (mouse_idx == 0 && !(data & 0x08)) return;
mouse_packet[mouse_idx++] = data;
if (mouse_idx == mouse_packet_size) {
mouse_packet_t pkt = {0};
uint8_t status = mouse_packet[0];
pkt.overflow_x = !!(status & (1 << 6));
pkt.overflow_y = !!(status & (1 << 7));
if (pkt.overflow_x || pkt.overflow_y) {
mouse_idx = 0;
return;
}
pkt.buttons = status & 0x07;
pkt.x_delta = (int8_t)mouse_packet[1];
pkt.y_delta = -(int8_t)mouse_packet[2];
pkt.z_delta = (mouse_packet_size == 4) ? (int8_t)mouse_packet[3] : 0;
mouse_enqueue(&pkt);
if (pkt.x_delta || pkt.y_delta)
input_push_mouse_rel(pkt.x_delta, pkt.y_delta);
if (pkt.z_delta)
input_push_mouse_wheel(pkt.z_delta);
static uint8_t prev_buttons = 0;
uint8_t changed = pkt.buttons ^ prev_buttons;
for (int i = 0; i < 3; i++) {
if (changed & (1 << i))
input_push_mouse_btn(i, !!(pkt.buttons & (1 << i)));
}
prev_buttons = pkt.buttons;
mouse_idx = 0;
}
}
/* ====================== DEVICE INITIALIZATION ====================== */
static bool ps2_controller_init(void) {
ps2_command(0xAD);
ps2_command(0xA7);
while (x86_64_inb(PS2_COMMAND) & 1)
x86_64_inb(PS2_DATA);
ps2_command(0xAA);
if (ps2_read() != 0x55) {
printf("PS/2: Controller self-test failed!\n");
return false;
}
ps2_command(0xAB);
if (ps2_read() != 0x00) {
printf("PS/2: Keyboard interface test failed!\n");
return false;
}
ps2_command(0xA9);
if (ps2_read() != 0x00) {
printf("PS/2: Mouse interface test failed (no mouse?)\n");
}
ps2_command(0x20);
uint8_t cfg = ps2_read();
cfg |= (1 << 0) | (1 << 1) | (1 << 6);
cfg &= ~((1 << 4) | (1 << 5));
ps2_command(0x60);
ps2_write(cfg);
ps2_command(0xAE);
ps2_command(0xA8);
printf("PS/2: Controller ready (IRQs enabled, translation on)\n");
return true;
}
static void ps2_keyboard_init(void) {
ps2_write(0xFF);
ps2_read(); // ACK
ps2_read(); ps2_read(); // AA 00
ps2_write(0xF4);
if (ps2_read() != 0xFA)
printf("PS/2: Keyboard enable failed\n");
printf("PS/2: Keyboard initialized (scancode set 2)\n");
}
static void ps2_mouse_init(void) {
ps2_write_mouse(0xFF);
ps2_read(); ps2_read(); ps2_read(); // ACK, AA, 00
ps2_write_mouse(0xF3); ps2_write_mouse(200);
ps2_write_mouse(0xF3); ps2_write_mouse(100);
ps2_write_mouse(0xF3); ps2_write_mouse(80);
ps2_write_mouse(0xF2);
uint8_t id = ps2_read();
if (id == 0x03) {
mouse_packet_size = 4;
printf("PS/2: IntelliMouse (wheel) detected\n");
} else {
mouse_packet_size = 3;
printf("PS/2: Standard mouse detected\n");
}
ps2_write_mouse(0xF6);
ps2_write_mouse(0xF4);
printf("PS/2: Mouse initialized (packet size %d)\n", mouse_packet_size);
}
/* ====================== MAIN INIT ====================== */
void ps2_init(void) {
if (!ps2_controller_init()) {
printf("PS/2: Controller initialization failed - input disabled\n");
return;
}
input_init();
ps2_keyboard_init();
ps2_mouse_init();
x86_64_IRQ_RegisterHandler(1, keyboard_irq_handler);
x86_64_IRQ_RegisterHandler(12, mouse_irq_handler);
x86_64_IRQ_Unmask(1);
x86_64_IRQ_Unmask(12);
printf("PS/2 subsystem initialized successfully!\n");
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "mp/spinlock.h"
/* ====================== PUBLIC API ====================== */
/* Keyboard */
uint8_t ps2_keyboard_get_scancode(void); // non-blocking, returns 0 if empty
bool ps2_keyboard_has_data(void);
char ps2_keyboard_get_char(void); // processed ASCII (handles shift/caps)
/* Mouse */
typedef struct {
int8_t x_delta;
int8_t y_delta;
int8_t z_delta; // wheel (0 if not supported)
uint8_t buttons; // bit 0 = left, bit 1 = right, bit 2 = middle
bool overflow_x;
bool overflow_y;
} mouse_packet_t;
bool ps2_mouse_get_packet(mouse_packet_t* out_packet); // non-blocking, returns false if empty
bool ps2_mouse_has_packet(void);
/* Subsystem */
void ps2_init(void);