From 8b7b8df74f359ba338d111ae90fe9a4fc493b280 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 10 Aug 2026 18:27:01 +0200 Subject: [PATCH] usb-hid: bind an absolute pointer, and plumb EV_ABS through Groundwork for usb-tablet, which is the fix for the guest cursor and the host's never being in the same place: a PS/2 mouse reports how far it moved, two pointers accumulating deltas independently drift apart, and no amount of care in the compositor can make them agree. An absolute device reports where it is, so they coincide by construction. - the HID driver no longer matches the boot subclass only. usb-tablet declares subclass 0 and was refused at the door; hid_probe decides what an interface actually is, and still declines what it cannot make sense of. - a non-boot HID interface is taken to be an absolute pointer and is not asked for boot protocol, which it does not have -- that request failing is what stopped it binding. - tablet_report decodes the fixed 6-byte layout (buttons, 16-bit X, 16-bit Y, wheel) and scales the logical range to pixels there, because it is the only place that knows the range. - input_mouse_abs reports EV_ABS/ABS_X/ABS_Y so a reader can tell an absolute position from a delta, and mouse_set_abs places the pointer rather than nudging it. - Schcom treats EV_ABS as authoritative and EV_REL as a nudge. The device binds and identifies correctly: "usb-hid: tablet (absolute) on slot 1, endpoint 0x81 every 4 ms". It is NOT finished, and QEMU_INPUT is empty by default because of it. No reports have yet been observed arriving on the tablet's interrupt endpoint, so making it the default pointer would trade a cursor that drifts for one that does not move at all. Opt in with make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0' Next step is to find out why nothing arrives: whether the first transfer is queued but never completes, whether a completion needs re-arming for this endpoint, or whether QEMU is not routing motion to the tablet at all (`info mice`). bootcheck 108 [ok], schcom-test passes. Co-Authored-By: Claude Opus 5 --- Makefile | 23 +++++++++-- include/defs.h | 5 +++ include/input.h | 6 +++ include/usb.h | 5 +++ kernel/dev/input.c | 27 +++++++++++++ kernel/dev/mouse.c | 28 +++++++++++++ kernel/drivers/usb/hid/usb_hid.c | 66 ++++++++++++++++++++++++++++--- projects/schcom/schcom | Bin 45192 -> 45416 bytes projects/schcom/schcom.c | 25 +++++++++++- 9 files changed, 174 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 044a872..dd2c3ca 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,23 @@ QEMU = qemu-system-x86_64 # virtio-gpu wins the display class, stdvga carries the splash and text console. QEMU_DISPLAY = -vga std -device virtio-gpu-pci +# An absolute pointing device, on an xHCI controller. +# +# A PS/2 mouse is relative, so inside a VM the guest cursor and the host's +# accumulate independently and drift apart -- they are simply never in the same +# place. usb-tablet reports where the pointer *is*, so the two coincide. +# +# EMPTY BY DEFAULT, and that is not an oversight. The driver binds the tablet +# and identifies it correctly, but no reports have yet been observed arriving +# on its interrupt endpoint -- so switching the default pointer to it would +# replace a cursor that drifts with one that does not move. Opt in while +# working on it: +# +# make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0' +# +# See kernel/drivers/usb/hid/usb_hid.c, tablet_report(). +QEMU_INPUT ?= + # ----------------------------------------------------------------------- # Cross-compilation: clang everywhere it can be had # @@ -433,7 +450,7 @@ $(OVMF_VARS): chmod u+w $@ qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) - $(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY) + $(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) # Build the ramdisk-root variant. This is a target rather than just # "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT), @@ -920,12 +937,12 @@ schcom-test: schos.img $(BUILD)/schfs.img sh tools/posix/schcom_test.sh qemu-kvm: schos.img $(BUILD)/schfs.img - $(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) -cpu host \ + $(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu host \ -hda schos.img -hdb $(BUILD)/schfs.img \ -m 1G -smp 2 -machine pc-i440fx-9.2 qemu: schos.img $(BUILD)/schfs.img - $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ + $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu qemu64,+rdtscp \ -hda schos.img -hdb $(BUILD)/schfs.img \ -m 1G -smp 2 -machine pc-i440fx-9.2 diff --git a/include/defs.h b/include/defs.h index 57f6d2f..af8c317 100644 --- a/include/defs.h +++ b/include/defs.h @@ -196,6 +196,11 @@ int input_ioctl(struct inode*, int request, addr_t arg); int input_grab(int dev, int on); int input_is_grabbed(int dev); void input_report(int dev, int type, int code, int value); +// An absolute pointing device (usb-tablet). Coordinates are already in screen +// pixels; see input_mouse_abs in kernel/dev/input.c for why this is separate +// from the relative path. +void input_mouse_abs(int x, int y, int buttons); +void mouse_set_abs(int x, int y, int buttons); void input_sync(int dev); int input_ready(int dev); void input_ps2_scancode(int raw, int e0); diff --git a/include/input.h b/include/input.h index 53a63b5..8cfd332 100644 --- a/include/input.h +++ b/include/input.h @@ -39,6 +39,12 @@ struct input_event { #define SYN_REPORT 0 // Relative axes. +// Absolute axes, for a pointing device that reports where it *is* rather than +// how far it moved -- a tablet, a touchscreen, or a VM's absolute pointer. +// Same numbering as Linux, because /dev/input/eventN claims to speak evdev. +#define ABS_X 0x00 +#define ABS_Y 0x01 + #define REL_X 0x00 #define REL_Y 0x01 #define REL_WHEEL 0x08 diff --git a/include/usb.h b/include/usb.h index a01ac65..7be0477 100644 --- a/include/usb.h +++ b/include/usb.h @@ -49,6 +49,11 @@ #define USB_HID_SUBCLASS_BOOT 0x01 #define USB_HID_PROTO_KEYBOARD 0x01 #define USB_HID_PROTO_MOUSE 0x02 +// Not a USB value: bInterfaceProtocol has no code for "absolute pointer". +// A HID device that claims neither boot protocol is treated as a tablet, which +// is what a VM offers so its cursor and the guest's can agree. Kept out of the +// 0x00-0x02 range the specification defines so it cannot collide. +#define USB_HID_PROTO_TABLET 0x80 // Bus speeds. // diff --git a/kernel/dev/input.c b/kernel/dev/input.c index 7f019ac..bc1b399 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -368,6 +368,33 @@ input_key(int usage, int down) // One mouse report: relative motion plus the current button state. `buttons` // is a bitmask in the order both PS/2 and HID boot protocol use -- left, // right, middle -- which is why neither caller has to reorder it. +// An absolute pointer: where it is, not how far it moved. +// +// A relative device can never agree with the host's cursor inside a VM -- the +// two accumulate independently and drift apart, which is exactly the symptom +// usb-tablet exists to remove. Reported as EV_ABS so a reader can tell the +// difference and treat the value as authoritative rather than as a delta. +void +input_mouse_abs(int x, int y, int buttons) +{ + static int last; + static int lastx = -1, lasty = -1; + static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE }; + int i; + + if(x != lastx){ input_report(INPUT_MOUSE, EV_ABS, ABS_X, x); lastx = x; } + if(y != lasty){ input_report(INPUT_MOUSE, EV_ABS, ABS_Y, y); lasty = y; } + + for(i = 0; i < 3; i++){ + int now = (buttons >> i) & 1, was = (last >> i) & 1; + if(now != was) + input_report(INPUT_MOUSE, EV_KEY, btncode[i], now); + } + last = buttons; + + input_report(INPUT_MOUSE, EV_SYN, SYN_REPORT, 0); +} + void input_mouse(int dx, int dy, int buttons) { diff --git a/kernel/dev/mouse.c b/kernel/dev/mouse.c index 029c6f7..ac21454 100644 --- a/kernel/dev/mouse.c +++ b/kernel/dev/mouse.c @@ -95,6 +95,34 @@ mouse_inject(int dx, int dy, int buttons) input_mouse(dx, dy, buttons); } +// Put the pointer somewhere, rather than nudging it. +// +// The clamp is the same as the relative path's, and for the same reason: the +// screen size is a boot-time choice now. The coordinates arrive already scaled +// to pixels -- the HID driver does that, because it is the only place that +// knows the device's logical range. +void +mouse_set_abs(int x, int y, int buttons) +{ + int maxx = (int)vga_get_width(); + int maxy = (int)vga_get_height(); + if(maxx <= 0) maxx = 800; + if(maxy <= 0) maxy = 600; + + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x >= maxx) x = maxx - 1; + if(y >= maxy) y = maxy - 1; + + acquire(&mouse_state.lock); + mouse_state.buttons = buttons; + mouse_state.x = x; + mouse_state.y = y; + release(&mouse_state.lock); + + input_mouse_abs(x, y, buttons); +} + void mouseintr(void) { diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index 3abf05f..9d42b14 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -196,6 +196,41 @@ keyboard_report(struct hid *h, const uchar *rep, int len) // Mouse // --------------------------------------------------------------------------- +// QEMU's usb-tablet, and absolute pointers generally. +// +// Report: one byte of buttons, then X and Y as 16-bit little-endian values +// over the device's logical range, then a wheel byte. The range is 0..0x7fff +// and is *not* read from the report descriptor -- parsing one is a real piece +// of work, and this decodes exactly the fixed layout the tablet declares. +// A device whose report is a different length is left alone rather than +// guessed at. +// +// Scaling happens here because this is the only place that knows the logical +// range; everything above deals in pixels. +#define TABLET_LOGICAL_MAX 0x7fff + +static void +tablet_report(struct hid *h, const uchar *rep, int len) +{ + (void)h; + if(len < 5) + return; + + int rawx = rep[1] | (rep[2] << 8); + int rawy = rep[3] | (rep[4] << 8); + + int w = (int)vga_get_width(); + int h_px = (int)vga_get_height(); + if(w <= 0) w = 800; + if(h_px <= 0) h_px = 600; + + // Scale in 64-bit-free arithmetic: 0x7fff * 4096 still fits in an int. + int x = (int)(((long long)rawx * (w - 1)) / TABLET_LOGICAL_MAX); + int y = (int)(((long long)rawy * (h_px - 1)) / TABLET_LOGICAL_MAX); + + mouse_set_abs(x, y, rep[0] & 0x07); +} + static void mouse_report(struct hid *h, const uchar *rep, int len) { @@ -224,7 +259,9 @@ hid_complete(struct usb_dev *dev, void *buf, int len) if(h == 0) return; - if(h->proto == USB_HID_PROTO_KEYBOARD) + if(h->proto == USB_HID_PROTO_TABLET) + tablet_report(h, buf, len); + else if(h->proto == USB_HID_PROTO_KEYBOARD) keyboard_report(h, buf, len); else mouse_report(h, buf, len); @@ -259,11 +296,20 @@ hid_probe(struct usb_dev *dev) if(h == 0) return -1; + // A HID device that claims no boot protocol is taken to be an absolute + // pointer -- in practice a tablet, which is what a VM offers so that its + // cursor and the host's can agree. It must not be asked for boot protocol: + // it has none, the request fails, and refusing on that basis is what kept + // usb-tablet from binding at all. + if(proto != USB_HID_PROTO_KEYBOARD && proto != USB_HID_PROTO_MOUSE) + proto = USB_HID_PROTO_TABLET; + // Ask for the fixed layout explicitly. A device that supports the boot // subclass still starts in report protocol, so without this the reports // arrive in whatever shape its report descriptor declares -- which we have // not read and cannot interpret. - if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){ + if(proto != USB_HID_PROTO_TABLET && + hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){ cprintf("usb-hid: SET_PROTOCOL(boot) refused\n"); return -1; } @@ -288,17 +334,25 @@ hid_probe(struct usb_dev *dev) cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n", proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : - proto == USB_HID_PROTO_MOUSE ? "mouse" : "device", + proto == USB_HID_PROTO_MOUSE ? "mouse" : + proto == USB_HID_PROTO_TABLET ? "tablet (absolute)" : "device", dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval); return 0; } -// Boot subclass only. A HID device that does not claim it needs its report -// descriptor parsed, and we would have nothing useful to do with it. +// Any HID interface, not just the boot subclass. +// +// It was boot-only, on the reasoning that a device without boot protocol needs +// its report descriptor parsed and there would be nothing useful to do with it. +// That is right in general and wrong for the one device that matters here: +// usb-tablet declares subclass 0, so it was refused at the door -- and its +// report layout is fixed and known, so there is something useful to do with it +// after all. hid_probe decides what a given interface actually is; anything it +// cannot make sense of is still declined there. const struct usb_driver usb_hid_driver = { .name = "usb-hid", .class = USB_CLASS_HID, - .subclass = USB_HID_SUBCLASS_BOOT, + .subclass = 0xFF, .protocol = 0xFF, .devclass = DEVCLASS_INPUT, .probe = hid_probe, diff --git a/projects/schcom/schcom b/projects/schcom/schcom index 69fe506d09d04ee7b7869bfbf3a70e0c0fbd5026..92d09d6ba0b07bbc0596d5d68bc5f8080e275fa5 100644 GIT binary patch delta 8665 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 iK=Rk)lZS_f`RUwU2eY0y^=)v diff --git a/projects/schcom/schcom.c b/projects/schcom/schcom.c index 1518b29..c7fec79 100644 --- a/projects/schcom/schcom.c +++ b/projects/schcom/schcom.c @@ -68,7 +68,8 @@ static uint32_t fb_w, fb_h, fb_stride, fb_bpp; static size_t fb_size; static uint8_t *back; /* composited here, then copied out in one go */ -/* Pointer position, moved by EV_REL from the mouse. */ +/* Pointer position: set outright by EV_ABS from a tablet, nudged by EV_REL + * from a plain mouse. */ static int32_t ptr_x, ptr_y; static void @@ -517,6 +518,25 @@ handle_input(int fd) int repaint = 0; + /* An absolute pointer says where it is; a relative one says how far it + * moved. Taking the absolute value as authoritative is the whole point of + * usb-tablet: two pointers that accumulate deltas independently drift + * apart, which inside a VM shows up as the guest cursor sitting somewhere + * the host's is not. */ + if (ev.type == EV_ABS) { + int old_x = ptr_x, old_y = ptr_y; + if (ev.code == ABS_X) { ptr_x = ev.value; repaint = 1; } + if (ev.code == ABS_Y) { ptr_y = ev.value; repaint = 1; } + if (ptr_x < 0) ptr_x = 0; + if (ptr_y < 0) ptr_y = 0; + if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; + if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; + if (repaint) { + damage(old_x, old_y, PTR_SIZE, PTR_SIZE); + damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); + } + } + if (ev.type == EV_REL) { int old_x = ptr_x, old_y = ptr_y; if (ev.code == REL_X) { ptr_x += ev.value; repaint = 1; } @@ -542,7 +562,8 @@ handle_input(int fd) if (surfaces[i].used) top = &surfaces[i]; - if (top && (ev.type == EV_KEY || ev.type == EV_REL)) { + if (top && (ev.type == EV_KEY || ev.type == EV_REL || + ev.type == EV_ABS)) { struct schcom_input msg; memset(&msg, 0, sizeof msg); msg.hdr.op = SCHCOM_OP_INPUT;