Files
frostbyte/src/keyboard.c
T

220 lines
6.6 KiB
C

#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',
0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
0, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0,
'*', 0, ' ', 0
};
static const char scancodeUpper[128] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0,
'*', 0, ' ', 0
};
static bool shiftHeld;
#define consoleReaderBufferSize 32
typedef struct ConsoleReader {
RingBuffer ring;
uint8 backing[consoleReaderBufferSize];
bool used;
} ConsoleReader;
static ConsoleReader consoleReaders[maxConsoleReaders];
void keyboardInit(void)
{
shiftHeld = false;
ctrlHeld = false;
foregroundPgid = 0;
for (int i = 0; i < maxConsoleReaders; i++)
consoleReaders[i].used = false;
}
void keyboardSetForegroundPgid(uint32 pgid)
{
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();
}
}
const void* keyboardReaderChannel(int handle)
{
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-<letter> 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;
}
if (scancode == 0xAA || scancode == 0xB6) {
shiftHeld = false;
return;
}
if (scancode & 0x80)
return;
char c = shiftHeld ? scancodeUpper[scancode] : scancodeLower[scancode];
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());
}
void keyboardHandler(void)
{
uint8 scancode = inb(keyboardData);
keyboardHandleScancode(scancode);
}