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