Three bugs in FreeBSD's ig4 driver, one dead touchpad
Bottom line up front
On a Dell XPS 13 9343 running FreeBSD 15.1, the touchpad stopped working after
every suspend. It looked like a resume bug in psm(4), the PS/2 mouse driver.
It was not. The touchpad is an I2C HID device, and FreeBSD never brought up the
I2C controller it sits on, so the system fell back to a PS/2 compatibility mode
that the firmware does not restore after S3.
ig4(4), the Designware/LPSS I2C driver, fails on every Haswell and Broadwell
machine whose controllers are enumerated through ACPI rather than PCI. Three
independent defects, all on the ACPI attach path:
- The controller is left in D3, so every register reads as
0xffffffff. - Broadwell is classified as an Atom SoC, taking the wrong configuration path.
- The LPSS functional clock is never ungated, so the controller accepts data but never drives the bus.
A 23-line patch fixes all three, filed with FreeBSD as bug 298558. The touchpad now attaches as a proper multi-touch clickpad and survives suspend.
The problem
The symptom was simple and reproducible: suspend to RAM, open the lid, no pointer. Only a reboot brought it back. The kernel was explicit about which device had gone:
atkbdc0: resume: selftest=1 cmdbyte=0x47 auxcmd=1 auxport=0 reset=0 tries=60
psm0: failed to enable the aux device.
psm0: the aux device has gone! (reinitialize).
That instrumentation came from three patched kernels, each testing a theory about the i8042 controller: that its command byte was not restored, that the auxiliary port needed an explicit enable, that the retry window was too short. All three came back the same way. The controller passed its self-test, accepted the enable command and reported a clean port. The device on the other end simply never answered, through 60 attempts over three full seconds.
That is where the PS/2 theory should have ended, and where a different question
belonged: what does this touchpad look like to an operating system the vendor
actually tested? The DSDT answered it. There is a device TPD8 under the second
I2C bus, _HID DLL0665, _CID PNP0C50 — the generic identifier for
HID-over-I2C — at bus address 0x2c. Windows and Linux both drive it that way.
PS/2 is the fallback path, and the firmware has no obligation to restore a
fallback nobody uses.
FreeBSD has the drivers for this: iichid(4) speaks HID over I2C, hmt(4)
handles Precision Touchpads. The bus underneath them was missing:
ig4iic1: <Designware I2C Controller> iomem 0xfe105000-0xfe105fff irq 7 on acpi0
ig4iic1: controller error during attach-1
device_attach: ig4iic1 attach returned 6
Three causes, not one
The controller is asleep. Reading its registers through /dev/mem returned
all ones, which on a memory-mapped device means nothing is home. The PCI
configuration space of the LPSS function — mapped at 0xfe106000 on this
machine, device ID 8086:9ce2 — explained why:
0xfe106000+0x084 = 0x0000000b /* PMCSR: PowerState = D3hot */
The DSDT has a _PS0 method that clears those two bits.
ig4iic_acpi_attach()
never calls it. The PCI attach path does not need to, which is exactly why the
gap survived: the same driver works on machines that expose LPSS as PCI devices.
Broadwell is not an Atom. The ACPI path assigns IG4_ATOM to every
identifier except one:
if (strcmp(str, "APMC0D0F") == 0) {
sc->version = IG4_EMAG;
} else {
/* All the other HIDs matched are Atom SOCs. */
sc->version = IG4_ATOM;
}
Meanwhile ig4_pci.c
maps the very same silicon to IG4_HASWELL:
{ PCI_CHIP_LYNXPT_LP_I2C_1, "Intel Lynx Point-LP I2C Controller-1", IG4_HASWELL},
{ PCI_CHIP_LYNXPT_LP_I2C_2, "Intel Lynx Point-LP I2C Controller-2", IG4_HASWELL},
The clock is gated. This was the interesting one. With the controller powered and configured, every transfer still timed out. Not a NACK — a timeout, which is a different failure with a different cause. The registers showed a controller that was accepting work and doing nothing with it:
0xfe105000+0x070 (IC_STATUS) = 0x00000000 /* no activity */
0xfe105000+0x074 (TXFLR) = 0x00000020 /* TX FIFO full, never drains */
0xfe105000+0x034 (RAW_INTR_STAT) = 0x00000000
0xfe105000+0x800 (CLK_PARMS) = 0x00000000
Bit 0 of CLK_PARMS is the functional clock gate. Linux sets it for these parts
in acpi_lpss.c
(lpt_i2c_dev_desc, prv_offset = 0x800). FreeBSD defines the register in
ig4_reg.h and
never writes it. Setting that single bit, with nothing else changed:
0xfe105000+0x070 = 0x0000000e
0xfe105000+0x074 = 0x00000000 /* FIFO drained */
0xfe105000+0x034 = 0x00000714 /* STOP_DET, ACTIVITY, TX_EMPTY, RX_FULL */
The fix
Three changes in sys/dev/ichiic: run _PS0 before mapping the registers,
recognise INT33C2/INT33C3/INT3432/INT3433 as IG4_HASWELL, and ungate
the clock in
ig4iic_set_config(),
which also covers the resume path:
if (sc->version == IG4_HASWELL) {
v = reg_read(sc, IG4_REG_CLK_PARMS);
if ((v & IG4_CLK_PARMS_EN) == 0)
reg_write(sc, IG4_REG_CLK_PARMS, v | IG4_CLK_PARMS_EN);
}
Evidence
The touchpad answers a HID descriptor read at 0x2c — Synaptics 06CB:76AD,
521 bytes of report descriptor:
1e 00 00 01 09 02 21 00 24 00 3c 00 25 00 17 00 22 00 23 00 cb 06 ad 76 06 00 00 00 00 00
wHIDDescLength=30 bcdVersion=0x0100 wReportDescLength=521 wMaxInputLength=60 VID=0x06cb PID=0x76ad
With the patched module in place, the whole stack attaches at boot with no manual steps:
iicbus7: <Philips I2C bus (ACPI-hinted)> on ig4iic1
iichid0: <DLL0665:00 06CB:76AD I2C HID device> at addr 0x2c irq 39 on iicbus7
hmt1: <DLL0665:00 06CB:76AD TouchPad> on hidbus1
hmt1: Multitouch touchpad with 0 external buttons, click-pad
hmt1: 3 contacts with [C] properties. Report range [0:0] - [1216:680]
The touchpad survives suspend and resume, and gains multi-touch it never had on
PS/2. One more number changed: vmstat -i used to report irq7: ig4iic0+ 0.
A controller with no clock raises no interrupts either, and transfers only made
progress because
wait_intr()
re-reads the status registers every 10 ms. That also explains the intermittent
codec: I2C read failed: 60 errors from the audio driver sharing the first bus.
Who else this affects
Any Haswell or Broadwell laptop whose LPSS is in ACPI mode: the XPS 13 9343 and
its siblings, and a good part of the 2014-2015 ultrabook generation. The visible
symptom varies with what is wired to those buses — a touchpad here, a sensor hub
or an audio codec elsewhere. The common signature is
controller error during attach-1 in dmesg, and an irq counter for the
controller that never moves.
The patch is filed with FreeBSD as
bug 298558, and the
patch itself, a Makefile that builds and installs the module, and the report
are in spagu/freebsd-ig4-lpss. One
caveat worth stating plainly for anyone applying it locally in the meantime:
freebsd-update replaces /boot/kernel/ig4.ko, so the module has to be rebuilt
after every system update until the fix lands upstream.