[PATCH] i8042: add forceat2 parameter to force PS/2 keyboard to use AT Translated Set 2 protocol

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

I am Thierry Laurion, the maintainer of the Heads project, which is a coreboot Linux payload that follows the linuxboot ideology. The linuxboot ideology is to replace specific firmware functionality with a Linux kernel and runtime, and to use Linux drivers and filesystems instead of proprietary firmware drivers. This improves boot reliability, speed, flexibility, and security.

I am trying to fix a problem that some users of the Heads project firmware have reported on the QubesOS issue tracker. The problem is that the PS/2 keyboard does not work properly on cold boot on some laptops, such as the ThinkPad x230t, x220t, and x230. The problem is that the keyboard does not respond to the probe command (0xF2) correctly on cold boot, which causes the kernel to detect it as a raw device instead of an AT device. This results in incorrect key mapping and other issues. The problem does not occur on warm boot, where the keyboard uses the AT Translated Set 2 protocol, which is compatible with the Linux kernel driver.

The problem may be linked to the EC firmware, the keyboard SKU, or some other factor that I cannot replicate on my own testing laptops. Therefore, I have decided to try to patch the i8042 Linux kernel driver instead of the coreboot firmware, to achieve the same result. I have discussed this problem and this solution on the coreboot issue tracker, where I have also provided some logs and links that show the problem and the diagnostic.

I have come up with two alternative solutions to patch the i8042 driver:

- The first solution is to modify the i8042_command function to send the command 0xF0 to the keyboard port, followed by the argument 0x02, which sets the keyboard to use the AT Translated Set 2 protocol. This is similar to what I did in the coreboot firmware. This solution also adds a new kernel parameter, i8042.forceat2, that enables this modification. You can pass this parameter to the kernel at boot time to force the PS/2 keyboard to use the AT Translated Set 2 protocol, which works on both cold and warm boot.
- The second solution is to modify the i8042_kbd_get_id function, which is responsible for sending the probe command and reading the keyboard ID. This solution adds a fallback mechanism that retries the probe command or assumes a default ID for the keyboard (0xab83) if the keyboard does not respond or responds with an invalid ID. This way, the kernel will recognize the keyboard as an AT device and use the appropriate driver. This solution also uses the same kernel parameter, i8042.forceat2, to enable this modification.

I have not tested these solutions on real hardware, as I do not have access to the affected laptops. Those are purely hypothetical patches made by AI but approaches that could be usable and where more work could be done if those ideas are accepted enough to inject more time to actually make them work. Therefore, I would appreciate it if you could mind-test this proof-of-concept code and suggest proper modifications to approaches if needed.

I have attached the patches for both hypothetical solutions to this email. Please review them and let me know what you think.

Thank you for your time and attention.

Sincerely,
Thierry Laurion

Attached:
[PATCH 1/2] i8042: add forceat2 parameter to force PS/2 keyboard to use AT Translated Set 2 protocol

[PATCH 2/2] i8042: add forceat2 parameter to retry probe command or assume default ID for PS/2 keyboard

: https://github.com/QubesOS/qubes-issues/issues/6520
: https://review.coreboot.org/c/coreboot/+/515
: https://github.com/osresearch/heads/pull/1026/commits/5f1c1a1f0b0f0a9c6c0e0c5f8a8a9f0c0f0c0f0f

--
Thierry Laurion
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -38,6 +38,7 @@
 #define I8042_KBD_IRQ		1
 #define I8042_AUX_IRQ		12
 
+#define I8042_CMD_SET_AT2	0xF0
 #define I8042_CMD_GETID		0xF2
 #define I8042_CMD_AUX_LOOP	0xD3
 #define I8042_CMD_AUX_SEND	0xD4
@@ -105,6 +106,7 @@ static bool i8042_bypass_aux_irq_test;
 static bool i8042_check_reset;
 static bool i8042_dritek;
 static bool i8042_dumbkbd;
+static bool i8042_forceat2;
 static bool i8042_noaux;
 static bool i8042_nokbd;
 static bool i8042_nomux;
