SchOS/kernel/dev/input.c
Nexoniarz 8351e0323b kernel: HTTPS through wget, half-close, and /proc/<pid>/fd
busybox wget hands TLS to ssl_client over a socketpair and then does
shutdown(SHUT_WR). That cut the AF_UNIX link both ways, so ssl_client's
reply was a SIGPIPE and every https:// download hung. SHUT_WR is now a
half-close: the peer reads EOF and can still answer. TCP sockets get
shutdown() at all (tcp_shutdown for the FIN); it used to do nothing.

- exec keeps ignored signals and the signal mask, as execve(2) says.
- /proc/<pid>/fd: a link per descriptor -- the path it was opened by,
  socket:[n], pipe:[n], anon_inode:[eventpoll]. /proc/self too. The
  global inode bit moves above pid << 8; at 0x10000 it was /proc/256.
- fstat on a socket or pipe answers S_IFSOCK / S_IFIFO instead of EINVAL.
- poll, select and epoll wake as soon as a pipe, socket or input device
  changes, not on the next tick.
- Pipes are 64 KB of lazily allocated pages, copied in blocks, with
  PIPE_BUF atomicity: 300 MB through `yes | head | wc` in 7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-24 20:28:03 +02:00

591 lines
18 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 "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/mmu.h"
#include "core/proc.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "dev/input.h"
#include "core/uaccess.h"
#include "gfx/gfx.h"
#include "uapi/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;
// What the device has said, for the EVIOCG* queries. `caps` is every type
// and code it can produce: its defaults, set in inputinit(), and anything
// it has actually reported since -- a USB tablet makes the mouse device
// absolute the first time it moves. `keys` is which keys and buttons are
// down right now, and `abs` where each absolute axis last was. Tracked
// whether or not anybody has the device open; a program that opens it with
// a key held needs to know.
uchar caps[EV_MAX + 1][(KEY_MAX + 1) / 8];
uchar keys[(KEY_MAX + 1) / 8];
int32 abs[2];
};
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");
// Every key a PS/2 or USB keyboard here can produce is below 0x80, but a
// keyboard advertises the whole first page on Linux too, and both Xorg's
// evdev and libinput call a device a keyboard by finding letter keys in it.
struct input_dev *k = &idev[INPUT_KBD], *m = &idev[INPUT_MOUSE];
for(i = 1; i < 0x100; i++)
k->caps[EV_KEY][i / 8] |= 1 << (i % 8);
k->caps[0][0] = (1 << EV_SYN) | (1 << EV_KEY);
k->caps[0][EV_REP / 8] |= 1 << (EV_REP % 8);
m->caps[0][0] = (1 << EV_SYN) | (1 << EV_KEY) | (1 << EV_REL);
for(i = BTN_LEFT; i <= BTN_MIDDLE; i++)
m->caps[EV_KEY][i / 8] |= 1 << (i % 8);
m->caps[EV_REL][0] = (1 << REL_X) | (1 << REL_Y);
m->caps[EV_REL][REL_WHEEL / 8] |= 1 << (REL_WHEEL % 8);
// 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];
if(type > 0 && type <= EV_MAX && code >= 0 && code <= KEY_MAX){
d->caps[0][type / 8] |= 1 << (type % 8);
d->caps[type][code / 8] |= 1 << (code % 8);
if(type == EV_KEY){
if(value)
d->keys[code / 8] |= 1 << (code % 8);
else
d->keys[code / 8] &= ~(1 << (code % 8));
}
if(type == EV_ABS && code < 2)
d->abs[code] = value;
}
// 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]);
poll_kick(); // Xorg polls its devices; a mouse moves at the next tick otherwise
}
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);
}
}
// Copy `n` bytes of `src` out for a variable-length query: at most the
// caller's buffer, and the return value is how much went, as Linux's is.
static int
evioc_out(addr_t arg, uint cap, const void *src, uint n)
{
if(n > cap)
n = cap;
void *dst = user_ptr(arg, n ? n : 1);
if(!dst)
return -EFAULT;
memmove(dst, src, n);
return (int)n;
}
static int
evioc_str(addr_t arg, uint cap, const char *s)
{
return evioc_out(arg, cap, s, strlen(s) + 1);
}
// ioctl on /dev/input/eventN. See the EVIOC* block in include/dev/input.h.
int
input_ioctl(struct inode *ip, int request, addr_t arg)
{
int dev = ip->minor;
uint r = (uint)request;
if(dev < 0 || dev >= NINPUTDEV)
return -ENODEV;
struct input_dev *d = &idev[dev];
switch(r){
case EVIOCGRAB:
// Linux passes the flag *by value* in the argument, not by pointer.
return input_grab(dev, (int)arg);
case EVIOCREVOKE:
// A session manager taking the device back from a client on a VT
// switch. With one reader per device, dropping the grab is the part
// that has an effect.
return input_grab(dev, 0);
case EVIOCGVERSION: {
int *v = user_ptr(arg, sizeof *v);
if(!v)
return -EFAULT;
*v = 0x010001;
return 0;
}
case EVIOCGID: {
struct input_id *id = user_ptr(arg, sizeof *id);
if(!id)
return -EFAULT;
id->bustype = BUS_I8042;
id->vendor = 0x0001;
id->product = dev == INPUT_KBD ? 0x0001 : 0x0002;
id->version = 0x0100;
return 0;
}
case EVIOCGREP: {
uint *rep = user_ptr(arg, 2 * sizeof *rep);
if(!rep)
return -EFAULT;
rep[0] = 250; // delay, ms
rep[1] = 33; // period, ms
return 0;
}
case EVIOCSREP:
case EVIOCSCLOCKID:
// Repeat is the reader's business here -- the kernel queues no repeat
// events -- and the clock is the only one there is. Accepted, so that a
// program setting either at startup carries on.
return 0;
}
if(EVIOC_TYPE(r) != 'E' || EVIOC_DIR(r) != 2)
return -ENOTTY;
uint nr = EVIOC_NR(r), cap = EVIOC_SIZE(r);
int got;
if(nr >= EVIOCNR_GABS && nr < EVIOCNR_GABS + ABS_MAX + 1){
struct input_absinfo *a = user_ptr(arg, sizeof *a);
uint axis = nr - EVIOCNR_GABS;
if(!a)
return -EFAULT;
memset(a, 0, sizeof *a);
// Absolute positions arrive already in pixels; see mouse_set_abs().
if(axis < 2){
a->value = d->abs[axis];
a->maximum = (axis == 0 ? (int)gfx_width() : (int)gfx_height()) - 1;
if(a->maximum < 1)
a->maximum = axis == 0 ? 799 : 599;
}
return 0;
}
acquire(&d->lock);
if(nr >= EVIOCNR_GBIT && nr <= EVIOCNR_GBIT + EV_MAX){
uint type = nr - EVIOCNR_GBIT;
// EVIOCGBIT(0) is the bitmap of event types, which is one byte's worth
// of interest in a field Linux sizes for EV_MAX.
uint len = type == 0 ? (EV_MAX + 1) / 8 :
type == EV_KEY ? (KEY_MAX + 1) / 8 :
type == EV_REL ? (REL_MAX + 1) / 8 :
type == EV_ABS ? (ABS_MAX + 1) / 8 : 8;
uchar buf[(KEY_MAX + 1) / 8];
memmove(buf, d->caps[type], len);
release(&d->lock);
return evioc_out(arg, cap, buf, len);
}
if(nr == EVIOCNR_GKEY){
uchar buf[(KEY_MAX + 1) / 8];
memmove(buf, d->keys, sizeof buf);
release(&d->lock);
return evioc_out(arg, cap, buf, sizeof buf);
}
release(&d->lock);
switch(nr){
case EVIOCNR_GNAME:
return evioc_str(arg, cap, dev == INPUT_KBD ? "SchOS keyboard"
: "SchOS pointer");
case EVIOCNR_GPHYS:
return evioc_str(arg, cap, dev == INPUT_KBD ? "isa0060/serio0/input0"
: "isa0060/serio1/input0");
case EVIOCNR_GUNIQ:
return evioc_str(arg, cap, "");
case EVIOCNR_GPROP: {
uchar prop[4] = { 0 };
if(dev == INPUT_MOUSE)
prop[0] = 1 << INPUT_PROP_POINTER;
return evioc_out(arg, cap, prop, sizeof prop);
}
case EVIOCNR_GLED:
case EVIOCNR_GSND:
case EVIOCNR_GSW: {
// None of these exist here: all off.
uchar zero[8] = { 0 };
got = evioc_out(arg, cap, zero, sizeof zero);
return got;
}
}
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);
}