libpayload/keyboard: Revise keyboard_cmd() error handling

Even if we are careful, it's still possible that we read spurious
data from the keyboard, e.g. keystrokes. Namely, when we send the
reset/disable command, there is a race before the command is pro-
cessed.

So we should always process data from the keyboard in a loop. We
break it, when an ACK (0xfa) or a NAK (0xfe) is received, and warn
on unexpected data unless it might be due to the mentioned race.

This also gives us the opportunity to use command-specific timeouts
which we take from Linux: 1s for the keyboard self-test (as there
are keyboards that perform the test before acking the command) and
200ms for all other commands.

Change-Id: I60a2643a8ff4b9231c63bf970c8749c97c7d8926
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/47083
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
diff --git a/payloads/libpayload/drivers/i8042/keyboard.c b/payloads/libpayload/drivers/i8042/keyboard.c
index 22747ff..42d4fdc 100644
--- a/payloads/libpayload/drivers/i8042/keyboard.c
+++ b/payloads/libpayload/drivers/i8042/keyboard.c
@@ -28,6 +28,7 @@
  */
 
 #include <stdbool.h>
+#include <stdint.h>
 
 #include <keycodes.h>
 #include <libpayload-config.h>
@@ -173,9 +174,33 @@
 
 static bool keyboard_cmd(unsigned char cmd)
 {
+	const uint64_t timeout_us = cmd == I8042_KBCMD_RESET ? 1*1000*1000 : 200*1000;
+	const uint64_t start_time = timer_us(0);
+
 	i8042_write_data(cmd);
 
-	return i8042_wait_read_ps2() == 0xfa;
+	do {
+		if (!i8042_data_ready_ps2()) {
+			udelay(50);
+			continue;
+		}
+
+		const uint8_t data = i8042_read_data_ps2();
+		switch (data) {
+		case 0xfa:
+			return true;
+		case 0xfe:
+			return false;
+		default:
+			/* Warn only if we already disabled keyboard input. */
+			if (cmd != I8042_KBCMD_DEFAULT_DIS)
+				printf("WARNING: Keyboard sent spurious 0x%02x.\n", data);
+			break;
+		}
+	} while (timer_us(start_time) < timeout_us);
+
+	printf("ERROR: Keyboard command timed out.\n");
+	return false;
 }
 
 bool keyboard_havechar(void)