diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc index e7fff78df94fb..82f504e294c20 100644 --- a/OvmfPkg/OvmfPkgIa32X64.dsc +++ b/OvmfPkg/OvmfPkgIa32X64.dsc @@ -801,6 +801,7 @@ OvmfPkg/VirtioBlkDxe/VirtioBlk.inf OvmfPkg/VirtioScsiDxe/VirtioScsi.inf OvmfPkg/VirtioSerialDxe/VirtioSerial.inf + OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf !if $(PVSCSI_ENABLE) == TRUE OvmfPkg/PvScsiDxe/PvScsiDxe.inf !endif diff --git a/OvmfPkg/OvmfPkgIa32X64.fdf b/OvmfPkg/OvmfPkgIa32X64.fdf index 98c05e491dee9..605d9cd47afa0 100644 --- a/OvmfPkg/OvmfPkgIa32X64.fdf +++ b/OvmfPkg/OvmfPkgIa32X64.fdf @@ -234,6 +234,7 @@ INF OvmfPkg/Virtio10Dxe/Virtio10.inf INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf INF OvmfPkg/VirtioSerialDxe/VirtioSerial.inf +INF OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf !if $(PVSCSI_ENABLE) == TRUE INF OvmfPkg/PvScsiDxe/PvScsiDxe.inf !endif diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc index 556984bdaadbb..678f43a75b946 100644 --- a/OvmfPkg/OvmfPkgX64.dsc +++ b/OvmfPkg/OvmfPkgX64.dsc @@ -866,6 +866,7 @@ OvmfPkg/VirtioBlkDxe/VirtioBlk.inf OvmfPkg/VirtioScsiDxe/VirtioScsi.inf OvmfPkg/VirtioSerialDxe/VirtioSerial.inf + OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf !if $(PVSCSI_ENABLE) == TRUE OvmfPkg/PvScsiDxe/PvScsiDxe.inf !endif diff --git a/OvmfPkg/OvmfPkgX64.fdf b/OvmfPkg/OvmfPkgX64.fdf index 489d03b60e727..3745a6fd1e386 100644 --- a/OvmfPkg/OvmfPkgX64.fdf +++ b/OvmfPkg/OvmfPkgX64.fdf @@ -265,6 +265,7 @@ INF OvmfPkg/Virtio10Dxe/Virtio10.inf INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf INF OvmfPkg/VirtioSerialDxe/VirtioSerial.inf +INF OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf !if $(PVSCSI_ENABLE) == TRUE INF OvmfPkg/PvScsiDxe/PvScsiDxe.inf !endif diff --git a/OvmfPkg/VirtioKeyboardDxe/VirtioKeyCodes.h b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyCodes.h new file mode 100644 index 0000000000000..457911c268aed --- /dev/null +++ b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyCodes.h @@ -0,0 +1,294 @@ +/** @file + + Key codes definitions for the VirtioKeyboard driver + + This is a fork of common Linux key codes: + https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h + + Copyright (C) 2024, Red Hat + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VIRTIO_KEYBOARD_KEY_CODES_H_ +#define _VIRTIO_KEYBOARD_KEY_CODES_H_ + +/* + * Event types + */ +#define EV_SYN 0x00 +#define EV_KEY 0x01 + +/* + * Keys and buttons + */ +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 +#define KEY_SCALE 120 /* AL Compiz Scale (Expose) */ + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_ROTATE_DISPLAY 153 /* Display orientation for e.g. tablets */ +#define KEY_DIRECTION KEY_ROTATE_DISPLAY +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_ALL_APPLICATIONS 204 /* AC Desktop Show All Applications */ +#define KEY_DASHBOARD KEY_ALL_APPLICATIONS +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_AUTO 244 /* Set Auto Brightness: manual + brightness control is off, + rely on ambient */ +#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WWAN 246 /* Wireless WAN (LTE, UMTS, GSM, etc.) */ +#define KEY_WIMAX KEY_WWAN +#define KEY_RFKILL 247 /* Key that controls all radios */ + +#define KEY_MICMUTE 248 /* Mute / unmute the microphone */ + +/* Code 255 is reserved for special needs of AT keyboard driver */ + +#endif diff --git a/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.c b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.c new file mode 100644 index 0000000000000..f1df35582d840 --- /dev/null +++ b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.c @@ -0,0 +1,1560 @@ +/** @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 +#include +#include +#include +#include +#include + +#include +#include + +// ----------------------------------------------------------------------------- +// 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; + } + + // + // VirtioSerial 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 +#define MAX_KEYBOARD_CODE 255 + +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 + }; + + // 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__)); + Key->ScanCode = SCAN_NULL; + Key->UnicodeChar = CHAR_NULL; + return; + } + + // Handle F1 - F10 keys + if ((Code >= KEY_F1) && (Code <= KEY_F10)) { + Key->ScanCode = SCAN_F1 + (Code - KEY_F1); + Key->UnicodeChar = CHAR_NULL; + return; + } + + switch (Code) { + case KEY_PAGEUP: + Key->ScanCode = SCAN_PAGE_UP; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_PAGEDOWN: + Key->ScanCode = SCAN_PAGE_DOWN; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_HOME: + Key->ScanCode = SCAN_HOME; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_END: + Key->ScanCode = SCAN_END; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_DELETE: + Key->ScanCode = SCAN_DELETE; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_INSERT: + Key->ScanCode = SCAN_INSERT; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_UP: + Key->ScanCode = SCAN_UP; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_LEFT: + Key->ScanCode = SCAN_LEFT; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_RIGHT: + Key->ScanCode = SCAN_RIGHT; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_DOWN: + Key->ScanCode = SCAN_DOWN; + Key->UnicodeChar = CHAR_NULL; + break; + + case KEY_BACKSPACE: + Key->ScanCode = SCAN_NULL; + Key->UnicodeChar = CHAR_BACKSPACE; + break; + + case KEY_TAB: + Key->ScanCode = SCAN_NULL; + Key->UnicodeChar = CHAR_TAB; + break; + + case KEY_ENTER: + Key->ScanCode = SCAN_NULL; + // Key->UnicodeChar = CHAR_LINEFEED; + Key->UnicodeChar = CHAR_CARRIAGE_RETURN; + break; + + case KEY_ESC: + Key->ScanCode = SCAN_ESC; + Key->UnicodeChar = CHAR_NULL; + break; + + default: + if (Dev->ShiftActive) { + Key->ScanCode = MapShift[Code]; + Key->UnicodeChar = MapShift[Code]; + } else { + Key->ScanCode = Map[Code]; + Key->UnicodeChar = Map[Code]; + } + + if (Dev->CtrlActive) { + // Convert Ctrl+[a-z] and Ctrl+[A-Z] into [1-26] ASCII table entries + // Key->ScanCode = SCAN_NULL; + if ((Key->UnicodeChar >= L'a') && (Key->UnicodeChar <= L'z')) { + Key->UnicodeChar = (CHAR16)(Key->UnicodeChar - L'a' + 1); + } else if ((Key->UnicodeChar >= L'A') && (Key->UnicodeChar <= L'Z')) { + Key->UnicodeChar = (CHAR16)(Key->UnicodeChar - L'A' + 1); + } + } + + 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) { + // Check key modifiers first + if ((Event.Code == KEY_LEFTSHIFT) || (Event.Code == KEY_RIGHTSHIFT)) { + Dev->ShiftActive = TRUE; + // This is not visible character by itself + break; + } + + if ((Event.Code == KEY_LEFTCTRL) || (Event.Code == KEY_RIGHTCTRL)) { + Dev->CtrlActive = TRUE; + // This is not visible character by itself + break; + } + + if ((Event.Code == KEY_LEFTALT) || (Event.Code == KEY_RIGHTALT)) { + Dev->AltActive = TRUE; + // This is not visible character by itself + break; + } + + // Evaluate key + VirtioKeyboardConvertKeyCode (Dev, Event.Code, &Dev->LastKey); + + // Flag that printable character is ready to be send + Dev->KeyReady = TRUE; + } else { + // Check key modifiers first + if ((Event.Code == KEY_LEFTSHIFT) || (Event.Code == KEY_RIGHTSHIFT)) { + Dev->ShiftActive = FALSE; + // This is not visible character by itself + break; + } + + if ((Event.Code == KEY_LEFTCTRL) || (Event.Code == KEY_RIGHTCTRL)) { + Dev->CtrlActive = FALSE; + // This is not visible character by itself + break; + } + + if ((Event.Code == KEY_LEFTALT) || (Event.Code == KEY_RIGHTALT)) { + Dev->AltActive = FALSE; + // This is not visible character by itself + break; + } + } + + 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; + + if (Dev->ShiftActive) { + KeyState.KeyShiftState |= EFI_LEFT_SHIFT_PRESSED; + } + + if (Dev->CtrlActive) { + KeyState.KeyShiftState |= EFI_LEFT_CONTROL_PRESSED; + } + + if (Dev->AltActive) { + KeyState.KeyShiftState |= EFI_LEFT_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 = 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 + ); +} diff --git a/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.h b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.h new file mode 100644 index 0000000000000..adf946778ce36 --- /dev/null +++ b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.h @@ -0,0 +1,208 @@ +/** @file + + Private definitions of the VirtioKeyboard driver + + Copyright (C) 2024, Red Hat + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VIRTIO_KEYBOARD_DXE_H_ +#define _VIRTIO_KEYBOARD_DXE_H_ + +#include +#include +#include +#include + +#include + +#define VIRTIO_KBD_SIG SIGNATURE_32 ('V', 'K', 'B', 'D') + +#define KEYBOARD_MAX_RINGS 2 +#define KEYBOARD_RX_BUFSIZE 64 + +// Fetch new key from VirtIO every 50ms +#define KEYBOARD_PROBE_TIME_MS 50 + +typedef struct { + UINTN Signature; + EFI_KEY_DATA KeyData; + EFI_KEY_NOTIFY_FUNCTION KeyNotificationFn; + LIST_ENTRY NotifyEntry; +} VIRTIO_KBD_IN_EX_NOTIFY; + +// Data structure representing payload delivered from VirtIo +typedef struct { + UINT16 Type; + UINT16 Code; + UINT32 Value; +} VIRTIO_KBD_EVENT; + +// Data structure representing ring buffer +typedef struct { + VRING Ring; + VOID *RingMap; + DESC_INDICES Indices; /* Avail Ring */ + UINT16 LastUsedIdx; /* Used Ring */ + + UINT32 BufferSize; + UINT32 BufferCount; + UINT32 BufferPages; + UINT8 *Buffers; + VOID *BufferMap; + EFI_PHYSICAL_ADDRESS DeviceAddress; + + BOOLEAN Ready; +} VIRTIO_KBD_RING; + +// Declaration of data structure representing driver context +typedef struct { + // Device 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; + + // Virtio device hook + VIRTIO_DEVICE_PROTOCOL *VirtIo; + + // Hook for ring buffer + VIRTIO_KBD_RING Rings[KEYBOARD_MAX_RINGS]; + + // Timer event for checking key presses from VirtIo + EFI_EVENT KeyReadTimer; + + // List for notifications + LIST_ENTRY NotifyList; + EFI_EVENT KeyNotifyTimer; + + // Last pressed key + // typedef struct { + // UINT16 ScanCode; + // CHAR16 UnicodeChar; + // } EFI_INPUT_KEY; + EFI_INPUT_KEY LastKey; + + // Key modifiers + BOOLEAN ShiftActive; + BOOLEAN CtrlActive; + BOOLEAN AltActive; + + // If key is ready + BOOLEAN KeyReady; +} VIRTIO_KBD_DEV; + +// Helper functions to extract VIRTIO_KBD_DEV structure pointers +#define VIRTIO_KEYBOARD_FROM_THIS(KbrPointer) \ + CR (KbrPointer, VIRTIO_KBD_DEV, Txt, VIRTIO_KBD_SIG) +#define VIRTIO_KEYBOARD_EX_FROM_THIS(KbrPointer) \ + CR (KbrPointer, VIRTIO_KBD_DEV, TxtEx, VIRTIO_KBD_SIG) + +// Bellow candidates to be included as Linux header +#define KEY_PRESSED 1 + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API +EFI_STATUS +EFIAPI +VirtioKeyboardSimpleTextInputReset ( + IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ); + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API +EFI_STATUS +EFIAPI +VirtioKeyboardSimpleTextInputReadKeyStroke ( + IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, + OUT EFI_INPUT_KEY *Key + ); + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_PROTOCOL API +VOID +EFIAPI +VirtioKeyboardWaitForKey ( + IN EFI_EVENT Event, + IN VOID *Context + ); + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API +EFI_STATUS +EFIAPI +VirtioKeyboardResetEx ( + IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ); + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API +EFI_STATUS +EFIAPI +VirtioKeyboardReadKeyStrokeEx ( + IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + OUT EFI_KEY_DATA *KeyData + ); + +// ----------------------------------------------------------------------------- +// EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL API +VOID +EFIAPI +VirtioKeyboardWaitForKeyEx ( + IN EFI_EVENT Event, + IN VOID *Context + ); + +// ----------------------------------------------------------------------------- +// 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 + ); + +// ----------------------------------------------------------------------------- +// 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_SIMPLE_TEXT_INPUT_EX_PROTOCOL API +EFI_STATUS +EFIAPI +VirtioKeyboardUnregisterKeyNotify ( + IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + IN VOID *NotificationHandle + ); + +#endif diff --git a/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf new file mode 100644 index 0000000000000..6c35f2a4cef64 --- /dev/null +++ b/OvmfPkg/VirtioKeyboardDxe/VirtioKeyboard.inf @@ -0,0 +1,41 @@ +## @file +# This driver produces EFI_SIMPLE_TEXT_INPUT_PROTOCOL for virt ARM devices. +# +# Copyright (C) 2024, Red Hat +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 1.29 + BASE_NAME = VirtioKeyboardDxe + FILE_GUID = F141B1E5-9C7C-44CC-AFAA-E87D7689B113 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = VirtioKeyboardEntryPoint + +[Sources] + VirtioKeyboard.c + VirtioKeyboard.h + VirtioKeyCodes.h + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + VirtioLib + +# Protocols used by this driver +[Protocols] + #gEfiDriverBindingProtocolGuid + gEfiSimpleTextInProtocolGuid + gEfiSimpleTextInputExProtocolGuid + gVirtioDeviceProtocolGuid