OvmfPkg/VirtioInputDxe: Split keyboard logic into VirtioKeyboard.c

Move all keyboard-specific code including probing, initialization,
keycode translation and Simple Text Input (Ex) protocol implementation
into new VirtioKeyboard.c. VirtioInput.c retains only the shared virtio
transport, the input poll timer and the event dispatcher.

This is also the groundwork for the mouse and tablet support added in
later commits.

No functional change.

Signed-off-by: Jiaqing Zhao <Zhao.Jiaqing@amd.com>
This commit is contained in:
Jiaqing Zhao 2026-06-24 17:08:09 +08:00 committed by mergify[bot]
parent d0c42935e2
commit fd63e80508
4 changed files with 687 additions and 624 deletions

View file

@ -351,7 +351,6 @@ VirtioInputRingFillRx (
Dev->VirtIo->SetQueueNotify (Dev->VirtIo, Index);
}
STATIC
EFI_STATUS
VirtioInputConfigQuerySize (
IN VIRTIO_INPUT_DEV *Dev,
@ -376,236 +375,6 @@ VirtioInputConfigQuerySize (
return Status;
}
STATIC
BOOLEAN
VirtioInputHasKeyboard (
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;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
STATIC
EFI_STATUS
EFIAPI
VirtioInputSimpleTextInputReset (
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
VirtioInputSimpleTextInputReadKeyStroke (
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;
}
// -----------------------------------------------------------------------------
// Function converting VirtIO key codes to UEFI key codes
STATIC
VOID
EFIAPI
VirtioInputConvertKeyCode (
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_LINEFEED;
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;
}
}
// -----------------------------------------------------------------------------
// Main function processing virtio input events
STATIC
@ -633,13 +402,10 @@ VirtioInputGetDeviceData (
continue;
}
// Clearing last character is not needed as it will be overwritten anyway
// Dev->LastKey.ScanCode = SCAN_NULL;
// Dev->LastKey.UnicodeChar = CHAR_NULL;
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
CopyMem (&Event, Data, sizeof (Event));
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
switch (Event.Type) {
case EV_SYN:
// Sync event received
@ -650,20 +416,7 @@ VirtioInputGetDeviceData (
// DEBUG ((DEBUG_INFO, "%a: ---------------------- \nType: %x Code: %x Value: %x\n",
// __func__, Event.Type, Event.Code, Event.Value));
if (Event.Value == KEY_PRESSED) {
// Key pressed event received
Dev->KeyActive[(UINT8)Event.Code] = TRUE;
// Evaluate key
VirtioInputConvertKeyCode (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;
}
VirtioKeyboardHandleEvent (Dev, &Event);
break;
default:
@ -677,7 +430,6 @@ VirtioInputGetDeviceData (
// -----------------------------------------------------------------------------
// Callback hook for timer interrupt
STATIC
VOID
EFIAPI
VirtioInputTimer (
@ -690,308 +442,6 @@ VirtioInputTimer (
VirtioInputGetDeviceData (Dev);
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
STATIC
VOID
EFIAPI
VirtioInputWaitForKey (
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
VirtioInputResetEx (
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
VirtioInputReadKeyStrokeEx (
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
VirtioInputSetState (
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
VirtioInputRegisterKeyNotify (
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
VirtioInputUnregisterKeyNotify (
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;
}
// -----------------------------------------------------------------------------
// Driver init
STATIC
@ -1083,7 +533,7 @@ VirtioInputInit (
//
// Check input device capabilities
//
Dev->HasKeyboard = VirtioInputHasKeyboard (Dev);
Dev->HasKeyboard = VirtioKeyboardProbe (Dev);
if (!Dev->HasKeyboard) {
Status = EFI_UNSUPPORTED;
goto Failed;
@ -1093,41 +543,7 @@ VirtioInputInit (
// populate the exported interface's attributes
//
if (Dev->HasKeyboard) {
Dev->Txt.Reset = VirtioInputSimpleTextInputReset;
Dev->Txt.ReadKeyStroke = VirtioInputSimpleTextInputReadKeyStroke;
Dev->Txt.WaitForKey = (EFI_EVENT)VirtioInputWaitForKey;
Dev->TxtEx.Reset = VirtioInputResetEx;
Dev->TxtEx.ReadKeyStrokeEx = VirtioInputReadKeyStrokeEx;
Dev->TxtEx.SetState = VirtioInputSetState;
Dev->TxtEx.RegisterKeyNotify = VirtioInputRegisterKeyNotify;
Dev->TxtEx.UnregisterKeyNotify = VirtioInputUnregisterKeyNotify;
InitializeListHead (&Dev->KeyNotifyList);
//
// Setup the WaitForKey event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioInputWaitForKey,
Dev,
&(Dev->Txt.WaitForKey)
);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// Setup the WaitForKeyEx event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioInputWaitForKey,
Dev,
&(Dev->TxtEx.WaitForKeyEx)
);
Status = VirtioKeyboardInit (Dev);
if (EFI_ERROR (Status)) {
goto Failed;
}
@ -1185,8 +601,7 @@ VirtioInputUninit (
gBS->CloseEvent (Dev->PollTimer);
if (Dev->HasKeyboard) {
gBS->CloseEvent (Dev->Txt.WaitForKey);
gBS->CloseEvent (Dev->TxtEx.WaitForKeyEx);
VirtioKeyboardUninit (Dev);
}
//

View file

@ -55,29 +55,11 @@ typedef struct {
// Declaration of data structure representing driver context
typedef struct {
// Device signature
UINT32 Signature;
UINT32 Signature;
// Hook for the function which shall be caled when driver is closed
// before system state changes to boot
EFI_EVENT ExitBoot;
// Hooks for functions required by UEFI keyboard API
// struct _EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
// EFI_INPUT_RESET Reset;
// EFI_INPUT_READ_KEY ReadKeyStroke;
// EFI_EVENT WaitForKey;
// };
EFI_SIMPLE_TEXT_INPUT_PROTOCOL Txt;
// struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL {
// EFI_INPUT_RESET_EX Reset;
// EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx;
// EFI_EVENT WaitForKeyEx;
// EFI_SET_STATE SetState;
// EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify;
// EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify;
// }
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL TxtEx;
EFI_EVENT ExitBoot;
// Virtio device hook
VIRTIO_DEVICE_PROTOCOL *VirtIo;
@ -88,22 +70,13 @@ typedef struct {
// Timer event for checking input from VirtIo
EFI_EVENT PollTimer;
// List for notifications
LIST_ENTRY KeyNotifyList;
// Keyboard implementation
BOOLEAN HasKeyboard;
// Last pressed key
// typedef struct {
// UINT16 ScanCode;
// CHAR16 UnicodeChar;
// } EFI_INPUT_KEY;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL Txt;
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL TxtEx;
EFI_INPUT_KEY LastKey;
// Key modifiers
BOOLEAN KeyActive[MAX_KEYBOARD_CODE];
// If key is ready
LIST_ENTRY KeyNotifyList;
BOOLEAN KeyActive[MAX_KEYBOARD_CODE + 1]; // Key modifiers
BOOLEAN KeyReady;
} VIRTIO_INPUT_DEV;
@ -115,3 +88,45 @@ typedef struct {
// Bellow candidates to be included as Linux header
#define KEY_PRESSED 1
//
// VirtioInput.c
//
EFI_STATUS
VirtioInputConfigQuerySize (
IN VIRTIO_INPUT_DEV *Dev,
IN VIRTIO_INPUT_CONFIG_SELECT Select,
IN UINT8 Subsel,
OUT UINT8 *Size
);
VOID
EFIAPI
VirtioInputTimer (
IN EFI_EVENT Event,
IN VOID *Context
);
//
// VirtioKeyboard.c
//
BOOLEAN
VirtioKeyboardProbe (
IN VIRTIO_INPUT_DEV *Dev
);
VOID
VirtioKeyboardHandleEvent (
IN OUT VIRTIO_INPUT_DEV *Dev,
IN VIRTIO_INPUT_EVENT *Event
);
EFI_STATUS
VirtioKeyboardInit (
IN OUT VIRTIO_INPUT_DEV *Dev
);
VOID
VirtioKeyboardUninit (
IN OUT VIRTIO_INPUT_DEV *Dev
);

View file

@ -19,6 +19,7 @@
VirtioInput.c
VirtioInput.h
VirtioKeyCodes.h
VirtioKeyboard.c
[Packages]
MdePkg/MdePkg.dec

View file

@ -0,0 +1,632 @@
/** @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);
}