From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Renaud=20M=C3=A9trich?= <rmetrich@redhat.com>
Date: Fri, 18 Dec 2020 15:39:26 +0100
Subject: [PATCH] Add 'at_keyboard_fallback_set' var to force the set manually

This seems required with HP DL380p Gen 8 systems.
Indeed, with this system, we can see the following sequence:

1. controller is queried to get current configuration (returns 0x30 which is quite standard)
2. controller is queried to get the current keyboard set in used, using code 0xf0 (first part)
3. controller answers with 0xfa which means "ACK" (== ok)
4. then we send "0" to tell "we want to know which set your are supporting"
5. controller answers with 0xfa ("ACK")
6. controller should then give us 1, 2, 3 or 0x43, 0x41, 0x3f, but here it gives us 0xfe which means "NACK"

Since there seems no way to determine the current set, and in fact the
controller expects set2 to be used, we need to rely on an environment
variable.
Everything has been tested on this system: using 0xFE (resend command),
making sure we wait for ACK in the 2 steps "write_mode", etc.

Below is litterature I used to come up with "there is no other
solution":
- https://wiki.osdev.org/%228042%22_PS/2_Controller
- http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm
- http://www.s100computers.com/My%20System%20Pages/MSDOS%20Board/PC%20Keyboard.pdf
---
 grub-core/term/at_keyboard.c | 121 ++++++++++++++++++++++++++++++++++---------
 1 file changed, 96 insertions(+), 25 deletions(-)

diff --git a/grub-core/term/at_keyboard.c b/grub-core/term/at_keyboard.c
index 69d99b61df5..c805cccbdde 100644
--- a/grub-core/term/at_keyboard.c
+++ b/grub-core/term/at_keyboard.c
@@ -31,6 +31,7 @@ GRUB_MOD_LICENSE ("GPLv3+");
 static grub_uint8_t grub_keyboard_controller_orig;
 static grub_uint8_t grub_keyboard_orig_set;
 struct grub_ps2_state ps2_state;
+static int fallback_set;
 
 static int ping_sent;
 
@@ -76,6 +77,8 @@ at_command (grub_uint8_t data)
 	break;
       return 0;
     }
+  if (i == GRUB_AT_TRIES)
+    grub_dprintf ("atkeyb", "at_command() timed out! (stopped after %d tries)\n", i);
   return (i != GRUB_AT_TRIES);
 }
 
@@ -105,6 +108,21 @@ grub_keyboard_controller_read (void)
 
 #endif
 
+static int
+resend_last_result (void)
+{
+  grub_uint8_t ret;
+  keyboard_controller_wait_until_ready ();
+  grub_dprintf ("atkeyb", "resend_last_result: sending 0xfe\n");
+  grub_outb (0xfe, KEYBOARD_REG_DATA);
+  ret = wait_ack ();
+  grub_dprintf ("atkeyb", "resend_last_result: wait_ack() returned 0x%x\n", ret);
+  keyboard_controller_wait_until_ready ();
+  ret = grub_inb (KEYBOARD_REG_DATA);
+  grub_dprintf ("atkeyb", "resend_last_result: read 0x%x from controller\n", ret);
+  return ret;
+}
+
 static int
 write_mode (int mode)
 {
@@ -113,11 +131,14 @@ write_mode (int mode)
     {
       grub_uint8_t ack;
       keyboard_controller_wait_until_ready ();
+      grub_dprintf ("atkeyb", "write_mode: sending 0xf0\n");
       grub_outb (0xf0, KEYBOARD_REG_DATA);
       keyboard_controller_wait_until_ready ();
+      grub_dprintf ("atkeyb", "write_mode: sending mode %d\n", mode);
       grub_outb (mode, KEYBOARD_REG_DATA);
       keyboard_controller_wait_until_ready ();
       ack = wait_ack ();
+      grub_dprintf ("atkeyb", "write_mode: wait_ack() returned 0x%x\n", ack);
       if (ack == GRUB_AT_NACK)
 	continue;
       if (ack == GRUB_AT_ACK)
@@ -125,6 +146,9 @@ write_mode (int mode)
       return 0;
     }
 
+  if (i == GRUB_AT_TRIES)
+    grub_dprintf ("atkeyb", "write_mode() timed out! (stopped after %d tries)\n", i);
+
   return (i != GRUB_AT_TRIES);
 }
 