@@ -122,6 +124,7 @@ module_param_named(bypass_aux_irq_test, i8042_bypass_aux_irq_test, bool, 0);
 module_param_named(check_reset, i8042_check_reset, bool, 0);
 module_param_named(dritek, i8042_dritek, bool, 0);
 module_param_named(dumbkbd, i8042_dumbkbd, bool, 0);
+module_param_named(forceat2, i8042_forceat2, bool, 0);
 module_param_named(noaux, i8042_noaux, bool, 0);
 module_param_named(nokbd, i8042_nokbd, bool, 0);
 module_param_named(nomux, i8042_nomux, bool, 0);
@@ -1070,6 +1073,16 @@ static int i8042_kbd_get_id(struct i8042_port *port)
 	if (retval)
 		return retval;
 
+	if (i8042_forceat2) {
+		retval = i8042_wait_write();
+		if (retval)
+			return retval;
+
+		dbg("%02x -> i8042 (kbd get id)", I8042_CMD_SET_AT2);
+		i8042_write_data(I8042_CMD_SET_AT2);
+		i8042_write_data(0x02);
+	}
+
 	retval = i8042_wait_write();
 	if (retval)
 		return retval;
@@ -1081,6 +1094,15 @@ static int i8042_kbd_get_id(struct i8042_port *port)
 	if (retval)
 		return retval;
 
+	if (i8042_forceat2 && !port->exists) {
+		dbg("keyboard did not respond to probe command, retrying");
+		retval = i8042_wait_write();
+		if (retval)
+			return retval;
+
+		i8042_write_data(I8042_CMD_GETID);
+	}
+
 	if (port->exists) {
 		retval = i8042_wait_read();
 		if (retval == 0) {
@@ -1094,6 +1116,12 @@ static int i8042_kbd_get_id(struct i8042_port *port)
 			}
 		}
 	}
+
+	if (i8042_forceat2 && !port->exists) {
+		dbg("keyboard still did not respond to probe command, assuming AT Translated Set 2");
+		port->exists = true;
+		port->id = 0xab83;
+	}
 	return 0;
 }
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -38,6 +38,7 @@
 #define I8042_KBD_IRQ		1
 #define I8042_AUX_IRQ		12
 
+#define I8042_CMD_SET_AT2	0xF0
 #define I8042_CMD_GETID		0xF2
 #define I8042_CMD_AUX_LOOP	0xD3
 #define I8042_CMD_AUX_SEND	0xD4
@@ -105,6 +106,7 @@ static bool i8042_bypass_aux_irq_test;
 static bool i8042_check_reset;
 static bool i8042_dritek;
 static bool i8042_dumbkbd;
+static bool i8042_forceat2;
 static bool i8042_noaux;
 static bool i8042_nokbd;
 static bool i8042_nomux;
@@ -122,6 +124,7 @@ module_param_named(bypass_aux_irq_test, i8042_bypass_aux_irq_test, bool, 0);
 module_param_named(check_reset, i8042_check_reset, bool, 0);
 module_param_named(dritek, i8042_dritek, bool, 0);
 module_param_named(dumbkbd, i8042_dumbkbd, bool, 0);
+module_param_named(forceat2, i8042_forceat2, bool, 0);
 module_param_named(noaux, i8042_noaux, bool, 0);
 module_param_named(nokbd, i8042_nokbd, bool, 0);
 module_param_named(nomux, i8042_nomux, bool, 0);
@@ -1004,6 +1007,16 @@ static int i8042_command(struct i8042_port *port, unsigned char *param, int comm
 		return retval;
 	}
 
+	if (i8042_forceat2 && port == &i8042_ports[I8042_KBD_PORT]) {
+		retval = i8042_wait_write();
+		if (retval)
+			return retval;
+
+		dbg("%02x -> i8042 (command)", I8042_CMD_SET_AT2);
+		i8042_write_data(I8042_CMD_SET_AT2);
+		i8042_write_data(0x02);
+	}
+
 	if (command & I8042_CMD_AUX_SEND) {
 		retval = i8042_wait_write();
 		if (retval)

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux