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
233
cmd/xready/main.c
Normal file
233
cmd/xready/main.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// xready: the kernel calls Xorg makes before it draws anything, checked one
|
||||
// by one -- the evdev queries its input driver decides what a device is from,
|
||||
// the framebuffer calls its fbdev driver sets the mode with, and the console
|
||||
// calls it takes a terminal with, including the switch handshake.
|
||||
//
|
||||
// And the part of /sys libpciaccess reads to find the card to use.
|
||||
//
|
||||
// Uses tty2 for the terminal part, and puts everything back before it exits.
|
||||
// A musl program, linked dynamically, like evtest.
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kd.h>
|
||||
#include <linux/vt.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int bad;
|
||||
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (!(cond)) { printf("xready: FAIL " __VA_ARGS__); printf("\n"); bad++; } \
|
||||
} while (0)
|
||||
|
||||
#define HAS(bits, n) ((bits)[(n) / 8] & (1 << ((n) % 8)))
|
||||
|
||||
static void
|
||||
evdev(void)
|
||||
{
|
||||
unsigned char ev[EV_MAX / 8 + 1], key[KEY_MAX / 8 + 1], rel[REL_MAX / 8 + 1];
|
||||
char name[64];
|
||||
struct input_id id;
|
||||
int v, fd;
|
||||
|
||||
fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
CHECK(fd >= 0, "open event0: %s", strerror(errno));
|
||||
if (fd < 0)
|
||||
return;
|
||||
CHECK(ioctl(fd, EVIOCGVERSION, &v) == 0 && v == EV_VERSION, "EVIOCGVERSION");
|
||||
CHECK(ioctl(fd, EVIOCGID, &id) == 0 && id.bustype == BUS_I8042, "EVIOCGID");
|
||||
CHECK(ioctl(fd, EVIOCGNAME(sizeof name), name) > 0 && strstr(name, "keyboard"),
|
||||
"EVIOCGNAME");
|
||||
memset(ev, 0, sizeof ev);
|
||||
CHECK(ioctl(fd, EVIOCGBIT(0, sizeof ev), ev) > 0 && HAS(ev, EV_KEY) &&
|
||||
!HAS(ev, EV_REL), "keyboard EVIOCGBIT(0)");
|
||||
memset(key, 0, sizeof key);
|
||||
CHECK(ioctl(fd, EVIOCGBIT(EV_KEY, sizeof key), key) > 0 && HAS(key, KEY_A) &&
|
||||
HAS(key, KEY_ENTER) && !HAS(key, BTN_LEFT), "keyboard EVIOCGBIT(EV_KEY)");
|
||||
CHECK(ioctl(fd, EVIOCGKEY(sizeof key), key) > 0, "EVIOCGKEY");
|
||||
CHECK(ioctl(fd, EVIOCSCLOCKID, &(int){CLOCK_MONOTONIC}) == 0, "EVIOCSCLOCKID");
|
||||
close(fd);
|
||||
|
||||
fd = open("/dev/input/event1", O_RDONLY | O_NONBLOCK);
|
||||
CHECK(fd >= 0, "open event1: %s", strerror(errno));
|
||||
if (fd < 0)
|
||||
return;
|
||||
memset(ev, 0, sizeof ev);
|
||||
CHECK(ioctl(fd, EVIOCGBIT(0, sizeof ev), ev) > 0 && HAS(ev, EV_REL),
|
||||
"pointer EVIOCGBIT(0)");
|
||||
memset(key, 0, sizeof key);
|
||||
CHECK(ioctl(fd, EVIOCGBIT(EV_KEY, sizeof key), key) > 0 && HAS(key, BTN_LEFT) &&
|
||||
!HAS(key, KEY_A), "pointer EVIOCGBIT(EV_KEY)");
|
||||
memset(rel, 0, sizeof rel);
|
||||
CHECK(ioctl(fd, EVIOCGBIT(EV_REL, sizeof rel), rel) > 0 && HAS(rel, REL_X) &&
|
||||
HAS(rel, REL_WHEEL), "pointer EVIOCGBIT(EV_REL)");
|
||||
unsigned char prop[4] = {0};
|
||||
CHECK(ioctl(fd, EVIOCGPROP(sizeof prop), prop) >= 0 && HAS(prop, INPUT_PROP_POINTER),
|
||||
"EVIOCGPROP");
|
||||
struct input_absinfo abs;
|
||||
CHECK(ioctl(fd, EVIOCGABS(ABS_X), &abs) == 0 && abs.maximum > 0, "EVIOCGABS");
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
fbdev(void)
|
||||
{
|
||||
struct fb_var_screeninfo var, want;
|
||||
int fd = open("/dev/fb0", O_RDWR);
|
||||
|
||||
CHECK(fd >= 0, "open fb0: %s", strerror(errno));
|
||||
if (fd < 0)
|
||||
return;
|
||||
CHECK(ioctl(fd, FBIOGET_VSCREENINFO, &var) == 0, "FBIOGET_VSCREENINFO");
|
||||
want = var;
|
||||
want.activate = FB_ACTIVATE_TEST;
|
||||
CHECK(ioctl(fd, FBIOPUT_VSCREENINFO, &want) == 0 && want.xres == var.xres,
|
||||
"FBIOPUT_VSCREENINFO, the current mode");
|
||||
want = var;
|
||||
want.xres++;
|
||||
CHECK(ioctl(fd, FBIOPUT_VSCREENINFO, &want) < 0 && errno == EINVAL,
|
||||
"FBIOPUT_VSCREENINFO, another mode");
|
||||
want = var;
|
||||
CHECK(ioctl(fd, FBIOPAN_DISPLAY, &want) == 0, "FBIOPAN_DISPLAY");
|
||||
CHECK(ioctl(fd, FBIOBLANK, FB_BLANK_UNBLANK) == 0, "FBIOBLANK");
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
pci(void)
|
||||
{
|
||||
DIR *d = opendir("/sys/bus/pci/devices");
|
||||
struct dirent *e;
|
||||
char path[300], buf[8];
|
||||
int vgas = 0;
|
||||
|
||||
CHECK(d != 0, "opendir /sys/bus/pci/devices");
|
||||
if (!d)
|
||||
return;
|
||||
while ((e = readdir(d)) != 0) {
|
||||
if (e->d_name[0] == '.')
|
||||
continue;
|
||||
snprintf(path, sizeof path, "/sys/bus/pci/devices/%s/boot_vga", e->d_name);
|
||||
int fd = open(path, O_RDONLY);
|
||||
CHECK(fd >= 0, "open %s", path);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
memset(buf, 0, sizeof buf);
|
||||
read(fd, buf, sizeof buf - 1);
|
||||
close(fd);
|
||||
if (buf[0] != '1')
|
||||
continue;
|
||||
vgas++;
|
||||
snprintf(path, sizeof path, "/sys/bus/pci/devices/%s/graphics/fb0", e->d_name);
|
||||
CHECK(access(path, F_OK) == 0, "%s", path);
|
||||
snprintf(path, sizeof path, "/sys/bus/pci/devices/%s/enable", e->d_name);
|
||||
fd = open(path, O_RDWR);
|
||||
CHECK(fd >= 0 && write(fd, "1", 1) == 1, "write %s", path);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
closedir(d);
|
||||
CHECK(vgas == 1, "one boot_vga device (found %d)", vgas);
|
||||
}
|
||||
|
||||
static volatile sig_atomic_t rel, acq;
|
||||
static void on_rel(int s) { (void)s; rel++; }
|
||||
static void on_acq(int s) { (void)s; acq++; }
|
||||
|
||||
static int
|
||||
active(int fd)
|
||||
{
|
||||
struct vt_stat st;
|
||||
return ioctl(fd, VT_GETSTATE, &st) == 0 ? st.v_active : -1;
|
||||
}
|
||||
|
||||
static void
|
||||
settle(void)
|
||||
{
|
||||
usleep(50000); // let a signal be delivered
|
||||
}
|
||||
|
||||
static void
|
||||
console(void)
|
||||
{
|
||||
int fd = open("/dev/tty2", O_RDWR), mode;
|
||||
|
||||
CHECK(fd >= 0, "open tty2: %s", strerror(errno));
|
||||
if (fd < 0)
|
||||
return;
|
||||
int was = active(fd);
|
||||
CHECK(was > 0, "VT_GETSTATE");
|
||||
|
||||
char kbtype = 0;
|
||||
CHECK(ioctl(fd, KDGKBTYPE, &kbtype) == 0 && kbtype == KB_101, "KDGKBTYPE");
|
||||
|
||||
signal(SIGUSR1, on_rel);
|
||||
signal(SIGUSR2, on_acq);
|
||||
struct vt_mode vm = { .mode = VT_PROCESS, .relsig = SIGUSR1, .acqsig = SIGUSR2 };
|
||||
CHECK(ioctl(fd, VT_SETMODE, &vm) == 0, "VT_SETMODE");
|
||||
CHECK(ioctl(fd, KDSETMODE, KD_GRAPHICS) == 0, "KDSETMODE");
|
||||
CHECK(ioctl(fd, KDGETMODE, &mode) == 0 && mode == KD_GRAPHICS, "KDGETMODE");
|
||||
CHECK(ioctl(fd, KDSKBMODE, K_OFF) == 0, "KDSKBMODE");
|
||||
CHECK(ioctl(fd, KDGKBMODE, &mode) == 0 && mode == K_OFF, "KDGKBMODE");
|
||||
|
||||
// On to tty2: it happens, and we are told to redraw.
|
||||
CHECK(ioctl(fd, VT_ACTIVATE, 2) == 0 && active(fd) == 2, "VT_ACTIVATE 2");
|
||||
settle();
|
||||
CHECK(acq == 1, "acqsig on arrival (got %d)", (int)acq);
|
||||
|
||||
// Away from it: we are asked first, and nothing moves until we answer.
|
||||
CHECK(ioctl(fd, VT_ACTIVATE, was) == 0, "VT_ACTIVATE back");
|
||||
settle();
|
||||
CHECK(rel == 1, "relsig on leaving (got %d)", (int)rel);
|
||||
CHECK(active(fd) == 2, "switch waits for VT_RELDISP (active %d)", active(fd));
|
||||
CHECK(ioctl(fd, VT_RELDISP, 0) == 0 && active(fd) == 2, "VT_RELDISP 0 refuses");
|
||||
CHECK(ioctl(fd, VT_ACTIVATE, was) == 0, "VT_ACTIVATE again");
|
||||
settle();
|
||||
CHECK(rel == 2 && ioctl(fd, VT_RELDISP, 1) == 0 && active(fd) == was,
|
||||
"VT_RELDISP 1 lets it go (active %d)", active(fd));
|
||||
|
||||
vm.mode = VT_AUTO;
|
||||
ioctl(fd, KDSKBMODE, K_UNICODE);
|
||||
ioctl(fd, KDSETMODE, KD_TEXT);
|
||||
ioctl(fd, VT_SETMODE, &vm);
|
||||
|
||||
// A display server that dies holding the terminal must not take the
|
||||
// console with it.
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
struct vt_mode cm = { .mode = VT_PROCESS, .relsig = SIGUSR1, .acqsig = SIGUSR2 };
|
||||
signal(SIGUSR2, SIG_IGN);
|
||||
ioctl(fd, VT_SETMODE, &cm);
|
||||
ioctl(fd, KDSETMODE, KD_GRAPHICS);
|
||||
ioctl(fd, KDSKBMODE, K_OFF);
|
||||
ioctl(fd, VT_ACTIVATE, 2);
|
||||
_exit(0);
|
||||
}
|
||||
waitpid(pid, 0, 0);
|
||||
CHECK(active(fd) == 2, "the child took tty2");
|
||||
CHECK(ioctl(fd, VT_ACTIVATE, was) == 0 && active(fd) == was,
|
||||
"switch away from a dead owner happens at once (active %d)", active(fd));
|
||||
CHECK(ioctl(fd, KDGETMODE, &mode) == 0 && mode == KD_TEXT,
|
||||
"a dead owner's terminal is text again");
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
evdev();
|
||||
fbdev();
|
||||
pci();
|
||||
console();
|
||||
printf(bad ? "xready: %d failed\n" : "xready: ok\n", bad);
|
||||
return bad != 0;
|
||||
}
|
||||
Loading…
Reference in a new issue