fastfetch reported 3 seconds of uptime after 15 real ones, `time` claimed
3.12s for a 20-second command, and ping's one-second interval was closer to
six. One cause, not three: `ticks` was advancing at ~14.5 Hz instead of 100.
It was not the timer. virtio-gpu-pci shares IRQ 11 with the e1000 and
registered no interrupt handler at all. PCI INTx is level-triggered, so the
device held the line asserted, the kernel signalled EOI, and it fired again
immediately -- 72,000 interrupts a second, 83% of CPU0, starving the tick.
The e1000's handler read ICR as zero half a million times, correctly
answering "not mine", which is what made it look like a network-free problem.
Reading virtio's ISR byte clears it and drops the line. That is the fix.
irq_dispatch now measures each line's rate against the TSC -- not ticks,
which is the first casualty of a storm -- and names the attached handlers
once above 20000/s. It reproduces the original fault as a single line.
Found along the way, and fixed because they were real:
- lapicinit loaded TICR with xv6's uncalibrated 10000000, whose own comment
admitted it was never measured. calibrate_timers() now measures the LAPIC
and the TSC against the 8254 PIT. (It came back at 999.9 MHz, so this was
not the bug -- but the next machine is not obliged to be 1 GHz.)
- microdelay() was an empty function body. Every microdelay(1000) in the
xHCI reset path and the USB hub power-on sequence returned instantly.
- HZ is a constant in param.h now, not the literal 100 written out at a
dozen ticks-to-seconds conversions. USER_HZ is separate and deliberately
does not follow it: musl answers sysconf(_SC_CLK_TCK) with 100 always, so
times() and /proc/stat scale at the syscall boundary the way Linux does.
- TIOCGWINSZ answered a hardcoded 24x80. gfx_cols()/gfx_rows() have known
the real geometry all along; at 1024x768 the console is 128x48, so every
program that lays out a screen was formatting for the wrong one.
- TCFLSH fell through to the catch-all `return 0`, claiming to discard
input without discarding any.
uptime now advances 5.01 seconds per 5 real seconds. schfs-check passes.
Full investigation, including the two wrong hypotheses and the measurement
flaw that protected them, in docs/history/kernel.md section 13.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
421 lines
12 KiB
C
421 lines
12 KiB
C
// Raw input event devices -- /dev/input/event0 and /dev/input/event1.
|
|
//
|
|
// Everything that produces input feeds this: the PS/2 keyboard and mouse, and
|
|
// the USB HID driver. The console keeps its own path and is unaffected; these
|
|
// nodes are a *second* consumer of the same interrupts, so a program can read
|
|
// keys while the shell still gets characters.
|
|
//
|
|
// Two devices rather than one because they are two devices. A compositor
|
|
// grabs the pointer and leaves the keyboard alone, or the other way round, and
|
|
// merging them into a single stream makes that impossible to express.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "input.h"
|
|
#include "errno.h"
|
|
|
|
#define NEVENT 256 // per device; ~10 seconds of frantic typing
|
|
|
|
struct input_dev {
|
|
struct spinlock lock;
|
|
struct input_event ev[NEVENT];
|
|
uint r, w; // free-running; the difference is the count
|
|
|
|
// Exclusive grab (EVIOCGRAB). While set, keys still arrive here but stop
|
|
// being handed to the console's line discipline.
|
|
//
|
|
// Without this a full-screen program does not really own the keyboard: it
|
|
// reads /dev/input/event0 *and* the shell behind it reads the same keystrokes
|
|
// off the tty. The observed failure was a full-screen program quietly typing
|
|
// its entire session into the shell underneath, so the first Enter after
|
|
// exiting ran whatever had accumulated. The grab is also exactly what a
|
|
// compositor needs, which is why it lives here rather than in any one client.
|
|
int grabbed;
|
|
|
|
// How many descriptors are open on this device.
|
|
//
|
|
// Events are only queued while this is non-zero, and the backlog is dropped
|
|
// on the first open. Both halves matter: without them the ring is a log of
|
|
// every key pressed since boot, and the next program to open the device is
|
|
// handed the lot as though the user had just typed it.
|
|
int opens;
|
|
};
|
|
|
|
static struct input_dev idev[NINPUTDEV];
|
|
static int input_up;
|
|
|
|
extern uint ticks;
|
|
|
|
void
|
|
inputinit(void)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < NINPUTDEV; i++)
|
|
initlock(&idev[i].lock, "input");
|
|
|
|
// Producers run from interrupt handlers and can fire before this point in
|
|
// boot. Until the locks exist, an event has nowhere safe to go.
|
|
__sync_synchronize();
|
|
input_up = 1;
|
|
}
|
|
|
|
// Append one event. Called from interrupt context, so it never sleeps and
|
|
// never blocks on a full ring.
|
|
void
|
|
input_report(int dev, int type, int code, int value)
|
|
{
|
|
struct input_dev *d;
|
|
struct input_event *e;
|
|
|
|
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
|
return;
|
|
d = &idev[dev];
|
|
|
|
// Nobody is listening, so there is nowhere for this to go. Linux does the
|
|
// same: an evdev device with no clients does not accumulate.
|
|
if(d->opens == 0)
|
|
return;
|
|
|
|
acquire(&d->lock);
|
|
|
|
// A full ring drops the *oldest* event, not the newest. Dropping the newest
|
|
// is the obvious choice and it is wrong: the event most likely to be lost is
|
|
// then a key release, and a lost release leaves the key held down forever
|
|
// as far as every reader is concerned. Losing the oldest costs history, not
|
|
// state.
|
|
if(d->w - d->r >= NEVENT)
|
|
d->r++;
|
|
|
|
e = &d->ev[d->w % NEVENT];
|
|
e->time.tv_sec = ticks / HZ;
|
|
e->time.tv_usec = (ticks % HZ) * USEC_PER_TICK;
|
|
e->type = (uint16)type;
|
|
e->code = (uint16)code;
|
|
e->value = (int32)value;
|
|
d->w++;
|
|
|
|
release(&d->lock);
|
|
}
|
|
|
|
// End a report. A reader that acts on each event as it arrives will see a
|
|
// diagonal mouse move as two separate moves; the SYN is how it knows the two
|
|
// axes belong to the same physical motion.
|
|
void
|
|
input_sync(int dev)
|
|
{
|
|
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
|
return;
|
|
input_report(dev, EV_SYN, SYN_REPORT, 0);
|
|
wakeup(&idev[dev]);
|
|
}
|
|
|
|
int
|
|
input_ready(int dev)
|
|
{
|
|
int n;
|
|
|
|
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
|
return 0;
|
|
acquire(&idev[dev].lock);
|
|
n = idev[dev].w != idev[dev].r;
|
|
release(&idev[dev].lock);
|
|
return n;
|
|
}
|
|
|
|
// A descriptor has been opened on this device.
|
|
//
|
|
// The backlog is discarded, which is the whole point. Events queued before a
|
|
// program opened the device were meant for whoever had it *then* -- almost
|
|
// always the shell, which already consumed them as a command line. Replaying
|
|
// them into the new owner means a program is handed the keystrokes that
|
|
// launched it, as input, the instant it starts. That was a real crash and not a
|
|
// cosmetic one: the replayed characters were interpreted as commands, and one of
|
|
// them asked for a mode the program had not initialised yet.
|
|
//
|
|
// Linux gets this for free by giving every open its own client queue. One
|
|
// shared ring plus "start from now" is the same answer for a single reader,
|
|
// which is the case that exists.
|
|
void
|
|
inputopen(struct inode *ip)
|
|
{
|
|
int dev = ip->minor;
|
|
|
|
if(dev < 0 || dev >= NINPUTDEV)
|
|
return;
|
|
acquire(&idev[dev].lock);
|
|
idev[dev].opens++;
|
|
idev[dev].r = idev[dev].w; // start from now, not from boot
|
|
release(&idev[dev].lock);
|
|
}
|
|
|
|
// The devsw ready hook: would a read block?
|
|
int
|
|
inputready(struct inode *ip)
|
|
{
|
|
return input_ready(ip->minor);
|
|
}
|
|
|
|
// Is this device grabbed? Asked by the keyboard drivers before they hand a
|
|
// character to the console.
|
|
int
|
|
input_is_grabbed(int dev)
|
|
{
|
|
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
|
return 0;
|
|
return idev[dev].grabbed;
|
|
}
|
|
|
|
// EVIOCGRAB. Linux's semantics: one holder at a time, and it is not an error
|
|
// to grab what you already hold.
|
|
int
|
|
input_grab(int dev, int on)
|
|
{
|
|
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
|
return -ENODEV;
|
|
on = on ? 1 : 0;
|
|
if(idev[dev].grabbed != on){
|
|
idev[dev].grabbed = on;
|
|
// Said out loud. A grabbed keyboard is a console that has gone deaf on
|
|
// purpose, and that is indistinguishable from one that has gone deaf by
|
|
// accident unless the kernel says which it was.
|
|
cprintf("input: event%d %s by pid %d\n", dev,
|
|
on ? "grabbed" : "released", myproc() ? myproc()->pid : -1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// The last descriptor on this device is going away.
|
|
//
|
|
// Releasing the grab here is not tidiness, it is the difference between a
|
|
// crashed game and a machine you have to reboot: a program that grabs the
|
|
// keyboard and dies without ungrabbing would leave the console permanently
|
|
// deaf. The close hook makes the release unconditional.
|
|
void
|
|
inputclose(struct inode *ip)
|
|
{
|
|
int dev = ip->minor;
|
|
|
|
if(dev < 0 || dev >= NINPUTDEV)
|
|
return;
|
|
if(idev[dev].opens > 0)
|
|
idev[dev].opens--;
|
|
if(idev[dev].grabbed){
|
|
idev[dev].grabbed = 0;
|
|
cprintf("input: event%d grab released\n", dev);
|
|
}
|
|
}
|
|
|
|
// ioctl on /dev/input/eventN. Only EVIOCGRAB so far; the EVIOCG* query
|
|
// requests (name, id, capability bitmaps) can arrive when something needs
|
|
// them.
|
|
int
|
|
input_ioctl(struct inode *ip, int request, addr_t arg)
|
|
{
|
|
int on;
|
|
|
|
if(request == EVIOCGRAB){
|
|
// Linux passes the flag *by value* in the argument, not by pointer.
|
|
on = (int)arg;
|
|
return input_grab(ip->minor, on);
|
|
}
|
|
return -ENOTTY;
|
|
}
|
|
|
|
// A read returns whole events only. Handing back a partial struct would
|
|
// desynchronise the reader permanently -- every later event would be split
|
|
// across two reads and reassembled wrong.
|
|
int
|
|
inputread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
struct input_dev *d;
|
|
int got = 0;
|
|
int dev = ip->minor;
|
|
|
|
if(dev < 0 || dev >= NINPUTDEV)
|
|
return -ENODEV;
|
|
if(n < (int)sizeof(struct input_event))
|
|
return -EINVAL;
|
|
d = &idev[dev];
|
|
|
|
acquire(&d->lock);
|
|
while(d->r == d->w){
|
|
if(myproc()->killed){
|
|
release(&d->lock);
|
|
return -EINTR;
|
|
}
|
|
sleep(d, &d->lock);
|
|
}
|
|
|
|
while(d->r != d->w && got + (int)sizeof(struct input_event) <= n){
|
|
memmove(dst + got, &d->ev[d->r % NEVENT], sizeof(struct input_event));
|
|
d->r++;
|
|
got += sizeof(struct input_event);
|
|
}
|
|
release(&d->lock);
|
|
return got;
|
|
}
|
|
|
|
// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here,
|
|
// and a program that writes one should not see an error for it.
|
|
int
|
|
inputwrite(struct inode *ip, uint64 off, char *src, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Producers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// AT scancode set 1, E0-prefixed. The unprefixed bytes need no table at all:
|
|
// Linux's keycodes were numbered from this set and still match it below 0x54.
|
|
// These are the ones that do not.
|
|
static const struct { uint8 sc; uint8 key; } e0map[] = {
|
|
{0x1C, 96}, // KP_ENTER
|
|
{0x1D, KEY_RIGHTCTRL},
|
|
{0x35, 98}, // KP_SLASH
|
|
{0x38, KEY_RIGHTALT},
|
|
{0x47, KEY_HOME},
|
|
{0x48, KEY_UP},
|
|
{0x49, KEY_PAGEUP},
|
|
{0x4B, KEY_LEFT},
|
|
{0x4D, KEY_RIGHT},
|
|
{0x4F, KEY_END},
|
|
{0x50, KEY_DOWN},
|
|
{0x51, KEY_PAGEDOWN},
|
|
{0x52, KEY_INSERT},
|
|
{0x53, KEY_DELETE},
|
|
{0x5B, KEY_LEFTMETA},
|
|
{0x5C, KEY_RIGHTMETA},
|
|
};
|
|
|
|
// One byte from the PS/2 port, before it has been turned into a character.
|
|
void
|
|
input_ps2_scancode(int raw, int e0)
|
|
{
|
|
int code = raw & 0x7F;
|
|
int down = (raw & 0x80) ? 0 : 1;
|
|
int key = 0;
|
|
unsigned i;
|
|
|
|
if(e0){
|
|
for(i = 0; i < sizeof(e0map)/sizeof(e0map[0]); i++)
|
|
if(e0map[i].sc == code){
|
|
key = e0map[i].key;
|
|
break;
|
|
}
|
|
if(key == 0)
|
|
return; // an E0 key nothing has a name for
|
|
} else {
|
|
if(code == 0 || code > 0x58)
|
|
return;
|
|
key = code;
|
|
}
|
|
|
|
input_report(INPUT_KBD, EV_KEY, key, down);
|
|
input_sync(INPUT_KBD);
|
|
}
|
|
|
|
// USB HID usage -> Linux keycode. The mapping is not derivable: HID numbers
|
|
// keys by position on a US layout starting at 'a', and the AT set numbers them
|
|
// by the order IBM wired the matrix. The table is the same one Linux carries
|
|
// for the same reason.
|
|
static const uint8 hid_keycode[] = {
|
|
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
|
|
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
|
|
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
|
|
27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
|
|
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
|
|
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
|
|
72, 73, 82, 83, 86,127,116,117,
|
|
};
|
|
|
|
// The eight modifier bits in a boot report, in bit order.
|
|
static const uint8 hid_modkey[8] = {
|
|
KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA,
|
|
KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA,
|
|
};
|
|
|
|
int
|
|
input_hid_keycode(int usage)
|
|
{
|
|
if(usage >= 0xE0 && usage <= 0xE7)
|
|
return hid_modkey[usage - 0xE0];
|
|
if(usage < 0 || usage >= (int)sizeof(hid_keycode))
|
|
return 0;
|
|
return hid_keycode[usage];
|
|
}
|
|
|
|
void
|
|
input_key(int usage, int down)
|
|
{
|
|
int key = input_hid_keycode(usage);
|
|
|
|
if(key == 0)
|
|
return;
|
|
input_report(INPUT_KBD, EV_KEY, key, down);
|
|
input_sync(INPUT_KBD);
|
|
}
|
|
|
|
// One mouse report: relative motion plus the current button state. `buttons`
|
|
// is a bitmask in the order both PS/2 and HID boot protocol use -- left,
|
|
// right, middle -- which is why neither caller has to reorder it.
|
|
// An absolute pointer: where it is, not how far it moved.
|
|
//
|
|
// A relative device can never agree with the host's cursor inside a VM -- the
|
|
// two accumulate independently and drift apart, which is exactly the symptom
|
|
// usb-tablet exists to remove. Reported as EV_ABS so a reader can tell the
|
|
// difference and treat the value as authoritative rather than as a delta.
|
|
void
|
|
input_mouse_abs(int x, int y, int buttons)
|
|
{
|
|
static int last;
|
|
static int lastx = -1, lasty = -1;
|
|
static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE };
|
|
int i;
|
|
|
|
if(x != lastx){ input_report(INPUT_MOUSE, EV_ABS, ABS_X, x); lastx = x; }
|
|
if(y != lasty){ input_report(INPUT_MOUSE, EV_ABS, ABS_Y, y); lasty = y; }
|
|
|
|
for(i = 0; i < 3; i++){
|
|
int now = (buttons >> i) & 1, was = (last >> i) & 1;
|
|
if(now != was)
|
|
input_report(INPUT_MOUSE, EV_KEY, btncode[i], now);
|
|
}
|
|
last = buttons;
|
|
|
|
input_report(INPUT_MOUSE, EV_SYN, SYN_REPORT, 0);
|
|
}
|
|
|
|
void
|
|
input_mouse(int dx, int dy, int buttons)
|
|
{
|
|
static int last;
|
|
static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE };
|
|
int i;
|
|
|
|
if(dx)
|
|
input_report(INPUT_MOUSE, EV_REL, REL_X, dx);
|
|
if(dy)
|
|
input_report(INPUT_MOUSE, EV_REL, REL_Y, dy);
|
|
|
|
// Only transitions. A button held down for a second is one press event, not
|
|
// a hundred -- a reader that has to filter repeats itself cannot tell a held
|
|
// button from a very fast double-click.
|
|
for(i = 0; i < 3; i++){
|
|
int now = (buttons >> i) & 1;
|
|
if(now != ((last >> i) & 1))
|
|
input_report(INPUT_MOUSE, EV_KEY, btncode[i], now);
|
|
}
|
|
last = buttons;
|
|
|
|
input_sync(INPUT_MOUSE);
|
|
}
|