input, fb, vt, sysfs: the kernel calls Xorg makes
Xorg on a framebuffer -- the fbdev video driver, the evdev input driver, libpciaccess -- asks the kernel a fixed set of questions before it draws anything, and gives up on the first wrong answer. These are those answers. evdev: EVIOCGVERSION, GID, GNAME, GPHYS, GUNIQ, GPROP, GBIT, GKEY, GABS, GREP and the accepted setters. Xorg and libinput decide what a device *is* from the capability bitmaps; /dev/input/eventN answered only EVIOCGRAB, which reads as no device at all. Each device's bitmaps are its defaults plus whatever it has reported, so a USB tablet makes the pointer absolute. fbdev: FBIOPUT_VSCREENINFO succeeds for the mode already set, as on Linux -- the fbdev driver sets it every time it starts -- and still refuses any other. FBIOPAN_DISPLAY to the origin; FBIOBLANK. The console: KDSETMODE(KD_GRAPHICS) stops it drawing on a terminal, KDSKBMODE(K_OFF) stops keys reaching its tty, and VT_PROCESS now does the switch handshake: leaving the terminal sends relsig and waits for VT_RELDISP, arriving sends acqsig. A terminal whose owner has died goes back to the kernel whole, so a crashed X cannot leave the console dead. /sys: boot_vga, a writable enable, and graphics/fb0 under the display device, which is how the fbdev driver pairs a card with /dev/fb0. ports/linux-headers: <linux/*.h> into musl's prefix, from sabotage- linux's kernel-headers 4.19.88-2. Its checksum is recorded from the first download; upstream publishes none. cmd/xready makes each call and checks the answer, including the switch handshake on tty2 and an owner that dies holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
132a2981b1
commit
b1323e193a
14 changed files with 860 additions and 32 deletions
|
|
@ -19,6 +19,8 @@
|
|||
#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
|
||||
|
|
@ -46,6 +48,17 @@ struct input_dev {
|
|||
// 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];
|
||||
|
|
@ -61,6 +74,21 @@ inputinit(void)
|
|||
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();
|
||||
|
|
@ -79,6 +107,19 @@ input_report(int dev, int type, int code, int value)
|
|||
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)
|
||||
|
|
@ -213,18 +254,146 @@ inputclose(struct inode *ip)
|
|||
}
|
||||
}
|
||||
|
||||
// ioctl on /dev/input/eventN. Only EVIOCGRAB so far; the EVIOCG* query
|
||||
// requests (name, id, capability bitmaps) can arrive when something needs
|
||||
// them.
|
||||
// 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 on;
|
||||
int dev = ip->minor;
|
||||
uint r = (uint)request;
|
||||
|
||||
if(request == EVIOCGRAB){
|
||||
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.
|
||||
on = (int)arg;
|
||||
return input_grab(ip->minor, on);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue