blob: fa2bf4a124b42e6ae003fc519e8ef717a0ae1877 [file] [log] [blame]
/** @file
This driver produces EFI_SIMPLE_TEXT_INPUT_PROTOCOL for virtarm devices.
Copyright (C) 2024, Red Hat, 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 <VirtioKeyboard.h>
#include <VirtioKeyCodes.h>
// -----------------------------------------------------------------------------
// Return buffer pointer out of the ring buffer
STATIC
VOID *
BufferPtr (
IN VIRTIO_KBD_RING *Ring,
IN UINT32 BufferNr
)
{
return Ring->Buffers + Ring->BufferSize * BufferNr;
}
// -----------------------------------------------------------------------------
// Return buffer physical address out of the ring buffer
STATIC
EFI_PHYSICAL_ADDRESS
BufferAddr (
IN VIRTIO_KBD_RING *Ring,
IN UINT32 BufferNr
)
{
return Ring->DeviceAddress + Ring->BufferSize * BufferNr;
}
// Return next buffer from ring
STATIC
UINT32
BufferNext (
IN VIRTIO_KBD_RING *Ring
)
{
return Ring->Indices.NextDescIdx % Ring->Ring.QueueSize;
}
// -----------------------------------------------------------------------------
// Push the buffer to the device
EFI_STATUS
EFIAPI
VirtioKeyboardRingSendBuffer (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index,
IN VOID *Data,
IN UINT32 DataSize,
IN BOOLEAN Notify
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
UINT32 BufferNr = BufferNext (Ring);
UINT16 Idx = *Ring->Ring.Avail.Idx;
UINT16 Flags = 0;
ASSERT (DataSize <= Ring->BufferSize);
if (Data) {
/* driver -> device */
CopyMem (BufferPtr (Ring, BufferNr), Data, DataSize);
} else {
/* device -> driver */
Flags |= VRING_DESC_F_WRITE;
}
VirtioAppendDesc (
&Ring->Ring,
BufferAddr (Ring, BufferNr),
DataSize,
Flags,
&Ring->Indices
);
Ring->Ring.Avail.Ring[Idx % Ring->Ring.QueueSize] =
Ring->Indices.HeadDescIdx % Ring->Ring.QueueSize;
Ring->Indices.HeadDescIdx = Ring->Indices.NextDescIdx;
Idx++;
// Force compiler to not optimize this code
MemoryFence ();
*Ring->Ring.Avail.Idx = Idx;
MemoryFence ();
if (Notify) {
Dev->VirtIo->SetQueueNotify (Dev->VirtIo, Index);
}
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// Look for buffer ready to be processed
BOOLEAN
EFIAPI
VirtioKeyboardRingHasBuffer (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
UINT16 UsedIdx = *Ring->Ring.Used.Idx;
if (!Ring->Ready) {
return FALSE;
}
if (Ring->LastUsedIdx == UsedIdx) {
return FALSE;
}
return TRUE;
}
// -----------------------------------------------------------------------------
// Get data from buffer which is marked as ready from device
BOOLEAN
EFIAPI
VirtioKeyboardRingGetBuffer (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index,
OUT VOID *Data,
OUT UINT32 *DataSize
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
UINT16 UsedIdx = *Ring->Ring.Used.Idx;
volatile VRING_USED_ELEM *UsedElem;
if (!Ring->Ready) {
return FALSE;
}
if (Ring->LastUsedIdx == UsedIdx) {
return FALSE;
}
UsedElem = Ring->Ring.Used.UsedElem + (Ring->LastUsedIdx % Ring->Ring.QueueSize);
if (UsedElem->Len > Ring->BufferSize) {
DEBUG ((DEBUG_ERROR, "%a:%d: %d: invalid length\n", __func__, __LINE__, Index));
UsedElem->Len = 0;
}
if (Data && DataSize) {
CopyMem (Data, BufferPtr (Ring, UsedElem->Id), UsedElem->Len);
*DataSize = UsedElem->Len;
}
if (Index % 2 == 0) {
/* RX - re-queue buffer */
VirtioKeyboardRingSendBuffer (Dev, Index, NULL, Ring->BufferSize, FALSE);
}
Ring->LastUsedIdx++;
return TRUE;
}
// -----------------------------------------------------------------------------
// Initialize ring buffer
EFI_STATUS
EFIAPI
VirtioKeyboardInitRing (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index,
IN UINT32 BufferSize
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
EFI_STATUS Status;
UINT16 QueueSize;
UINT64 RingBaseShift;
//
// step 4b -- allocate request virtqueue
//
Status = Dev->VirtIo->SetQueueSel (Dev->VirtIo, Index);
if (EFI_ERROR (Status)) {
goto Failed;
}
Status = Dev->VirtIo->GetQueueNumMax (Dev->VirtIo, &QueueSize);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// VirtioKeyboard uses one descriptor
//
if (QueueSize < 1) {
Status = EFI_UNSUPPORTED;
goto Failed;
}
Status = VirtioRingInit (Dev->VirtIo, QueueSize, &Ring->Ring);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// If anything fails from here on, we must release the ring resources.
//
Status = VirtioRingMap (
Dev->VirtIo,
&Ring->Ring,
&RingBaseShift,
&Ring->RingMap
);
if (EFI_ERROR (Status)) {
goto ReleaseQueue;
}
//
// Additional steps for MMIO: align the queue appropriately, and set the
// size. If anything fails from here on, we must unmap the ring resources.
//
Status = Dev->VirtIo->SetQueueNum (Dev->VirtIo, QueueSize);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
Status = Dev->VirtIo->SetQueueAlign (Dev->VirtIo, EFI_PAGE_SIZE);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
//
// step 4c -- Report GPFN (guest-physical frame number) of queue.
//
Status = Dev->VirtIo->SetQueueAddress (
Dev->VirtIo,
&Ring->Ring,
RingBaseShift
);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
Ring->BufferCount = QueueSize;
Ring->BufferSize = BufferSize;
Ring->BufferPages = EFI_SIZE_TO_PAGES (Ring->BufferCount * Ring->BufferSize);
Status = Dev->VirtIo->AllocateSharedPages (Dev->VirtIo, Ring->BufferPages, (VOID **)&Ring->Buffers);
if (EFI_ERROR (Status)) {
goto UnmapQueue;
}
Status = VirtioMapAllBytesInSharedBuffer (
Dev->VirtIo,
VirtioOperationBusMasterCommonBuffer,
Ring->Buffers,
EFI_PAGES_TO_SIZE (Ring->BufferPages),
&Ring->DeviceAddress,
&Ring->BufferMap
);
if (EFI_ERROR (Status)) {
goto ReleasePages;
}
VirtioPrepare (&Ring->Ring, &Ring->Indices);
Ring->Ready = TRUE;
return EFI_SUCCESS;
ReleasePages:
Dev->VirtIo->FreeSharedPages (
Dev->VirtIo,
Ring->BufferPages,
Ring->Buffers
);
Ring->Buffers = NULL;
UnmapQueue:
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Ring->RingMap);
Ring->RingMap = NULL;
ReleaseQueue:
VirtioRingUninit (Dev->VirtIo, &Ring->Ring);
Failed:
return Status;
}
// -----------------------------------------------------------------------------
// Deinitialize ring buffer
VOID
EFIAPI
VirtioKeyboardUninitRing (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
if (Ring->BufferMap) {
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Ring->BufferMap);
Ring->BufferMap = NULL;
}
if (Ring->Buffers) {
Dev->VirtIo->FreeSharedPages (
Dev->VirtIo,
Ring->BufferPages,
Ring->Buffers
);
Ring->Buffers = NULL;
}
if (!Ring->RingMap) {
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Ring->RingMap);
Ring->RingMap = NULL;
}
if (Ring->Ring.Base) {
VirtioRingUninit (Dev->VirtIo, &Ring->Ring);
}
ZeroMem (Ring, sizeof (*Ring));
}
// -----------------------------------------------------------------------------
// Deinitialize all rings allocated in driver
STATIC
VOID
EFIAPI
VirtioKeyboardUninitAllRings (
IN OUT VIRTIO_KBD_DEV *Dev
)
{
UINT16 Index;
for (Index = 0; Index < KEYBOARD_MAX_RINGS; Index++) {
VirtioKeyboardUninitRing (Dev, Index);
}
}
// -----------------------------------------------------------------------------
// Mark all buffers as ready to write and push to device
VOID
EFIAPI
VirtioKeyboardRingFillRx (
IN OUT VIRTIO_KBD_DEV *Dev,
IN UINT16 Index
)
{
VIRTIO_KBD_RING *Ring = Dev->Rings + Index;
UINT32 BufferNr;
for (BufferNr = 0; BufferNr < Ring->BufferCount; BufferNr++) {
VirtioKeyboardRingSendBuffer (Dev, Index, NULL, Ring->BufferSize, FALSE);
}
Dev->VirtIo->SetQueueNotify (Dev->VirtIo, Index);
}
// Forward declaration of module Uninit function
STATIC
VOID
EFIAPI
VirtioKeyboardUninit (
IN OUT VIRTIO_KBD_DEV *Dev
);
// Forward declaration of module Init function
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardInit (
IN OUT VIRTIO_KBD_DEV *Dev
);
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardSimpleTextInputReset (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN BOOLEAN ExtendedVerification
)
{
VIRTIO_KBD_DEV *Dev;
Dev = VIRTIO_KEYBOARD_FROM_THIS (This);
VirtioKeyboardUninit (Dev);
VirtioKeyboardInit (Dev);
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardSimpleTextInputReadKeyStroke (
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
OUT EFI_INPUT_KEY *Key
)
{
VIRTIO_KBD_DEV *Dev;
EFI_TPL OldTpl;
if (Key == NULL) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_KEYBOARD_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
VirtioKeyboardConvertKeyCode (
IN OUT VIRTIO_KBD_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 keyboard events
STATIC
VOID
EFIAPI
VirtioKeyboardGetDeviceData (
IN OUT VIRTIO_KBD_DEV *Dev
)
{
BOOLEAN HasData;
UINT8 Data[KEYBOARD_RX_BUFSIZE + 1];
UINT32 DataSize;
VIRTIO_KBD_EVENT Event;
EFI_TPL OldTpl;
for ( ; ; ) {
HasData = VirtioKeyboardRingGetBuffer (Dev, 0, Data, &DataSize);
// Exit if no new data
if (!HasData) {
return;
}
if (DataSize < sizeof (Event)) {
continue;
}
// Clearing last character is not needed as it will be overwritten anyway
// Dev->LastKey.ScanCode = SCAN_NULL;
// Dev->LastKey.UnicodeChar = CHAR_NULL;
CopyMem (&Event, Data, sizeof (Event));
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
switch (Event.Type) {
case EV_SYN:
// Sync event received
break;
case EV_KEY:
// Key press event received
// 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
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;
}
break;
default:
DEBUG ((DEBUG_INFO, "%a: Unhandled VirtIo event\n", __func__));
break;
}
gBS->RestoreTPL (OldTpl);
}
}
// -----------------------------------------------------------------------------
// Callback hook for timer interrupt
STATIC
VOID
EFIAPI
VirtioKeyboardTimer (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VIRTIO_KBD_DEV *Dev = Context;
VirtioKeyboardGetDeviceData (Dev);
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API
VOID
EFIAPI
VirtioKeyboardWaitForKey (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VIRTIO_KBD_DEV *Dev = VIRTIO_KEYBOARD_FROM_THIS (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
VirtioKeyboardTimer (NULL, Dev);
// If there is a new key ready - send signal
if (Dev->KeyReady) {
gBS->SignalEvent (Event);
}
}
/// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardResetEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN BOOLEAN ExtendedVerification
)
{
VIRTIO_KBD_DEV *Dev;
EFI_STATUS Status;
EFI_TPL OldTpl;
Dev = VIRTIO_KEYBOARD_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;
}
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardReadKeyStrokeEx (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
OUT EFI_KEY_DATA *KeyData
)
{
VIRTIO_KBD_DEV *Dev;
EFI_STATUS Status;
EFI_INPUT_KEY Key;
EFI_KEY_STATE KeyState;
if (KeyData == NULL) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_KEYBOARD_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
VOID
EFIAPI
VirtioKeyboardWaitForKeyEx (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VIRTIO_KBD_DEV *Dev;
Dev = VIRTIO_KEYBOARD_EX_FROM_THIS (Context);
VirtioKeyboardWaitForKey (Event, &Dev->Txt);
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardSetState (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN EFI_KEY_TOGGLE_STATE *KeyToggleState
)
{
if (KeyToggleState == NULL) {
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
BOOLEAN
IsKeyRegistered (
IN EFI_KEY_DATA *RegsiteredData,
IN EFI_KEY_DATA *InputData
)
{
ASSERT (RegsiteredData != NULL && InputData != NULL);
if ((RegsiteredData->Key.ScanCode != InputData->Key.ScanCode) ||
(RegsiteredData->Key.UnicodeChar != InputData->Key.UnicodeChar))
{
return FALSE;
}
//
// Assume KeyShiftState/KeyToggleState = 0 in Registered key data means
// these state could be ignored.
//
if ((RegsiteredData->KeyState.KeyShiftState != 0) &&
(RegsiteredData->KeyState.KeyShiftState != InputData->KeyState.KeyShiftState))
{
return FALSE;
}
if ((RegsiteredData->KeyState.KeyToggleState != 0) &&
(RegsiteredData->KeyState.KeyToggleState != InputData->KeyState.KeyToggleState))
{
return FALSE;
}
return TRUE;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardRegisterKeyNotify (
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_KBD_DEV *Dev;
EFI_TPL OldTpl;
LIST_ENTRY *Link;
VIRTIO_KBD_IN_EX_NOTIFY *NewNotify;
VIRTIO_KBD_IN_EX_NOTIFY *CurrentNotify;
if ((KeyData == NULL) ||
(NotifyHandle == NULL) ||
(KeyNotificationFunction == NULL))
{
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_KEYBOARD_EX_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
// Check if the (KeyData, NotificationFunction) pair is already registered.
for (Link = Dev->NotifyList.ForwardLink;
Link != &Dev->NotifyList;
Link = Link->ForwardLink)
{
CurrentNotify = CR (
Link,
VIRTIO_KBD_IN_EX_NOTIFY,
NotifyEntry,
VIRTIO_KBD_SIG
);
if (IsKeyRegistered (&CurrentNotify->KeyData, KeyData)) {
if (CurrentNotify->KeyNotificationFn == KeyNotificationFunction) {
*NotifyHandle = CurrentNotify;
Status = EFI_SUCCESS;
goto Exit;
}
}
}
NewNotify = (VIRTIO_KBD_IN_EX_NOTIFY *)AllocateZeroPool (sizeof (VIRTIO_KBD_IN_EX_NOTIFY));
if (NewNotify == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
NewNotify->Signature = VIRTIO_KBD_SIG;
NewNotify->KeyNotificationFn = KeyNotificationFunction;
CopyMem (&NewNotify->KeyData, KeyData, sizeof (EFI_KEY_DATA));
InsertTailList (&Dev->NotifyList, &NewNotify->NotifyEntry);
*NotifyHandle = NewNotify;
Status = EFI_SUCCESS;
Exit:
gBS->RestoreTPL (OldTpl);
return Status;
}
// -----------------------------------------------------------------------------
// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API
EFI_STATUS
EFIAPI
VirtioKeyboardUnregisterKeyNotify (
IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
IN VOID *NotificationHandle
)
{
EFI_STATUS Status;
VIRTIO_KBD_DEV *Dev;
EFI_TPL OldTpl;
LIST_ENTRY *Link;
VIRTIO_KBD_IN_EX_NOTIFY *CurrentNotify;
if (NotificationHandle == NULL) {
return EFI_INVALID_PARAMETER;
}
if (((VIRTIO_KBD_IN_EX_NOTIFY *)NotificationHandle)->Signature != VIRTIO_KBD_SIG) {
return EFI_INVALID_PARAMETER;
}
Dev = VIRTIO_KEYBOARD_EX_FROM_THIS (This);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
for (Link = Dev->NotifyList.ForwardLink;
Link != &Dev->NotifyList;
Link = Link->ForwardLink)
{
CurrentNotify = CR (
Link,
VIRTIO_KBD_IN_EX_NOTIFY,
NotifyEntry,
VIRTIO_KBD_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
EFI_STATUS
EFIAPI
VirtioKeyboardInit (
IN OUT VIRTIO_KBD_DEV *Dev
)
{
UINT8 NextDevStat;
EFI_STATUS Status;
UINT64 Features;
//
// Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence.
//
NextDevStat = 0; // step 1 -- reset device
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// Set Page Size - MMIO VirtIo Specific
//
Status = Dev->VirtIo->SetPageSize (Dev->VirtIo, EFI_PAGE_SIZE);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// step 4a -- retrieve and validate features
//
Status = Dev->VirtIo->GetDeviceFeatures (Dev->VirtIo, &Features);
if (EFI_ERROR (Status)) {
goto Failed;
}
Features &= VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM;
//
// In virtio-1.0, feature negotiation is expected to complete before queue
// discovery, and the device can also reject the selected set of features.
//
if (Dev->VirtIo->Revision >= VIRTIO_SPEC_REVISION (1, 0, 0)) {
Status = Virtio10WriteFeatures (Dev->VirtIo, Features, &NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
}
Status = VirtioKeyboardInitRing (Dev, 0, KEYBOARD_RX_BUFSIZE);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// step 5 -- Report understood features and guest-tuneables.
//
if (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) {
Features &= ~(UINT64)(VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM);
Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, Features);
if (EFI_ERROR (Status)) {
goto Failed;
}
}
//
// step 6 -- initialization complete
//
NextDevStat |= VSTAT_DRIVER_OK;
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// populate the exported interface's attributes
//
// struct _EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
// EFI_INPUT_RESET Reset;
// EFI_INPUT_READ_KEY ReadKeyStroke;
// EFI_EVENT WaitForKey;
// };
Dev->Txt.Reset = (EFI_INPUT_RESET)VirtioKeyboardSimpleTextInputReset;
Dev->Txt.ReadKeyStroke = VirtioKeyboardSimpleTextInputReadKeyStroke;
Dev->Txt.WaitForKey = (EFI_EVENT)VirtioKeyboardWaitForKey;
// 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;
// }
Dev->TxtEx.Reset = (EFI_INPUT_RESET_EX)VirtioKeyboardResetEx;
Dev->TxtEx.ReadKeyStrokeEx = VirtioKeyboardReadKeyStrokeEx;
Dev->TxtEx.SetState = VirtioKeyboardSetState;
Dev->TxtEx.RegisterKeyNotify = VirtioKeyboardRegisterKeyNotify;
Dev->TxtEx.UnregisterKeyNotify = VirtioKeyboardUnregisterKeyNotify;
InitializeListHead (&Dev->NotifyList);
//
// Setup the WaitForKey event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioKeyboardWaitForKey,
&(Dev->Txt),
&((Dev->Txt).WaitForKey)
);
if (EFI_ERROR (Status)) {
goto Failed;
}
//
// Setup the WaitForKeyEx event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_WAIT,
TPL_NOTIFY,
VirtioKeyboardWaitForKeyEx,
&(Dev->TxtEx),
&((Dev->TxtEx).WaitForKeyEx)
);
if (EFI_ERROR (Status)) {
goto Failed;
}
VirtioKeyboardRingFillRx (Dev, 0);
//
// Event for reading key in time intervals
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
VirtioKeyboardTimer,
Dev,
&Dev->KeyReadTimer
);
if (EFI_ERROR (Status)) {
goto Failed;
}
Status = gBS->SetTimer (
Dev->KeyReadTimer,
TimerPeriodic,
EFI_TIMER_PERIOD_MILLISECONDS (KEYBOARD_PROBE_TIME_MS)
);
if (EFI_ERROR (Status)) {
goto Failed;
}
return EFI_SUCCESS;
Failed:
VirtioKeyboardUninitAllRings (Dev);
// VirtualKeyboardFreeNotifyList (&VirtualKeyboardPrivate->NotifyList);
//
// Notify the host about our failure to setup: virtio-0.9.5, 2.2.2.1 Device
// Status. VirtIo access failure here should not mask the original error.
//
NextDevStat |= VSTAT_FAILED;
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
return Status; // reached only via Failed above
}
// -----------------------------------------------------------------------------
// Deinitialize driver
STATIC
VOID
EFIAPI
VirtioKeyboardUninit (
IN OUT VIRTIO_KBD_DEV *Dev
)
{
gBS->CloseEvent (Dev->KeyReadTimer);
//
// Reset the virtual device -- see virtio-0.9.5, 2.2.2.1 Device Status. When
// VIRTIO_CFG_WRITE() returns, the host will have learned to stay away from
// the old comms area.
//
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0);
VirtioKeyboardUninitAllRings (Dev);
}
// -----------------------------------------------------------------------------
// Handle device exit before switch to boot
STATIC
VOID
EFIAPI
VirtioKeyboardExitBoot (
IN EFI_EVENT Event,
IN VOID *Context
)
{
VIRTIO_KBD_DEV *Dev;
DEBUG ((DEBUG_INFO, "%a: Context=0x%p\n", __func__, Context));
//
// Reset the device. This causes the hypervisor to forget about the virtio
// ring.
//
// We allocated said ring in EfiBootServicesData type memory, and code
// executing after ExitBootServices() is permitted to overwrite it.
//
Dev = Context;
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0);
}
// -----------------------------------------------------------------------------
// Binding validation function
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE DeviceHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
VIRTIO_DEVICE_PROTOCOL *VirtIo;
//
// Attempt to open the device with the VirtIo set of interfaces. On success,
// the protocol is "instantiated" for the VirtIo device. Covers duplicate
// open attempts (EFI_ALREADY_STARTED).
//
Status = gBS->OpenProtocol (
DeviceHandle, // candidate device
&gVirtioDeviceProtocolGuid, // for generic VirtIo access
(VOID **)&VirtIo, // handle to instantiate
This->DriverBindingHandle, // requestor driver identity
DeviceHandle, // ControllerHandle, according to
// the UEFI Driver Model
EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive VirtIo access to
// the device; to be released
);
if (EFI_ERROR (Status)) {
if (Status != EFI_UNSUPPORTED) {
DEBUG ((DEBUG_INFO, "%a:%d: %r\n", __func__, __LINE__, Status));
}
return Status;
}
DEBUG ((DEBUG_INFO, "%a:%d: 0x%x\n", __func__, __LINE__, VirtIo->SubSystemDeviceId));
if (VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_INPUT) {
Status = EFI_UNSUPPORTED;
}
//
// We needed VirtIo access only transitorily, to see whether we support the
// device or not.
//
gBS->CloseProtocol (
DeviceHandle,
&gVirtioDeviceProtocolGuid,
This->DriverBindingHandle,
DeviceHandle
);
return Status;
}
// -----------------------------------------------------------------------------
// Driver binding function API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE DeviceHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
VIRTIO_KBD_DEV *Dev;
EFI_STATUS Status;
Dev = (VIRTIO_KBD_DEV *)AllocateZeroPool (sizeof *Dev);
if (Dev == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gBS->OpenProtocol (
DeviceHandle,
&gVirtioDeviceProtocolGuid,
(VOID **)&Dev->VirtIo,
This->DriverBindingHandle,
DeviceHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
goto FreeVirtioKbd;
}
//
// VirtIo access granted, configure virtio keyboard device.
//
Status = VirtioKeyboardInit (Dev);
if (EFI_ERROR (Status)) {
goto CloseVirtIo;
}
Status = gBS->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_CALLBACK,
&VirtioKeyboardExitBoot,
Dev,
&Dev->ExitBoot
);
if (EFI_ERROR (Status)) {
goto UninitDev;
}
//
// Setup complete, attempt to export the driver instance's EFI_SIMPLE_TEXT_INPUT_PROTOCOL
// interface.
//
Dev->Signature = VIRTIO_KBD_SIG;
Status = gBS->InstallMultipleProtocolInterfaces (
&DeviceHandle,
&gEfiSimpleTextInProtocolGuid,
&Dev->Txt,
&gEfiSimpleTextInputExProtocolGuid,
&Dev->TxtEx,
NULL
);
if (EFI_ERROR (Status)) {
goto CloseExitBoot;
}
return EFI_SUCCESS;
CloseExitBoot:
gBS->CloseEvent (Dev->ExitBoot);
UninitDev:
VirtioKeyboardUninit (Dev);
CloseVirtIo:
gBS->CloseProtocol (
DeviceHandle,
&gVirtioDeviceProtocolGuid,
This->DriverBindingHandle,
DeviceHandle
);
FreeVirtioKbd:
FreePool (Dev);
return Status;
}
// -----------------------------------------------------------------------------
// Driver unbinding function API
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE DeviceHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *Txt;
VIRTIO_KBD_DEV *Dev;
Status = gBS->OpenProtocol (
DeviceHandle, // candidate device
&gEfiSimpleTextInProtocolGuid, // retrieve the RNG iface
(VOID **)&Txt, // target pointer
This->DriverBindingHandle, // requestor driver ident.
DeviceHandle, // lookup req. for dev.
EFI_OPEN_PROTOCOL_GET_PROTOCOL // lookup only, no new ref.
);
if (EFI_ERROR (Status)) {
return Status;
}
Dev = VIRTIO_KEYBOARD_FROM_THIS (Txt);
//
// Handle Stop() requests for in-use driver instances gracefully.
//
Status = gBS->UninstallMultipleProtocolInterfaces (
&DeviceHandle,
&gEfiSimpleTextInProtocolGuid,
&Dev->Txt,
&gEfiSimpleTextInputExProtocolGuid,
&Dev->TxtEx,
NULL
);
if (EFI_ERROR (Status)) {
return Status;
}
gBS->CloseEvent (Dev->ExitBoot);
VirtioKeyboardUninit (Dev);
gBS->CloseProtocol (
DeviceHandle,
&gVirtioDeviceProtocolGuid,
This->DriverBindingHandle,
DeviceHandle
);
FreePool (Dev);
return EFI_SUCCESS;
}
// -----------------------------------------------------------------------------
// Forward declaration of global variable
STATIC
EFI_COMPONENT_NAME_PROTOCOL gComponentName;
// -----------------------------------------------------------------------------
// Driver name to be displayed
STATIC
EFI_UNICODE_STRING_TABLE mDriverNameTable[] = {
{ "eng;en", L"Virtio Keyboard Driver" },
{ NULL, NULL }
};
// -----------------------------------------------------------------------------
// Driver name lookup function
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardGetDriverName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN CHAR8 *Language,
OUT CHAR16 **DriverName
)
{
return LookupUnicodeString2 (
Language,
This->SupportedLanguages,
mDriverNameTable,
DriverName,
(BOOLEAN)(This == &gComponentName) // Iso639Language
);
}
// -----------------------------------------------------------------------------
// Device name to be displayed
STATIC
EFI_UNICODE_STRING_TABLE mDeviceNameTable[] = {
{ "eng;en", L"RHEL virtio virtual keyboard BOB (Basic Operation Board)" },
{ NULL, NULL }
};
// -----------------------------------------------------------------------------
STATIC
EFI_COMPONENT_NAME_PROTOCOL gDeviceName;
// -----------------------------------------------------------------------------
STATIC
EFI_STATUS
EFIAPI
VirtioKeyboardGetDeviceName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN EFI_HANDLE DeviceHandle,
IN EFI_HANDLE ChildHandle,
IN CHAR8 *Language,
OUT CHAR16 **ControllerName
)
{
return LookupUnicodeString2 (
Language,
This->SupportedLanguages,
mDeviceNameTable,
ControllerName,
(BOOLEAN)(This == &gDeviceName) // Iso639Language
);
}
// -----------------------------------------------------------------------------
// General driver UEFI interface for showing driver name
STATIC
EFI_COMPONENT_NAME_PROTOCOL gComponentName = {
&VirtioKeyboardGetDriverName,
&VirtioKeyboardGetDeviceName,
"eng" // SupportedLanguages, ISO 639-2 language codes
};
// -----------------------------------------------------------------------------
// General driver UEFI interface for showing driver name
STATIC
EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = {
(EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&VirtioKeyboardGetDriverName,
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&VirtioKeyboardGetDeviceName,
"en" // SupportedLanguages, RFC 4646 language codes
};
// -----------------------------------------------------------------------------
// General driver UEFI interface for loading / unloading driver
STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = {
&VirtioKeyboardBindingSupported,
&VirtioKeyboardBindingStart,
&VirtioKeyboardBindingStop,
0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers
NULL, // ImageHandle, to be overwritten by
// EfiLibInstallDriverBindingComponentName2() in VirtioKeyboardEntryPoint()
NULL // DriverBindingHandle, ditto
};
// -----------------------------------------------------------------------------
// Driver entry point set in INF file, registers all driver functions into UEFI
EFI_STATUS
EFIAPI
VirtioKeyboardEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
DEBUG ((DEBUG_INFO, "Virtio keyboard has been loaded.......................\n"));
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gDriverBinding,
ImageHandle,
&gComponentName,
&gComponentName2
);
}