The touchpad driver crashed the ASUS TUF A15 at boot. Its thread runs the firmware's AML -- _DSM, _PS0, _CRS -- through uACPI at -O0, on an 8 KB kernel stack, and kernel stacks are one array: running off the end lands in the next thread's stack, and the machine dies a moment later in some other code. The thread now runs on a 64 KB stack of its own, through SCFF's call_on_stack(). And a crashed laptop sat with its fans up until somebody held the power button, since the other processors keep running. On an ordinary boot the PSOGM now says it will power off, and does, after three minutes: SLP_TYP from \_S5, read at boot, written straight to PM1 control with no AML, which could sleep or wait on a lock a dead processor holds. A verbose boot keeps the screen up for ever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
675 lines
20 KiB
C
675 lines
20 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 "arch/memlayout.h"
|
|
#include "arch/schboot.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>
|
|
|
|
// Each step said before it is taken, in a verbose boot: a machine that stops
|
|
// here stops with the step it was on as the last line on the screen.
|
|
#define STEP(...) do { if(boot_verbose) cprintf(__VA_ARGS__); } while(0)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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;
|
|
STEP("i2c: controller registers at 0x%lx\n", c->phys);
|
|
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);
|
|
|
|
STEP("i2c-hid: %s: reading its resources\n", d->name);
|
|
if(resources(node, &f) < 0 || !f.i2c){
|
|
cprintf("i2c-hid: %s has no I2C connection\n", d->name);
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
}
|
|
STEP("i2c-hid: %s: _DSM\n", d->name);
|
|
if(hid_desc_register(node, &descreg) < 0){
|
|
cprintf("i2c-hid: %s: _DSM gave no descriptor register\n", d->name);
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
}
|
|
STEP("i2c-hid: %s: controller %s, address 0x%x\n", d->name, f.bus, f.addr);
|
|
if((d->c = controller(f.bus, f.speed)) == 0)
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
d->addr = f.addr;
|
|
STEP("i2c-hid: %s: _PS0\n", d->name);
|
|
uacpi_execute(node, "_PS0", 0);
|
|
|
|
STEP("i2c-hid: %s: HID descriptor at register 0x%x\n", d->name, descreg);
|
|
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;
|
|
|
|
STEP("i2c-hid: %s: power on and reset, pin %s\n", d->name,
|
|
d->pin ? "watched" : "none");
|
|
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;
|
|
|
|
STEP("i2c-hid: %s: report descriptor, %d bytes\n", d->name, rlen);
|
|
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;
|
|
}
|
|
|
|
// **The thread runs on a stack of its own, 64 KB.** A kernel thread's stack
|
|
// is 8 KB, and this one runs the firmware's AML -- _DSM, _PS0, _CRS, and
|
|
// whatever those call -- through uACPI's interpreter, built at -O0. On the
|
|
// ASUS TUF A15 that ran off the end: kernel stacks are one array, so it ran
|
|
// into the next thread's, and the machine died a moment later in whatever
|
|
// that thread was doing, with a crash report about the wrong code.
|
|
// call_on_stack() is SCFF's (kernel/scff/scff.c). Sleeping here is fine:
|
|
// the context is saved on this stack, and only this thread ever uses it.
|
|
static unsigned char i2c_stack[64 * 1024] __attribute__((aligned(16)));
|
|
long call_on_stack(long (*fn)(void *), void *arg, void *top);
|
|
|
|
static long
|
|
i2c_hid_main(void *arg)
|
|
{
|
|
static const char *const ids[] = { "PNP0C50", "ACPI0C50", 0 };
|
|
int i, n;
|
|
|
|
(void)arg;
|
|
|
|
STEP("i2c-hid: looking for touchpads\n");
|
|
uacpi_find_devices_at(uacpi_namespace_root(), ids, probe, 0);
|
|
STEP("i2c-hid: %d found\n", ndev);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
i2c_hid_thread(void)
|
|
{
|
|
call_on_stack(i2c_hid_main, 0, i2c_stack + sizeof(i2c_stack));
|
|
}
|
|
|
|
void
|
|
i2c_hid_start(void)
|
|
{
|
|
if(acpi_rsdp_phys() == 0)
|
|
return;
|
|
if(*(volatile uchar *)P2V(SCHBOOT_OFF_FLAGS) & SCHBOOT_OFF_I2C_HID){
|
|
cprintf("i2c-hid: off, as chosen at the boot menu\n");
|
|
return;
|
|
}
|
|
if(kthread_create(i2c_hid_thread, "i2chid") < 0)
|
|
cprintf("i2c-hid: no thread; touchpads will not work\n");
|
|
}
|