283 lines
10 KiB
C
283 lines
10 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>
|
|
#include <hal_debug.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 = 5000000;
|
|
while (!(__inbyte(PS2_STAT) & 0x01u) && --timeout) { /* spin */ }
|
|
}
|
|
|
|
// Reads from PS2_DATA until the output buffer is empty
|
|
static void Ps2FlushOutput(void) {
|
|
int timeout = 100000;
|
|
while ((__inbyte(PS2_STAT) & 0x01u) && --timeout) {
|
|
__inbyte(PS2_DATA);
|
|
}
|
|
}
|
|
|
|
// 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 0: Deafen the controller and flush
|
|
// -----------------------------------------------------------------
|
|
Ps2WaitInput();
|
|
__outbyte(PS2_CMD, 0xA7u); // Disable Aux port (ignores mouse)
|
|
Ps2WaitInput();
|
|
__outbyte(PS2_CMD, 0xADu); // Disable Main port (ignores keyboard)
|
|
|
|
// Now flush the buffer. Because the ports are disabled, we guarantee
|
|
// no new data can sneak in behind our backs.
|
|
Ps2FlushOutput();
|
|
|
|
// -----------------------------------------------------------------
|
|
// Step 1: Safely configure the 8042 Command Byte
|
|
// -----------------------------------------------------------------
|
|
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);
|
|
|
|
// Re-enable both ports now that config is safely written
|
|
Ps2WaitInput();
|
|
__outbyte(PS2_CMD, 0xA8u); // Enable Aux port
|
|
Ps2WaitInput();
|
|
__outbyte(PS2_CMD, 0xAEu); // Enable Main port
|
|
|
|
// -----------------------------------------------------------------
|
|
// Step 2: Reset mouse (0xFF).
|
|
// -----------------------------------------------------------------
|
|
MouseSendByte(0xFFu); // reset
|
|
|
|
// The BIOS or an earlier phase might have left a movement packet
|
|
// partially in flight right as we re-enabled the port.
|
|
// We loop and discard anything that isn't the 0xFA ACK.
|
|
uint8_t ack;
|
|
int retries = 10;
|
|
do {
|
|
ack = MouseRecvByte();
|
|
} while (ack != 0xFAu && --retries > 0);
|
|
|
|
// Thanks to the increased Ps2WaitOutput timeout, we will actually
|
|
// wait long enough for the physical hardware self-test to finish here.
|
|
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;
|
|
}
|