#include // PS/2 input driver package entry point. This file registers keyboard and mouse devices. #include "ps2.h" static KDPC gKeyboardDpc; static KDPC gMouseDpc; static volatile bool gKeyboardNextExtended; static INPUT_KEY_EVENT gKeyboardDpcQueue[KEYBOARD_DPC_QUEUE_SIZE]; static volatile int gKeyboardDpcHead; static volatile int gKeyboardDpcTail; static volatile uint8_t gKeyboardDown[256]; static volatile uint8_t gMousePacket[3]; static volatile int gMousePacketIndex; static PS2_MOUSE_PACKET gMouseDpcQueue[MOUSE_DPC_QUEUE_SIZE]; static volatile int gMouseDpcHead; static volatile int gMouseDpcTail; static volatile uint32_t gKeyboardIrqCount; static volatile uint32_t gKeyboardByteCount; static volatile uint32_t gKeyboardEventCount; static volatile uint32_t gMouseIrqCount; static volatile uint32_t gMouseByteCount; static volatile uint32_t gMousePacketCount; static volatile uint32_t gKeyboardForeignIrqCount; static volatile uint32_t gMouseForeignIrqCount; // Implements the ps2 Set1 To Key operation. static INPUT_KEY Ps2Set1ToKey(uint8_t make, bool extended) { if (extended) { switch (make) { case 0x1C: return INPUT_KEY_KP_ENTER; case 0x1D: return INPUT_KEY_RIGHT_CTRL; case 0x35: return INPUT_KEY_KP_DIVIDE; case 0x38: return INPUT_KEY_RIGHT_ALT; case 0x47: return INPUT_KEY_HOME; case 0x48: return INPUT_KEY_UP; case 0x49: return INPUT_KEY_PAGE_UP; case 0x4B: return INPUT_KEY_LEFT; case 0x4D: return INPUT_KEY_RIGHT; case 0x4F: return INPUT_KEY_END; case 0x50: return INPUT_KEY_DOWN; case 0x51: return INPUT_KEY_PAGE_DOWN; case 0x52: return INPUT_KEY_INSERT; case 0x53: return INPUT_KEY_DELETE; default: return INPUT_KEY_NONE; } } switch (make) { case 0x01: return INPUT_KEY_ESCAPE; case 0x02: return INPUT_KEY_1; case 0x03: return INPUT_KEY_2; case 0x04: return INPUT_KEY_3; case 0x05: return INPUT_KEY_4; case 0x06: return INPUT_KEY_5; case 0x07: return INPUT_KEY_6; case 0x08: return INPUT_KEY_7; case 0x09: return INPUT_KEY_8; case 0x0A: return INPUT_KEY_9; case 0x0B: return INPUT_KEY_0; case 0x0C: return INPUT_KEY_MINUS; case 0x0D: return INPUT_KEY_EQUAL; case 0x0E: return INPUT_KEY_BACKSPACE; case 0x0F: return INPUT_KEY_TAB; case 0x10: return INPUT_KEY_Q; case 0x11: return INPUT_KEY_W; case 0x12: return INPUT_KEY_E; case 0x13: return INPUT_KEY_R; case 0x14: return INPUT_KEY_T; case 0x15: return INPUT_KEY_Y; case 0x16: return INPUT_KEY_U; case 0x17: return INPUT_KEY_I; case 0x18: return INPUT_KEY_O; case 0x19: return INPUT_KEY_P; case 0x1A: return INPUT_KEY_LEFT_BRACKET; case 0x1B: return INPUT_KEY_RIGHT_BRACKET; case 0x1C: return INPUT_KEY_ENTER; case 0x1D: return INPUT_KEY_LEFT_CTRL; case 0x1E: return INPUT_KEY_A; case 0x1F: return INPUT_KEY_S; case 0x20: return INPUT_KEY_D; case 0x21: return INPUT_KEY_F; case 0x22: return INPUT_KEY_G; case 0x23: return INPUT_KEY_H; case 0x24: return INPUT_KEY_J; case 0x25: return INPUT_KEY_K; case 0x26: return INPUT_KEY_L; case 0x27: return INPUT_KEY_SEMICOLON; case 0x28: return INPUT_KEY_APOSTROPHE; case 0x29: return INPUT_KEY_GRAVE; case 0x2A: return INPUT_KEY_LEFT_SHIFT; case 0x2B: return INPUT_KEY_BACKSLASH; case 0x2C: return INPUT_KEY_Z; case 0x2D: return INPUT_KEY_X; case 0x2E: return INPUT_KEY_C; case 0x2F: return INPUT_KEY_V; case 0x30: return INPUT_KEY_B; case 0x31: return INPUT_KEY_N; case 0x32: return INPUT_KEY_M; case 0x33: return INPUT_KEY_COMMA; case 0x34: return INPUT_KEY_PERIOD; case 0x35: return INPUT_KEY_SLASH; case 0x36: return INPUT_KEY_RIGHT_SHIFT; case 0x37: return INPUT_KEY_KP_MULTIPLY; case 0x38: return INPUT_KEY_LEFT_ALT; case 0x39: return INPUT_KEY_SPACE; case 0x3A: return INPUT_KEY_CAPS_LOCK; case 0x3B: return INPUT_KEY_F1; case 0x3C: return INPUT_KEY_F2; case 0x3D: return INPUT_KEY_F3; case 0x3E: return INPUT_KEY_F4; case 0x3F: return INPUT_KEY_F5; case 0x40: return INPUT_KEY_F6; case 0x41: return INPUT_KEY_F7; case 0x42: return INPUT_KEY_F8; case 0x43: return INPUT_KEY_F9; case 0x44: return INPUT_KEY_F10; case 0x45: return INPUT_KEY_NUM_LOCK; case 0x47: return INPUT_KEY_KP_7; case 0x48: return INPUT_KEY_KP_8; case 0x49: return INPUT_KEY_KP_9; case 0x4A: return INPUT_KEY_KP_MINUS; case 0x4B: return INPUT_KEY_KP_4; case 0x4C: return INPUT_KEY_KP_5; case 0x4D: return INPUT_KEY_KP_6; case 0x4E: return INPUT_KEY_KP_PLUS; case 0x4F: return INPUT_KEY_KP_1; case 0x50: return INPUT_KEY_KP_2; case 0x51: return INPUT_KEY_KP_3; case 0x52: return INPUT_KEY_KP_0; case 0x53: return INPUT_KEY_KP_PERIOD; case 0x57: return INPUT_KEY_F11; case 0x58: return INPUT_KEY_F12; default: return INPUT_KEY_NONE; } } // Implements the ps2 Keyboard Dpc Routine operation. static void Ps2KeyboardDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; (void)context; (void)systemArgument1; (void)systemArgument2; while (gKeyboardDpcHead != gKeyboardDpcTail) { INPUT_KEY_EVENT event = gKeyboardDpcQueue[gKeyboardDpcHead]; gKeyboardDpcHead = (gKeyboardDpcHead + 1) & (int)KEYBOARD_DPC_QUEUE_MASK; KeInputSubmitKey(&event); uint32_t n = ++gKeyboardEventCount; if ((n & (n - 1u)) == 0u) DbgPrint("ps2: keyboard progress irq=%u raw=%u events=%u foreign=%u\n", gKeyboardIrqCount, gKeyboardByteCount, n, gKeyboardForeignIrqCount); } } // Implements the ps2 Queue Keyboard Event operation. static void Ps2QueueKeyboardEvent(INPUT_KEY key, bool pressed) { if (key >= sizeof(gKeyboardDown)) return; bool wasDown = gKeyboardDown[key] != 0; if (pressed == wasDown) return; int next = (gKeyboardDpcTail + 1) & (int)KEYBOARD_DPC_QUEUE_MASK; if (next == gKeyboardDpcHead) return; gKeyboardDown[key] = pressed ? 1u : 0u; gKeyboardDpcQueue[gKeyboardDpcTail].Key = key; gKeyboardDpcQueue[gKeyboardDpcTail].Pressed = pressed; gKeyboardDpcTail = next; KeInsertQueueDpc(&gKeyboardDpc, 0, 0); } // Implements the ps2 Keyboard Isr operation. static void Ps2KeyboardIsr(void) { ++gKeyboardIrqCount; uint8_t status = __inbyte(PS2_STAT); // The 8042 output buffer is shared; never consume AUX bytes as scancodes. if (!(status & PS2_STAT_OUT_FULL) || (status & PS2_STAT_AUX_DATA)) { ++gKeyboardForeignIrqCount; return; } uint8_t sc = __inbyte(PS2_DATA); ++gKeyboardByteCount; if (sc == SC1_EXT_PREFIX) { gKeyboardNextExtended = true; return; } bool extended = gKeyboardNextExtended; bool released = (sc & SC1_BREAK_BIT) != 0; uint8_t make = sc & SC1_MAKE_MASK; gKeyboardNextExtended = false; INPUT_KEY key = Ps2Set1ToKey(make, extended); if (key == INPUT_KEY_NONE) return; Ps2QueueKeyboardEvent(key, !released); } // Implements the ps2 Mouse Dpc Routine operation. static void Ps2MouseDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; (void)context; (void)systemArgument1; (void)systemArgument2; while (gMouseDpcHead != gMouseDpcTail) { PS2_MOUSE_PACKET packet = gMouseDpcQueue[gMouseDpcHead]; gMouseDpcHead = (gMouseDpcHead + 1) & (int)MOUSE_DPC_QUEUE_MASK; INPUT_MOUSE_EVENT event = { .dx = packet.dx, .dy = packet.dy, .wheel = 0, .buttons = packet.buttons, }; KeInputSubmitMouse(&event); uint32_t n = ++gMousePacketCount; if ((n & (n - 1u)) == 0u) DbgPrint("ps2: mouse progress irq=%u raw=%u packets=%u foreign=%u\n", gMouseIrqCount, gMouseByteCount, n, gMouseForeignIrqCount); } } // Implements the ps2 Mouse Isr operation. static void Ps2MouseIsr(void) { ++gMouseIrqCount; uint8_t status = __inbyte(PS2_STAT); if (!(status & PS2_STAT_OUT_FULL) || !(status & PS2_STAT_AUX_DATA)) { ++gMouseForeignIrqCount; return; } uint8_t data = __inbyte(PS2_DATA); ++gMouseByteCount; if (gMousePacketIndex == 0 && !(data & 0x08u)) return; gMousePacket[gMousePacketIndex++] = data; if (gMousePacketIndex < 3) return; gMousePacketIndex = 0; uint8_t flags = gMousePacket[0]; if (flags & 0xC0u) return; int32_t dx = (int32_t)gMousePacket[1] - ((flags & 0x10u) ? 256 : 0); int32_t dy = (int32_t)gMousePacket[2] - ((flags & 0x20u) ? 256 : 0); dy = -dy; int next = (gMouseDpcTail + 1) & (int)MOUSE_DPC_QUEUE_MASK; if (next != gMouseDpcHead) { gMouseDpcQueue[gMouseDpcTail].dx = dx; gMouseDpcQueue[gMouseDpcTail].dy = dy; gMouseDpcQueue[gMouseDpcTail].buttons = flags & 0x07u; gMouseDpcTail = next; KeInsertQueueDpc(&gMouseDpc, 0, 0); } } // Implements the ps2 Controller Prepare operation. static void Ps2ControllerPrepare(void) { Ps2WriteCommand(0xADu); // disable keyboard port Ps2WriteCommand(0xA7u); // disable aux port Ps2FlushOutput(); uint8_t commandByte = Ps2ReadCommandByte(); commandByte &= (uint8_t)~0x01u; // keep keyboard IRQ disabled during polled init commandByte &= (uint8_t)~0x02u; // keep mouse IRQ disabled during polled init commandByte |= 0x40u; // translate keyboard stream to set 1 commandByte &= (uint8_t)~0x10u; // keyboard clock enabled commandByte &= (uint8_t)~0x20u; // mouse clock enabled Ps2WriteCommandByte(commandByte); Ps2WriteCommand(0xAEu); // enable keyboard port Ps2WriteCommand(0xA8u); // enable aux port Ps2FlushOutput(); } // Implements the ps2 Controller Enable Interrupts operation. static void Ps2ControllerEnableInterrupts(void) { uint8_t commandByte = Ps2ReadCommandByte(); commandByte |= 0x01u; commandByte |= 0x02u; Ps2WriteCommandByte(commandByte); } // Implements the ps2 Keyboard Initialize operation. static void Ps2KeyboardInitialize(void) { // Enable scanning. If the keyboard is absent, the timeout/ACK failure is // non-fatal; the IRQ path simply never produces events. if (!Ps2SendKeyboard(0xF4u)) DbgPrint("ps2: keyboard enable did not ACK\n"); } // Implements the ps2 Mouse Initialize operation. static void Ps2MouseInitialize(void) { if (!Ps2SendMouse(0xFFu)) { DbgPrint("ps2: mouse reset did not ACK\n"); return; } (void)Ps2ReadData(); // BAT completion, usually 0xAA (void)Ps2ReadData(); // device id, usually 0x00 if (!Ps2SendMouse(0xF6u)) DbgPrint("ps2: mouse set-defaults did not ACK\n"); if (!Ps2SendMouse(0xF4u)) DbgPrint("ps2: mouse enable did not ACK\n"); } static KSTATUS DriverInitialize(NTL_MODULE* module) { (void)module; uint32_t keyboardId = 0, mouseId = 0; KeInputRegisterDevice("ps2-keyboard", 0, &keyboardId); KeInputRegisterDevice("ps2-mouse", 0, &mouseId); KeInitializeDpc(&gKeyboardDpc, Ps2KeyboardDpcRoutine, 0); KeInitializeDpc(&gMouseDpc, Ps2MouseDpcRoutine, 0); // Keep 8042 IRQ generation disabled while doing polled controller/device // setup. The old ordering routed IRQ1/IRQ12 first, which let ACK/BAT bytes // race the ISR and get consumed as fake key/mouse input before the polled // init path could read them. Ps2ControllerPrepare(); Ps2KeyboardInitialize(); Ps2MouseInitialize(); KeIrqRegister(PS2_IRQ_KEYBOARD, Ps2KeyboardIsr); KeIrqRegister(PS2_IRQ_MOUSE, Ps2MouseIsr); Ps2ControllerEnableInterrupts(); DbgPrint("ps2: initialized keyboard=%u mouse=%u\n", keyboardId, mouseId); return NTLSTATUS_SUCCESS; } NTL_DECLARE_MODULE(DriverInitialize);