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
17
Makefile
17
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
|
||||
|
||||
# <linux/*.h>, 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
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
// 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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
129
kernel/dev/vt.c
129
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,14 +896,65 @@ 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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; };
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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/ <linux/*.h> for the ioctls -- link-time only, ships nothing
|
||||
#
|
||||
# A port directory holds only a Makefile and optional patches/. Everything
|
||||
# generated lives in build/ports/<name>/ — 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 $@
|
||||
|
|
|
|||
52
ports/linux-headers/Makefile
Normal file
52
ports/linux-headers/Makefile
Normal file
|
|
@ -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/
|
||||
#
|
||||
# <linux/input.h>, <linux/fb.h>, <linux/kd.h>, <linux/vt.h> 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)
|
||||
Loading…
Reference in a new issue