diff mbox series

[v2] Input: atkbd - allow skipping the check of keyboard ID when probing

Message ID 20230530131340.39961-1-yesh25@mail2.sysu.edu.cn (mailing list archive)
State New, archived
Headers show
Series [v2] Input: atkbd - allow skipping the check of keyboard ID when probing | expand

Commit Message

Shang Ye May 30, 2023, 1:13 p.m. UTC
There have been several reports about keyboard not working on 9 types of
Lenovo Yoga / XiaoXinPro / IdeaPad (14", Intel) laptops. Here is a dmesg
log that illustrates the problem on a Yoga 14sIHU 2021:
https://gist.github.com/yescallop/5a97d010f226172fafab0933ce8ea8af

At first the KBD port was successfully set up by `i8042`, but then the
first initialization attempt by `atkbd` failed:

    [    2.698474] i8042: [17] f2 -> i8042 (kbd-data)
    [    2.698678] i8042: [17] fa <- i8042 (interrupt, 0, 1)
    [    2.698746] i8042: [17] 83 <- i8042 (interrupt, 0, 1)
    [    2.698767] i8042: [17] 60 -> i8042 (command)
    [    2.698856] i8042: [17] 66 -> i8042 (parameter)
    [    2.698951] i8042: [17] 60 -> i8042 (command)
    [    2.699092] i8042: [17] 67 -> i8042 (parameter)

It seems that the i8042 implementation on the laptop omitted the byte
0xab from its response to the Get ID command, thus making atkbd_probe()
fail on receiving an invalid keyboard ID (should normally be 0xab83).

This situation went on for a few rounds when I pressed and released the
space key (scan code: 0x39 when pressed, 0xb9 when released). The sixth
time I pressed the space key, something different happened:

    [   48.188540] i8042: [13664] 39 <- i8042 (interrupt, 0, 1)
    [   48.188658] i8042: [13664] f2 -> i8042 (kbd-data)
    [   48.188998] i8042: [13664] fa <- i8042 (interrupt, 0, 1)
    [   48.709743] i8042: [13821] ed -> i8042 (kbd-data)
    [   48.913069] i8042: [13882] 60 -> i8042 (command)
    [   48.913235] i8042: [13882] 66 -> i8042 (parameter)
    [   48.913446] i8042: [13882] 60 -> i8042 (command)
    [   48.913591] i8042: [13882] 67 -> i8042 (parameter)
    [   48.913672] i8042: [13882] fa <- i8042 (interrupt, 0, 0)

This time even the byte 0x83 was omitted, so the Get ID command failed
and atkbd_probe() tried to set the LEDs on the keyboard, but failed
again for not receiving an ACK to the command byte 0xed. However, when
i8042_port_close() was later called, an ACK was read from the KBD port,
which indicates that the i8042 implementation might have failed to raise
an interrupt for this ACK.

And the next time I released the space key, the byte 0x83 was omitted
again, but atkbd_probe() somehow succeeded in receiving an ACK to the
Set LEDs command, and the keyboard was finally initialized properly.

Until now, the only workaround is to boot with the kernel parameter
`i8042.dumbkbd`, which isn't very desirable as it disables the Caps Lock
LED. I have considered an alternative, generic fix that involves
flushing the keyboard buffer immediately after the Get ID command and
trying to set the LEDs even if the keyboard ID is invalid. This worked
on my laptop at least, but I really doubt that it won't cause any
regressions.

This patch adds a kernel parameter `atkbd.skip_id_check` that, when
enabled, skip the check of keyboard ID when probing and try to set the
LEDs directly, and adds quirks that enable the parameter by default on
`82NC` and `82TK` models, as these are the only models on which the
patch was tested to work.

Part of the patch is based on Bernhard's initial work.

Link: https://lore.kernel.org/linux-input/A3D566C5B262EC0F+4ede8371-9a20-6e5d-6a8c-b44d15634e26@mail2.sysu.edu.cn/
Cc: Bernhard Kaindl <bernhard.kaindl@cloud.com>
Reported-by: Bernhard Kaindl <bernhard.kaindl@cloud.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=216994
Signed-off-by: Shang Ye <yesh25@mail2.sysu.edu.cn>
---
V1 -> V2: Added kernel parameter and removed untested models

 .../admin-guide/kernel-parameters.txt         |  3 ++
 drivers/input/keyboard/atkbd.c                | 49 +++++++++++++++++--
 2 files changed, 48 insertions(+), 4 deletions(-)


base-commit: b00315628095075da4af8d6d519d85d95117de09
diff mbox series

Patch

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 6221a1d057dd..2a679a81e6fa 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -421,6 +421,9 @@ 
 	atkbd.scroll=	[HW] Enable scroll wheel on MS Office and similar
 			keyboards
 
+	atkbd.skip_id_check=
+			[HW] Skip the check of keyboard ID when probing
+
 	atkbd.softraw=	[HW] Choose between synthetic and real raw mode
 			Format: <bool> (0 = real, 1 = synthetic (default))
 
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index c92e544c792d..daf5a1821c42 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -65,6 +65,10 @@  static bool atkbd_terminal;
 module_param_named(terminal, atkbd_terminal, bool, 0);
 MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard connected via AT/PS2");
 
+static bool atkbd_skip_id_check;
+module_param_named(skip_id_check, atkbd_skip_id_check, bool, 0);
+MODULE_PARM_DESC(skip_id_check, "Skip the check of keyboard ID when probing");
+
 #define SCANCODE(keymap)	((keymap >> 16) & 0xFFFF)
 #define KEYCODE(keymap)		(keymap & 0xFFFF)
 
@@ -794,12 +798,12 @@  static int atkbd_probe(struct atkbd *atkbd)
  */
 
 	param[0] = param[1] = 0xa5;	/* initialize with invalid values */
-	if (ps2_command(ps2dev, param, ATKBD_CMD_GETID)) {
+	if (atkbd_skip_id_check || ps2_command(ps2dev, param, ATKBD_CMD_GETID)) {
 
 /*
- * If the get ID command failed, we check if we can at least set the LEDs on
- * the keyboard. This should work on every keyboard out there. It also turns
- * the LEDs off, which we want anyway.
+ * If the Get ID command failed or is skipped, we check if we can at least set
+ * the LEDs on the keyboard. This should work on every keyboard out there. It
+ * also turns the LEDs off, which we want anyway.
  */
 		param[0] = 0;
 		if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS))
@@ -1751,6 +1755,12 @@  static int __init atkbd_deactivate_fixup(const struct dmi_system_id *id)
 	return 1;
 }
 
