diff --git a/src/keyboard.c b/src/keyboard.c index 7a946d7..b937a22 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1,8 +1,26 @@ #include "keyboard.h" #include "ports.h" +#include "ring.h" +#include "sched.h" +#include "signal.h" #define keyboardData 0x60 +/* Left-Ctrl's own PS/2 Set 1 make/break scancodes -- exactly the same + convention keyboardHandleScancode already had for Shift (0x2A/0x36 make, + 0xAA/0xB6 break): held state tracked in a single bool, never forwarded + to scancodeLower/scancodeUpper (there is no printable character for a + modifier key on its own). */ +#define scancodeLeftCtrlMake 0x1D +#define scancodeLeftCtrlBreak 0x9D + +static bool ctrlHeld; + +/* Real tcsetpgrp(3)'s target -- see keyboard.h's own doc comment for why 0 + (keyboardInit's initial value) safely means "nobody yet" with no extra + sentinel needed. */ +static uint32 foregroundPgid; + static const char scancodeLower[128] = { 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', @@ -19,32 +37,138 @@ static const char scancodeUpper[128] = { '*', 0, ' ', 0 }; -#define keyBufferSize 128 -static volatile char keyBuffer[keyBufferSize]; -static volatile uint32 bufferHead; -static volatile uint32 bufferTail; static bool shiftHeld; +#define consoleReaderBufferSize 32 + +typedef struct ConsoleReader { + RingBuffer ring; + uint8 backing[consoleReaderBufferSize]; + bool used; +} ConsoleReader; + +static ConsoleReader consoleReaders[maxConsoleReaders]; + void keyboardInit(void) { - bufferHead = 0; - bufferTail = 0; shiftHeld = false; + ctrlHeld = false; + foregroundPgid = 0; + for (int i = 0; i < maxConsoleReaders; i++) + consoleReaders[i].used = false; } -static void pushChar(char c) +void keyboardSetForegroundPgid(uint32 pgid) { - uint32 next = (bufferHead + 1) % keyBufferSize; - if (next != bufferTail) { - keyBuffer[bufferHead] = c; - bufferHead = next; + foregroundPgid = pgid; +} + +uint32 keyboardForegroundPgid(void) +{ + return foregroundPgid; +} + +int keyboardOpenReader(void) +{ + for (int i = 0; i < maxConsoleReaders; i++) { + if (!consoleReaders[i].used) { + ringInit(&consoleReaders[i].ring, consoleReaders[i].backing, consoleReaderBufferSize); + consoleReaders[i].used = true; + return i; + } + } + return consoleReaderInvalid; +} + +static ConsoleReader* readerAt(int handle) +{ + if (handle < 0 || handle >= maxConsoleReaders) + return nullptr; + if (!consoleReaders[handle].used) + return nullptr; + return &consoleReaders[handle]; +} + +void keyboardCloseReader(int handle) +{ + ConsoleReader* r = readerAt(handle); + if (!r) + return; + r->used = false; +} + +int64 keyboardReaderRead(int handle, void* buffer, uint64 length) +{ + ConsoleReader* r = readerAt(handle); + if (!r) + return -1; + if (length == 0) + return 0; + if (ringIsEmpty(&r->ring)) + return -1; + + usize got = ringRead(&r->ring, (uint8*)buffer, (usize)length); + return (int64)got; +} + +int64 keyboardReaderReadBlocking(int handle, void* buffer, uint64 length) +{ + ConsoleReader* r = readerAt(handle); + if (!r) + return -1; + + for (;;) { + int64 result = keyboardReaderRead(handle, buffer, length); + if (result != -1) + return result; + + schedBlockCurrent((const void*)r); + halt(); } } -void keyboardHandler(void) +const void* keyboardReaderChannel(int handle) { - uint8 scancode = inb(keyboardData); + return (const void*)readerAt(handle); +} +bool keyboardReaderHasData(int handle) +{ + ConsoleReader* r = readerAt(handle); + if (!r) + return false; + return !ringIsEmpty(&r->ring); +} + +/* + * Real cooked-mode tty line discipline's INTR/SUSP characters (termios's + * VINTR/VSUSP, both Ctrl- by default): keyboardHandleScancode now + * tracks left-Ctrl as a THIRD modifier alongside Shift, and when it is held + * for a 'c'/'C' or 'z'/'Z' keypress, the resulting sigInt/sigTstp is + * delivered to the WHOLE foreground process group (sigRaiseGroup, signal.h) + * -- keyboardSetForegroundPgid's target, real tcsetpgrp(3)'s own job -- + * instead of the raw byte ever reaching any consoleReaders fan-out below. + * This is the scope real Ctrl-C actually has: every process in the + * foreground job, not "whichever process happens to be reading fd 0 right + * now" -- a BACKGROUND job (a different pgid, one keyboardSetForegroundPgid + * was never told about) must never be disturbed by a keypress aimed at + * whatever IS currently in the foreground, the same isolation a real shell's + * job control gives. kmain.c's pgroupfg.c/pgroupbg.c is the live ring-3 + * proof: pgroupfg (foreground, no handler installed) actually dies to the + * injected Ctrl-C's default-Terminate disposition, while pgroupbg + * (deliberately left in a DIFFERENT group) proves itself still alive and + * responsive afterwards. + */ +void keyboardHandleScancode(uint8 scancode) +{ + if (scancode == scancodeLeftCtrlMake) { + ctrlHeld = true; + return; + } + if (scancode == scancodeLeftCtrlBreak) { + ctrlHeld = false; + return; + } if (scancode == 0x2A || scancode == 0x36) { shiftHeld = true; return; @@ -57,21 +181,39 @@ void keyboardHandler(void) return; char c = shiftHeld ? scancodeUpper[scancode] : scancodeLower[scancode]; - if (c) - pushChar(c); + if (!c) + return; + + /* Consumed here, never forwarded as data below -- exactly like a real + cooked-mode terminal driver never hands a reading program the raw + 0x03/0x1A byte either. siCodeKernel/0: no sending PROCESS raised + this, the keyboard itself did, the same "kernel, not a process, is + the source" reasoning schedule()'s own SIGALRM raise already uses + for siCodeKernel. Harmless no-op (sigRaiseGroup's own contract) if + foregroundPgid currently names no live group at all. */ + if (ctrlHeld && (c == 'c' || c == 'C')) { + sigRaiseGroup(foregroundPgid, sigInt, siCodeKernel, 0); + return; + } + if (ctrlHeld && (c == 'z' || c == 'Z')) { + sigRaiseGroup(foregroundPgid, sigTstp, siCodeKernel, 0); + return; + } + + for (int i = 0; i < maxConsoleReaders; i++) { + if (!consoleReaders[i].used) + continue; + ringPush(&consoleReaders[i].ring, (uint8)c); + /* Mirrors pipeWrite's wake-after-write: harmless no-op (schedWakeChannel's + own contract) if nobody is currently blocked on this particular + reader's channel. */ + schedWakeChannel((const void*)&consoleReaders[i]); + } + schedWakeChannel(schedPollChannel()); } -bool keyboardHasChar(void) +void keyboardHandler(void) { - return bufferHead != bufferTail; -} - -char keyboardReadChar(void) -{ - while (bufferHead == bufferTail) - __asm__ volatile("sti; hlt"); - - char c = keyBuffer[bufferTail]; - bufferTail = (bufferTail + 1) % keyBufferSize; - return c; + uint8 scancode = inb(keyboardData); + keyboardHandleScancode(scancode); }