diff --git a/Makefile b/Makefile index fff6338..e972c13 100644 --- a/Makefile +++ b/Makefile @@ -128,7 +128,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) # Third-party software does NOT go here — see ports/. # ----------------------------------------------------------------------- ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) -CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest +CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest xready CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) # ----------------------------------------------------------------------- @@ -1058,6 +1058,21 @@ DYNLD = -Wl,-dynamic-linker,/lib/ld-musl-x86_64.so.1 -Wl,-rpath,/lib $(MUSLPREFIX)/lib/libc.so: $(MAKE) -C ports musl CC=gcc +# , for programs that spell ioctls the way Linux does. See +# ports/linux-headers/Makefile. +LINUXHDR = $(MUSLPREFIX)/include/linux/input.h +$(LINUXHDR): $(MUSLPREFIX)/lib/libc.so + $(MAKE) -C ports linux-headers + +# xready: the kernel calls Xorg makes before drawing, checked. Musl and +# dynamic, like evtest. +$(BUILD)/cmd/xready: cmd/xready/main.c $(MUSLPREFIX)/lib/libc.so $(LINUXHDR) + @mkdir -p $(BUILD)/cmd + $(DYNCC) -fno-pic -c $< -o $(BUILD)/cmd/xready.o + $(DYNCC) -no-pie -nostdlib $(DYNLD) -o $@ \ + $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/xready.o \ + -L$(MUSLPREFIX)/lib -lc $(PORTEND) + $(BUILD)/cmd/libdynhello.so: cmd/dynhello/lib.c $(MUSLPREFIX)/lib/libc.so @mkdir -p $(BUILD)/cmd $(DYNCC) -fPIC -c $< -o $(BUILD)/cmd/libdynhello.o diff --git a/cmd/xready/main.c b/cmd/xready/main.c new file mode 100644 index 0000000..dd680fe --- /dev/null +++ b/cmd/xready/main.c @@ -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 +#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; +} diff --git a/include/dev/input.h b/include/dev/input.h index 260d623..9a6ce2b 100644 --- a/include/dev/input.h +++ b/include/dev/input.h @@ -35,6 +35,9 @@ struct input_event { #define EV_REL 0x02 #define EV_ABS 0x03 #define EV_MSC 0x04 +#define EV_LED 0x11 +#define EV_REP 0x14 +#define EV_MAX 0x1f #define SYN_REPORT 0 @@ -48,6 +51,8 @@ struct input_event { #define REL_X 0x00 #define REL_Y 0x01 #define REL_WHEEL 0x08 +#define REL_MAX 0x0f +#define ABS_MAX 0x3f // Buttons live in the EV_KEY code space alongside keys, which is why the // keyboard's codes stop well below 0x100. @@ -82,7 +87,7 @@ struct input_event { #define KEY_LEFTMETA 125 #define KEY_RIGHTMETA 126 -#define KEY_MAX 255 +#define KEY_MAX 0x2ff // Linux's; the bitmaps are this wide // EVIOCGRAB -- take/release exclusive access to a device. // @@ -91,6 +96,59 @@ struct input_event { // inventing a set just for this would be a bigger change than the feature. // A non-zero argument grabs, zero releases; the argument is passed by value. #define EVIOCGRAB 0x40044590 +#define EVIOCREVOKE 0x40044591 + +// The queries a program makes before it reads anything: what the device is +// called, what it can report, what state it is in now. Xorg's evdev driver +// and libinput both decide what a device *is* -- keyboard, mouse, tablet -- +// from the capability bitmaps alone, and treat a device that answers ENOTTY +// as no device at all. +// +// Linux's _IOC encoding: direction in bits 31:30 (2 read, 1 write), the size +// of the argument in 29:16, the type ('E') in 15:8, the number in 7:0. The +// variable-length ones carry the caller's buffer size in the size field, so +// they are matched with it masked out. +#define EVIOC_DIR(r) (((uint)(r) >> 30) & 3) +#define EVIOC_SIZE(r) (((uint)(r) >> 16) & 0x3fff) +#define EVIOC_TYPE(r) (((uint)(r) >> 8) & 0xff) +#define EVIOC_NR(r) ((uint)(r) & 0xff) + +#define EVIOCGVERSION 0x80044501 +#define EVIOCGID 0x80084502 +#define EVIOCGREP 0x80084503 +#define EVIOCSREP 0x40084503 +#define EVIOCSCLOCKID 0x400445a0 +// By number, with the length taken from the request: +#define EVIOCNR_GNAME 0x06 +#define EVIOCNR_GPHYS 0x07 +#define EVIOCNR_GUNIQ 0x08 +#define EVIOCNR_GPROP 0x09 +#define EVIOCNR_GKEY 0x18 +#define EVIOCNR_GLED 0x19 +#define EVIOCNR_GSND 0x1a +#define EVIOCNR_GSW 0x1b +#define EVIOCNR_GBIT 0x20 // + event type, 0x20..0x3f +#define EVIOCNR_GABS 0x40 // + axis, 0x40..0x7f +#define EVIOCNR_SABS 0xc0 // + axis + +struct input_id { + uint16 bustype; + uint16 vendor; + uint16 product; + uint16 version; +}; + +struct input_absinfo { + int32 value; + int32 minimum; + int32 maximum; + int32 fuzz; + int32 flat; + int32 resolution; +}; + +#define BUS_I8042 0x11 +#define INPUT_PROP_POINTER 0x00 // Which device an event came from. Both are minor numbers under the input // major, and both are the index into the driver's array. diff --git a/include/dev/vt.h b/include/dev/vt.h index 2438f00..b21700d 100644 --- a/include/dev/vt.h +++ b/include/dev/vt.h @@ -134,6 +134,12 @@ struct vt { // VT_AUTO or VT_PROCESS. In VT_PROCESS a program has claimed switching and // the kernel stops acting on the chord -- see vt_kernel_switches(). int mode; + int owner; // pid that set VT_PROCESS + int relsig, acqsig; // what to send it; see vt_switch() + int leaving_to; // a switch waiting on VT_RELDISP, or -1 + + int kdmode; // KD_TEXT, or KD_GRAPHICS: not drawn + int kbmode; // K_UNICODE..., or K_OFF: no keys }; // The active terminal, and the one the framebuffer is showing. @@ -160,5 +166,9 @@ void vt_repaint(struct vt *v); // grid -> framebuffer, if active uint32 vt_rgb(uint8 idx); // palette index -> RGB int vt_index(struct vt *v); // 0-based number of a terminal int vt_kernel_switches(void); // 0 if a program owns switching +int vt_reldisp(struct vt *v, int arg); // VT_RELDISP +void vt_set_process(struct vt *v, int mode, int relsig, int acqsig); +void vt_set_kdmode(struct vt *v, int mode); +int vt_takes_keys(struct vt *v); // 0 in K_OFF or a raw mode #endif // VT_H diff --git a/include/gfx/fb.h b/include/gfx/fb.h index e80fbdf..e32ee79 100644 --- a/include/gfx/fb.h +++ b/include/gfx/fb.h @@ -20,6 +20,7 @@ #define FBIOPUT_VSCREENINFO 0x4601 #define FBIOGET_FSCREENINFO 0x4602 #define FBIOBLANK 0x4611 +#define FBIOPAN_DISPLAY 0x4606 // fb_fix_screeninfo.type #define FB_TYPE_PACKED_PIXELS 0 diff --git a/include/uapi/posix.h b/include/uapi/posix.h index 7d5e3c5..d39fe7c 100644 --- a/include/uapi/posix.h +++ b/include/uapi/posix.h @@ -145,8 +145,34 @@ struct rlimit { #define VT_GETMODE 0x5601 #define VT_SETMODE 0x5602 #define VT_GETSTATE 0x5603 // struct vt_stat +#define VT_RELDISP 0x5605 // by value: 1 release, 0 refuse, VT_ACKACQ #define VT_ACTIVATE 0x5606 #define VT_WAITACTIVE 0x5607 +#define VT_DISALLOCATE 0x5608 + +// The console's keyboard and display modes, which a display server sets on +// the terminal it takes: KD_GRAPHICS so the console stops drawing on it, and +// K_OFF so keys stop reaching its tty -- the server reads them from +// /dev/input instead. Linux's numbers. +#define KIOCSOUND 0x4B2F +#define KDMKTONE 0x4B30 +#define KDGETLED 0x4B31 +#define KDSETLED 0x4B32 +#define KDGKBTYPE 0x4B33 +#define KDSETMODE 0x4B3A +#define KDGETMODE 0x4B3B +#define KDGKBMODE 0x4B44 +#define KDSKBMODE 0x4B45 +#define KDGKBLED 0x4B64 +#define KDSKBLED 0x4B65 +#define KD_TEXT 0x00 +#define KD_GRAPHICS 0x01 +#define K_RAW 0x00 +#define K_XLATE 0x01 +#define K_MEDIUMRAW 0x02 +#define K_UNICODE 0x03 +#define K_OFF 0x04 +#define KB_101 0x02 // VT_SETMODE modes. VT_AUTO is the kernel switching on its own; VT_PROCESS // means a program has taken switching over and the kernel must keep its hands @@ -200,8 +226,8 @@ struct vt_chord { struct vt_mode { char mode; // VT_AUTO or VT_PROCESS char waitv; // unused here - short relsig; // signal on release -- not delivered yet - short acqsig; // signal on acquire -- not delivered yet + short relsig; // signal on release; see vt_switch() + short acqsig; // signal on acquire short frsig; // unused }; diff --git a/kernel/core/console.c b/kernel/core/console.c index b5e56ed..099d000 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -489,6 +489,8 @@ consoleintr(int (*getc)(void)) // Input goes to whichever terminal is on screen. That is what makes a // terminal a terminal: the keyboard follows the eyes. struct vt *v = vt_current(); + if (!vt_takes_keys(v)) + continue; // K_OFF: its owner reads /dev/input // Input mapping, before anything looks at the character. ISTRIP is about // seven-bit lines and applies to characters only -- masking a keycode diff --git a/kernel/dev/fb.c b/kernel/dev/fb.c index a5c991f..0b9412a 100644 --- a/kernel/dev/fb.c +++ b/kernel/dev/fb.c @@ -189,14 +189,47 @@ fb_ioctl(int request, addr_t arg) return 0; } - // The mode came from the bootloader's VBE call and we have no way to - // change it. Saying so is more useful than accepting the request and - // leaving the caller to discover, by drawing, that nothing happened. - case FBIOPUT_VSCREENINFO: - return -EINVAL; + // The mode came from the loader and there is no way to change it. A + // request for a *different* one is refused, which is more useful than + // accepting it and leaving the caller to discover by drawing that nothing + // happened. A request for the mode already set succeeds and is answered + // with it, as on Linux: Xorg's fbdev driver sets the mode it was just told + // about, every time it starts, and gives up if that fails. + case FBIOPUT_VSCREENINFO: { + struct fb_var_screeninfo *v = + (struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v)); + if (!v) + return -EFAULT; + if (v->xres != (unsigned int)w || v->yres != (unsigned int)h || + (v->bits_per_pixel && v->bits_per_pixel != (unsigned int)gfx_fb_bpp()) || + (v->xres_virtual && v->xres_virtual != (unsigned int)w) || + (v->yres_virtual && v->yres_virtual != (unsigned int)h)) + return -EINVAL; + unsigned int act = v->activate; + memset(v, 0, sizeof(*v)); + v->xres = v->xres_virtual = (unsigned int)w; + v->yres = v->yres_virtual = (unsigned int)h; + v->bits_per_pixel = gfx_fb_bpp(); + v->activate = act; + pixel_format(v, v->bits_per_pixel); + return 0; + } + // One screen-sized buffer, so the only pan there is is to the origin. + case FBIOPAN_DISPLAY: { + struct fb_var_screeninfo *v = + (struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v)); + if (!v) + return -EFAULT; + return (v->xoffset || v->yoffset) ? -EINVAL : 0; + } + + // Blanking is a screen saver's request, and saying no makes Xorg log a + // warning every time one starts. There is no power control behind the + // framebuffer, so unblanking is all that can honestly succeed -- and + // blanking does too, as Linux's own drivers without the hook do. case FBIOBLANK: - return -EINVAL; + return 0; } return -ENOTTY; diff --git a/kernel/dev/input.c b/kernel/dev/input.c index 14ed5de..78c8801 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -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; } diff --git a/kernel/dev/vt.c b/kernel/dev/vt.c index c70c12c..b2e005d 100644 --- a/kernel/dev/vt.c +++ b/kernel/dev/vt.c @@ -20,6 +20,9 @@ #include "gfx/theme.h" #include "gfx/gfx.h" #include "dev/vt.h" +#include "arch/mmu.h" +#include "core/proc.h" +#include "uapi/errno.h" int vt_active = 0; @@ -54,10 +57,13 @@ cell_at(struct vt *v, int col, int row) return &v->cells[row * v->cols + col]; } +// On screen, and the console's to draw. A terminal in KD_GRAPHICS belongs to +// a display server: its grid still takes writes -- a kernel message lands in +// it -- but nothing is drawn, or the text would be painted over the desktop. static int is_active(struct vt *v) { - return vt_ready && v->index == vt_active; + return vt_ready && v->index == vt_active && v->kdmode != KD_GRAPHICS; } // Paint one cell from the grid. The single place that turns stored state into @@ -164,6 +170,8 @@ vt_init(void) // pgid 1, which is init's, so the first shell on each terminal already // sees itself as the foreground job rather than stopping with SIGTTIN. v->fg_pgrp = 1; + v->leaving_to = -1; + v->kbmode = K_UNICODE; termios_defaults(&v->tio); for (int r = 0; r < rows; r++) blank_row(v, r); @@ -826,6 +834,58 @@ vt_feed_1(struct vt *v, int c) // Switching // --------------------------------------------------------------------------- +// A terminal whose VT_PROCESS owner is gone goes back to the kernel, whole: +// switching, drawing and the keyboard. Otherwise a display server that +// crashed would leave the screen frozen on its last frame, the keyboard +// deaf and the chord ignored, with no way back short of a reset. +static int +owner_alive(struct vt *v) +{ + if (v->mode != VT_PROCESS) + return 0; + if (v->owner && proc_exists(v->owner)) + return 1; + v->mode = VT_AUTO; + v->owner = 0; + v->leaving_to = -1; + v->kbmode = K_UNICODE; + if (v->kdmode == KD_GRAPHICS) { + v->kdmode = KD_TEXT; + if (v->index == vt_active) { + vt_repaint(v); + gfx_flush(); + } + } + return 0; +} + +static void +switch_now(int i) +{ + struct vt *to = &vts[i]; + + vt_active = i; + + // Repaint from the grid. Nothing was saved on the way out because nothing + // needed to be: the outgoing terminal's grid has been current all along, + // which is the whole reason the grid exists. A terminal in graphics mode is + // not repainted -- its owner is told, and draws its own. + if (to->kdmode != KD_GRAPHICS) { + vt_repaint(to); + gfx_flush(); + } + if (owner_alive(to) && to->acqsig) + sigsend(to->owner, to->acqsig); +} + +// Switch terminals. +// +// Away from one in VT_PROCESS, the switch is a request: its owner is sent +// relsig and the switch waits for VT_RELDISP. That is how a display server +// learns it is about to lose the screen -- it stops drawing, and only then +// says yes. Switching without asking left it drawing straight over the text +// console it had been switched away from. Back to such a terminal, the +// switch happens and the owner is sent acqsig, to redraw. int vt_switch(int i) { @@ -836,16 +896,67 @@ vt_switch(int i) if (i == vt_active) return 0; - vt_active = i; - - // Repaint from the grid. Nothing was saved on the way out because nothing - // needed to be: the outgoing terminal's grid has been current all along, - // which is the whole reason the grid exists. - vt_repaint(&vts[i]); - gfx_flush(); + struct vt *from = &vts[vt_active]; + if (owner_alive(from) && from->relsig) { + from->leaving_to = i; + if (sigsend(from->owner, from->relsig) == 0) + return 0; + owner_alive(from); // died in between; fall through + } + switch_now(i); return 0; } +int +vt_reldisp(struct vt *v, int arg) +{ + if (arg == VT_ACKACQ) + return 0; + if (v->mode != VT_PROCESS || v->leaving_to < 0) + return -EINVAL; + int to = v->leaving_to; + v->leaving_to = -1; + if (arg && v->index == vt_active) + switch_now(to); + return 0; +} + +void +vt_set_process(struct vt *v, int mode, int relsig, int acqsig) +{ + v->mode = mode; + v->leaving_to = -1; + if (mode == VT_PROCESS) { + v->owner = myproc() ? myproc()->pid : 0; + v->relsig = relsig > 0 && relsig < 32 ? relsig : 0; + v->acqsig = acqsig > 0 && acqsig < 32 ? acqsig : 0; + } else { + v->owner = v->relsig = v->acqsig = 0; + } +} + +void +vt_set_kdmode(struct vt *v, int mode) +{ + int was = v->kdmode; + v->kdmode = mode; + // Back to text on the terminal on screen: the grid has been kept current + // underneath the whole time, so putting it back is a repaint. + if (was == KD_GRAPHICS && mode == KD_TEXT && v->index == vt_active) { + vt_repaint(v); + gfx_flush(); + } +} + +int +vt_takes_keys(struct vt *v) +{ + if (v->kbmode == K_XLATE || v->kbmode == K_UNICODE) + return 1; + // A raw or K_OFF terminal whose owner died gets its keyboard back. + return v->mode == VT_PROCESS && !owner_alive(v); +} + int vt_index(struct vt *v) { @@ -870,7 +981,7 @@ vt_index(struct vt *v) int vt_kernel_switches(void) { - return vt_current()->mode != VT_PROCESS; + return !owner_alive(vt_current()); } // Step to the previous or next terminal that is actually in use. diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 7693241..0910261 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -4,7 +4,8 @@ // // /sys/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq // subsystem_vendor subsystem_device -// resource config uevent +// resource config uevent boot_vga +// enable graphics/fb0/ // /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum // devnum speed bDevice* uevent // /sys/firmware/acpi/tables/DSDT the tables, byte for byte @@ -40,6 +41,7 @@ #include "dev/pci.h" #include "dev/usb.h" #include "core/hwinfo.h" +#include "gfx/gfx.h" // --------------------------------------------------------------------------- // Inode numbers: kind, index, file -- the whole identity, since iget gives @@ -87,11 +89,43 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = { static const char *const pcifiles[] = { 0, "vendor", "device", "class", "revision", "subsystem_vendor", - "subsystem_device", "irq", "resource", "config", "uevent", + "subsystem_device", "irq", "resource", "config", "uevent", "boot_vga", + "enable", }; #define PCI_CONFIG 9 +#define PCI_ENABLE 12 #define NPCIFILES ((int)(sizeof pcifiles / sizeof pcifiles[0])) +// Two directories under the display device only: graphics/ and graphics/fb0/. +// Xorg's fbdev driver finds the framebuffer that belongs to a PCI device by +// looking for exactly that path, and libpciaccess picks the primary card by +// boot_vga. Empty, because the path existing is the whole answer. +#define PCI_GRAPHICS 13 +#define PCI_FB0 14 + +// The display device: the one whose memory the framebuffer is in, which is +// what boot_vga means on Linux -- the card firmware brought up and drew on. +// Failing that, the first display controller. -1 when there is neither. +static int +boot_vga_idx(void) +{ + uint64 fb = gfx_fb_phys(); + int first = -1; + + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + if (d->class != 0x03) + continue; + if (first < 0) + first = i; + for (int b = 0; fb && b < 6; b++) + if (!d->bar[b].is_io && d->bar[b].base && fb >= d->bar[b].base && + fb < d->bar[b].base + d->bar[b].size) + return i; + } + return first; +} + static const char *const usbfiles[] = { 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", @@ -276,6 +310,12 @@ children(uint64 dino, child_fn cb, void *ctx) for (int f = 1; f < NPCIFILES; f++) if (cb(ctx, INO(K_PCI, idx, f), 0, pcifiles[f])) return 1; + if (idx == boot_vga_idx() && gfx_fb_phys() && + cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics")) + return 1; + } else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) { + if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0")) + return 1; } else if (kind == K_USB && I_FILE(dino) == 0) { for (int f = 1; f < NUSBFILES; f++) if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f])) @@ -293,7 +333,10 @@ parent_of(uint64 dino) { switch (I_KIND(dino)) { case K_DIR: return INO(K_DIR, dirs[I_IDX(dino)].parent, 0); - case K_PCI: return INO(K_DIR, D_PCIDEV, 0); + case K_PCI: + if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0); + if (I_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS); + return INO(K_DIR, D_PCIDEV, 0); case K_USB: return INO(K_DIR, D_USBDEV, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); } @@ -363,6 +406,13 @@ gen_pci(struct out *o, struct pci_dev *d, int f) case 5: puts_(o, "0x"); putx(o, svid, 4, 0); break; case 6: puts_(o, "0x"); putx(o, sdid, 4, 0); break; case 7: putu(o, d->irq == 0xFF ? 0 : d->irq); break; + case 11: putc_(o, d == pci_get(boot_vga_idx()) ? '1' : '0'); break; + case PCI_ENABLE: { + // Whether memory or I/O decoding is on: what "enabled" amounts to. + uint32 cmd = pci_read32(d->bus, d->slot, d->func, 0x04); + putc_(o, (cmd & 3) ? '1' : '0'); + break; + } case 8: // start, end, flags per BAR, then the ROM, as Linux prints them. for (int b = 0; b < 7; b++) { @@ -487,6 +537,8 @@ exists_dir(uint64 ino) case K_DIR: return I_IDX(ino) < D_NDIRS && (I_IDX(ino) != D_EFI || boot_is_efi()); case K_PCI: + if (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0) + return I_IDX(ino) == boot_vga_idx() && gfx_fb_phys() != 0; return I_IDX(ino) < pci_count(); case K_USB: { struct usb_info u; @@ -514,7 +566,9 @@ sysfs_iload(struct inode *ip) ip->size = 0; ip->nlink = 1; - if (I_FILE(ino) == 0 && exists_dir(ino)) { + int subdir = I_KIND(ino) == K_PCI && + (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0); + if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) { ip->type = T_DIR; ip->nlink = 2; return; @@ -533,6 +587,8 @@ sysfs_iload(struct inode *ip) kmfree(buf); if (n >= 0) { ip->type = T_FILE; + if (I_KIND(ino) == K_PCI && I_FILE(ino) == PCI_ENABLE) + ip->rwmode = 3; return; } } @@ -585,9 +641,18 @@ sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) return r; } +// Only `enable`, which libpciaccess writes before it touches a device. The +// kernel's drivers already turned decoding on for every device they use, and +// one nobody drives does not need it -- so the write is accepted and changes +// nothing, which is what it would have done on Linux for a device already on. static int sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) -{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -EROFS; } +{ + (void)u; (void)s; (void)o; + if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) + return (int)n; + return -EROFS; +} struct find { const char *name; uint64 ino; }; diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 486396f..85ad503 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -1544,7 +1544,8 @@ addr_t sys_ioctl(void) struct vt_mode *m = (struct vt_mode*)user_ptr(argp, sizeof(*m)); if (!m) return -EFAULT; if (m->mode != VT_AUTO && m->mode != VT_PROCESS) return -EINVAL; - vt_for_minor(f->ip->minor)->mode = m->mode; + vt_set_process(vt_for_minor(f->ip->minor), m->mode, m->relsig, + m->acqsig); return 0; } // Which chord the kernel switches on. SchOS's own ioctl -- see the long @@ -1601,10 +1602,60 @@ addr_t sys_ioctl(void) struct vt_mode *m = (struct vt_mode*)user_ptr(argp, sizeof(*m)); if (!m) return -EFAULT; memset(m, 0, sizeof(*m)); - m->mode = (char)vt_for_minor(f->ip->minor)->mode; + struct vt *v = vt_for_minor(f->ip->minor); + m->mode = (char)v->mode; + m->relsig = (short)v->relsig; + m->acqsig = (short)v->acqsig; return 0; } + // The display server's half of a switch; see vt_switch(). By value. + if (request == VT_RELDISP) + return vt_reldisp(vt_for_minor(f->ip->minor), (int)argp); + + // Terminals are a fixed set here and are never freed. + if (request == VT_DISALLOCATE) + return 0; + + if (request == KDSETMODE) { + if (argp != KD_TEXT && argp != KD_GRAPHICS) return -EINVAL; + vt_set_kdmode(vt_for_minor(f->ip->minor), (int)argp); + return 0; + } + if (request == KDGETMODE) { + int *out = (int*)user_ptr(argp, sizeof(int)); + if (!out) return -EFAULT; + *out = vt_for_minor(f->ip->minor)->kdmode; + return 0; + } + if (request == KDSKBMODE) { + if (argp > K_OFF) return -EINVAL; + vt_for_minor(f->ip->minor)->kbmode = (int)argp; + return 0; + } + if (request == KDGKBMODE) { + int *out = (int*)user_ptr(argp, sizeof(int)); + if (!out) return -EFAULT; + *out = vt_for_minor(f->ip->minor)->kbmode; + return 0; + } + if (request == KDGKBTYPE) { + char *out = (char*)user_ptr(argp, 1); + if (!out) return -EFAULT; + *out = KB_101; + return 0; + } + // LEDs and the bell: nothing to light and no speaker on this path. + if (request == KDGETLED || request == KDGKBLED) { + char *out = (char*)user_ptr(argp, 1); + if (!out) return -EFAULT; + *out = 0; + return 0; + } + if (request == KDSETLED || request == KDSKBLED || + request == KIOCSOUND || request == KDMKTONE) + return 0; + return 0; } diff --git a/ports/Makefile b/ports/Makefile index 34315f9..5a75fd8 100644 --- a/ports/Makefile +++ b/ports/Makefile @@ -8,6 +8,7 @@ # ports/kilo/ the text editor — installed as sysroot/bin/{kilo,edit} # ports/ncurses/ terminfo + screen library — link-time only, ships nothing # ports/zsh/ the Z shell — installed as sysroot/bin/zsh, beside /bin/sh +# ports/linux-headers/ for the ioctls -- link-time only, ships nothing # # A port directory holds only a Makefile and optional patches/. Everything # generated lives in build/ports// — see mk/port.mk for the contract. @@ -33,6 +34,7 @@ kilo: musl fastfetch: musl ncurses: musl zsh: musl ncurses +linux-headers: musl $(PORTS): $(MAKE) -C $@ diff --git a/ports/linux-headers/Makefile b/ports/linux-headers/Makefile new file mode 100644 index 0000000..0e68df6 --- /dev/null +++ b/ports/linux-headers/Makefile @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Linux's userspace headers -- GPL-2.0 WITH Linux-syscall-note, which is the +# exception that makes them includable by anything: https://kernel.org/ +# +# , , , and the rest are +# how a program spells the ioctls SchOS answers in Linux's numbering, and +# Xorg, libevdev and libinput include them without asking whether the kernel +# underneath is Linux. Taken from sabotage-linux's kernel-headers: the +# headers alone, sanitised, a megabyte rather than a kernel tree. +# +# Installed into musl's prefix, next to libc's own, because that is where +# every port already looks. Not scsi/: musl ships its own scsi/*.h, and these +# would overwrite them. Nothing goes into sysroot/ -- headers are build input. + +include ../mk/port.mk + +LH_VERSION := 4.19.88-2 +LH_URL := https://github.com/sabotage-linux/kernel-headers/releases/download/v$(LH_VERSION)/linux-headers-$(LH_VERSION).tar.xz +# Recorded on first download; upstream publishes no checksum to compare with. +LH_SHA256 := dc7abf734487553644258a3822cfd429d74656749e309f2b25f09f4282e05588 + +TARBALL := $(DISTDIR)/linux-headers-$(LH_VERSION).tar.xz +STAMP := $(MUSL)/include/linux/input.h + +$(TARBALL): | $(DISTDIR) + curl -fsSL -o $@ $(LH_URL) + sh $(SHA256CHECK) "$(LH_SHA256)" "$@" + +download: $(TARBALL) + +$(SRCDIR): $(TARBALL) + mkdir -p $(PORTDIR) + rm -rf $@ $(PORTDIR)/linux-headers-$(LH_VERSION) + tar -xJf $< -C $(PORTDIR) + mv $(PORTDIR)/linux-headers-$(LH_VERSION) $@ + +# A file in the prefix as the target, not a stamp here: musl's prefix is +# rebuilt from scratch when musl is, and the headers have to come back with it. +$(STAMP): | $(SRCDIR) + test -d $(MUSL)/include || { echo "linux-headers: build musl first"; exit 1; } + cd $(SRCDIR)/x86/include && for d in *; do \ + [ "$$d" = scsi ] && continue; \ + mkdir -p $(MUSL)/include/$$d && cp -R $$d/. $(MUSL)/include/$$d/; \ + done + +build install: $(STAMP) + +clean: + rm -rf $(PORTDIR) + +distclean: clean + rm -f $(TARBALL)