blob: 08134159021adfca74646cfc23820ce29a9adf56 [file]
/** @file
EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
implementation for virtio keyboard.
Copyright (C) 2026, Advanced Micro Devices, Inc.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/VirtioLib.h>
#include <IndustryStandard/VirtioInput.h>
#include "VirtioInput.h"
#include "VirtioKeyCodes.h"
BOOLEAN
VirtioKeyboardProbe (
IN VIRTIO_INPUT_DEV *Dev
)
{
EFI_STATUS Status;
UINT8 Size;
UINT8 Bitmap;
UINTN Index;
Status = VirtioInputConfigQuerySize (Dev, VirtioInputCfgEvBits, EV_KEY, &Size);
if (EFI_ERROR (Status)) {
return FALSE;
}
// Keyboard keys are 0 ~ 255, so if any of them is supported, we have a keyboard
Size = MIN (Size, (MAX_KEYBOARD_CODE / 8) + 1);
for (Index = 0; Index < Size; Index++) {
Status = Dev->VirtIo->ReadDevice (Dev->VirtIo, OFFSET_OF_VINPUT (Data) + Index, 1, 1, &Bitmap);
if (EFI_ERROR (Status)) {
return FALSE;
}
if (Bitmap) {
return TRUE;
}
}
return FALSE;
}
// -----------------------------------------------------------------------------
// Function converting VirtIO key codes to UEFI key codes
STATIC
VOID
VirtioKeyboardConvertKeyCode (
IN OUT VIRTIO_INPUT_DEV *Dev,
IN UINT16 Code,
OUT EFI_INPUT_KEY *Key
)
{
// Key mapping in between Linux and UEFI
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
// https://dox.ipxe.org/SimpleTextIn_8h_source.html#l00048
// https://uefi.org/specs/UEFI/2.10/Apx_B_Console.html
static const UINT16 Map[] = {
[KEY_1] = '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
[KEY_MINUS] = '-', '=',
[KEY_Q] = 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
[KEY_LEFTBRACE] = '[', ']',
[KEY_A] = 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
[KEY_SEMICOLON] = ';', '\'', '`',
[KEY_BACKSLASH] = '\\',
[KEY_Z] = 'z', 'x', 'c', 'v', 'b', 'n', 'm',
[KEY_COMMA] = ',', '.', '/',
[KEY_SPACE] = ' ',
[MAX_KEYBOARD_CODE] = 0x00
};
static const UINT16 MapShift[] = {
[KEY_1] = '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
[KEY_MINUS] = '_', '+',
[KEY_Q] = 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
[KEY_LEFTBRACE] = '{', '}',
[KEY_A] = 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
[KEY_SEMICOLON] = ':', '\"', '~',
[KEY_BACKSLASH] = '|',
[KEY_Z] = 'Z', 'X', 'C', 'V', 'B', 'N', 'M',
[KEY_COMMA] = '<', '>', '?',
[KEY_SPACE] = ' ',
[MAX_KEYBOARD_CODE] = 0x00
};
// Set default readings
Key->ScanCode = SCAN_NULL;
Key->UnicodeChar = CHAR_NULL;
// Check if key code is not out of the keyboard mapping boundaries
if (Code >= MAX_KEYBOARD_CODE) {
DEBUG ((DEBUG_INFO, "%a: Key code out of range \n", __func__));
return;
}
// Handle F1 - F10 keys
if ((Code >= KEY_F1) && (Code <= KEY_F10)) {
Key->ScanCode = SCAN_F1 + (Code - KEY_F1);
return;
}
switch (Code) {
case KEY_PAGEUP:
Key->ScanCode = SCAN_PAGE_UP;
break;
case KEY_PAGEDOWN:
Key->ScanCode = SCAN_PAGE_DOWN;
break;
case KEY_HOME:
Key->ScanCode = SCAN_HOME;
break;
case KEY_END:
Key->ScanCode = SCAN_END;
break;
case KEY_DELETE:
Key->ScanCode = SCAN_DELETE;
break;
case KEY_INSERT:
Key->ScanCode = SCAN_INSERT;
break;
case KEY_UP:
Key->ScanCode = SCAN_UP;
break;
case KEY_LEFT:
Key->ScanCode = SCAN_LEFT;
break;
case KEY_RIGHT:
Key->ScanCode = SCAN_RIGHT;
break;
case KEY_DOWN:
Key->ScanCode = SCAN_DOWN;
break;
case KEY_BACKSPACE:
Key->UnicodeChar = CHAR_BACKSPACE;
break;
case KEY_TAB:
Key->UnicodeChar = CHAR_TAB;
break;
case KEY_ENTER:
Key->UnicodeChar = CHAR_CARRIAGE_RETURN;
break;
case KEY_ESC:
Key->ScanCode = SCAN_ESC;
break;
default:
if (Dev->KeyActive[KEY_LEFTSHIFT] || Dev->KeyActive[KEY_RIGHTSHIFT]) {
Key->ScanCode = MapShift[Code];
Key->UnicodeChar = MapShift[Code];
} else {
Key->ScanCode = Map[Code];
Key->UnicodeChar = Map[Code];
}
if (Dev->KeyActive[KEY_LEFTCTRL] || Dev->KeyActive[KEY_RIGHTCTRL]) {
// Convert Ctrl+[a-z] and Ctrl+[A-Z] into [1-26] ASCII table entries
Key->UnicodeChar &= 0x1F;
}
break;
}
}
// -----------------------------------------------------------------------------
// Function handling VirtIO keyboard events
VOID
VirtioKeyboardHandleEvent (
IN OUT VIRTIO_INPUT_DEV *Dev,
IN VIRTIO_INPUT_EVENT *Event
)
{
if (Event->Value == KEY_PRESSED) {
// Key pressed event received
Dev->KeyActive[(UINT8)Event->Code] = TRUE;
// Evaluate key
VirtioKeyboardConvertKeyCode (Dev, Event->Code, &Dev->LastKey);
// Flag that printable character is ready to be send
Dev->KeyReady = TRUE;
} else {
// Key released event received
Dev->KeyActive[(UINT8)Event->Code] = FALSE;
}
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardReset (
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
IN BOOLEAN ExtendedVerification
)
{
VIRTIO_INPUT_DEV *Dev;
EFI_TPL OldTpl;
Dev = VIRTIO_INPUT_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
Dev->KeyReady = FALSE;
Dev->LastKey.ScanCode = SCAN_NULL;
Dev->LastKey.UnicodeChar = CHAR_NULL;
ZeroMem (Dev->KeyActive, sizeof (Dev->KeyActive));
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardReadKeyStroke (
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
OUT EFI_INPUT_KEY *Key
)
{
VIRTIO_INPUT_DEV *Dev;
EFI_TPL OldTpl;
if (Key == NULL) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_INPUT_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
if (Dev->KeyReady) {
// Get last key from the buffer
*Key = Dev->LastKey;
// Mark key as consumed
Dev->KeyReady = FALSE;
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
gBS->RestoreTPL (OldTpl);
return EFI_NOT_READY;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
STATIC
VOID
EFIAPI
VirtioKeyboardWaitForKey (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VIRTIO_INPUT_DEV *Dev = (VIRTIO_INPUT_DEV *)Context;
//
// Stall 1ms to give a chance to let other driver interrupt this routine
// for their timer event.
// e.g. UI setup or Shell, other drivers which are driven by timer event
// will have a bad performance during this period,
// e.g. usb keyboard driver.
// Add a stall period can greatly increate other driver performance during
// the WaitForKey is recursivly invoked. 1ms delay will make little impact
// to the thunk keyboard driver, and user can not feel the delay at all when
// input.
gBS->Stall (1000);
// Use TimerEvent callback function to check whether there's any key pressed
VirtioInputTimer (NULL, Dev);
// If there is a new key ready - send signal
if (Dev->KeyReady) {
gBS->SignalEvent (Event);
}
}
/// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardResetEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN BOOLEAN ExtendedVerification
)
{
VIRTIO_INPUT_DEV *Dev;
EFI_STATUS Status;
Dev = VIRTIO_INPUT_EX_FROM_THIS (This);
// Call the reset function from SIMPLE_TEXT_INPUT protocol
Status = Dev->Txt.Reset (
&Dev->Txt,
ExtendedVerification
);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardReadKeyStrokeEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
OUT EFI_KEY_DATA *KeyData
)
{
VIRTIO_INPUT_DEV *Dev;
EFI_STATUS Status;
EFI_INPUT_KEY Key;
EFI_KEY_STATE KeyState;
if (KeyData == NULL) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_INPUT_EX_FROM_THIS (This);
// Get the last pressed key
Status = Dev->Txt.ReadKeyStroke (&Dev->Txt, &Key);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
// Add key state informations
KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID;
KeyState.KeyToggleState = EFI_TOGGLE_STATE_VALID;
// Shift key modifier
if (Dev->KeyActive[KEY_LEFTSHIFT]) {
KeyState.KeyShiftState |= EFI_LEFT_SHIFT_PRESSED;
}
if (Dev->KeyActive[KEY_RIGHTSHIFT]) {
KeyState.KeyShiftState |= EFI_RIGHT_SHIFT_PRESSED;
}
// Ctrl key modifier
if (Dev->KeyActive[KEY_LEFTCTRL]) {
KeyState.KeyShiftState |= EFI_LEFT_CONTROL_PRESSED;
}
if (Dev->KeyActive[KEY_RIGHTCTRL]) {
KeyState.KeyShiftState |= EFI_RIGHT_CONTROL_PRESSED;
}
// ALt key modifier
if (Dev->KeyActive[KEY_LEFTALT]) {
KeyState.KeyShiftState |= EFI_LEFT_ALT_PRESSED;
}
if (Dev->KeyActive[KEY_RIGHTALT]) {
KeyState.KeyShiftState |= EFI_RIGHT_ALT_PRESSED;
}
// Return value only when there is no failure
KeyData->Key = Key;
KeyData->KeyState = KeyState;
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardSetStateEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN EFI_KEY_TOGGLE_STATE *KeyToggleState
)
{
if (KeyToggleState == NULL) {
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
STATIC
BOOLEAN
IsKeyRegistered (
IN EFI_KEY_DATA *RegisteredData,
IN EFI_KEY_DATA *InputData
)
{
ASSERT (RegisteredData != NULL && InputData != NULL);
if ((RegisteredData->Key.ScanCode != InputData->Key.ScanCode) ||
(RegisteredData->Key.UnicodeChar != InputData->Key.UnicodeChar))
{
return FALSE;
}
//
// Assume KeyShiftState/KeyToggleState = 0 in Registered key data means
// these state could be ignored.
//
if ((RegisteredData->KeyState.KeyShiftState != 0) &&
(RegisteredData->KeyState.KeyShiftState != InputData->KeyState.KeyShiftState))
{
return FALSE;
}
if ((RegisteredData->KeyState.KeyToggleState != 0) &&
(RegisteredData->KeyState.KeyToggleState != InputData->KeyState.KeyToggleState))
{
return FALSE;
}
return TRUE;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardRegisterKeyNotifyEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN EFI_KEY_DATA *KeyData,
IN EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction,
OUT VOID **NotifyHandle
)
{
EFI_STATUS Status;
VIRTIO_INPUT_DEV *Dev;
EFI_TPL OldTpl;
LIST_ENTRY *Link;
VIRTIO_INPUT_IN_EX_NOTIFY *NewNotify;
VIRTIO_INPUT_IN_EX_NOTIFY *CurrentNotify;
if ((KeyData == NULL) ||
(NotifyHandle == NULL) ||
(KeyNotificationFunction == NULL))
{
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_INPUT_EX_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
// Check if the (KeyData, NotificationFunction) pair is already registered.
for (Link = Dev->KeyNotifyList.ForwardLink;
Link != &Dev->KeyNotifyList;
Link = Link->ForwardLink)
{
CurrentNotify = CR (
Link,
VIRTIO_INPUT_IN_EX_NOTIFY,
NotifyEntry,
VIRTIO_INPUT_SIG
);
if (IsKeyRegistered (&CurrentNotify->KeyData, KeyData)) {
if (CurrentNotify->KeyNotificationFn == KeyNotificationFunction) {
*NotifyHandle = CurrentNotify;
Status = EFI_SUCCESS;
goto Exit;
}
}
}
NewNotify = (VIRTIO_INPUT_IN_EX_NOTIFY *)AllocateZeroPool (sizeof (VIRTIO_INPUT_IN_EX_NOTIFY));
if (NewNotify == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
NewNotify->Signature = VIRTIO_INPUT_SIG;
NewNotify->KeyNotificationFn = KeyNotificationFunction;
CopyMem (&NewNotify->KeyData, KeyData, sizeof (EFI_KEY_DATA));
InsertTailList (&Dev->KeyNotifyList, &NewNotify->NotifyEntry);
*NotifyHandle = NewNotify;
Status = EFI_SUCCESS;
Exit:
gBS->RestoreTPL (OldTpl);
return Status;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardUnregisterKeyNotifyEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN VOID *NotificationHandle
)
{
EFI_STATUS Status;
VIRTIO_INPUT_DEV *Dev;
EFI_TPL OldTpl;
LIST_ENTRY *Link;
VIRTIO_INPUT_IN_EX_NOTIFY *CurrentNotify;
if (NotificationHandle == NULL) {
return EFI_INVALID_PARAMETER;
}
if (((VIRTIO_INPUT_IN_EX_NOTIFY *)NotificationHandle)->Signature != VIRTIO_INPUT_SIG) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_INPUT_EX_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
for (Link = Dev->KeyNotifyList.ForwardLink;
Link != &Dev->KeyNotifyList;
Link = Link->ForwardLink)
{
CurrentNotify = CR (
Link,
VIRTIO_INPUT_IN_EX_NOTIFY,
NotifyEntry,
VIRTIO_INPUT_SIG
);
if (CurrentNotify == NotificationHandle) {
RemoveEntryList (&CurrentNotify->NotifyEntry);
Status = EFI_SUCCESS;
goto Exit;
}
}
// Notification has not been found
Status = EFI_INVALID_PARAMETER;
Exit:
gBS->RestoreTPL (OldTpl);
return Status;
}
EFI_STATUS
VirtioKeyboardInit (
IN OUT VIRTIO_INPUT_DEV *Dev
)
{
EFI_STATUS Status;
InitializeListHead (&Dev->KeyNotifyList);
Dev->Txt.Reset = VirtioKeyboardReset;
Dev->Txt.ReadKeyStroke = VirtioKeyboardReadKeyStroke;
Dev->TxtEx.Reset = VirtioKeyboardResetEx;
Dev->TxtEx.ReadKeyStrokeEx = VirtioKeyboardReadKeyStrokeEx;
Dev->TxtEx.SetState = VirtioKeyboardSetStateEx;
Dev->TxtEx.RegisterKeyNotify = VirtioKeyboardRegisterKeyNotifyEx;
Dev->TxtEx.UnregisterKeyNotify = VirtioKeyboardUnregisterKeyNotifyEx;
//
// Setup the WaitForKey event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioKeyboardWaitForKey,
Dev,
&(Dev->Txt.WaitForKey)
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Setup the WaitForKeyEx event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioKeyboardWaitForKey,
Dev,
&(Dev->TxtEx.WaitForKeyEx)
);
if (EFI_ERROR (Status)) {
return Status;
}
return EFI_SUCCESS;
}
VOID
VirtioKeyboardUninit (
IN OUT VIRTIO_INPUT_DEV *Dev
)
{
gBS->CloseEvent (Dev->Txt.WaitForKey);
gBS->CloseEvent (Dev->TxtEx.WaitForKeyEx);
}