diff --git a/include/efi_api.h b/include/efi_api.h index f1c4e59..d423521 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -587,11 +587,67 @@ struct efi_simple_text_output_protocol { struct simple_text_output_mode *mode; }; +#define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ + EFI_GUID(0xdd9e7534, 0x7762, 0x4698, \ + 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa) + struct efi_input_key { u16 scan_code; s16 unicode_char; }; +#define EFI_SHIFT_STATE_INVALID 0x00000000 +#define EFI_RIGHT_SHIFT_PRESSED 0x00000001 +#define EFI_LEFT_SHIFT_PRESSED 0x00000002 +#define EFI_RIGHT_CONTROL_PRESSED 0x00000004 +#define EFI_LEFT_CONTROL_PRESSED 0x00000008 +#define EFI_RIGHT_ALT_PRESSED 0x00000010 +#define EFI_LEFT_ALT_PRESSED 0x00000020 +#define EFI_RIGHT_LOGO_PRESSED 0x00000040 +#define EFI_LEFT_LOGO_PRESSED 0x00000080 +#define EFI_MENU_KEY_PRESSED 0x00000100 +#define EFI_SYS_REQ_PRESSED 0x00000200 +#define EFI_SHIFT_STATE_VALID 0x80000000 + +#define EFI_TOGGLE_STATE_INVALID 0x00 +#define EFI_SCROLL_LOCK_ACTIVE 0x01 +#define EFI_NUM_LOCK_ACTIVE 0x02 +#define EFI_CAPS_LOCK_ACTIVE 0x04 +#define EFI_KEY_STATE_EXPOSED 0x40 +#define EFI_TOGGLE_STATE_VALID 0x80 + +struct efi_key_state { + u32 key_shift_state; + u8 key_toggle_state; +}; + +struct efi_key_data { + struct efi_input_key key; + struct efi_key_state key_state; +}; + +struct efi_simple_text_input_ex_protocol { + efi_status_t (EFIAPI *reset) ( + struct efi_simple_text_input_ex_protocol *this, + bool extended_verification); + efi_status_t (EFIAPI *read_key_stroke_ex) ( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data); + struct efi_event *wait_for_key_ex; + efi_status_t (EFIAPI *set_state) ( + struct efi_simple_text_input_ex_protocol *this, + u8 key_toggle_state); + efi_status_t (EFIAPI *register_key_notify) ( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data, + efi_status_t (EFIAPI *key_notify_function)( + struct efi_key_data *key_data), + void **notify_handle); + efi_status_t (EFIAPI *unregister_key_notify) ( + struct efi_simple_text_input_ex_protocol *this, + void *notification_handle); +}; + #define EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID \ EFI_GUID(0x387477c1, 0x69c7, 0x11d2, \ 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b) diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index e191e02..d17f0a1 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -42,10 +42,12 @@ static struct cout_mode efi_cout_modes[] = { }, }; -const efi_guid_t efi_guid_text_output_protocol = - EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_input_ex_protocol = + EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; const efi_guid_t efi_guid_text_input_protocol = EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_output_protocol = + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID; #define cESC '\x1b' #define ESC "\x1b" @@ -391,19 +393,19 @@ struct efi_simple_text_output_protocol efi_con_out = { }; static bool key_available; -static struct efi_input_key next_key; +static struct efi_key_data next_key; /** - * skip_modifiers() - analyze modifiers (shift, alt, ctrl) for function keys + * analyze_modifiers() - analyze modifiers (shift, alt, ctrl) for function keys * * This gets called when we have already parsed CSI. * * @modifiers: bitmask (shift, alt, ctrl) * @return: the unmodified code */ -static char skip_modifiers(int *modifiers) +static int analyze_modifiers(struct efi_key_state *key_state) { - char c, mod = 0, ret = 0; + int c, mod = 0, ret = 0; c = getc(); @@ -429,8 +431,17 @@ static char skip_modifiers(int *modifiers) out: if (mod) --mod; - if (modifiers) - *modifiers = mod; + key_state->key_shift_state = EFI_SHIFT_STATE_VALID; + if (mod) { + if (mod & 1) + key_state->key_shift_state |= EFI_LEFT_SHIFT_PRESSED; + if (mod & 2) + key_state->key_shift_state |= EFI_LEFT_ALT_PRESSED; + if (mod & 4) + key_state->key_shift_state |= EFI_LEFT_CONTROL_PRESSED; + if (mod & 8) + key_state->key_shift_state |= EFI_LEFT_LOGO_PRESSED; + } if (!ret) ret = c; return ret; @@ -442,7 +453,7 @@ out: * @key: - key received * Return: - status code */ -static efi_status_t efi_cin_read_key(struct efi_input_key *key) +static efi_status_t efi_cin_read_key(struct efi_key_data *key) { efi_status_t ret; struct efi_input_key pressed_key = { @@ -454,6 +465,10 @@ static efi_status_t efi_cin_read_key(struct efi_input_key *key) ret = console_read_unicode(&ch); if (ret) return EFI_NOT_READY; + + key->key_state.key_shift_state = EFI_SHIFT_STATE_INVALID; + key->key_state.key_toggle_state = EFI_TOGGLE_STATE_INVALID; + /* We do not support multi-word codes */ if (ch >= 0x10000) ch = '?'; @@ -490,7 +505,7 @@ static efi_status_t efi_cin_read_key(struct efi_input_key *key) pressed_key.scan_code = 5; break; case '1': - ch = skip_modifiers(NULL); + ch = analyze_modifiers(&key->key_state); switch (ch) { case '1'...'5': /* F1 - F5 */ pressed_key.scan_code = ch - '1' + 11; @@ -510,7 +525,7 @@ static efi_status_t efi_cin_read_key(struct efi_input_key *key) } break; case '2': - ch = skip_modifiers(NULL); + ch = analyze_modifiers(&key->key_state); switch (ch) { case '0'...'1': /* F9 - F10 */ pressed_key.scan_code = ch - '0' + 19; @@ -525,15 +540,15 @@ static efi_status_t efi_cin_read_key(struct efi_input_key *key) break; case '3': /* DEL */ pressed_key.scan_code = 8; - skip_modifiers(NULL); + analyze_modifiers(&key->key_state); break; case '5': /* PG UP */ pressed_key.scan_code = 9; - skip_modifiers(NULL); + analyze_modifiers(&key->key_state); break; case '6': /* PG DOWN */ pressed_key.scan_code = 10; - skip_modifiers(NULL); + analyze_modifiers(&key->key_state); break; } break; @@ -542,9 +557,28 @@ static efi_status_t efi_cin_read_key(struct efi_input_key *key) /* Backspace */ ch = 0x08; } - if (!pressed_key.scan_code) + if (pressed_key.scan_code) { + key->key_state.key_shift_state |= EFI_SHIFT_STATE_VALID; + } else { pressed_key.unicode_char = ch; - *key = pressed_key; + + /* + * Assume left control key for control characters typically + * entered using the control key. + */ + if (ch >= 0x01 && ch <= 0x1f) { + key->key_state.key_shift_state = + EFI_SHIFT_STATE_VALID; + switch (ch) { + case 0x01 ... 0x07: + case 0x0b ... 0x0c: + case 0x0e ... 0x1f: + key->key_state.key_shift_state |= + EFI_LEFT_CONTROL_PRESSED; + } + } + } + key->key = pressed_key; return EFI_SUCCESS; } @@ -573,6 +607,170 @@ static void efi_cin_check(void) } /** + * efi_cin_empty_buffer() - empty input buffer + */ +static void efi_cin_empty_buffer(void) +{ + while (tstc()) + getc(); + key_available = false; +} + +/** + * efi_cin_reset_ex() - reset console input + * + * @this: - the extended simple text input protocol + * @extended_verification: - extended verification + * + * This function implements the reset service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: old value of the task priority level + */ +static efi_status_t EFIAPI efi_cin_reset_ex( + struct efi_simple_text_input_ex_protocol *this, + bool extended_verification) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + efi_cin_empty_buffer(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_read_key_stroke_ex() - read key stroke + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key read from console + * Return: status code + * + * This function implements the ReadKeyStrokeEx service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_read_key_stroke_ex( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, key_data); + + /* Check parameters */ + if (!this || !key_data) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + /* Enable console input after ExitBootServices */ + efi_cin_check(); + + if (!key_available) { + ret = EFI_NOT_READY; + goto out; + } + *key_data = next_key; + key_available = false; + efi_con_in.wait_for_key->is_signaled = false; +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_set_state() - set toggle key state + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_toggle_state: key toggle state + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_set_state( + struct efi_simple_text_input_ex_protocol *this, + u8 key_toggle_state) +{ + EFI_ENTRY("%p, %u", this, key_toggle_state); + /* + * U-Boot supports multiple console input sources like serial and + * net console for which a key toggle state cannot be set at all. + * + * According to the UEFI specification it is allowable to not implement + * this service. + */ + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_cin_register_key_notify() - register key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key to be notified + * @key_notify_function: function to be called if the key is pressed + * @notify_handle: handle for unregistering the notification + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_register_key_notify( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data, + efi_status_t (EFIAPI *key_notify_function)( + struct efi_key_data *key_data), + void **notify_handle) +{ + EFI_ENTRY("%p, %p, %p, %p", + this, key_data, key_notify_function, notify_handle); + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +/** + * efi_cin_unregister_key_notify() - unregister key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @notification_handle: handle received when registering + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_unregister_key_notify( + struct efi_simple_text_input_ex_protocol *this, + void *notification_handle) +{ + EFI_ENTRY("%p, %p", this, notification_handle); + return EFI_EXIT(EFI_INVALID_PARAMETER); +} + + +/** * efi_cin_reset() - drain the input buffer * * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL @@ -599,16 +797,13 @@ static efi_status_t EFIAPI efi_cin_reset goto out; } - /* Empty input buffer */ - while (tstc()) - getc(); - key_available = false; + efi_cin_empty_buffer(); out: return EFI_EXIT(ret); } /** - * efi_cin_reset() - drain the input buffer + * efi_cin_read_key_stroke() - read key stroke * * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL * @key: key read from console @@ -644,13 +839,22 @@ static efi_status_t EFIAPI efi_cin_read_key_stroke ret = EFI_NOT_READY; goto out; } - *key = next_key; + *key = next_key.key; key_available = false; efi_con_in.wait_for_key->is_signaled = false; out: return EFI_EXIT(ret); } +static struct efi_simple_text_input_ex_protocol efi_con_in_ex = { + .reset = efi_cin_reset_ex, + .read_key_stroke_ex = efi_cin_read_key_stroke_ex, + .wait_for_key_ex = NULL, + .set_state = efi_cin_set_state, + .register_key_notify = efi_cin_register_key_notify, + .unregister_key_notify = efi_cin_unregister_key_notify, +}; + struct efi_simple_text_input_protocol efi_con_in = { .reset = efi_cin_reset, .read_key_stroke = efi_cin_read_key_stroke, @@ -721,6 +925,10 @@ int efi_console_register(void) if (r != EFI_SUCCESS) goto out_of_memory; systab.con_in_handle = efi_console_input_obj->handle; + r = efi_add_protocol(efi_console_input_obj->handle, + &efi_guid_text_input_ex_protocol, &efi_con_in_ex); + if (r != EFI_SUCCESS) + goto out_of_memory; /* Create console events */ r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, efi_key_notify, @@ -729,6 +937,7 @@ int efi_console_register(void) printf("ERROR: Failed to register WaitForKey event\n"); return r; } + efi_con_in_ex.wait_for_key_ex = efi_con_in.wait_for_key; r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_console_timer_notify, NULL, NULL, &console_timer_event);