Files
nt-lite/code/hal/hal_mouse.c
T
2026-05-04 09:17:00 +02:00

246 lines
9.0 KiB
C

// hal_mouse.c - PS/2 auxiliary mouse driver
//
// Sits on IRQ 12, sharing the 8042 controller with the keyboard (IRQ 1).
// hal_keyboard.c has already issued command 0xA8 (enable aux port) by the
// time HalMouseInitialize is called, so we only need to:
// 1. Enable aux interrupts in the 8042 command byte (bit 1, clears bit 5).
// 2. Send the standard mouse init sequence (reset → defaults → enable).
// 3. Wire up the ISR and expose the HAL_MOUSE vtable.
//
// Packet format - standard 3-byte PS/2 mouse (no scroll wheel):
// Byte 0: [YOV][XOV][YS][XS][1][M][R][L]
// Byte 1: delta-X unsigned magnitude (sign in byte-0 bit 4)
// Byte 2: delta-Y unsigned magnitude (sign in byte-0 bit 5); PS/2 Y+ = up
//
// The ISR accumulates the 3-byte packet, converts it to a screen-space delta
// (Y-axis flipped), clamps the absolute position to [0, bounds), enqueues a
// MOUSE_EVENT, and then calls EraseOldCursor / DrawNewCursor via the two
// screen helpers that hal_screen.c exports.
//
// Threading model
// ---------------
// Single-producer (ISR on IRQ 12) / single-consumer ring buffer.
// All ISR-written fields are volatile; on a uniprocessor this is sufficient.
// On SMP, add acquire/release barriers around gHead / gTail.
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <hal_api.h>
// -- PS/2 port addresses -------------------------------------------------------
#define PS2_DATA 0x60u
#define PS2_STAT 0x64u
#define PS2_CMD 0x64u
// -- Ring buffer ---------------------------------------------------------------
#define MOUSE_BUF_SIZE 64u
#define MOUSE_BUF_MASK (MOUSE_BUF_SIZE - 1u)
typedef struct {
int dx, dy;
uint8_t buttons; // bit0=left, bit1=right, bit2=middle
} MOUSE_EVENT;
static MOUSE_EVENT gBuf[MOUSE_BUF_SIZE];
static volatile int gHead = 0; // consumer read pointer
static volatile int gTail = 0; // producer write pointer (updated by ISR)
// -- Absolute cursor position & screen bounds ----------------------------------
static volatile int gMouseX = 0;
static volatile int gMouseY = 0;
static volatile int gBoundsW = 1920;
static volatile int gBoundsH = 1080;
// -- 3-byte packet assembly ----------------------------------------------------
static volatile uint8_t gPacket[3];
static volatile int gPacketIdx = 0; // 0, 1, or 2
// -- Optional callback (called from ISR) ---------------------------------------
static void (* volatile gCallback)(int dx, int dy, uint8_t buttons) = NULL;
// -- Cursor helpers provided by hal_screen.c -----------------------------------
// Declared here; defined in hal_screen.c.
extern void HalScreenDrawCursor(int x, int y);
extern void HalScreenEraseCursor(void);
// -- 8042 low-level helpers ----------------------------------------------------
// Wait until the 8042's input buffer is empty (bit 1 of status = 0) so we
// can safely write to PS2_DATA or PS2_CMD.
static void Ps2WaitInput(void) {
int timeout = 100000;
while ((__inbyte(PS2_STAT) & 0x02u) && --timeout) { /* spin */ }
}
// Wait until the 8042's output buffer has data (bit 0 of status = 1).
static void Ps2WaitOutput(void) {
int timeout = 100000;
while (!(__inbyte(PS2_STAT) & 0x01u) && --timeout) { /* spin */ }
}
// Route the next write to the auxiliary (mouse) device rather than the keyboard.
static void MouseSendByte(uint8_t byte) {
Ps2WaitInput();
__outbyte(PS2_CMD, 0xD4u); // "write to mouse" prefix
Ps2WaitInput();
__outbyte(PS2_DATA, byte);
}
// Read one byte from the PS/2 output buffer (shared by keyboard and mouse).
static uint8_t MouseRecvByte(void) {
Ps2WaitOutput();
return __inbyte(PS2_DATA);
}
// -- Interrupt service routine --------------------------------------------------
// Called with interrupts disabled by KeIrqDispatch (IRQ 12).
// The dispatcher sends the APIC EOI before this function is invoked.
static void MouseIsr(void) {
uint8_t data = __inbyte(PS2_DATA);
// Synchronisation guard: the first byte of every packet has bit 3 set.
// If we lose sync, wait for the next valid first byte before assembling.
if (gPacketIdx == 0) {
if (!(data & 0x08u)) return; // not a valid first byte - discard
}
gPacket[gPacketIdx++] = data;
if (gPacketIdx < 3) return; // packet not yet complete
gPacketIdx = 0;
// -- Decode flags ----------------------------------------------------------
uint8_t flags = gPacket[0];
// Overflow bits: if either is set the movement data is meaningless.
if (flags & 0xC0u) return;
uint8_t buttons = flags & 0x07u;
// Convert unsigned magnitudes + sign bits to signed 9-bit values.
int dx = (int)gPacket[1] - ((flags & 0x10u) ? 256 : 0);
int dy = (int)gPacket[2] - ((flags & 0x20u) ? 256 : 0);
dy = -dy; // PS/2 Y+ = up; screen Y+ = down, so invert.
// -- Update absolute position (clamped to screen bounds) ------------------
HalScreenEraseCursor();
int nx = gMouseX + dx;
int ny = gMouseY + dy;
if (nx < 0) nx = 0;
if (nx >= gBoundsW) nx = gBoundsW - 1;
if (ny < 0) ny = 0;
if (ny >= gBoundsH) ny = gBoundsH - 1;
gMouseX = nx;
gMouseY = ny;
HalScreenDrawCursor(nx, ny);
// -- Enqueue event; silently drop if buffer is full -----------------------
int next = (gTail + 1) & (int)MOUSE_BUF_MASK;
if (next != gHead) {
gBuf[gTail].dx = dx;
gBuf[gTail].dy = dy;
gBuf[gTail].buttons = buttons;
gTail = next;
}
// -- Optional callback (raw ISR context) -----------------------------------
void (*cb)(int, int, uint8_t) = (void (*)(int, int, uint8_t))gCallback;
if (cb) cb(dx, dy, buttons);
}
// -- Consumer API --------------------------------------------------------------
static bool MousePoll(void) {
return gHead != gTail;
}
static bool MouseRead(int* dx, int* dy, uint8_t* buttons) {
if (gHead == gTail) return false;
*dx = gBuf[gHead].dx;
*dy = gBuf[gHead].dy;
*buttons = gBuf[gHead].buttons;
gHead = (gHead + 1) & (int)MOUSE_BUF_MASK;
return true;
}
static void MouseGetPosition(int* x, int* y) {
*x = gMouseX;
*y = gMouseY;
}
static void MouseSetPosition(int x, int y) {
gMouseX = x;
gMouseY = y;
}
static void MouseSetBounds(int w, int h) {
gBoundsW = w;
gBoundsH = h;
}
static void MouseSetCallback(void (*cb)(int dx, int dy, uint8_t buttons)) {
gCallback = cb;
}
// -- Initializer ---------------------------------------------------------------
// Called from HalInitialize in hal_main.c after the screen is ready.
// hal_keyboard.c has already sent 0xA8 (enable aux port) to port 0x64.
void HalMouseInitialize(HAL_MOUSE* out, int screenW, int screenH) {
gBoundsW = screenW;
gBoundsH = screenH;
gMouseX = screenW / 2;
gMouseY = screenH / 2;
// -----------------------------------------------------------------
// Step 1: Enable aux interrupts in the 8042 command byte.
// - Bit 1 (0x02): enable auxiliary interrupt (drives IRQ 12).
// - Bit 5 (0x20): disables the aux clock when set; clear it.
// -----------------------------------------------------------------
Ps2WaitInput();
__outbyte(PS2_CMD, 0x20u); // read current command byte
Ps2WaitOutput();
uint8_t cmd = __inbyte(PS2_DATA);
cmd |= 0x02u; // enable aux interrupt
cmd &= ~0x20u; // clear "disable aux clock"
Ps2WaitInput();
__outbyte(PS2_CMD, 0x60u); // write command byte
Ps2WaitInput();
__outbyte(PS2_DATA, cmd);
// -----------------------------------------------------------------
// Step 2: Reset mouse (0xFF).
// Mouse replies: ACK (0xFA), self-test pass (0xAA), ID (0x00).
// -----------------------------------------------------------------
MouseSendByte(0xFFu); // reset
MouseRecvByte(); // 0xFA - ACK
MouseRecvByte(); // 0xAA - BAT passed
MouseRecvByte(); // 0x00 - device ID
// -----------------------------------------------------------------
// Step 3: Set default parameters (sampling rate 100, 4 counts/mm).
// Mouse replies: ACK.
// -----------------------------------------------------------------
MouseSendByte(0xF6u); // set defaults
MouseRecvByte(); // ACK
// -----------------------------------------------------------------
// Step 4: Enable data reporting.
// Mouse will now send 3-byte packets on every movement/button event.
// -----------------------------------------------------------------
MouseSendByte(0xF4u); // enable data reporting
MouseRecvByte(); // ACK
// Draw the initial cursor at the centre of the screen.
HalScreenDrawCursor(gMouseX, gMouseY);
// Populate vtable.
out->Poll = MousePoll;
out->Read = MouseRead;
out->SetCallback = MouseSetCallback;
out->GetPosition = MouseGetPosition;
out->SetPosition = MouseSetPosition;
out->SetBounds = MouseSetBounds;
out->Isr = MouseIsr;
out->IrqNumber = 12;
}