SchOS/cmd/xready/main.c
Nexoniarz b1323e193a 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
2026-10-24 12:06:04 +02:00

233 lines
7.8 KiB
C

// 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;
}