aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2021-01-26 20:46:57 +0000
committerMichael Brown <mcb30@ipxe.org>2021-01-27 12:45:53 +0000
commita08244ecc4caad567d2607f84cd303e8a3c0ae98 (patch)
tree481554ed373a0c9ed7dd7da7afe4ff89cf5fc20b
parent8488c989cc109efc8eead4a089d773848d092d02 (diff)
downloadipxe-a08244ecc4caad567d2607f84cd303e8a3c0ae98.zip
ipxe-a08244ecc4caad567d2607f84cd303e8a3c0ae98.tar.gz
ipxe-a08244ecc4caad567d2607f84cd303e8a3c0ae98.tar.bz2
[efi] Use EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL if available
The original EFI_SIMPLE_TEXT_INPUT_PROTOCOL is not technically required to handle the use of the Ctrl key, and the long-obsolete EFI 1.10 specification lists only backspace, tab, linefeed, and carriage return as required. Some particularly brain-dead vendor UEFI firmware implementations dutifully put in the extra effort of ensuring that all other control characters (such as Ctrl-C) are impossible to type via EFI_SIMPLE_TEXT_INPUT_PROTOCOL. Current versions of the UEFI specification mandate that the console input handle must support both EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL, the latter of which at least provides access to modifier key state. Unlike EFI_SIMPLE_TEXT_INPUT_PROTOCOL, the pointer to the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL instance does not appear within the EFI system table and must therefore be opened explicitly. The UEFI specification provides no safe way to do so, since we cannot open the handle BY_DRIVER or BY_CHILD_CONTROLLER and so nothing guarantees that this pointer will remain valid for the lifetime of iPXE. We must simply hope that no UEFI firmware implementation ever discovers a motivation for reinstalling the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL instance. Use EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL if available, falling back to the existing EFI_SIMPLE_TEXT_PROTOCOL otherwise. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/interface/efi/efi_console.c77
1 files changed, 66 insertions, 11 deletions
diff --git a/src/interface/efi/efi_console.c b/src/interface/efi/efi_console.c
index 047baed..98ebbf3 100644
--- a/src/interface/efi/efi_console.c
+++ b/src/interface/efi/efi_console.c
@@ -54,6 +54,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define ATTR_DEFAULT ATTR_FCOL_WHITE
+#define CTRL_MASK 0x1f
+
/* Set default console usage if applicable */
#if ! ( defined ( CONSOLE_EFI ) && CONSOLE_EXPLICIT ( CONSOLE_EFI ) )
#undef CONSOLE_EFI
@@ -67,6 +69,9 @@ static unsigned int efi_attr = ATTR_DEFAULT;
static EFI_CONSOLE_CONTROL_PROTOCOL *conctrl;
EFI_REQUEST_PROTOCOL ( EFI_CONSOLE_CONTROL_PROTOCOL, &conctrl );
+/** Extended simple text input protocol, if present */
+static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *efi_conin_ex;
+
/**
* Handle ANSI CUP (cursor position)
*
@@ -278,8 +283,9 @@ static const char * scancode_to_ansi_seq ( unsigned int scancode ) {
*/
static int efi_getchar ( void ) {
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn;
+ EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *conin_ex = efi_conin_ex;
const char *ansi_seq;
- EFI_INPUT_KEY key;
+ EFI_KEY_DATA key;
EFI_STATUS efirc;
int rc;
@@ -288,20 +294,42 @@ static int efi_getchar ( void ) {
return *(ansi_input++);
/* Read key from real EFI console */
- if ( ( efirc = conin->ReadKeyStroke ( conin, &key ) ) != 0 ) {
- rc = -EEFI ( efirc );
- DBG ( "EFI could not read keystroke: %s\n", strerror ( rc ) );
- return 0;
+ memset ( &key, 0, sizeof ( key ) );
+ if ( conin_ex ) {
+ if ( ( efirc = conin_ex->ReadKeyStrokeEx ( conin_ex,
+ &key ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBG ( "EFI could not read extended keystroke: %s\n",
+ strerror ( rc ) );
+ return 0;
+ }
+ } else {
+ if ( ( efirc = conin->ReadKeyStroke ( conin,
+ &key.Key ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBG ( "EFI could not read keystroke: %s\n",
+ strerror ( rc ) );
+ return 0;
+ }
+ }
+ DBG2 ( "EFI read key stroke shift %08x toggle %02x unicode %04x "
+ "scancode %04x\n", key.KeyState.KeyShiftState,
+ key.KeyState.KeyToggleState, key.Key.UnicodeChar,
+ key.Key.ScanCode );
+
+ /* Translate Ctrl-<key> */
+ if ( ( key.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID ) &&
+ ( key.KeyState.KeyShiftState & ( EFI_LEFT_CONTROL_PRESSED |
+ EFI_RIGHT_CONTROL_PRESSED ) ) ) {
+ key.Key.UnicodeChar &= CTRL_MASK;
}
- DBG2 ( "EFI read key stroke with unicode %04x scancode %04x\n",
- key.UnicodeChar, key.ScanCode );
/* If key has a Unicode representation, return it */
- if ( key.UnicodeChar )
- return key.UnicodeChar;
+ if ( key.Key.UnicodeChar )
+ return key.Key.UnicodeChar;
/* Otherwise, check for a special key that we know about */
- if ( ( ansi_seq = scancode_to_ansi_seq ( key.ScanCode ) ) ) {
+ if ( ( ansi_seq = scancode_to_ansi_seq ( key.Key.ScanCode ) ) ) {
/* Start of escape sequence: return ESC (0x1b) */
ansi_input = ansi_seq;
return 0x1b;
@@ -319,6 +347,8 @@ static int efi_getchar ( void ) {
static int efi_iskey ( void ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn;
+ EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *conin_ex = efi_conin_ex;
+ EFI_EVENT *event;
EFI_STATUS efirc;
/* If we are mid-sequence, we are always ready */
@@ -326,7 +356,8 @@ static int efi_iskey ( void ) {
return 1;
/* Check to see if the WaitForKey event has fired */
- if ( ( efirc = bs->CheckEvent ( conin->WaitForKey ) ) == 0 )
+ event = ( conin_ex ? conin_ex->WaitForKeyEx : conin->WaitForKey );
+ if ( ( efirc = bs->CheckEvent ( event ) ) == 0 )
return 1;
return 0;
@@ -345,7 +376,14 @@ struct console_driver efi_console __console_driver = {
*
*/
static void efi_console_init ( void ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_CONSOLE_CONTROL_SCREEN_MODE mode;
+ union {
+ void *interface;
+ EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *wtf;
+ } u;
+ EFI_STATUS efirc;
+ int rc;
/* On some older EFI 1.10 implementations, we must use the
* (now obsolete) EFI_CONSOLE_CONTROL_PROTOCOL to switch the
@@ -358,6 +396,23 @@ static void efi_console_init ( void ) {
EfiConsoleControlScreenText );
}
}
+
+ /* Attempt to open the Simple Text Input Ex protocol on the
+ * console input handle. This is provably unsafe, but is
+ * apparently the expected behaviour for all UEFI
+ * applications. Don't ask.
+ */
+ if ( ( efirc = bs->OpenProtocol ( efi_systab->ConsoleInHandle,
+ &efi_simple_text_input_ex_protocol_guid,
+ &u.interface, efi_image_handle,
+ efi_systab->ConsoleInHandle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) == 0 ) {
+ efi_conin_ex = u.wtf;
+ DBG ( "EFI using SimpleTextInputEx\n" );
+ } else {
+ rc = -EEFI ( efirc );
+ DBG ( "EFI has no SimpleTextInputEx: %s\n", strerror ( rc ) );
+ }
}
/**