#include #include #include #include #include #include #include #include #include // PS/2 Mouse ports #define PS2_DATA_PORT 0x60 #define PS2_STATUS_PORT 0x64 #define PS2_COMMAND_PORT 0x64 // PIC data ports (for unmasking IRQ12 on the slave PIC) #define PIC1_DATA_PORT 0x21 #define PIC2_DATA_PORT 0xA1 // PS/2 Mouse commands #define MOUSE_CMD_RESET 0xFF #define MOUSE_CMD_RESEND 0xFE #define MOUSE_CMD_SET_DEFAULTS 0xF6 #define MOUSE_CMD_DISABLE 0xF5 #define MOUSE_CMD_ENABLE 0xF4 #define MOUSE_CMD_SET_SAMPLE_RATE 0xF3 #define MOUSE_CMD_GET_DEVICE_ID 0xF2 #define MOUSE_CMD_SET_REMOTE_MODE 0xF0 #define MOUSE_CMD_SET_WRAP_MODE 0xEE #define MOUSE_CMD_RESET_WRAP_MODE 0xEC #define MOUSE_CMD_READ_DATA 0xEB #define MOUSE_CMD_SET_STREAM_MODE 0xEA #define MOUSE_CMD_STATUS_REQUEST 0xE9 #define MOUSE_CMD_SET_RESOLUTION 0xE8 #define MOUSE_CMD_SET_SCALING_2_1 0xE7 #define MOUSE_CMD_SET_SCALING_1_1 0xE6 // PS/2 Mouse responses #define MOUSE_ACK 0xFA #define MOUSE_NACK 0xFE #define MOUSE_ERROR 0xFC // Mouse packet sizes #define MOUSE_PACKET_SIZE 4 /* default to 4 if wheel is enabled; will use 3 if not */ // Global mouse state mouse_state_t g_mouse_state = {0}; static const rei_image_t* g_cursor_image_ptr = NULL; // Mouse bounds static int mouse_min_x = 0, mouse_min_y = 0; static int mouse_max_x = 79, mouse_max_y = 24; // VGA text mode bounds // Mouse packet buffer static uint8 mouse_packet_buffer[MOUSE_PACKET_SIZE]; static int mouse_packet_index = 0; static int mouse_packet_len = 3; // 3 by default; may upgrade to 4 if wheel detected // Device capabilities static int mouse_has_wheel = 0; static int mouse_has_5btn = 0; // Helpers for packet synchronization and sign extension static inline int mouse_packet_is_sync(uint8 b0) { return (b0 & 0x08) != 0; } static inline int mouse_packet_has_overflow(uint8 b0) { return (b0 & 0xC0) != 0; } static inline int8 sign_extend_8(uint8 v) { return (int8)v; } static void mouse_enable_irq12(void) { // Unmask cascade IRQ2 on the master PIC so slave PIC IRQs can be delivered uint8 m1 = inportb(PIC1_DATA_PORT); m1 &= (uint8)~(1u << 2); outportb(PIC1_DATA_PORT, m1); // Unmask IRQ12 (bit 4) on the slave PIC uint8 m2 = inportb(PIC2_DATA_PORT); m2 &= (uint8)~(1u << 4); outportb(PIC2_DATA_PORT, m2); } static int mouse_ctx_allow(uint32 caps, uint32 cost) { command_context_t* ctx = current_command_context; if (ctx && !cap_check(ctx->caps, caps)) return 0; if (ctx) { scheduler_account(ctx->wo, cost); scheduler_yield_if_needed(ctx->wo); if (sched_det_is_enabled()) ctx->det_seq++; } return 1; } static void mouse_ctx_account(uint32 cost) { command_context_t* ctx = current_command_context; if (!ctx) return; scheduler_account(ctx->wo, cost); scheduler_yield_if_needed(ctx->wo); if (sched_det_is_enabled()) ctx->det_seq++; } // Wait for mouse to be ready static int mouse_wait_for_ready(void) { int timeout = 100000; while (timeout--) { if ((timeout & 0x3FF) == 0) mouse_ctx_account(SCHED_COST_CONSOLE); if (!(inportb(PS2_STATUS_PORT) & 0x02)) { return 0; // Ready } } return -1; // Timeout } // Wait for mouse data static int mouse_wait_for_data(void) { int timeout = 100000; while (timeout--) { if ((timeout & 0x3FF) == 0) mouse_ctx_account(SCHED_COST_CONSOLE); if (inportb(PS2_STATUS_PORT) & 0x01) { return 0; // Data available } } return -1; // Timeout } // Send command to mouse static int mouse_send_command(uint8 command) { if (!mouse_ctx_allow(CAP_DEV_INPUT, SCHED_COST_CONSOLE)) return -1; if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_COMMAND_PORT, 0xD4); // Tell controller we're sending to mouse if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_DATA_PORT, command); // Wait for ACK if (mouse_wait_for_data() != 0) return -1; uint8 response = inportb(PS2_DATA_PORT); return (response == MOUSE_ACK) ? 0 : -1; } // Read byte from mouse static int mouse_read_byte(uint8* byte) { if (!mouse_ctx_allow(CAP_DEV_INPUT, SCHED_COST_CONSOLE)) return -1; if (mouse_wait_for_data() != 0) return -1; *byte = inportb(PS2_DATA_PORT); return 0; } // Initialize mouse int mouse_init(void) { if (!mouse_ctx_allow(CAP_DEV_INPUT, SCHED_COST_CONSOLE)) return -1; memset(&g_mouse_state, 0, sizeof(mouse_state_t)); // Enable mouse in PS/2 controller if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_COMMAND_PORT, 0xA8); // Enable mouse // Enable mouse interrupts if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_COMMAND_PORT, 0x20); // Read command byte if (mouse_wait_for_data() != 0) return -1; uint8 status = inportb(PS2_DATA_PORT); // Command byte (typical i8042): // bit0: enable IRQ1 (keyboard), bit1: enable IRQ12 (mouse) // bit4: disable keyboard clock, bit5: disable mouse clock (1=disabled) // Some emulators/firmware leave clock-disable bits set; clear them explicitly. status |= 0x03; // enable IRQ1 + IRQ12 status &= (uint8)~0x30; // ensure both ports are enabled (clear clock-disable bits) if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_COMMAND_PORT, 0x60); // Write command byte if (mouse_wait_for_ready() != 0) return -1; outportb(PS2_DATA_PORT, status); // Reset mouse (best-effort, avoid noisy logs) if (mouse_send_command(MOUSE_CMD_RESET) != 0) { return -1; } // Wait for reset response if (mouse_wait_for_data() != 0) return -1; uint8 response = inportb(PS2_DATA_PORT); if (response != 0xAA) { return -1; } // Wait for device ID if (mouse_wait_for_data() != 0) return -1; response = inportb(PS2_DATA_PORT); // Set default settings if (mouse_send_command(MOUSE_CMD_SET_DEFAULTS) != 0) { return -1; } // Try to enable IntelliMouse wheel (rate sequence 200,100,80 then read device ID) // Best-effort: ignore failures if (mouse_send_command(MOUSE_CMD_SET_SAMPLE_RATE) == 0) (void)mouse_send_command(200); if (mouse_send_command(MOUSE_CMD_SET_SAMPLE_RATE) == 0) (void)mouse_send_command(100); if (mouse_send_command(MOUSE_CMD_SET_SAMPLE_RATE) == 0) (void)mouse_send_command(80); // Read device ID if (mouse_send_command(MOUSE_CMD_GET_DEVICE_ID) == 0) { if (mouse_wait_for_data() == 0) { uint8 id = inportb(PS2_DATA_PORT); if (id == 3) { mouse_has_wheel = 1; mouse_packet_len = 4; } else if (id == 4) { mouse_has_wheel = 1; mouse_has_5btn = 1; mouse_packet_len = 4; } } } // Enable mouse streaming if (mouse_send_command(MOUSE_CMD_ENABLE) != 0) { return -1; } // Set sample rate to 200 for better responsiveness (many emulators/hardware support this) if (mouse_send_command(MOUSE_CMD_SET_SAMPLE_RATE) == 0) { (void)mouse_send_command(200); } // Initialize mouse position to center of screen g_mouse_state.x = mouse_max_x / 2; g_mouse_state.y = mouse_max_y / 2; g_mouse_state.initialized = 1; // Register mouse interrupt handler register_interrupt_handler(12, mouse_interrupt_handler); // IRQ 12 is mouse // Ensure IRQ12 can actually fire (irq_init only unmasks IRQ0 by default). // Without this, QEMU can queue mouse bytes faster than our frame-loop polling drains them, // making cursor movement appear to "stop" after a small initial delta. mouse_enable_irq12(); return 0; } // Cleanup mouse void mouse_cleanup(void) { if (!mouse_ctx_allow(CAP_DEV_INPUT, SCHED_COST_CONSOLE)) return; if (g_mouse_state.initialized) { mouse_send_command(MOUSE_CMD_DISABLE); g_mouse_state.initialized = 0; } } // Mouse interrupt handler void mouse_interrupt_handler(void) { if (!g_mouse_state.initialized) { return; } // Drain a bounded number of AUX bytes. QEMU can enqueue multiple bytes between interrupts; // draining here avoids output-buffer buildup that makes the cursor appear to "freeze". for (int i = 0; i < 256; ++i) { uint8 st = inportb(PS2_STATUS_PORT); if (!(st & 0x01)) break; // no data if (!(st & 0x20)) break; // not AUX data uint8 data = inportb(PS2_DATA_PORT); // Packet synchronization: the first byte must have bit 3 set. if (mouse_packet_index == 0) { if (!mouse_packet_is_sync(data)) { continue; } } mouse_packet_buffer[mouse_packet_index] = data; mouse_packet_index++; if (mouse_packet_index == mouse_packet_len) { uint8 status = mouse_packet_buffer[0]; if (mouse_packet_has_overflow(status)) { // Drop overflow packets; their deltas are truncated and can cause large jumps. mouse_packet_index = 0; continue; } int8 delta_x = sign_extend_8(mouse_packet_buffer[1]); int8 delta_y = sign_extend_8(mouse_packet_buffer[2]); int wheel = 0; if (mouse_has_wheel && mouse_packet_len >= 4) { int8 z = sign_extend_8(mouse_packet_buffer[3]); z = (z & 0x0F); if (z & 0x08) z |= 0xF0; wheel = (int)z; } g_mouse_state.prev_buttons = g_mouse_state.buttons; g_mouse_state.buttons = status & 0x07; if (mouse_has_5btn && mouse_packet_len >= 4) { uint8 b4 = mouse_packet_buffer[3]; if (b4 & 0x10) g_mouse_state.buttons |= MOUSE_BUTTON_4; if (b4 & 0x20) g_mouse_state.buttons |= MOUSE_BUTTON_5; } g_mouse_state.delta_x += delta_x; g_mouse_state.delta_y += delta_y; g_mouse_state.wheel_delta += wheel; g_mouse_state.x += delta_x; g_mouse_state.y -= delta_y; if (g_mouse_state.x < mouse_min_x) g_mouse_state.x = mouse_min_x; if (g_mouse_state.x > mouse_max_x) g_mouse_state.x = mouse_max_x; if (g_mouse_state.y < mouse_min_y) g_mouse_state.y = mouse_min_y; if (g_mouse_state.y > mouse_max_y) g_mouse_state.y = mouse_max_y; mouse_packet_index = 0; } } } // Read mouse event int mouse_read_event(mouse_event_t* event) { if (!g_mouse_state.initialized || !event) return -1; event->x = g_mouse_state.x; event->y = g_mouse_state.y; event->delta_x = g_mouse_state.delta_x; event->delta_y = g_mouse_state.delta_y; event->wheel_delta = g_mouse_state.wheel_delta; event->buttons = g_mouse_state.buttons; event->button_changes = g_mouse_state.buttons ^ g_mouse_state.prev_buttons; // Consume the button transition so that subsequent reads within the // same hardware-interrupt cycle see button_changes == 0 (no repeat). g_mouse_state.prev_buttons = g_mouse_state.buttons; // Clear deltas after reading g_mouse_state.delta_x = 0; g_mouse_state.delta_y = 0; g_mouse_state.wheel_delta = 0; return 0; } // Get mouse position int mouse_get_position(int* x, int* y) { if (!g_mouse_state.initialized || !x || !y) return -1; *x = g_mouse_state.x; *y = g_mouse_state.y; return 0; } // Get mouse button state int mouse_get_buttons(uint8* buttons) { if (!g_mouse_state.initialized || !buttons) return -1; *buttons = g_mouse_state.buttons; return 0; } // Set mouse position void mouse_set_position(int x, int y) { if (!g_mouse_state.initialized) return; g_mouse_state.x = x; g_mouse_state.y = y; // Clamp to bounds if (g_mouse_state.x < mouse_min_x) g_mouse_state.x = mouse_min_x; if (g_mouse_state.x > mouse_max_x) g_mouse_state.x = mouse_max_x; if (g_mouse_state.y < mouse_min_y) g_mouse_state.y = mouse_min_y; if (g_mouse_state.y > mouse_max_y) g_mouse_state.y = mouse_max_y; } // Set mouse bounds void mouse_set_bounds(int min_x, int min_y, int max_x, int max_y) { mouse_min_x = min_x; mouse_min_y = min_y; mouse_max_x = max_x; mouse_max_y = max_y; // Clamp current position to new bounds if (g_mouse_state.initialized) { mouse_set_position(g_mouse_state.x, g_mouse_state.y); } } void mouse_set_cursor_image(const rei_image_t* image) { g_cursor_image_ptr = image; // currently unused by driver; UI layer uses its own copy } // Non-blocking poll for AUX (mouse) bytes; safe to call from main loop int mouse_poll(void) { int progressed = 0; // Drain a few AUX bytes per call to avoid starving the UI for (int i = 0; i < 256; ++i) { uint8 status = inportb(PS2_STATUS_PORT); if (!(status & 0x01)) break; // no data waiting if (!(status & 0x20)) break; // data is for keyboard; don't consume here uint8 data = inportb(PS2_DATA_PORT); // AUX data: assemble packet with synchronization if (mouse_packet_index == 0 && !mouse_packet_is_sync(data)) { // skip until a valid header continue; } mouse_packet_buffer[mouse_packet_index] = data; mouse_packet_index++; progressed = 1; if (mouse_packet_index == mouse_packet_len) { uint8 st = mouse_packet_buffer[0]; if (mouse_packet_has_overflow(st)) { mouse_packet_index = 0; continue; } int8 dx = sign_extend_8(mouse_packet_buffer[1]); int8 dy = sign_extend_8(mouse_packet_buffer[2]); int wheel = 0; if (mouse_has_wheel && mouse_packet_len >= 4) { int8 z = sign_extend_8(mouse_packet_buffer[3]); z = (z & 0x0F); if (z & 0x08) z |= 0xF0; // sign-extend nibble wheel = (int)z; } if (!g_mouse_state.initialized) g_mouse_state.initialized = 1; g_mouse_state.prev_buttons = g_mouse_state.buttons; g_mouse_state.buttons = st & 0x07; if (mouse_has_5btn && mouse_packet_len >= 4) { uint8 b4 = mouse_packet_buffer[3]; if (b4 & 0x10) g_mouse_state.buttons |= MOUSE_BUTTON_4; if (b4 & 0x20) g_mouse_state.buttons |= MOUSE_BUTTON_5; } g_mouse_state.delta_x += dx; g_mouse_state.delta_y += dy; g_mouse_state.wheel_delta += wheel; g_mouse_state.x += dx; g_mouse_state.y -= dy; if (g_mouse_state.x < mouse_min_x) g_mouse_state.x = mouse_min_x; if (g_mouse_state.x > mouse_max_x) g_mouse_state.x = mouse_max_x; if (g_mouse_state.y < mouse_min_y) g_mouse_state.y = mouse_min_y; if (g_mouse_state.y > mouse_max_y) g_mouse_state.y = mouse_max_y; mouse_packet_index = 0; // ready for next packet } } return progressed ? 0 : 1; // 0=updated, 1=nothing to read }