SchOS/kernel/acpi/i2c_hid.c
Nexoniarz e43f56c619 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
2026-10-24 21:54:33 +02:00

635 lines
18 KiB
C

// 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 <uacpi/uacpi.h>
#include <uacpi/utilities.h>
#include <uacpi/namespace.h>
#include <uacpi/resources.h>
// ---------------------------------------------------------------------------
// 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");
}