| git.druid.rocks | index | druid520 | kaboom | src/ | drivers/ | kbd.nsc |
src/drivers/kbd.nsc
/*
* ps/2 keyboard, scancode set 1, us qwerty, make codes only (break
* codes -- key release, scancode | 0x80 -- are read and mostly
* discarded so the controller doesn't stall, not acted on, except
* for shift's own release which has to be tracked). every mapped
* keypress is echoed straight to vga+serial (local echo) AND pushed
* into a small circular buffer that kbd_getchar() drains -- the
* read() syscall (idt.nsc, by way of fd.nsc's fd_read_stdin) is the
* actual reader.
*
* scancode-to-ascii mapping, shift, and caps lock are a real port of
* tape-kernel's own kb.c (scntasci/gtchr) -- not reinvented, ported
* as a lookup-driven if-chain instead of its switch (the actual
* scancode->char table is identical) since this is irq-driven, not
* gtchr's own polling loop. two things this deliberately does NOT
* copy from tape-kernel: extended (0xE0-prefixed) keys are still
* just discarded (tape-kernel dropped them too, "for now"), and
* backspace's actual erase behavior lives in fd.nsc's
* fd_read_stdin, not here -- see that file's own note on why an
* irq handler that echoes immediately, with no idea how many
* characters the current read has accepted, is the wrong place to
* decide whether there's anything to erase.
*/
u8 inb(u16 port);
void vga_putc(u8 ch);
void serial_putc(u8 ch);
global i8 kbd_buf[256];
global u64 kbd_head;
global u64 kbd_tail;
global u64 kbd_count;
global u8 kbd_shift;
global u8 kbd_caps;
/* scancode -> ascii, given the current shift/caps state -- a direct
* port of tape-kernel's scntasci, as an if-chain instead of a switch
* (same mapping, same shift ^ caps rule for letters, plain shift for
* everything else that has a shifted form). returns 0 for anything
* unmapped (function keys, extended keys, ...), same as the
* original. */
i8
kbd_scancode_to_ascii(u8 sc, u8 shift, u8 caps)
{
u8 up;
up = shift ^ caps;
if(sc == (u8)0x1E) { if(up != (u8)0) { return 65; } return 97; } /* a/A */
if(sc == (u8)0x30) { if(up != (u8)0) { return 66; } return 98; } /* b/B */
if(sc == (u8)0x2E) { if(up != (u8)0) { return 67; } return 99; } /* c/C */
if(sc == (u8)0x20) { if(up != (u8)0) { return 68; } return 100; } /* d/D */
if(sc == (u8)0x12) { if(up != (u8)0) { return 69; } return 101; } /* e/E */
if(sc == (u8)0x21) { if(up != (u8)0) { return 70; } return 102; } /* f/F */
if(sc == (u8)0x22) { if(up != (u8)0) { return 71; } return 103; } /* g/G */
if(sc == (u8)0x23) { if(up != (u8)0) { return 72; } return 104; } /* h/H */
if(sc == (u8)0x17) { if(up != (u8)0) { return 73; } return 105; } /* i/I */
if(sc == (u8)0x24) { if(up != (u8)0) { return 74; } return 106; } /* j/J */
if(sc == (u8)0x25) { if(up != (u8)0) { return 75; } return 107; } /* k/K */
if(sc == (u8)0x26) { if(up != (u8)0) { return 76; } return 108; } /* l/L */
if(sc == (u8)0x32) { if(up != (u8)0) { return 77; } return 109; } /* m/M */
if(sc == (u8)0x31) { if(up != (u8)0) { return 78; } return 110; } /* n/N */
if(sc == (u8)0x18) { if(up != (u8)0) { return 79; } return 111; } /* o/O */
if(sc == (u8)0x19) { if(up != (u8)0) { return 80; } return 112; } /* p/P */
if(sc == (u8)0x10) { if(up != (u8)0) { return 81; } return 113; } /* q/Q */
if(sc == (u8)0x13) { if(up != (u8)0) { return 82; } return 114; } /* r/R */
if(sc == (u8)0x1F) { if(up != (u8)0) { return 83; } return 115; } /* s/S */
if(sc == (u8)0x14) { if(up != (u8)0) { return 84; } return 116; } /* t/T */
if(sc == (u8)0x16) { if(up != (u8)0) { return 85; } return 117; } /* u/U */
if(sc == (u8)0x2F) { if(up != (u8)0) { return 86; } return 118; } /* v/V */
if(sc == (u8)0x11) { if(up != (u8)0) { return 87; } return 119; } /* w/W */
if(sc == (u8)0x2D) { if(up != (u8)0) { return 88; } return 120; } /* x/X */
if(sc == (u8)0x15) { if(up != (u8)0) { return 89; } return 121; } /* y/Y */
if(sc == (u8)0x2C) { if(up != (u8)0) { return 90; } return 122; } /* z/Z */
if(sc == (u8)0x02) { if(shift != (u8)0) { return 33; } return 49; } /* 1/! */
if(sc == (u8)0x03) { if(shift != (u8)0) { return 64; } return 50; } /* 2/@ */
if(sc == (u8)0x04) { if(shift != (u8)0) { return 35; } return 51; } /* 3/# */
if(sc == (u8)0x05) { if(shift != (u8)0) { return 36; } return 52; } /* 4/$ */
if(sc == (u8)0x06) { if(shift != (u8)0) { return 37; } return 53; } /* 5/% */
if(sc == (u8)0x07) { if(shift != (u8)0) { return 94; } return 54; } /* 6/^ */
if(sc == (u8)0x08) { if(shift != (u8)0) { return 38; } return 55; } /* 7/& */
if(sc == (u8)0x09) { if(shift != (u8)0) { return 42; } return 56; } /* 8/* */
if(sc == (u8)0x0A) { if(shift != (u8)0) { return 40; } return 57; } /* 9/( */
if(sc == (u8)0x0B) { if(shift != (u8)0) { return 41; } return 48; } /* 0/) */
if(sc == (u8)0x0C) { if(shift != (u8)0) { return 95; } return 45; } /* -/_ */
if(sc == (u8)0x0D) { if(shift != (u8)0) { return 43; } return 61; } /* =/+ */
if(sc == (u8)0x1A) { if(shift != (u8)0) { return 123; } return 91; } /* [/{ */
if(sc == (u8)0x1B) { if(shift != (u8)0) { return 125; } return 93; } /* ]/} */
if(sc == (u8)0x2B) { if(shift != (u8)0) { return 124; } return 92; } /* \/| */
if(sc == (u8)0x27) { if(shift != (u8)0) { return 58; } return 59; } /* ;/: */
if(sc == (u8)0x28) { if(shift != (u8)0) { return 34; } return 39; } /* '/" */
if(sc == (u8)0x29) { if(shift != (u8)0) { return 126; } return 96; } /* `/~ */
if(sc == (u8)0x33) { if(shift != (u8)0) { return 60; } return 44; } /* ,/< */
if(sc == (u8)0x34) { if(shift != (u8)0) { return 62; } return 46; } /* ./> */
if(sc == (u8)0x35) { if(shift != (u8)0) { return 63; } return 47; } /* //? */
if(sc == (u8)0x39) { return 32; } /* space */
if(sc == (u8)0x1C) { return 10; } /* enter */
if(sc == (u8)0x0E) { return 8; } /* backspace */
if(sc == (u8)0x0F) { return 9; } /* tab */
return 0;
}
global void
kbd_init(void)
{
/* the ps/2 controller and keyboard device are already enabled and
* translating scancodes by the time a real bios/uefi hands off to
* a bootloader -- nothing to program here for a first working
* driver. a real driver would probe/reset the controller itself
* (port 0x64 command interface) rather than trust that, but nsc
* had no way to write structured hardware descriptors either
* before that gap was found (see gdt.nsc's note on the same
* theme), so keeping this minimal for now is the same trade every
* other driver here has made: correctness of what exists over
* completeness of everything that could exist.
*/
kbd_shift = 0;
kbd_caps = 0;
return;
}
global void
kbd_handle_irq(void)
{
u8 scancode;
i8 ch;
scancode = inb((u16)0x60);
if(scancode == (u8)0xE0)
{
/* extended scancode prefix -- the real byte follows as a
* second inb, discarded the same as tape-kernel's own gtchr
* does ("for now"): no arrow keys/home/end handling exists
* yet, just don't let the second byte fall through and get
* misread as an ordinary key. */
inb((u16)0x60);
return;
}
if(scancode == (u8)0x2A || scancode == (u8)0x36)
{
kbd_shift = 1;
return;
}
if(scancode == (u8)0xAA || scancode == (u8)0xB6)
{
kbd_shift = 0;
return;
}
if(scancode == (u8)0x3A)
{
/* caps lock toggles on the PRESS, same as a real keyboard's
* own latch -- the matching release code (0xBA) carries no
* new information and is just an ordinary break code. */
kbd_caps = kbd_caps ^ (u8)1;
return;
}
if(scancode >= (u8)128)
{
return; /* break code (key release) -- discard */
}
ch = kbd_scancode_to_ascii(scancode, kbd_shift, kbd_caps);
if(ch == (i8)0)
{
return; /* unmapped key */
}
kbd_push((u8)ch);
}
/* echoes and buffers one input byte, from EITHER source -- a real
* ps/2 keystroke (kbd_handle_irq, above) or a byte arriving over
* serial (serial.nsc's own irq handler, once -nographic is the only
* console and there's no ps/2 keyboard generating scancodes at all,
* only a raw byte stream from whatever real terminal the user is
* actually typing into). one shared buffer, one shared echo rule, so
* fd_read_stdin/kbd_getchar never need to know or care which source
* a given byte came from.
*
* backspace is echoed by fd_read_stdin instead, once it knows
* whether there's actually anything on the current line to erase --
* see that function's own note. every other character is still
* local-echoed here, the same as always. */
global void
kbd_push(u8 ch)
{
if(ch != (u8)8)
{
vga_putc(ch);
serial_putc(ch);
}
if(kbd_count < (u64)256)
{
kbd_buf[kbd_head] = (i8)ch;
kbd_head = (kbd_head + (u64)1) % (u64)256;
kbd_count = kbd_count + (u64)1;
}
/* buffer full: drop the keystroke rather than overwrite an
* unread one -- no reader has fallen behind by 256 keystrokes in
* practice, and silently corrupting older unread input would be
* worse than losing the newest one. */
}
/* pops one byte, or -1 if the buffer is empty. non-blocking --
* read() (idt.nsc) is what actually blocks (busy-waits on halt())
* when it wants data and there isn't any yet. */
global i32
kbd_getchar(void)
{
i8 ch;
if(kbd_count == (u64)0)
{
return -1;
}
ch = kbd_buf[kbd_tail];
kbd_tail = (kbd_tail + (u64)1) % (u64)256;
kbd_count = kbd_count - (u64)1;
return (i32)(u8)ch;
}