kernel: laptop touchpads over I2C-HID

A laptop's touchpad is on the chipset's I2C bus, found only through ACPI
(PNP0C50 / ACPI0C50), and SchOS had no I2C at all, so the touchpad of
every recent laptop did nothing.

kernel/acpi/i2c_hid.c drives the DesignWare I2C controller every x86
chipset uses (AMDI0010 on Ryzen) by polling, speaks HID over I2C to the
device the _DSM points at, and feeds the reports through the USB HID
parser. On AMD the interrupt pin's level is read every tick, so a report
is read exactly when one is waiting; elsewhere the input register is polled.

The touchpad is driven in mouse mode, which it starts in after a reset:
motion, clicks and taps become /dev/input/event1 like any mouse. The HID
parser now prefers a descriptor's relative pointer over an absolute one
for this, and takes descriptors from transports other than USB.

Untested on hardware: QEMU emulates no I2C touchpad.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-24 21:54:33 +02:00
commit e43f56c619
5 changed files with 733 additions and 15 deletions

View file

@ -25,6 +25,7 @@
#include "core/dma.h"
#include "dev/usb.h"
#include "dev/input.h"
#include "dev/hid.h"
#include "dev/kbd.h"
#include "dev/vt.h"
@ -49,6 +50,11 @@ struct hid {
// report, for a mouse whose descriptor could not be used.
int boot;
int report_id; // 0 when reports carry no ID byte
// 1 + the only report ID to take pointer fields from, 0 for any; and
// 1 + the first report ID with a relative X. A touchpad describes both a
// mouse report and a multi-finger one with absolute coordinates, and only
// the mouse report makes sense as a pointer; see hid_attach().
int only_id1, rel_id1;
struct hfield x, y, wheel, btn[HID_NBTN];
// A keyboard in report protocol: where its keys are, so that each report
@ -343,6 +349,10 @@ take_field(struct hid *h, uint32 usage, int bit, int size, int lmin, int lmax,
struct hfield *f = 0;
int page = usage >> 16, u = usage & 0xFFFF;
if(page == PAGE_DESKTOP && u == U_X && rel && h->rel_id1 == 0)
h->rel_id1 = id + 1;
if(h->only_id1 && id + 1 != h->only_id1)
return;
if(page == PAGE_DESKTOP && u == U_X) f = &h->x;
else if(page == PAGE_DESKTOP && u == U_Y) f = &h->y;
else if(page == PAGE_DESKTOP && u == U_WHEEL) f = &h->wheel;
@ -630,12 +640,8 @@ pointer_report(struct hid *h, const uchar *rep, int len)
// ---------------------------------------------------------------------------
static void
hid_complete(struct usb_dev *dev, void *buf, int len)
hid_input(struct hid *h, const uchar *buf, int len)
{
struct hid *h = dev->drvdata;
if(h == 0)
return;
// One interface can be both: a wireless receiver describes its keyboard
// and its mouse as two report IDs of one descriptor.
if(h->rkbd)
@ -646,6 +652,70 @@ hid_complete(struct usb_dev *dev, void *buf, int len)
pointer_report(h, buf, len);
}
static void
hid_complete(struct usb_dev *dev, void *buf, int len)
{
struct hid *h = dev->drvdata;
if(h != 0)
hid_input(h, buf, len);
}
static struct hid *
hid_slot(void)
{
int i;
if(!kqinit){
initlock(&kqlock, "usbkbd");
kqinit = 1;
}
for(i = 0; i < NHID; i++)
if(!hids[i].in_use){
memset(&hids[i], 0, sizeof(hids[i]));
return &hids[i];
}
return 0;
}
// A HID device on some other transport -- I2C, for a laptop's touchpad --
// handing over its report descriptor. Keyboards and pointers come out of it
// exactly as from USB. A descriptor with a relative pointer is driven
// through that report alone, even if an absolute one comes first: that is a
// touchpad's mouse report, which it sends until told to report fingers.
void *
hid_attach(const uchar *desc, int n, const char *what)
{
struct hid *h = hid_slot();
int rel;
if(h == 0)
return 0;
if(parse_report(h, desc, n) < 0)
return 0;
rel = h->rel_id1;
if(h->x.ok && !h->x.rel && rel){
memset(h, 0, sizeof(*h));
h->only_id1 = rel;
if(parse_report(h, desc, n) < 0)
return 0;
}
h->proto = !h->x.ok ? USB_HID_PROTO_KEYBOARD :
h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET;
h->in_use = 1;
cprintf("%s: %s%s\n", what,
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
h->proto == USB_HID_PROTO_MOUSE ? "pointer" : "absolute pointer",
h->rkbd && h->x.ok ? " and keyboard" : "");
return h;
}
void
hid_attached_input(void *h, const uchar *rep, int len)
{
hid_input(h, rep, len);
}
static int
hid_request(struct usb_dev *dev, int request, int value)
{
@ -721,18 +791,10 @@ hid_probe(struct usb_dev *dev)
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
return -1;
}
if(!kqinit){
initlock(&kqlock, "usbkbd");
kqinit = 1;
}
for(i = 0; i < NHID; i++)
if(!hids[i].in_use){ h = &hids[i]; break; }
if(h == 0){
if((h = hid_slot()) == 0){
cprintf("usb-hid: %d devices already; this one is not used\n", NHID);
return -1;
}
memset(h, 0, sizeof(*h));
if(boot && proto == USB_HID_PROTO_KEYBOARD){
// Ask for the fixed layout explicitly. A device that supports the boot