// SPDX-License-Identifier: GPL-2.0-or-later // // GMMOS's PS/2 keyboard: kernel/dev/kbd.c with everything but the keys taken // out. // // No interrupt: the status port is polled. That also makes it immune to the // IRQ 1 polarity quirk of some AMD laptops -- the ASUS TUF among them -- which // is about how the interrupt is routed, and nothing here is routed. No // /dev/input, no grab, no terminal chords. Scancode set 1, as the controller // translates it, through the same keymap the console uses. #include "core/types.h" #include "arch/x86.h" #include "core/defs.h" #include "dev/kbd.h" #include "dev/keymap.h" #include "gmmos/gmmos.h" // kbd.c's tables, which are static there. Hardware facts, so they are short. static const uchar shiftcode[256] = { [0x1D] = CTL, [0x2A] = SHIFT, [0x36] = SHIFT, [0x38] = ALT, [0x9D] = CTL, [0xB8] = RALT, }; static const uchar togglecode[256] = { [0x3A] = CAPSLOCK, [0x45] = NUMLOCK, [0x46] = SCROLLLOCK, }; static uint shift; static int e0, e1skip; // Bytes that arrived while a command was waiting for its answer. Setting the // lights is a command, and a key pressed during it would otherwise be eaten. static uchar held[16]; static int nheld, iheld; // A byte from the controller, or -1. A byte from the mouse is read too -- left // in the buffer, it blocks every keyboard byte behind it -- and dropped. static int rd(void) { uint st = inb(KBSTATP); if (!(st & KBS_DIB)) return -1; uchar d = inb(KBDATAP); if (st & 0x20) return -1; return d; } static void wr(uchar b) { for (int i = 0; i < 10000; i++) { if (!(inb(KBSTATP) & 0x02)) { outb(KBDATAP, b); return; } microdelay(10); } } // Wait up to 20 ms for the keyboard's answer to a command, keeping anything // else that arrives first. static void answer(void) { for (int i = 0; i < 2000; i++) { int d = rd(); if (d < 0) { microdelay(10); continue; } if (d == 0xFA || d == 0xFE) return; if (nheld < (int)sizeof(held)) held[nheld++] = (uchar)d; } } static int probe(void) { // No controller decodes the port: every bit reads as one. if (inb(KBSTATP) == 0xFF) return 0; for (int i = 0; i < 64 && (inb(KBSTATP) & KBS_DIB); i++) (void)inb(KBDATAP); shift = 0; e0 = e1skip = 0; // A controller with no keyboard behind it -- a desktop with only USB ones // -- must not be offered as one: GMMOS would list keys that do nothing. So // ask the keyboard itself to echo, and wait 50 ms for it to. wr(0xEE); for (int i = 0; i < 5000; i++) { int d = rd(); if (d == 0xEE) return 1; if (d < 0) microdelay(10); } return 0; } static void leds(int on) { wr(0xED); answer(); wr(on ? 0x07 : 0x00); answer(); } static int poll(void) { int d; if (iheld < nheld) { d = held[iheld++]; } else { nheld = iheld = 0; d = rd(); } if (d < 0) return 0; // Answers and self-test results, not keys. if (d == 0xFA || d == 0xFE || d == 0xAA || d == 0xEE || d == 0x00 || d == 0xFF) return 0; // Pause is E1 1D 45 E1 9D C5, and means nothing here. Read as keys, it // would be a Ctrl press and two Num Lock toggles. if (e1skip) { e1skip--; return 0; } if (d == 0xE1) { e1skip = 5; return 0; } if (d == 0xE0) { e0 = 1; return 0; } // As in kbdgetc: a release clears its modifier, a press with E0 before it // is looked up with the high bit set. if (d & 0x80) { d = e0 ? d : (d & 0x7F); shift &= ~shiftcode[d]; e0 = 0; return 0; } if (e0) { d |= 0x80; e0 = 0; } shift |= shiftcode[d]; shift ^= togglecode[d]; return (int)keymap_lookup((uint)d, shift); } const struct gmmos_kbd gmmos_ps2 = { .name = "PS/2", .probe = probe, .poll = poll, .leds = leds, };