@@ -132,31 +156,66 @@ static int
 query_mode (void)
 {
   grub_uint8_t ret;
+  grub_uint64_t endtime;
+  unsigned i;
   int e;
+  char *envvar;
 
-  e = write_mode (0);
-  if (!e) {
-    grub_dprintf("atkeyb", "query_mode: write_mode(0) failed\n");
-    return 0;
-  }
+  for (i = 0; i < GRUB_AT_TRIES; i++) {
+    grub_dprintf ("atkeyb", "query_mode: sending command to controller\n");
+    e = write_mode (0);
+    if (!e) {
+      grub_dprintf ("atkeyb", "query_mode: write_mode(0) failed\n");
+      return 0;
+    }
 
-  do {
-    keyboard_controller_wait_until_ready ();
-    ret = grub_inb (KEYBOARD_REG_DATA);
-  } while (ret == GRUB_AT_ACK);
-  /* QEMU translates the set even in no-translate mode.  */
-  if (ret == 0x43 || ret == 1) {
-    grub_dprintf("atkeyb", "query_mode: returning 1 (ret=0x%x)\n", ret);
-    return 1;
-  }
-  if (ret == 0x41 || ret == 2) {
-    grub_dprintf("atkeyb", "query_mode: returning 2 (ret=0x%x)\n", ret);
-    return 2;
+    endtime = grub_get_time_ms () + 20;
+    do {
+      keyboard_controller_wait_until_ready ();
+      ret = grub_inb (KEYBOARD_REG_DATA);
+      grub_dprintf ("atkeyb", "query_mode/loop: read 0x%x from controller\n", ret);
+    } while ((ret == GRUB_AT_ACK || ret == GRUB_AT_NACK) && grub_get_time_ms () < endtime);
+    if (ret == 0xfe) {
+      grub_dprintf ("atkeyb", "query_mode: asking controller to resend last result\n");
+      ret = resend_last_result();
+      grub_dprintf ("atkeyb", "query_mode: read 0x%x from controller\n", ret);
+    }
+    /* QEMU translates the set even in no-translate mode.  */
+    if (ret == 0x43 || ret == 1) {
+      grub_dprintf ("atkeyb", "query_mode: controller returned 0x%x, returning 1\n", ret);
+      return 1;
+    }
+    if (ret == 0x41 || ret == 2) {
+      grub_dprintf ("atkeyb", "query_mode: controller returned 0x%x, returning 2\n", ret);
+      return 2;
+    }
+    if (ret == 0x3f || ret == 3) {
+      grub_dprintf ("atkeyb", "query_mode: controller returned 0x%x, returning 3\n", ret);
+      return 3;
+    }
+    grub_dprintf ("atkeyb", "query_mode: controller returned unexpected value 0x%x, retrying\n", ret);
   }
-  if (ret == 0x3f || ret == 3) {
-    grub_dprintf("atkeyb", "query_mode: returning 3 (ret=0x%x)\n", ret);
-    return 3;
+
+  /*
+   * Falling here means we tried querying and the controller returned something
+   * we don't understand, try to use 'at_keyboard_fallback_set' if it exists,
+   * otherwise return 0.
+   */
+  envvar = grub_env_get ("at_keyboard_fallback_set");
+  if (envvar) {
+    fallback_set = grub_strtoul (envvar, 0, 10);
+    if ((grub_errno) || (fallback_set < 1) || (fallback_set > 3)) {
+      grub_dprintf ("atkeyb", "WARNING: ignoring unexpected value '%s' for '%s' variable\n",
+		    envvar, "at_keyboard_fallback_set");
+      fallback_set = 0;
+    } else {
+      grub_dprintf ("atkeyb", "query_mode: '%s' specified in environment, returning %d\n",
+		    "at_keyboard_fallback_set", fallback_set);
+    }
+    return fallback_set;
   }
+  grub_dprintf ("atkeyb", "WARNING: no '%s' specified in environment, returning 0\n",
+		"at_keyboard_fallback_set");
   return 0;
 }
 
@@ -165,14 +224,25 @@ set_scancodes (void)
 {
   /* You must have visited computer museum. Keyboard without scancode set
      knowledge. Assume XT. */
-  if (!grub_keyboard_orig_set)
-    {
-      grub_dprintf ("atkeyb", "No sets support assumed\n");
-      ps2_state.current_set = 1;
+  if (!grub_keyboard_orig_set) {
+    if (fallback_set) {
+      grub_dprintf ("atkeyb", "No sets support assumed but set forced to %d\n", fallback_set);
+      ps2_state.current_set = fallback_set;
       return;
     }
+    grub_dprintf ("atkeyb", "No sets support assumed, forcing to set 1\n");
+    ps2_state.current_set = 1;
+    return;
+  }
 
 #if !USE_SCANCODE_SET
+  if (fallback_set) {
+    grub_dprintf ("atkeyb", "queried set is %d but set forced to %d\n",
+		  grub_keyboard_orig_set, fallback_set);
+    ps2_state.current_set = fallback_set;
+    return;
+  }
+
   if ((grub_keyboard_controller_orig & KEYBOARD_AT_TRANSLATE) == KEYBOARD_AT_TRANSLATE) {
     grub_dprintf ("atkeyb", "queried set is %d but keyboard in Translate mode, so actually in set 1\n", grub_keyboard_orig_set);
     ps2_state.current_set = 1;
@@ -261,6 +331,7 @@ grub_at_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused)))
 static void
 grub_keyboard_controller_init (void)
 {
+  grub_dprintf ("atkeyb", "initializing the controller\n");
   ps2_state.at_keyboard_status = 0;
   /* Drain input buffer. */
   while (1)
@@ -282,6 +353,7 @@ grub_keyboard_controller_init (void)
   grub_keyboard_controller_orig = grub_keyboard_controller_read ();
   grub_dprintf ("atkeyb", "grub_keyboard_controller_orig = 0x%x\n", grub_keyboard_controller_orig);
   grub_keyboard_orig_set = query_mode ();
+  grub_dprintf ("atkeyb", "grub_keyboard_orig_set = %d\n", grub_keyboard_orig_set);
 #endif
   set_scancodes ();
   keyboard_controller_led (ps2_state.led_status);
@@ -329,7 +401,6 @@ grub_at_restore_hw (void)
   return GRUB_ERR_NONE;
 }
 
-
 static struct grub_term_input grub_at_keyboard_term =
   {
     .name = "at_keyboard",
