* [PATCH] i8042: Work around HP TGL bug
@ 2021-02-01 16:03 Anton Zhilyaev
0 siblings, 0 replies; only message in thread
From: Anton Zhilyaev @ 2021-02-01 16:03 UTC (permalink / raw)
To: dmitry.torokhov, hdegoede, linux-input; +Cc: Anton Zhilyaev
Some Tiger Lake laptops like HP Spectre x360 13-aw2xxx have a bug in i8042
implementation that causes keyboard initialization to take from few seconds
to sometimes a minute or more each time the system starts or resumes.
In a nutshell problem is caused by interrupt being deasserted after byte
read, even when there are more bytes readily available and I8042_STR_OBF is
still set. Once in a while it keeps interrupt asserted, I haven't found any
explanation for this.
This causes atkbd_probe to fail on both ATKBD_CMD_GETID (no id received)
and ATKBD_CMD_SETLEDS (id received instead of ack) approaches which causes
keyboard initialization to be re-tried multiple times until it randomly
scores to get a valid reply either by interrupt surviving long enough for
ID to be read or by ATKBD_CMD_SETLEDS luckily receiving some old ack.
This patch turns single-shot interrupt handler into a read loop that runs
until I8042_STR_OBF goes unset or 4 valid non-filtered bytes are read or
32 iterations are performed. Another loop passes the read bytes to the
serio layer later after unlocking i8042_lock.
Here's HP Spectre x360 issue discussion with this problem mentioned a few
times:
https://bbs.archlinux.org/viewtopic.php?id=261108
Here's some logs illustrating the problem and the solution:
http://cpp.in/dev/hp_x360_tgl_kbd_before.log
http://cpp.in/dev/hp_x360_tgl_kbd_after.log
Signed-off-by: Anton Zhilyaev <anton@cpp.in>
---
--- a/drivers/input/serio/i8042.c 2021-01-25 03:47:14.000000000 +0300
+++ b/drivers/input/serio/i8042.c 2021-02-01 02:57:54.556703786 +0300
@@ -159,6 +159,8 @@ struct i8042_port {
#define I8042_MUX_PORT_NO 2
#define I8042_NUM_PORTS (I8042_NUM_MUX_PORTS + 2)
+#define I8042_QUEUE_SIZE 4
+
static struct i8042_port i8042_ports[I8042_NUM_PORTS];
static unsigned char i8042_initial_ctr;
@@ -520,33 +522,36 @@ static irqreturn_t i8042_interrupt(int i
struct i8042_port *port;
struct serio *serio;
unsigned long flags;
- unsigned char str, data;
- unsigned int dfl;
- unsigned int port_no;
+ unsigned char str;
bool filtered;
+ unsigned char q_data[I8042_QUEUE_SIZE];
+ unsigned int q_port[I8042_QUEUE_SIZE];
+ unsigned int q_dfl[I8042_QUEUE_SIZE];
+ unsigned int q_used = 0, q_cycles = 0, q_i;
int ret = 1;
spin_lock_irqsave(&i8042_lock, flags);
str = i8042_read_status();
- if (unlikely(~str & I8042_STR_OBF)) {
- spin_unlock_irqrestore(&i8042_lock, flags);
- if (irq)
- dbg("Interrupt %d, without any data\n", irq);
- ret = 0;
- goto out;
- }
+ do {
+ if (unlikely(~str & I8042_STR_OBF)) {
+ spin_unlock_irqrestore(&i8042_lock, flags);
+ if (irq)
+ dbg("Interrupt %d, without any data\n", irq);
+ ret = 0;
+ goto out;
+ }
- data = i8042_read_data();
+ q_data[q_used] = i8042_read_data();
- if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
- static unsigned long last_transmit;
- static unsigned char last_str;
-
- dfl = 0;
- if (str & I8042_STR_MUXERR) {
- dbg("MUX error, status is %02x, data is %02x\n",
- str, data);
+ if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
+ static unsigned long last_transmit;
+ static unsigned char last_str;
+
+ q_dfl[q_used] = 0;
+ if (str & I8042_STR_MUXERR) {
+ dbg("MUX error, status is %02x, data is %02x\n",
+ str, q_data[q_used]);
/*
* When MUXERR condition is signalled the data register can only contain
* 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately
@@ -560,7 +565,7 @@ static irqreturn_t i8042_interrupt(int i
* was transmitted (if transmission happened not too long ago).
*/
- switch (data) {
+ switch (q_data[q_used]) {
default:
if (time_before(jiffies, last_transmit + HZ/10)) {
str = last_str;
@@ -569,37 +574,49 @@ static irqreturn_t i8042_interrupt(int i
fallthrough; /* report timeout */
case 0xfc:
case 0xfd:
- case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break;
- case 0xff: dfl = SERIO_PARITY; data = 0xfe; break;
+ case 0xfe:
+ q_dfl[q_used] = SERIO_TIMEOUT;
+ q_data[q_used] = 0xfe;
+ break;
+ case 0xff:
+ q_dfl[q_used] = SERIO_PARITY;
+ q_data[q_used] = 0xfe;
+ break;
+ }
}
- }
- port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3);
- last_str = str;
- last_transmit = jiffies;
- } else {
+ q_port[q_used] = I8042_MUX_PORT_NO + ((str >> 6) & 3);
+ last_str = str;
+ last_transmit = jiffies;
+ } else {
- dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
- ((str & I8042_STR_TIMEOUT && !i8042_notimeout) ? SERIO_TIMEOUT : 0);
+ q_dfl[q_used] = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
+ ((str & I8042_STR_TIMEOUT && !i8042_notimeout) ? SERIO_TIMEOUT : 0);
- port_no = (str & I8042_STR_AUXDATA) ?
- I8042_AUX_PORT_NO : I8042_KBD_PORT_NO;
- }
+ q_port[q_used] = (str & I8042_STR_AUXDATA) ?
+ I8042_AUX_PORT_NO : I8042_KBD_PORT_NO;
+ }
+
+ port = &i8042_ports[q_port[q_used]];
+ serio = port->exists ? port->serio : NULL;
+
+ filter_dbg(port->driver_bound, q_data[q_used], "<- i8042 (interrupt, %d, %d%s%s)\n",
+ q_port[q_used], irq,
+ q_dfl[q_used] & SERIO_PARITY ? ", bad parity" : "",
+ q_dfl[q_used] & SERIO_TIMEOUT ? ", timeout" : "");
- port = &i8042_ports[port_no];
- serio = port->exists ? port->serio : NULL;
+ filtered = i8042_filter(q_data[q_used], str, serio);
- filter_dbg(port->driver_bound, data, "<- i8042 (interrupt, %d, %d%s%s)\n",
- port_no, irq,
- dfl & SERIO_PARITY ? ", bad parity" : "",
- dfl & SERIO_TIMEOUT ? ", timeout" : "");
+ if (likely(serio && !filtered))
+ ++q_used;
- filtered = i8042_filter(data, str, serio);
+ str = i8042_read_status();
+ } while ((q_used < I8042_QUEUE_SIZE) && (++q_cycles < 32) && (str & I8042_STR_OBF));
spin_unlock_irqrestore(&i8042_lock, flags);
- if (likely(serio && !filtered))
- serio_interrupt(serio, data, dfl);
+ for (q_i = 0; q_i < q_used; ++q_i)
+ serio_interrupt(i8042_ports[q_port[q_i]].serio, q_data[q_i], q_dfl[q_i]);
out:
return IRQ_RETVAL(ret);
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2021-02-01 16:05 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-02-01 16:03 [PATCH] i8042: Work around HP TGL bug Anton Zhilyaev
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).