217 lines
7.3 KiB
C
217 lines
7.3 KiB
C
#include "ktest.h"
|
|
#include "keyboard.h"
|
|
#include "string.h"
|
|
|
|
/*
|
|
* Coverage for keyboard.c's per-reader console input queues (keyboard.h) --
|
|
* the fan-out/blocking-channel machinery added this wave to close the gap
|
|
* called out at the end of the previous one: a single global keyboard
|
|
* buffer shared by every consumer, with no per-process queueing and no
|
|
* scheduler-visible blocking. What is pure state machine (open/close,
|
|
* scancode decoding, fan-out to N independent queues, exhaustion/reuse)
|
|
* lives here, driven directly through keyboardHandleScancode -- the same
|
|
* seam the live PS/2 IRQ handler (keyboardHandler) calls into, just fed a
|
|
* synthetic scancode instead of a real inb() -- exactly the way
|
|
* pipeShareTest.c drives pipe.c's state machine without a live scheduler.
|
|
* The genuine cross-process park/wake handshake (real blocking read() on
|
|
* fd 0 from two independent ring-3 processes, woken by a real keystroke)
|
|
* only runs for real at boot (kmain.c's consolereader1/consolereader2 demo,
|
|
* needles "consolereader1: got byte" / "consolereader2: got byte" in
|
|
* tools/ci-test.sh), matching how every other blocking-I/O subsystem in
|
|
* this kernel splits its test coverage.
|
|
*
|
|
* Scancode reference (set 1, US QWERTY, keyboard.c's own tables): 0x25 is
|
|
* 'k'/'K', 0x2A/0xAA are left-shift make/break.
|
|
*/
|
|
#define scancodeK 0x25
|
|
#define scancodeLeftShiftMake 0x2A
|
|
#define scancodeLeftShiftBreak 0xAA
|
|
|
|
KTEST(keyboardOpenReaderReturnsDistinctHandlesAndEmptyReadWouldBlock)
|
|
{
|
|
int a = keyboardOpenReader();
|
|
int b = keyboardOpenReader();
|
|
KT_NE(t, a, consoleReaderInvalid);
|
|
KT_NE(t, b, consoleReaderInvalid);
|
|
KT_NE(t, a, b);
|
|
|
|
char buffer[4];
|
|
KT_EQ(t, keyboardReaderRead(a, buffer, sizeof(buffer)), -1);
|
|
KT_EQ(t, keyboardReaderRead(b, buffer, sizeof(buffer)), -1);
|
|
|
|
keyboardCloseReader(a);
|
|
keyboardCloseReader(b);
|
|
}
|
|
|
|
KTEST(keyboardReaderReadRejectsInvalidHandlesAndZeroLength)
|
|
{
|
|
KT_EQ(t, keyboardReaderRead(consoleReaderInvalid, nullptr, 4), -1);
|
|
KT_EQ(t, keyboardReaderRead(9999, nullptr, 4), -1);
|
|
|
|
int h = keyboardOpenReader();
|
|
KT_NE(t, h, consoleReaderInvalid);
|
|
KT_EQ(t, keyboardReaderRead(h, nullptr, 0), 0);
|
|
keyboardCloseReader(h);
|
|
|
|
/* Once closed, the handle is dead -- not silently reusable by the
|
|
caller who just gave it up. */
|
|
KT_EQ(t, keyboardReaderRead(h, nullptr, 0), -1);
|
|
}
|
|
|
|
KTEST(keyboardHandleScancodeBroadcastsToEveryOpenReaderIndependently)
|
|
{
|
|
int a = keyboardOpenReader();
|
|
int b = keyboardOpenReader();
|
|
KT_NE(t, a, consoleReaderInvalid);
|
|
KT_NE(t, b, consoleReaderInvalid);
|
|
|
|
keyboardHandleScancode(scancodeK);
|
|
|
|
char ca = 0, cb = 0;
|
|
KT_EQ(t, keyboardReaderRead(a, &ca, 1), 1);
|
|
KT_EQ(t, keyboardReaderRead(b, &cb, 1), 1);
|
|
KT_EQ(t, ca, 'k');
|
|
KT_EQ(t, cb, 'k');
|
|
|
|
/* Each reader's copy is independent: draining a's queue must not touch
|
|
b's, and vice versa -- proven above by both still having their own
|
|
byte to read rather than one starving the other. Both are empty now. */
|
|
KT_EQ(t, keyboardReaderRead(a, &ca, 1), -1);
|
|
KT_EQ(t, keyboardReaderRead(b, &cb, 1), -1);
|
|
|
|
keyboardCloseReader(a);
|
|
keyboardCloseReader(b);
|
|
}
|
|
|
|
KTEST(keyboardHandleScancodeAppliesShiftMakeAndBreak)
|
|
{
|
|
int h = keyboardOpenReader();
|
|
KT_NE(t, h, consoleReaderInvalid);
|
|
|
|
keyboardHandleScancode(scancodeK);
|
|
char c = 0;
|
|
KT_EQ(t, keyboardReaderRead(h, &c, 1), 1);
|
|
KT_EQ(t, c, 'k');
|
|
|
|
keyboardHandleScancode(scancodeLeftShiftMake);
|
|
keyboardHandleScancode(scancodeK);
|
|
KT_EQ(t, keyboardReaderRead(h, &c, 1), 1);
|
|
KT_EQ(t, c, 'K');
|
|
|
|
keyboardHandleScancode(scancodeLeftShiftBreak);
|
|
keyboardHandleScancode(scancodeK);
|
|
KT_EQ(t, keyboardReaderRead(h, &c, 1), 1);
|
|
KT_EQ(t, c, 'k');
|
|
|
|
keyboardCloseReader(h);
|
|
}
|
|
|
|
KTEST(keyboardHandleScancodeIgnoresKeyReleaseAndUnmappedCodes)
|
|
{
|
|
int h = keyboardOpenReader();
|
|
KT_NE(t, h, consoleReaderInvalid);
|
|
|
|
/* Key-release (high bit set) for 'k' must queue nothing. */
|
|
keyboardHandleScancode(scancodeK | 0x80);
|
|
char c;
|
|
KT_EQ(t, keyboardReaderRead(h, &c, 1), -1);
|
|
|
|
/* Scancode 0 maps to 0 (no character) in both tables. */
|
|
keyboardHandleScancode(0);
|
|
KT_EQ(t, keyboardReaderRead(h, &c, 1), -1);
|
|
|
|
keyboardCloseReader(h);
|
|
}
|
|
|
|
KTEST(keyboardReaderReadDeliversQueuedBytesInFifoOrderAcrossPartialReads)
|
|
{
|
|
int h = keyboardOpenReader();
|
|
KT_NE(t, h, consoleReaderInvalid);
|
|
|
|
/* "kkk" via three separate scancode events, like three real keystrokes. */
|
|
keyboardHandleScancode(scancodeK);
|
|
keyboardHandleScancode(scancodeK);
|
|
keyboardHandleScancode(scancodeK);
|
|
|
|
char buffer[8];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
|
|
/* Ask for only 2 of the 3 queued bytes -- must not block or fail, and
|
|
must leave the third for the next call, same partial-transfer
|
|
contract ring.h's ringRead already documents. */
|
|
int64 got = keyboardReaderRead(h, buffer, 2);
|
|
KT_EQ(t, got, 2);
|
|
KT_EQ(t, buffer[0], 'k');
|
|
KT_EQ(t, buffer[1], 'k');
|
|
|
|
got = keyboardReaderRead(h, buffer, 8);
|
|
KT_EQ(t, got, 1);
|
|
KT_EQ(t, buffer[0], 'k');
|
|
|
|
KT_EQ(t, keyboardReaderRead(h, buffer, 1), -1);
|
|
|
|
keyboardCloseReader(h);
|
|
}
|
|
|
|
KTEST(keyboardReaderChannelIsNullForInvalidAndDistinctPerReader)
|
|
{
|
|
KT_NULL(t, keyboardReaderChannel(consoleReaderInvalid));
|
|
KT_NULL(t, keyboardReaderChannel(9999));
|
|
|
|
int a = keyboardOpenReader();
|
|
int b = keyboardOpenReader();
|
|
KT_NE(t, a, consoleReaderInvalid);
|
|
KT_NE(t, b, consoleReaderInvalid);
|
|
|
|
const void* channelA = keyboardReaderChannel(a);
|
|
const void* channelB = keyboardReaderChannel(b);
|
|
KT_NOTNULL(t, channelA);
|
|
KT_NOTNULL(t, channelB);
|
|
KT_NE(t, (int64)(uint64)channelA, (int64)(uint64)channelB);
|
|
|
|
/* Stable across repeated calls -- schedBlockCurrent/schedWakeChannel
|
|
depend on the same reader always producing the same identity. */
|
|
KT_EQP(t, keyboardReaderChannel(a), channelA);
|
|
|
|
/* A closed reader's channel must go back to null immediately -- a
|
|
syscall handler using this as its retry condition must never park a
|
|
thread on a channel nobody can ever wake again. */
|
|
keyboardCloseReader(a);
|
|
KT_NULL(t, keyboardReaderChannel(a));
|
|
|
|
keyboardCloseReader(b);
|
|
}
|
|
|
|
KTEST(keyboardOpenReaderExhaustionThenCloseFreesSlotForReuse)
|
|
{
|
|
/* Does not assume the table starts fully empty -- by the time ktests
|
|
run, schedInit() has already given the idle thread (id 0) its own fd
|
|
0 reader (vfs.c's vfsFdInit), same convention vfsFifoTest.c's own
|
|
pipe-table exhaustion test uses for the shared pipes[] table. */
|
|
int handles[maxConsoleReaders];
|
|
int madeCount = 0;
|
|
while (madeCount < maxConsoleReaders) {
|
|
int h = keyboardOpenReader();
|
|
if (h == consoleReaderInvalid)
|
|
break;
|
|
handles[madeCount++] = h;
|
|
}
|
|
KT_GT(t, madeCount, 0);
|
|
|
|
/* Table is now genuinely full: one more must fail cleanly. */
|
|
KT_EQ(t, keyboardOpenReader(), consoleReaderInvalid);
|
|
|
|
keyboardCloseReader(handles[0]);
|
|
int reused = keyboardOpenReader();
|
|
KT_NE(t, reused, consoleReaderInvalid);
|
|
KT_EQ(t, reused, handles[0]);
|
|
|
|
/* Leave the shared reader table exactly as free as it was found, so
|
|
later boot-time console users (the shell, and the
|
|
consolereader1/consolereader2 live demo) are never starved by this
|
|
test having run. */
|
|
keyboardCloseReader(reused);
|
|
for (int i = 1; i < madeCount; i++)
|
|
keyboardCloseReader(handles[i]);
|
|
}
|