// 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 #include #include #include #include // -- 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; }