libpayload/keyboard: Turn init sequence into a state machine

We'll process the init sequence as part of the polling loop. This
should have several advantages:

* It eases error handling, i.e. we can return to an earlier state.
* We don't have to stall initialization when a keyboard takes a
  little longer.
* Generally, these keyboards can be hot-plugged (albeit not by
  design).

Change-Id: I9cf5cf31eb420b3994bec20e56a72d37f3d2996e
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/47086
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
This commit is contained in:
Nico Huber 2020-11-01 16:44:22 +01:00 committed by Hung-Te Lin
parent 828f6b428e
commit 260dd9eb7e
1 changed files with 138 additions and 38 deletions

View File

@ -209,9 +209,139 @@ static bool keyboard_cmd(unsigned char cmd)
return false; return false;
} }
static bool set_scancode_set(const unsigned char set)
{
bool ret;
if (set < 1 || set > 3)
return false;
ret = keyboard_cmd(I8042_KBCMD_SET_SCANCODE);
if (!ret) {
printf("ERROR: Keyboard set scancode failed!\n");
return ret;
}
ret = keyboard_cmd(set);
if (!ret) {
printf("ERROR: Keyboard scancode set#%u failed!\n", set);
return ret;
}
return ret;
}
static enum keyboard_state {
STATE_INIT = 0,
STATE_DISABLE_SCAN,
STATE_DRAIN_INPUT,
STATE_DISABLE_TRANSLATION,
STATE_CONFIGURE,
STATE_CONFIGURE_SET1,
STATE_ENABLE_TRANSLATION,
STATE_ENABLE_SCAN,
STATE_RUNNING,
STATE_IGNORE,
} keyboard_state;
static uint64_t keyboard_time;
static void keyboard_poll(void)
{
enum keyboard_state next_state = keyboard_state;
unsigned int i;
switch (keyboard_state) {
case STATE_INIT:
/* Wait until keyboard_init() has been called. */
break;
case STATE_DISABLE_SCAN:
(void)keyboard_cmd(I8042_KBCMD_DEFAULT_DIS);
next_state = STATE_DRAIN_INPUT;
break;
case STATE_DRAIN_INPUT:
/* Limit number of bytes drained per poll. */
for (i = 0; i < 50 && i8042_data_ready_ps2(); ++i)
(void)i8042_read_data_ps2();
if (i == 0)
next_state = STATE_DISABLE_TRANSLATION;
break;
case STATE_DISABLE_TRANSLATION:
/* Be opportunistic and assume it's disabled on failure. */
(void)i8042_set_kbd_translation(false);
next_state = STATE_CONFIGURE;
break;
case STATE_CONFIGURE:
if (set_scancode_set(2))
next_state = STATE_ENABLE_TRANSLATION;
else
next_state = STATE_CONFIGURE_SET1;
break;
case STATE_CONFIGURE_SET1:
if (!set_scancode_set(1)) {
printf("ERROR: Keyboard failed to set any scancode set.\n");
next_state = STATE_DISABLE_SCAN;
break;
}
next_state = STATE_ENABLE_SCAN;
break;
case STATE_ENABLE_TRANSLATION:
if (i8042_set_kbd_translation(true) != 0) {
printf("ERROR: Keyboard controller set translation failed!\n");
next_state = STATE_DISABLE_SCAN;
break;
}
next_state = STATE_ENABLE_SCAN;
break;
case STATE_ENABLE_SCAN:
if (!keyboard_cmd(I8042_KBCMD_EN)) {
printf("ERROR: Keyboard enable scanning failed!\n");
next_state = STATE_DISABLE_SCAN;
break;
}
next_state = STATE_RUNNING;
break;
case STATE_RUNNING:
/* TODO: Use echo command to detect detach. */
break;
case STATE_IGNORE:
/* TODO: Try again after timeout if it ever seems useful. */
break;
}
switch (next_state) {
case STATE_INIT:
case STATE_RUNNING:
case STATE_IGNORE:
break;
default:
if (timer_us(keyboard_time) > 30*1000*1000)
next_state = STATE_IGNORE;
break;
}
if (keyboard_state != next_state)
keyboard_state = next_state;
}
bool keyboard_havechar(void) bool keyboard_havechar(void)
{ {
return i8042_data_ready_ps2(); keyboard_poll();
return keyboard_state == STATE_RUNNING && i8042_data_ready_ps2();
} }
unsigned char keyboard_get_scancode(void) unsigned char keyboard_get_scancode(void)
@ -344,30 +474,11 @@ static struct console_input_driver cons = {
.input_type = CONSOLE_INPUT_TYPE_EC, .input_type = CONSOLE_INPUT_TYPE_EC,
}; };
static bool set_scancode_set(const unsigned char set)
{
bool ret;
if (set < 1 || set > 3)
return false;
ret = keyboard_cmd(I8042_KBCMD_SET_SCANCODE);
if (!ret) {
printf("ERROR: Keyboard set scancode failed!\n");
return ret;
}
ret = keyboard_cmd(set);
if (!ret) {
printf("ERROR: Keyboard scancode set#%u failed!\n", set);
return ret;
}
return ret;
}
void keyboard_init(void) void keyboard_init(void)
{ {
if (keyboard_state != STATE_INIT)
return;
map = &keyboard_layouts[0]; map = &keyboard_layouts[0];
/* Initialized keyboard controller. */ /* Initialized keyboard controller. */
@ -377,21 +488,8 @@ void keyboard_init(void)
/* Enable first PS/2 port */ /* Enable first PS/2 port */
i8042_cmd(I8042_CMD_EN_KB); i8042_cmd(I8042_CMD_EN_KB);
/* Disable scanning */ keyboard_state = STATE_DISABLE_SCAN;
keyboard_cmd(I8042_KBCMD_DEFAULT_DIS); keyboard_time = timer_us(0);
keyboard_drain_input();
i8042_set_kbd_translation(false);
if (set_scancode_set(2))
i8042_set_kbd_translation(true);
else if (!set_scancode_set(1))
return; /* give up, leave keyboard input disabled */
/* Enable scanning */
const bool ret = keyboard_cmd(I8042_KBCMD_EN);
if (!ret)
printf("ERROR: Keyboard enable scanning failed!\n");
console_add_input_driver(&cons); console_add_input_driver(&cons);
} }
@ -418,4 +516,6 @@ void keyboard_disconnect(void)
/* Release keyboard controller driver */ /* Release keyboard controller driver */
i8042_close(); i8042_close();
keyboard_state = STATE_INIT;
} }