From e43f56c619a2a86907f4955d994411a48c5edc49 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sat, 24 Oct 2026 21:54:33 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4 --- Makefile | 2 +- include/dev/hid.h | 17 + kernel/acpi/i2c_hid.c | 635 +++++++++++++++++++++++++++++++ kernel/core/main.c | 4 + kernel/drivers/usb/hid/usb_hid.c | 90 ++++- 5 files changed, 733 insertions(+), 15 deletions(-) create mode 100644 include/dev/hid.h create mode 100644 kernel/acpi/i2c_hid.c diff --git a/Makefile b/Makefile index 5e4e48a..ef58aff 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ KERN_ACPI = $(addprefix $(BUILD)/uacpi/, \ namespace.o notify.o opcodes.o opregion.o osi.o registers.o \ resources.o shareable.o sleep.o stdlib.o tables.o types.o \ uacpi.o utilities.o) -KERN_ACPI += $(addprefix $(BUILD)/kernel/acpi/, host.o aml.o) +KERN_ACPI += $(addprefix $(BUILD)/kernel/acpi/, host.o aml.o i2c_hid.o) # zstd's decoder, for SCFF -- the compressed live root (include/fs/scff.h). # Unpacked by ports/zstd into build/ports/zstd/src and compiled into the kernel diff --git a/include/dev/hid.h b/include/dev/hid.h new file mode 100644 index 0000000..a390ddb --- /dev/null +++ b/include/dev/hid.h @@ -0,0 +1,17 @@ +#pragma once + +#include "core/types.h" + +// HID over transports other than USB. The report parser and the keyboard and +// pointer paths are kernel/drivers/usb/hid/usb_hid.c's; a transport hands +// over the report descriptor once and every input report after it. + +// The device described by `desc`, or 0 if it is neither a keyboard nor a +// pointer. `what` names it in the log. +void *hid_attach(const uchar *desc, int n, const char *what); +// One input report, report ID included when the device uses them. +void hid_attached_input(void *h, const uchar *rep, int len); + +// I2C-HID: the touchpads (and some keyboards) of laptops. Started once ACPI +// and kernel threads are up; see kernel/drivers/i2c/i2c_hid.c. +void i2c_hid_start(void); diff --git a/kernel/acpi/i2c_hid.c b/kernel/acpi/i2c_hid.c new file mode 100644 index 0000000..d62ffc2 --- /dev/null +++ b/kernel/acpi/i2c_hid.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Laptop touchpads: HID over I2C, on the SoC's DesignWare I2C controller. +// +// A laptop's touchpad is not on PS/2 any more and not on USB either. It hangs +// off an I2C bus inside the chipset, and the only place that says so is ACPI: +// a device whose _HID or _CID is PNP0C50, with an I2cSerialBus resource +// naming the controller and the touchpad's address, a GpioInt naming the pin +// it pulls when it has something to say, and a _DSM giving the one register +// number the protocol needs to bootstrap itself. Without this the touchpad of +// every recent laptop -- the ASUS TUF A15's included -- does nothing at all. +// +// Three layers, all here because each is small: +// +// The controller. Every x86 chipset since Haswell, AMD's included, uses the +// same Synopsys DesignWare I2C block (AMDI0010 on Ryzen, INT33C3, INT3433 +// and the PCI LPSS functions on Intel). Driven by polling: a touchpad report +// is about 30 bytes, a millisecond on the bus. +// +// HID over I2C (Microsoft's specification, version 1.0). Read the HID +// descriptor from the register _DSM names; power the device on and reset +// it; read the report descriptor; then every input report is a read of the +// input register, two bytes of length and the report. +// +// The interrupt line. It is level-triggered and stays asserted for as long +// as a report is waiting, so instead of routing it through the GPIO +// controller's interrupt the pin's *level* is read, every tick: low means +// read a report. That is exact -- no report is read twice and none is +// missed -- and needs nothing from the GPIO controller but its registers. +// On AMD those are one 32-bit word per pin. Elsewhere there is no pin to +// watch, and the input register is simply read every tick; devices answer +// a read with nothing waiting with a zero length. +// +// The reports themselves go through the USB HID driver's parser and pointer +// path (include/dev/hid.h). A Windows Precision Touchpad starts in mouse mode +// after a reset -- relative motion, a button for a click and for a tap -- and +// that is the report taken: it is what makes it a mouse on /dev/input/event1, +// for the console and for X. Multi-finger reports (two-finger scroll) need the +// device switched to touchpad mode and a gesture layer; not yet. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/spinlock.h" +#include "arch/mmu.h" +#include "dev/hid.h" +#include "dev/acpi.h" + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// DesignWare I2C +// --------------------------------------------------------------------------- + +#define IC_CON 0x00 +#define IC_TAR 0x04 +#define IC_DATA_CMD 0x10 +#define IC_SS_SCL_HCNT 0x14 +#define IC_SS_SCL_LCNT 0x18 +#define IC_FS_SCL_HCNT 0x1C +#define IC_FS_SCL_LCNT 0x20 +#define IC_INTR_MASK 0x30 +#define IC_RAW_INTR_STAT 0x34 +#define IC_RX_TL 0x38 +#define IC_TX_TL 0x3C +#define IC_CLR_INTR 0x40 +#define IC_CLR_TX_ABRT 0x54 +#define IC_CLR_STOP_DET 0x60 +#define IC_ENABLE 0x6C +#define IC_STATUS 0x70 +#define IC_TXFLR 0x74 +#define IC_RXFLR 0x78 +#define IC_SDA_HOLD 0x7C +#define IC_TX_ABRT_SOURCE 0x80 +#define IC_ENABLE_STATUS 0x9C +#define IC_COMP_PARAM_1 0xF4 +#define IC_COMP_TYPE 0xFC + +#define DW_COMP_TYPE 0x44570140 + +#define CON_MASTER (1 << 0) +#define CON_SPEED_STD (1 << 1) +#define CON_SPEED_FAST (2 << 1) +#define CON_RESTART_EN (1 << 5) +#define CON_SLAVE_DISABLE (1 << 6) + +#define CMD_READ (1 << 8) +#define CMD_STOP (1 << 9) +#define CMD_RESTART (1 << 10) + +#define INTR_TX_ABRT (1 << 6) +#define INTR_STOP_DET (1 << 9) + +#define NCTL 4 + +struct dwi2c { + uint64 phys; + volatile uint32 *regs; + int txdepth, rxdepth; + int fast; +}; + +static struct dwi2c ctls[NCTL]; +static int nctl; + +static uint32 +rd(struct dwi2c *c, int reg) +{ + return c->regs[reg / 4]; +} + +static void +wr(struct dwi2c *c, int reg, uint32 v) +{ + c->regs[reg / 4] = v; +} + +static int +dw_enable(struct dwi2c *c, int on) +{ + int i; + + for(i = 0; i < 1000; i++){ + wr(c, IC_ENABLE, on); + if((rd(c, IC_ENABLE_STATUS) & 1) == (uint32)on) + return 0; + microdelay(25); + } + return -1; +} + +// SCL high and low periods, in controller clocks, from the firmware's own +// numbers when it gives them: SSCN and FMCN are packages of (HCNT, LCNT, +// SDA hold) for exactly this controller's clock. +static int +acpi_counts(uacpi_namespace_node *node, const char *name, + uint32 *hcnt, uint32 *lcnt, uint32 *hold) +{ + uacpi_object *o = 0; + uacpi_object_array a; + uacpi_u64 v[3]; + int i; + + if(uacpi_eval_package(node, name, 0, &o) != UACPI_STATUS_OK) + return -1; + if(uacpi_object_get_package(o, &a) != UACPI_STATUS_OK || a.count < 2){ + uacpi_object_unref(o); + return -1; + } + v[2] = 0; + for(i = 0; i < 3 && i < (int)a.count; i++) + if(uacpi_object_get_integer(a.objects[i], &v[i]) != UACPI_STATUS_OK){ + uacpi_object_unref(o); + return -1; + } + uacpi_object_unref(o); + *hcnt = v[0]; + *lcnt = v[1]; + *hold = v[2]; + return 0; +} + +static int +dw_init(struct dwi2c *c, uacpi_namespace_node *node, uint32 speed) +{ + uint32 type, param, hcnt, lcnt, hold = 0; + + c->regs = ioremap(c->phys, 0x1000); + if(c->regs == 0) + return -1; + type = rd(c, IC_COMP_TYPE); + if(type != DW_COMP_TYPE){ + cprintf("i2c: controller at 0x%lx is not DesignWare (0x%x)\n", + c->phys, type); + return -1; + } + param = rd(c, IC_COMP_PARAM_1); + c->txdepth = param ? ((param >> 16) & 0xFF) + 1 : 8; + c->rxdepth = param ? ((param >> 8) & 0xFF) + 1 : 8; + c->fast = speed >= 400000; + + if(dw_enable(c, 0) < 0) + return -1; + wr(c, IC_CON, CON_MASTER | CON_SLAVE_DISABLE | CON_RESTART_EN | + (c->fast ? CON_SPEED_FAST : CON_SPEED_STD)); + + // Without the firmware's numbers, periods long enough for a 150 MHz clock, + // the fastest any of these runs at: on a slower clock the bus is merely + // slower than it could be, never out of specification. + if(c->fast){ + if(acpi_counts(node, "FMCN", &hcnt, &lcnt, &hold) < 0){ + hcnt = 135; // 0.6 us high, 1.3 us low, plus rise and fall + lcnt = 240; + } + wr(c, IC_FS_SCL_HCNT, hcnt); + wr(c, IC_FS_SCL_LCNT, lcnt); + } else { + if(acpi_counts(node, "SSCN", &hcnt, &lcnt, &hold) < 0){ + hcnt = 645; // 4.0 us high, 4.7 us low, plus the same + lcnt = 750; + } + wr(c, IC_SS_SCL_HCNT, hcnt); + wr(c, IC_SS_SCL_LCNT, lcnt); + } + if(hold) + wr(c, IC_SDA_HOLD, hold); + wr(c, IC_INTR_MASK, 0); + wr(c, IC_RX_TL, 0); + wr(c, IC_TX_TL, 0); + rd(c, IC_CLR_INTR); + return 0; +} + +// Write `wn` bytes to `addr`, then read `rn` bytes back after a repeated +// start, all as one transaction. 0, or -1 if the device did not answer. +// +// The command FIFO holds reads as well as writes, and each read becomes a +// byte in the receive FIFO, so reads are queued no faster than they are +// drained: a full receive FIFO stalls the bus. +static int +dw_xfer(struct dwi2c *c, int addr, const uchar *w, int wn, uchar *r, int rn) +{ + int total = wn + rn, sent = 0, got = 0, spin; + uint32 raw; + + if(dw_enable(c, 0) < 0) + return -1; + wr(c, IC_TAR, addr); + rd(c, IC_CLR_INTR); + if(dw_enable(c, 1) < 0) + return -1; + + for(spin = 0; got < rn || sent < total; spin++){ + raw = rd(c, IC_RAW_INTR_STAT); + if(raw & INTR_TX_ABRT) + goto abort; + while(got < rn && rd(c, IC_RXFLR) > 0) + r[got++] = rd(c, IC_DATA_CMD); + while(sent < total && (int)rd(c, IC_TXFLR) < c->txdepth){ + uint32 cmd; + if(sent >= wn && (sent - wn) - got >= c->rxdepth) + break; + if(sent < wn) + cmd = w[sent]; + else + cmd = CMD_READ | (sent == wn && wn > 0 ? CMD_RESTART : 0); + if(sent == total - 1) + cmd |= CMD_STOP; + wr(c, IC_DATA_CMD, cmd); + sent++; + } + if(spin > 20000) // 200 ms: no transfer of ours takes that long + goto abort; + microdelay(10); + } + for(spin = 0; !(rd(c, IC_RAW_INTR_STAT) & INTR_STOP_DET); spin++){ + if(rd(c, IC_RAW_INTR_STAT) & INTR_TX_ABRT) + goto abort; + if(spin > 2000) + goto abort; + microdelay(10); + } + rd(c, IC_CLR_STOP_DET); + dw_enable(c, 0); + return 0; + +abort: + rd(c, IC_TX_ABRT_SOURCE); + rd(c, IC_CLR_TX_ABRT); + rd(c, IC_CLR_INTR); + dw_enable(c, 0); + return -1; +} + +// --------------------------------------------------------------------------- +// ACPI +// --------------------------------------------------------------------------- + +struct found { + uint64 mem; // the first memory range + int i2c, addr; + uint32 speed; + char bus[64]; // the controller's namespace path + int gpio, pin, active_low; + char gpioctl[64]; +}; + +static void +copy_source(char *dst, const uacpi_resource_source *s) +{ + int n = s->string && s->length ? s->length : 0; + if(n > 63) + n = 63; + if(n) + memmove(dst, s->string, n); + dst[n] = 0; +} + +static uacpi_iteration_decision +take_resource(void *user, uacpi_resource *r) +{ + struct found *f = user; + + switch(r->type){ + case UACPI_RESOURCE_TYPE_FIXED_MEMORY32: + if(!f->mem) f->mem = r->fixed_memory32.address; + break; + case UACPI_RESOURCE_TYPE_MEMORY32: + if(!f->mem) f->mem = r->memory32.minimum; + break; + case UACPI_RESOURCE_TYPE_ADDRESS32: + if(!f->mem && r->address32.common.type == UACPI_RANGE_MEMORY) + f->mem = r->address32.minimum; + break; + case UACPI_RESOURCE_TYPE_ADDRESS64: + if(!f->mem && r->address64.common.type == UACPI_RANGE_MEMORY) + f->mem = r->address64.minimum; + break; + case UACPI_RESOURCE_TYPE_SERIAL_I2C_CONNECTION: + if(!f->i2c){ + f->i2c = 1; + f->addr = r->i2c_connection.slave_address; + f->speed = r->i2c_connection.connection_speed; + copy_source(f->bus, &r->i2c_connection.common.source); + } + break; + case UACPI_RESOURCE_TYPE_GPIO_CONNECTION: + if(!f->gpio && r->gpio_connection.type == UACPI_GPIO_CONNECTION_INTERRUPT && + r->gpio_connection.pin_table_length > 0){ + f->gpio = 1; + f->pin = r->gpio_connection.pin_table[0]; + f->active_low = r->gpio_connection.intr.polarity != UACPI_POLARITY_ACTIVE_HIGH; + copy_source(f->gpioctl, &r->gpio_connection.source); + } + break; + } + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static int +resources(uacpi_namespace_node *node, struct found *f) +{ + memset(f, 0, sizeof(*f)); + return uacpi_for_each_device_resource(node, "_CRS", take_resource, f) == + UACPI_STATUS_OK ? 0 : -1; +} + +// The I2C-HID _DSM, function 1: the HID descriptor's register. +static int +hid_desc_register(uacpi_namespace_node *node, uint32 *reg) +{ + static const uchar uuid[16] = { // 3CDFF6F7-4267-4555-AD05-B30A3D8938DE + 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, + }; + uacpi_data_view view = { .const_bytes = uuid, .length = sizeof(uuid) }; + uacpi_object_array none = { 0, 0 }; + uacpi_object *args[4], *ret = 0; + uacpi_object_array a = { args, 4 }; + uacpi_u64 v = 0; + int rc = -1, i; + + args[0] = uacpi_object_create_buffer(view); + args[1] = uacpi_object_create_integer(1); // revision + args[2] = uacpi_object_create_integer(1); // function + args[3] = uacpi_object_create_package(none); + if(args[0] && args[1] && args[2] && args[3] && + uacpi_eval(node, "_DSM", &a, &ret) == UACPI_STATUS_OK && ret && + uacpi_object_get_integer(ret, &v) == UACPI_STATUS_OK){ + *reg = v; + rc = 0; + } + if(ret) + uacpi_object_unref(ret); + for(i = 0; i < 4; i++) + if(args[i]) + uacpi_object_unref(args[i]); + return rc; +} + +static uacpi_namespace_node * +node_at(const char *path) +{ + uacpi_namespace_node *n = 0; + if(path[0] == 0 || + uacpi_namespace_node_find(0, path, &n) != UACPI_STATUS_OK) + return 0; + return n; +} + +static struct dwi2c * +controller(const char *path, uint32 speed) +{ + uacpi_namespace_node *node = node_at(path); + struct found f; + struct dwi2c *c; + int i; + + if(node == 0 || resources(node, &f) < 0 || f.mem == 0){ + cprintf("i2c: no registers for controller %s\n", path); + return 0; + } + for(i = 0; i < nctl; i++) + if(ctls[i].phys == f.mem) + return &ctls[i]; + if(nctl == NCTL) + return 0; + uacpi_execute(node, "_PS0", 0); + c = &ctls[nctl]; + memset(c, 0, sizeof(*c)); + c->phys = f.mem; + if(dw_init(c, node, speed) < 0) + return 0; + nctl++; + cprintf("i2c: %s at 0x%lx, %s mode, FIFOs %d/%d\n", path, c->phys, + c->fast ? "fast" : "standard", c->txdepth, c->rxdepth); + return c; +} + +// --------------------------------------------------------------------------- +// HID over I2C +// --------------------------------------------------------------------------- + +#define NI2CHID 4 + +struct i2chid { + struct dwi2c *c; + int addr; + uint32 cmdreg, inreg, maxin; + volatile uint32 *pin; // the interrupt pin's register, or 0 + int active_low; + void *hid; + uchar *buf; // one page, for input reports + char name[8]; +}; + +static struct i2chid devs[NI2CHID]; +static int ndev; + +static int +reg_write(struct i2chid *d, const uchar *w, int n) +{ + return dw_xfer(d->c, d->addr, w, n, 0, 0); +} + +static int +reg_read(struct i2chid *d, uint32 reg, uchar *r, int n) +{ + uchar w[2] = { reg & 0xFF, reg >> 8 }; + return dw_xfer(d->c, d->addr, w, 2, r, n); +} + +// SET_POWER and RESET, the two commands needed: written to the command +// register as (register, opcode byte, power state / report byte). +static int +command(struct i2chid *d, int opcode, int arg) +{ + uchar w[4] = { d->cmdreg & 0xFF, d->cmdreg >> 8, arg, opcode }; + return reg_write(d, w, 4); +} + +#define OP_RESET 0x01 +#define OP_SET_POWER 0x08 + +static void +wait_ticks(uint n) +{ + acquire(&tickslock); + uint start = ticks; + while(ticks - start < n) + sleep(&ticks, &tickslock); + release(&tickslock); +} + +// AMD's GPIO controller: one 32-bit register per pin, its level in bit 16. +#define AMD_PIN_STS (1u << 16) + +static int +asserted(struct i2chid *d) +{ + int high = (*d->pin & AMD_PIN_STS) != 0; + return d->active_low ? !high : high; +} + +static volatile uint32 * +interrupt_pin(const struct found *f) +{ + static const char *const amd[] = { "AMDI0030", "AMD0030", 0 }; + uacpi_namespace_node *node = node_at(f->gpioctl); + struct found g; + + if(!f->gpio || node == 0 || !uacpi_device_matches_pnp_id(node, amd)) + return 0; + if(resources(node, &g) < 0 || g.mem == 0) + return 0; + return (volatile uint32 *)((char *)ioremap(g.mem, 0x1000) + f->pin * 4); +} + +// One input report: two bytes of length, length included, then the report. +// A length of 0 is the device saying it has nothing, or acknowledging a +// reset. +static int +read_input(struct i2chid *d) +{ + int len; + + if(dw_xfer(d->c, d->addr, 0, 0, d->buf, d->maxin) < 0) + return -1; + len = d->buf[0] | (d->buf[1] << 8); + if(len <= 2 || len > (int)d->maxin) + return 0; + hid_attached_input(d->hid, d->buf + 2, len - 2); + return 1; +} + +static uacpi_iteration_decision +probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth) +{ + struct i2chid *d; + struct found f; + uchar desc[30], *rdesc; + uint32 descreg, rlen, rreg; + int i; + + (void)user; + (void)depth; + if(ndev == NI2CHID) + return UACPI_ITERATION_DECISION_BREAK; + d = &devs[ndev]; + memset(d, 0, sizeof(*d)); + memmove(d->name, uacpi_namespace_node_name(node).text, 4); + + if(resources(node, &f) < 0 || !f.i2c){ + cprintf("i2c-hid: %s has no I2C connection\n", d->name); + return UACPI_ITERATION_DECISION_CONTINUE; + } + if(hid_desc_register(node, &descreg) < 0){ + cprintf("i2c-hid: %s: _DSM gave no descriptor register\n", d->name); + return UACPI_ITERATION_DECISION_CONTINUE; + } + if((d->c = controller(f.bus, f.speed)) == 0) + return UACPI_ITERATION_DECISION_CONTINUE; + d->addr = f.addr; + uacpi_execute(node, "_PS0", 0); + + if(reg_read(d, descreg, desc, sizeof(desc)) < 0 || + (desc[0] | desc[1] << 8) != 30){ + cprintf("i2c-hid: %s at 0x%x does not answer\n", d->name, d->addr); + return UACPI_ITERATION_DECISION_CONTINUE; + } + rlen = desc[4] | desc[5] << 8; + rreg = desc[6] | desc[7] << 8; + d->inreg = desc[8] | desc[9] << 8; + d->maxin = desc[10] | desc[11] << 8; + d->cmdreg = desc[16] | desc[17] << 8; + if(d->maxin < 3 || d->maxin > PGSIZE || rlen == 0 || rlen > PGSIZE){ + cprintf("i2c-hid: %s: unusable descriptor\n", d->name); + return UACPI_ITERATION_DECISION_CONTINUE; + } + d->pin = interrupt_pin(&f); + d->active_low = f.active_low; + + command(d, OP_SET_POWER, 0); // 0 = on + wait_ticks(2); + command(d, OP_RESET, 0); + wait_ticks(10); + d->buf = (uchar *)kalloc(); + rdesc = (uchar *)kalloc(); + if(d->buf == 0 || rdesc == 0) + return UACPI_ITERATION_DECISION_BREAK; + // The reset is acknowledged with an empty report. Read it, and anything + // else already waiting, so the first real report is not behind it. + for(i = 0; i < 4 && (d->pin == 0 || asserted(d)); i++) + if(dw_xfer(d->c, d->addr, 0, 0, d->buf, d->maxin) < 0) + break; + + if(reg_read(d, rreg, rdesc, rlen) < 0){ + cprintf("i2c-hid: %s: report descriptor unreadable\n", d->name); + kfree((char *)rdesc); + return UACPI_ITERATION_DECISION_CONTINUE; + } + char what[24] = "i2c-hid "; + memmove(what + 8, d->name, 4); + d->hid = hid_attach(rdesc, rlen, what); + kfree((char *)rdesc); + if(d->hid == 0){ + cprintf("i2c-hid: %s is neither a keyboard nor a pointer\n", d->name); + return UACPI_ITERATION_DECISION_CONTINUE; + } + cprintf("i2c-hid: %s: %04x:%04x at 0x%x, %s\n", d->name, + desc[20] | desc[21] << 8, desc[22] | desc[23] << 8, d->addr, + d->pin ? "interrupt pin watched" : "polled"); + ndev++; + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static void +i2c_hid_thread(void) +{ + static const char *const ids[] = { "PNP0C50", "ACPI0C50", 0 }; + int i, n; + + uacpi_find_devices_at(uacpi_namespace_root(), ids, probe, 0); + if(ndev == 0) + for(;;) + wait_ticks(1000000); + + for(;;){ + wait_ticks(1); + for(i = 0; i < ndev; i++){ + struct i2chid *d = &devs[i]; + if(d->pin == 0){ + read_input(d); + continue; + } + // Everything waiting, up to a bound so that a stuck line cannot keep + // the other devices from their turn. + for(n = 0; n < 16 && asserted(d); n++) + if(read_input(d) < 0) + break; + } + } +} + +void +i2c_hid_start(void) +{ + if(acpi_rsdp_phys() == 0) + return; + if(kthread_create(i2c_hid_thread, "i2chid") < 0) + cprintf("i2c-hid: no thread; touchpads will not work\n"); +} diff --git a/kernel/core/main.c b/kernel/core/main.c index 1a779c1..dd3c0a2 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -12,6 +12,7 @@ #include "core/klog.h" #include "dev/usb.h" #include "dev/acpi.h" +#include "dev/hid.h" #include "arch/vmap.h" #include "core/hwinfo.h" @@ -391,6 +392,9 @@ kmain(uint64 fb_addr) usb_hotplug_start(); // Where AML runs for the SCI: the power button, GPEs, notifications. acpi_thread_start(); + // Laptop touchpads, on the chipset's I2C bus; found through ACPI, so after + // the namespace is loaded, and polled from a thread of their own. + i2c_hid_start(); // The debugger's own thread, for the same reason netinit's comes after // sinit: kthread_create needs a usable process table. From here on Ctrl-G // on the console answers, with no filesystem mounted and nothing in diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index df1a924..94cea98 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -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