+static int __init atkbd_id_check_fixup(const struct dmi_system_id *id)
+{
+	atkbd_skip_id_check = true;
+	return 1;
+}
+
 /*
  * NOTE: do not add any more "force release" quirks to this table.  The
  * task of adjusting list of keys that should be "released" automatically
@@ -1900,6 +1910,37 @@  static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = {
 		},
 		.callback = atkbd_deactivate_fixup,
 	},
+	/*
+	 * Some laptops have erroneous i8042 implementations that may randomly
+	 * omit interrupts and/or bytes in response to the Get ID command and
+	 * then cause atkbd_probe() to fail on receiving an invalid ID, or on
+	 * receiving no ID and then no ACK to the consequent Set LEDs command.
+	 * For this reason, we simply skip the Get ID command on these laptops
+	 * to avoid any potential issues.
+	 */
+	{
+		/*
+		 * Lenovo Yoga 14sIHU 2021
+		 * Lenovo XiaoXinPro 14IHU 2021
+		 * Lenovo Yoga Slim 7 Pro 14IHU5
+		 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "82NC"),
+		},
+		.callback = atkbd_id_check_fixup,
+	},
+	{
+		/*
+		 * Lenovo Yoga Pro 14s IAH7
+		 * Lenovo Yoga Slim 7 ProX 14IAH7
+		 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "82TK"),
+		},
+		.callback = atkbd_id_check_fixup,
+	},
 	{ }
 };