SchOS/kernel/dev/input.c
Nexoniarz 8b7b8df74f usb-hid: bind an absolute pointer, and plumb EV_ABS through
Groundwork for usb-tablet, which is the fix for the guest cursor and the
host's never being in the same place: a PS/2 mouse reports how far it
moved, two pointers accumulating deltas independently drift apart, and no
amount of care in the compositor can make them agree. An absolute device
reports where it is, so they coincide by construction.

  - the HID driver no longer matches the boot subclass only. usb-tablet
    declares subclass 0 and was refused at the door; hid_probe decides
    what an interface actually is, and still declines what it cannot make
    sense of.
  - a non-boot HID interface is taken to be an absolute pointer and is
    not asked for boot protocol, which it does not have -- that request
    failing is what stopped it binding.
  - tablet_report decodes the fixed 6-byte layout (buttons, 16-bit X,
    16-bit Y, wheel) and scales the logical range to pixels there,
    because it is the only place that knows the range.
  - input_mouse_abs reports EV_ABS/ABS_X/ABS_Y so a reader can tell an
    absolute position from a delta, and mouse_set_abs places the pointer
    rather than nudging it.
  - Schcom treats EV_ABS as authoritative and EV_REL as a nudge.

The device binds and identifies correctly: "usb-hid: tablet (absolute) on
slot 1, endpoint 0x81 every 4 ms".

It is NOT finished, and QEMU_INPUT is empty by default because of it. No
reports have yet been observed arriving on the tablet's interrupt
endpoint, so making it the default pointer would trade a cursor that
drifts for one that does not move at all. Opt in with

  make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0'

Next step is to find out why nothing arrives: whether the first transfer
is queued but never completes, whether a completion needs re-arming for
this endpoint, or whether QEMU is not routing motion to the tablet at all
(`info mice`).

bootcheck 108 [ok], schcom-test passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:27:01 +02:00

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 / 100;
e->time.tv_usec = (ticks % 100) * 10000;
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, uint 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, uint 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);
}