feat: extend keyboard

This commit is contained in:
dualfroz committed 2026-09-01 19:38:36 +02:00
1 parent ed74e6df9b
commit c069cdcbe6
1 file changed
+154 -2
+154 -2
View File
@@ -4,8 +4,160 @@
#include "types.h" #include "types.h"
void keyboardInit(void); void keyboardInit(void);
/* The real PS/2 IRQ1 handler (isr.c): reads the scancode off the hardware
port and hands it to keyboardHandleScancode below. */
void keyboardHandler(void); void keyboardHandler(void);
bool keyboardHasChar(void);
char keyboardReadChar(void); /*
* Per-reader console input queues: a fixed table of ring-buffer-backed byte
* queues (ring.h), each independently allocated and each fed a copy of
* every decoded keystroke by keyboardHandleScancode -- like several
* terminals plugged into one keyboard, every open reader sees every key.
* This replaces an earlier design with exactly one global buffer shared by
* every consumer (the kernel shell and every ring-3 process's fd 0 alike),
* which had two real problems: concurrent readers raced directly on the
* same head/tail indices with no locking, and its blocking wait
* (keyboardReadChar's raw "sti; hlt" loop) was invisible to the scheduler --
* a thread parked there could never be woken by schedWakeChannel, never
* charged CFS-lite vruntime while "running" it, and never recovered by
* threadKill the way every other blocking read/write in this kernel can be
* (pipe.h's pipeReadBlocking/pipeWriteBlocking, vfs.h's fdFlagBlocking
* pipe/fifo ends). Every reader here uses the exact same
* schedBlockCurrent/schedWakeChannel wait-channel contract as pipe.c, wired
* into vfs.c's fdConsole read path (vfsBlockingChannel) so syscall.c's
* sysRead restart-syscall trick works on fd 0 exactly like it already does
* on a pipe end.
*/
/* Bumped from 48 (this pool's very first, never-since-revisited size, set
when sched.h's maxThreads was still 32): vfs.c's vfsFdInit grabs one
reader automatically for EVERY thread ever created (kernel or user alike,
whether it ever reads fd 0 or not, its own doc comment already says so)
at CREATION time, which happens sequentially and entirely inside kmain()
for every boot-time thread, well before schedStart() ever runs -- so the
first 48 creation-order calls always succeeded and every one after them
always silently got consoleReaderInvalid, deterministically, regardless
of scheduling. shell.c's shellRun makes a SEPARATE, later, explicit
keyboardOpenReader() call once the shell thread actually gets a
timeslice (its own real input queue, distinct from its automatic-but-
unused fd 0 slot) -- that call can only succeed if some earlier thread
has ALREADY died and been reaped (reclaimOpenFds, sched.c) by then,
freeing a slot back up. maxThreads grew from 32 to 168 across dozens of
falas' own headroom bumps (sched.c's own history comment) while this
completely separate pool never grew to match, so by FALA 33 kmain.c was
creating far more than 48 threads before schedStart() ever ran --
shellRun's own call was surviving purely on the timing-dependent hope
that at least one of the first 48 automatic grabs belonged to an
already-exited thread by the time it ran, not on any real guarantee.
Adding one more automatically-grabbing kernel thread (hereDriverThread,
kmain.c) tipped this from "usually still works" to "shell has no input"
on a clean run -- caught live by this fala's own gate, not a review.
Sized to maxThreads (168) plus the same +8 headroom margin sched.c's own
bumps use, which is enough for every one of maxThreads' automatic
grabs to always succeed deterministically PLUS shellRun's own extra
explicit call on top, eliminating the race entirely rather than
continuing to depend on exit timing. Bumped again (176 -> 184) in lockstep
with sched.c's own maxThreads bump (168 -> 176, hcat/hsink/
hereStringPipelineDriverThread) -- the same fixed maxThreads+8 relationship,
not re-derived from scratch each time. Bumped again (184 -> 192) in
lockstep with sched.c's own maxThreads bump (176 -> 184, pipecat/
threeStagePipelineDriverThread), same relationship. Bumped again
(192 -> 200) in lockstep with sched.c's own maxThreads bump (184 -> 192,
cwdtest/cwdtarget), same relationship. Bumped again (200 -> 208) in
lockstep with sched.c's own maxThreads bump (192 -> 200, envprint/
envDriverThread), same relationship. Bumped again (208 -> 216) in
lockstep with sched.c's own maxThreads bump (200 -> 208, nicetest/
nicetarget), same relationship. Bumped again (216 -> 224) in lockstep
with sched.c's own maxThreads bump (208 -> 216, date), same
relationship. Bumped again (224 -> 232) in lockstep with sched.c's own
maxThreads bump (216 -> 224, timetest), same relationship. Bumped again
(232 -> 240) in lockstep with sched.c's own maxThreads bump (224 -> 232,
lsDriverThread), same relationship. Bumped again (240 -> 248) in
lockstep with sched.c's own maxThreads bump (232 -> 240,
andOrDriverThread), same relationship. Bumped again (248 -> 256) in
lockstep with sched.c's own maxThreads bump (240 -> 248, rmtest), same
relationship. Bumped again (256 -> 264) in lockstep with sched.c's own
maxThreads bump (248 -> 256, permtest plus its one transient runtime-
spawned child), same relationship. Bumped again (264 -> 272) in lockstep
with sched.c's own maxThreads bump (256 -> 264, dirpermtest), same
relationship. Bumped again (272 -> 280) in lockstep with sched.c's own
maxThreads bump (264 -> 272, grouptest plus its two transient runtime-
spawned children), same relationship. Bumped again (280 -> 288) in
lockstep with sched.c's own maxThreads bump (272 -> 280, pidfdtest plus
its one transient runtime-spawned killvictim), same relationship.
Bumped again (288 -> 296) in lockstep with sched.c's own maxThreads bump
(280 -> 288, mmaptest plus its one transient runtime-spawned
mmapprotvictim), same relationship. */
#define maxConsoleReaders 296
#define consoleReaderInvalid (-1)
/* Allocates a fresh, empty reader queue and returns its handle, or
consoleReaderInvalid if the table is full. Every thread gets one for its
own fd 0 at creation time (vfs.c's vfsFdInit) -- one keystroke delivered
by keyboardHandleScancode reaches every thread's queue independently, so
two unrelated processes both reading fd 0 each see their own copy rather
than racing for the same byte. */
int keyboardOpenReader(void);
/* Releases a reader's slot for reuse. No-op on an invalid handle. */
void keyboardCloseReader(int handle);
/* Non-blocking: pops up to length queued bytes into buffer, in the order
they were typed. Returns the number of bytes actually copied (which may
be less than length, or 0 only when length itself was 0), or -1 for an
invalid/closed handle or when the queue is currently empty (a live
keyboard has no end-of-file the way a pipe's write end closing does --
"nothing typed yet" and "try again later" are the same thing). */
int64 keyboardReaderRead(int handle, void* buffer, uint64 length);
/* Blocks (parks the calling thread and halts) until at least one byte is
available, then behaves exactly like keyboardReaderRead. Only valid from
plain kernel-thread context (see shell.c) -- calling this from inside a
syscall's interrupt-gate handler would freeze the whole machine, not just
the caller, the same hazard pipeReadBlocking's doc comment (pipe.h)
describes; a syscall handler must instead use vfsBlockingChannel (vfs.h)
with the restart-syscall pattern. Returns -1 immediately, without
blocking, for an invalid handle. */
int64 keyboardReaderReadBlocking(int handle, void* buffer, uint64 length);
/* The address schedBlockCurrent/schedWakeChannel use for this reader's wait
queue -- mirrors pipe.h's pipeChannel exactly, including returning
nullptr for an invalid handle so a syscall handler can tell "do not
block, this fd cannot ever produce more data" apart from "wait and
retry". */
const void* keyboardReaderChannel(int handle);
/* Non-consuming readiness peek for poll.h's vfsPollReady (vfs.c): true iff
keyboardReaderRead would return actual bytes right now, without popping
any of them off the ring. False for an invalid handle. */
bool keyboardReaderHasData(int handle);
/* The scancode-decoding half of keyboardHandler, split out so tests (and
the live boot demo in kmain.c, which has no automated way to drive a real
PS/2 device under headless QEMU) can inject a keystroke exactly the way
real hardware would, without needing to fake port I/O. Handles shift and
(now) left-Ctrl make/break codes, intercepts the real tty INTR/SUSP
combination (Ctrl-C/Ctrl-Z) as job-control signals to the foreground
process group instead of ordinary data (see the file comment in
keyboard.c), and otherwise broadcasts the decoded character to every open
reader queue, waking anyone blocked on one via schedWakeChannel. */
void keyboardHandleScancode(uint8 scancode);
/* Real tcsetpgrp(3): names the process group (sched.h's Thread.pgid) that
OWNS "the terminal" from this point on -- the only pgid keyboardHandleScancode's
Ctrl-C/Ctrl-Z interception (below) ever delivers sigInt/sigTstp to.
0 (the default, before anything ever calls this -- see keyboardInit) can
never match any live user thread's own pgid (every boot-spawned process
becomes its own new group leader, sched.c's initialPgid, so the lowest
possible real pgid is 1; only the permanent idle thread, which never
reads a keyboard, is ever pgid 0), so "nobody has claimed the foreground
yet" and "deliver to no one" are naturally the same state, with no extra
sentinel needed. */
void keyboardSetForegroundPgid(uint32 pgid);
/* The pgid keyboardSetForegroundPgid last recorded (0 if never called since
keyboardInit) -- real tcgetpgrp(3)'s counterpart, exposed to userspace
through sysGetFgPgrp/userGetForegroundPgid. */
uint32 keyboardForegroundPgid(void);
#endif #endif