diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerial.c b/OvmfPkg/VirtioSerialDxe/VirtioSerial.c new file mode 100644 index 0000000000..bfb2b324ea --- /dev/null +++ b/OvmfPkg/VirtioSerialDxe/VirtioSerial.c @@ -0,0 +1,808 @@ +/** @file + + Driver for virtio-serial devices. + + The virtio serial device also known as virtio console device because + initially it had only support for a single tty, intended to be used + as console. Support for multiple streams and named data ports has + been added later on. + + https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-2900003 + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include +#include + +#include "VirtioSerial.h" + +STATIC LIST_ENTRY mVirtioSerialList; + +STATIC CONST CHAR8 *EventNames[] = { + [VIRTIO_SERIAL_DEVICE_READY] = "device-ready", + [VIRTIO_SERIAL_DEVICE_ADD] = "device-add", + [VIRTIO_SERIAL_DEVICE_REMOVE] = "device-remove", + [VIRTIO_SERIAL_PORT_READY] = "port-ready", + [VIRTIO_SERIAL_CONSOLE_PORT] = "console-port", + [VIRTIO_SERIAL_RESIZE] = "resize", + [VIRTIO_SERIAL_PORT_OPEN] = "port-open", + [VIRTIO_SERIAL_PORT_NAME] = "port-name", +}; + +VOID +EFIAPI +LogDevicePath ( + UINT32 Level, + const CHAR8 *Func, + CHAR16 *Note, + EFI_DEVICE_PATH_PROTOCOL *DevicePath + ) +{ + CHAR16 *Str; + + Str = ConvertDevicePathToText (DevicePath, FALSE, FALSE); + if (!Str) { + DEBUG ((DEBUG_INFO, "ConvertDevicePathToText failed\n")); + return; + } + + DEBUG ((Level, "%a: %s%s%s\n", Func, Note ? Note : L"", Note ? L": " : L"", Str)); + FreePool (Str); +} + +EFI_STATUS +EFIAPI +VirtioSerialTxControl ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 Id, + IN UINT16 Event, + IN UINT16 Value + ) +{ + VIRTIO_SERIAL_CONTROL Control = { + .Id = Id, + .Event = Event, + .Value = Value, + }; + + DEBUG (( + DEBUG_INFO, + "%a:%d: >>> event %a, port-id %d, value %d\n", + __func__, + __LINE__, + EventNames[Control.Event], + Control.Id, + Control.Value + )); + + VirtioSerialRingClearTx (Dev, VIRTIO_SERIAL_Q_TX_CTRL); + return VirtioSerialRingSendBuffer (Dev, VIRTIO_SERIAL_Q_TX_CTRL, &Control, sizeof (Control), TRUE); +} + +STATIC +VOID +EFIAPI +VirtioSerialRxControl ( + IN OUT VIRTIO_SERIAL_DEV *Dev + ) +{ + UINT8 Data[CTRL_RX_BUFSIZE+1]; + UINT32 DataSize; + VIRTIO_SERIAL_CONTROL Control; + EFI_STATUS Status; + BOOLEAN HasData; + UINT16 Ready; + + for ( ; ;) { + HasData = VirtioSerialRingGetBuffer (Dev, VIRTIO_SERIAL_Q_RX_CTRL, Data, &DataSize); + if (!HasData) { + return; + } + + if (DataSize < sizeof (Control)) { + DEBUG (( + DEBUG_ERROR, + "%a:%d: length mismatch: %d != %d\n", + __func__, + __LINE__, + DataSize, + sizeof (Control) + )); + continue; + } + + CopyMem (&Control, Data, sizeof (Control)); + + if (Control.Event < ARRAY_SIZE (EventNames)) { + DEBUG (( + DEBUG_INFO, + "%a:%d: <<< event %a, port-id %d, value %d\n", + __func__, + __LINE__, + EventNames[Control.Event], + Control.Id, + Control.Value + )); + } else { + DEBUG (( + DEBUG_ERROR, + "%a:%d: unknown event: %d\n", + __func__, + __LINE__, + Control.Event + )); + } + + switch (Control.Event) { + case VIRTIO_SERIAL_DEVICE_ADD: + if (Control.Id < MAX_PORTS) { + Status = VirtioSerialPortAdd (Dev, Control.Id); + Ready = (Status == EFI_SUCCESS) ? 1 : 0; + } else { + Ready = 0; + } + + VirtioSerialTxControl (Dev, Control.Id, VIRTIO_SERIAL_PORT_READY, Ready); + if (Ready) { + Dev->NumPorts++; + } + + break; + case VIRTIO_SERIAL_DEVICE_REMOVE: + if (Control.Id < MAX_PORTS) { + VirtioSerialPortRemove (Dev, Control.Id); + } + + break; + case VIRTIO_SERIAL_CONSOLE_PORT: + if (Control.Id < MAX_PORTS) { + VirtioSerialPortSetConsole (Dev, Control.Id); + Dev->NumConsoles++; + } + + break; + case VIRTIO_SERIAL_PORT_NAME: + if (Control.Id < MAX_PORTS) { + Data[DataSize] = 0; + VirtioSerialPortSetName (Dev, Control.Id, Data + sizeof (Control)); + Dev->NumNamedPorts++; + } + + break; + case VIRTIO_SERIAL_PORT_OPEN: + if (Control.Id < MAX_PORTS) { + VirtioSerialPortSetDeviceOpen (Dev, Control.Id, Control.Value); + } + + break; + default: + break; + } + } +} + +STATIC +VOID +EFIAPI +VirtioSerialTimer ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + VIRTIO_SERIAL_DEV *Dev = Context; + + VirtioSerialRxControl (Dev); +} + +STATIC +VOID +EFIAPI +VirtioSerialUninitAllRings ( + IN OUT VIRTIO_SERIAL_DEV *Dev + ) +{ + UINT16 Index; + + for (Index = 0; Index < MAX_RINGS; Index++) { + VirtioSerialUninitRing (Dev, Index); + } +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialInit ( + IN OUT VIRTIO_SERIAL_DEV *Dev + ) +{ + UINT8 NextDevStat; + EFI_STATUS Status; + UINT64 Features; + UINTN Retries; + + // + // 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 | + VIRTIO_SERIAL_F_MULTIPORT); + + // + // 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; + } + } + + DEBUG (( + DEBUG_INFO, + "%a:%d: features ok:%a%a%a\n", + __func__, + __LINE__, + (Features & VIRTIO_F_VERSION_1) ? " v1.0" : "", + (Features & VIRTIO_F_IOMMU_PLATFORM) ? " iommu" : "", + (Features & VIRTIO_SERIAL_F_MULTIPORT) ? " multiport" : "" + )); + + if (Features & VIRTIO_SERIAL_F_MULTIPORT) { + Dev->VirtIo->ReadDevice ( + Dev->VirtIo, + OFFSET_OF (VIRTIO_SERIAL_CONFIG, MaxPorts), + sizeof (Dev->Config.MaxPorts), + sizeof (Dev->Config.MaxPorts), + &Dev->Config.MaxPorts + ); + DEBUG (( + DEBUG_INFO, + "%a:%d: max device ports: %d\n", + __func__, + __LINE__, + Dev->Config.MaxPorts + )); + } + + Status = VirtioSerialInitRing (Dev, VIRTIO_SERIAL_Q_RX_CTRL, CTRL_RX_BUFSIZE); + if (EFI_ERROR (Status)) { + goto Failed; + } + + Status = VirtioSerialInitRing (Dev, VIRTIO_SERIAL_Q_TX_CTRL, CTRL_TX_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; + } + + VirtioSerialRingFillRx (Dev, VIRTIO_SERIAL_Q_RX_CTRL); + VirtioSerialTxControl (Dev, 0, VIRTIO_SERIAL_DEVICE_READY, 1); + for (Retries = 0; Retries < 100; Retries++) { + gBS->Stall (1000); + VirtioSerialRxControl (Dev); + if (Dev->NumPorts && (Dev->NumConsoles + Dev->NumNamedPorts == Dev->NumPorts)) { + // port discovery complete + break; + } + } + + Status = gBS->CreateEvent ( + EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + VirtioSerialTimer, + Dev, + &Dev->Timer + ); + if (EFI_ERROR (Status)) { + goto Failed; + } + + Status = gBS->SetTimer ( + Dev->Timer, + TimerPeriodic, + EFI_TIMER_PERIOD_MILLISECONDS (10) + ); + if (EFI_ERROR (Status)) { + goto Failed; + } + + DEBUG (( + DEBUG_INFO, + "%a:%d: OK, %d consoles, %d named ports\n", + __func__, + __LINE__, + Dev->NumConsoles, + Dev->NumNamedPorts + )); + return EFI_SUCCESS; + +Failed: + VirtioSerialUninitAllRings (Dev); + + // + // 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); + + DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); + return Status; // reached only via Failed above +} + +STATIC +VOID +EFIAPI +VirtioSerialUninit ( + IN OUT VIRTIO_SERIAL_DEV *Dev + ) +{ + UINT32 PortId; + + gBS->CloseEvent (Dev->Timer); + + // + // 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); + + for (PortId = 0; PortId < MAX_PORTS; PortId++) { + VirtioSerialPortRemove (Dev, PortId); + } + + VirtioSerialUninitAllRings (Dev); +} + +// +// Event notification function enqueued by ExitBootServices(). +// + +STATIC +VOID +EFIAPI +VirtioSerialExitBoot ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + VIRTIO_SERIAL_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); +} + +STATIC +VIRTIO_SERIAL_DEV * +VirtioSerialFind ( + EFI_HANDLE DeviceHandle + ) +{ + VIRTIO_SERIAL_DEV *Dev; + LIST_ENTRY *Entry; + + BASE_LIST_FOR_EACH (Entry, &mVirtioSerialList) { + Dev = CR (Entry, VIRTIO_SERIAL_DEV, Link, VIRTIO_SERIAL_SIG); + if (DeviceHandle == Dev->DeviceHandle) { + return Dev; + } + } + return NULL; +} + +// +// Probe, start and stop functions of this driver, called by the DXE core for +// specific devices. +// +// The following specifications document these interfaces: +// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol +// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol +// +// The implementation follows: +// - Driver Writer's Guide for UEFI 2.3.1 v1.01 +// - 5.1.3.4 OpenProtocol() and CloseProtocol() +// - UEFI Spec 2.3.1 + Errata C +// - 6.3 Protocol Handler Services +// + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialDriverBindingSupported ( + 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)) { + return Status; + } + + if (VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_CONSOLE) { + Status = EFI_UNSUPPORTED; + } + + DEBUG ((DEBUG_INFO, "%a:%d: subsystem %d -> %r\n", __func__, __LINE__, VirtIo->SubSystemDeviceId, Status)); + + // + // We needed VirtIo access only transitorily, to see whether we support the + // device or not. + // + gBS->CloseProtocol ( + DeviceHandle, + &gVirtioDeviceProtocolGuid, + This->DriverBindingHandle, + DeviceHandle + ); + return Status; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + VIRTIO_SERIAL_DEV *Dev; + EFI_STATUS Status; + + Dev = (VIRTIO_SERIAL_DEV *)AllocateZeroPool (sizeof *Dev); + if (Dev == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = gBS->OpenProtocol ( + DeviceHandle, + &gEfiDevicePathProtocolGuid, + (VOID **)&Dev->DevicePath, + This->DriverBindingHandle, + DeviceHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (EFI_ERROR (Status)) { + goto FreeVirtioSerial; + } + + Status = gBS->OpenProtocol ( + DeviceHandle, + &gVirtioDeviceProtocolGuid, + (VOID **)&Dev->VirtIo, + This->DriverBindingHandle, + DeviceHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + goto FreeVirtioSerial; + } + + LogDevicePath (DEBUG_INFO, __func__, L"Dev", Dev->DevicePath); + + // + // VirtIo access granted, configure virtio-serial device. + // + Dev->Signature = VIRTIO_SERIAL_SIG; + Dev->DriverBindingHandle = This->DriverBindingHandle; + Dev->DeviceHandle = DeviceHandle; + Status = VirtioSerialInit (Dev); + if (EFI_ERROR (Status)) { + goto CloseVirtIo; + } + + Status = gBS->CreateEvent ( + EVT_SIGNAL_EXIT_BOOT_SERVICES, + TPL_CALLBACK, + &VirtioSerialExitBoot, + Dev, + &Dev->ExitBoot + ); + if (EFI_ERROR (Status)) { + goto UninitDev; + } + + InsertTailList (&mVirtioSerialList, &(Dev->Link)); + return EFI_SUCCESS; + +UninitDev: + VirtioSerialUninit (Dev); + +CloseVirtIo: + gBS->CloseProtocol ( + DeviceHandle, + &gVirtioDeviceProtocolGuid, + This->DriverBindingHandle, + DeviceHandle + ); + +FreeVirtioSerial: + FreePool (Dev); + + return Status; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + VIRTIO_SERIAL_DEV *Dev; + UINT32 PortId; + UINT32 Child; + + Dev = VirtioSerialFind (DeviceHandle); + if (!Dev) { + return EFI_SUCCESS; + } + + if (NumberOfChildren) { + for (Child = 0; Child < NumberOfChildren; Child++) { + DEBUG ((DEBUG_INFO, "%a:%d: child handle 0x%x\n", __func__, __LINE__, ChildHandleBuffer[Child])); + for (PortId = 0; PortId < MAX_PORTS; PortId++) { + if (Dev->Ports[PortId].Ready && + Dev->Ports[PortId].SerialIo && + (Dev->Ports[PortId].SerialIo->DeviceHandle == ChildHandleBuffer[Child])) + { + VirtioSerialPortRemove (Dev, PortId); + } + } + } + + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_INFO, "%a:%d: controller handle 0x%x\n", __func__, __LINE__, DeviceHandle)); + + gBS->CloseEvent (Dev->ExitBoot); + + VirtioSerialUninit (Dev); + + gBS->CloseProtocol ( + DeviceHandle, + &gVirtioDeviceProtocolGuid, + This->DriverBindingHandle, + DeviceHandle + ); + + RemoveEntryList (&(Dev->Link)); + ZeroMem (Dev, sizeof (*Dev)); + FreePool (Dev); + + return EFI_SUCCESS; +} + +// +// The static object that groups the Supported() (ie. probe), Start() and +// Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata +// C, 10.1 EFI Driver Binding Protocol. +// +STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = { + &VirtioSerialDriverBindingSupported, + &VirtioSerialDriverBindingStart, + &VirtioSerialDriverBindingStop, + 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers + NULL, // ImageHandle, to be overwritten by + // EfiLibInstallDriverBindingComponentName2() in VirtioSerialEntryPoint() + NULL // DriverBindingHandle, ditto +}; + +// +// The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and +// EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name +// in English, for display on standard console devices. This is recommended for +// UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's +// Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names. +// + +STATIC +EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { + { "eng;en", L"Virtio Serial Driver" }, + { NULL, NULL } +}; + +STATIC +EFI_UNICODE_STRING_TABLE mDeviceNameTable[] = { + { "eng;en", L"Virtio Serial Device" }, + { NULL, NULL } +}; + +STATIC +EFI_UNICODE_STRING_TABLE mPortNameTable[] = { + { "eng;en", L"Virtio Serial Port" }, + { NULL, NULL } +}; + +STATIC +EFI_COMPONENT_NAME_PROTOCOL gComponentName; + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +{ + return LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + mDriverNameTable, + DriverName, + (BOOLEAN)(This == &gComponentName) // Iso639Language + ); +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialGetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +{ + EFI_UNICODE_STRING_TABLE *Table; + VIRTIO_SERIAL_DEV *Dev; + UINT32 PortId; + + Dev = VirtioSerialFind (DeviceHandle); + if (!Dev) { + return EFI_UNSUPPORTED; + } + + if (ChildHandle) { + for (PortId = 0; PortId < MAX_PORTS; PortId++) { + if (Dev->Ports[PortId].Ready && + Dev->Ports[PortId].SerialIo && + (Dev->Ports[PortId].SerialIo->DeviceHandle == ChildHandle)) + { + *ControllerName = Dev->Ports[PortId].Name; + return EFI_SUCCESS; + } + } + + Table = mPortNameTable; + } else { + Table = mDeviceNameTable; + } + + return LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + Table, + ControllerName, + (BOOLEAN)(This == &gComponentName) + ); +} + +STATIC +EFI_COMPONENT_NAME_PROTOCOL gComponentName = { + &VirtioSerialGetDriverName, + &VirtioSerialGetDeviceName, + "eng" // SupportedLanguages, ISO 639-2 language codes +}; + +STATIC +EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&VirtioSerialGetDriverName, + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&VirtioSerialGetDeviceName, + "en" // SupportedLanguages, RFC 4646 language codes +}; + +// +// Entry point of this driver. +// +EFI_STATUS +EFIAPI +VirtioSerialEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + InitializeListHead (&mVirtioSerialList); + return EfiLibInstallDriverBindingComponentName2 ( + ImageHandle, + SystemTable, + &gDriverBinding, + ImageHandle, + &gComponentName, + &gComponentName2 + ); +} diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerial.h b/OvmfPkg/VirtioSerialDxe/VirtioSerial.h new file mode 100644 index 0000000000..e626fdf430 --- /dev/null +++ b/OvmfPkg/VirtioSerialDxe/VirtioSerial.h @@ -0,0 +1,226 @@ +/** @file + + Private definitions of the VirtioRng RNG driver + + Copyright (C) 2016, Linaro Ltd. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VIRTIO_SERIAL_DXE_H_ +#define _VIRTIO_SERIAL_DXE_H_ + +#include +#include +#include + +#include +#include + +#define VIRTIO_SERIAL_SIG SIGNATURE_32 ('V', 'S', 'I', 'O') + +#define MAX_PORTS 8 +#define MAX_RINGS (MAX_PORTS * 2 + 2) + +#define CTRL_RX_BUFSIZE 128 +#define CTRL_TX_BUFSIZE sizeof(VIRTIO_SERIAL_CONTROL) +#define PORT_RX_BUFSIZE 128 +#define PORT_TX_BUFSIZE 128 + +// +// Data structures +// + +typedef struct _VIRTIO_SERIAL_DEV VIRTIO_SERIAL_DEV; +typedef struct _VIRTIO_SERIAL_RING VIRTIO_SERIAL_RING; +typedef struct _VIRTIO_SERIAL_PORT VIRTIO_SERIAL_PORT; +typedef struct _VIRTIO_SERIAL_IO_PROTOCOL VIRTIO_SERIAL_IO_PROTOCOL; + +struct _VIRTIO_SERIAL_RING { + 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; +}; + +struct _VIRTIO_SERIAL_PORT { + BOOLEAN Ready; + BOOLEAN Console; + BOOLEAN DeviceOpen; + + CHAR16 Name[32]; + + VIRTIO_SERIAL_IO_PROTOCOL *SerialIo; +}; + +struct _VIRTIO_SERIAL_DEV { + UINT32 Signature; + LIST_ENTRY Link; + + EFI_HANDLE DriverBindingHandle; + EFI_HANDLE DeviceHandle; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + + VIRTIO_DEVICE_PROTOCOL *VirtIo; + EFI_EVENT ExitBoot; + VIRTIO_SERIAL_CONFIG Config; + VIRTIO_SERIAL_PORT Ports[MAX_PORTS]; + VIRTIO_SERIAL_RING Rings[MAX_RINGS]; + EFI_EVENT Timer; + + UINT32 NumPorts; + UINT32 NumConsoles; + UINT32 NumNamedPorts; +}; + +struct _VIRTIO_SERIAL_IO_PROTOCOL { + EFI_SERIAL_IO_PROTOCOL SerialIo; + EFI_SERIAL_IO_MODE SerialIoMode; + + EFI_HANDLE DeviceHandle; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + + VIRTIO_SERIAL_DEV *Dev; + UINT32 PortId; + + UINT8 ReadBuffer[PORT_RX_BUFSIZE]; + UINT32 ReadOffset; + UINT32 ReadSize; + + UINT8 WriteBuffer[PORT_TX_BUFSIZE]; + UINT32 WriteOffset; +}; + +// +// VirtioSerial.c +// + +VOID +EFIAPI +LogDevicePath ( + UINT32 Level, + const CHAR8 *Func, + CHAR16 *Note, + EFI_DEVICE_PATH_PROTOCOL *DevicePath + ); + +EFI_STATUS +EFIAPI +VirtioSerialTxControl ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 Id, + IN UINT16 Event, + IN UINT16 Value + ); + +// +// VirtioSerialRing.c +// + +EFI_STATUS +EFIAPI +VirtioSerialInitRing ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + IN UINT32 BufferSize + ); + +VOID +EFIAPI +VirtioSerialUninitRing ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ); + +VOID +EFIAPI +VirtioSerialRingFillRx ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ); + +VOID +EFIAPI +VirtioSerialRingClearTx ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ); + +EFI_STATUS +EFIAPI +VirtioSerialRingSendBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + IN VOID *Data, + IN UINT32 DataSize, + IN BOOLEAN Notify + ); + +BOOLEAN +EFIAPI +VirtioSerialRingHasBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ); + +BOOLEAN +EFIAPI +VirtioSerialRingGetBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + OUT VOID *Data, + OUT UINT32 *DataSize + ); + +// +// VirtioSerialPort.c +// + +EFI_STATUS +EFIAPI +VirtioSerialPortAdd ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ); + +VOID +EFIAPI +VirtioSerialPortSetConsole ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ); + +VOID +EFIAPI +VirtioSerialPortSetName ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId, + IN UINT8 *Name + ); + +VOID +EFIAPI +VirtioSerialPortSetDeviceOpen ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId, + IN UINT16 Value + ); + +VOID +EFIAPI +VirtioSerialPortRemove ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ); + +#endif diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerial.inf b/OvmfPkg/VirtioSerialDxe/VirtioSerial.inf new file mode 100644 index 0000000000..d63a08b928 --- /dev/null +++ b/OvmfPkg/VirtioSerialDxe/VirtioSerial.inf @@ -0,0 +1,40 @@ +## @file +# This driver produces FIXME instances for virtio-serial devices. +# +# Copyright (C) 2016, Linaro Ltd. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = VirtioSerialDxe + FILE_GUID = 23CACE14-EBA4-49F6-9681-C697FF0B649E + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = VirtioSerialEntryPoint + +[Sources] + VirtioSerial.h + VirtioSerial.c + VirtioSerialPort.c + VirtioSerialRing.c + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + DevicePathLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + VirtioLib + +[Protocols] + gVirtioDeviceProtocolGuid ## TO_START + gEfiSerialIoProtocolGuid diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c b/OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c new file mode 100644 index 0000000000..522b25e969 --- /dev/null +++ b/OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c @@ -0,0 +1,465 @@ +/** @file + + Driver for virtio-serial devices. + + Helper functions to manage virtio serial ports. + Console ports will be registered as SerialIo UARTs. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VirtioSerial.h" + +ACPI_HID_DEVICE_PATH mAcpiSerialDevNode = { + { + ACPI_DEVICE_PATH, + ACPI_DP, + { + (UINT8)(sizeof (ACPI_HID_DEVICE_PATH)), + (UINT8)((sizeof (ACPI_HID_DEVICE_PATH)) >> 8) + }, + }, + EISA_PNP_ID (0x0501), + 0 +}; + +UART_DEVICE_PATH mUartDevNode = { + { + MESSAGING_DEVICE_PATH, + MSG_UART_DP, + { + (UINT8)(sizeof (UART_DEVICE_PATH)), + (UINT8)((sizeof (UART_DEVICE_PATH)) >> 8) + } + }, + 0, // Reserved + 115200, // Speed + 8, 1, 1 // 8n1 +}; + +STATIC +UINT16 +PortRx ( + IN UINT32 PortId + ) +{ + ASSERT (PortId < MAX_PORTS); + + if (PortId >= 1) { + return (UINT16)(VIRTIO_SERIAL_Q_RX_BASE + (PortId - 1) * 2); + } + + return VIRTIO_SERIAL_Q_RX_PORT0; +} + +STATIC +UINT16 +PortTx ( + IN UINT32 PortId + ) +{ + ASSERT (PortId < MAX_PORTS); + + if (PortId >= 1) { + return (UINT16)(VIRTIO_SERIAL_Q_TX_BASE + (PortId - 1) * 2); + } + + return VIRTIO_SERIAL_Q_TX_PORT0; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoReset ( + IN EFI_SERIAL_IO_PROTOCOL *This + ) +{ + DEBUG ((DEBUG_VERBOSE, "%a:%d:\n", __func__, __LINE__)); + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoSetAttributes ( + IN EFI_SERIAL_IO_PROTOCOL *This, + IN UINT64 BaudRate, + IN UINT32 ReceiveFifoDepth, + IN UINT32 Timeout, + IN EFI_PARITY_TYPE Parity, + IN UINT8 DataBits, + IN EFI_STOP_BITS_TYPE StopBits + ) +{ + DEBUG (( + DEBUG_VERBOSE, + "%a:%d: Rate %ld, Fifo %d, Bits %d\n", + __func__, + __LINE__, + BaudRate, + ReceiveFifoDepth, + DataBits + )); + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoSetControl ( + IN EFI_SERIAL_IO_PROTOCOL *This, + IN UINT32 Control + ) +{ + DEBUG ((DEBUG_INFO, "%a:%d: Control 0x%x\n", __func__, __LINE__, Control)); + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoGetControl ( + IN EFI_SERIAL_IO_PROTOCOL *This, + OUT UINT32 *Control + ) +{ + DEBUG ((DEBUG_VERBOSE, "%a:%d: Control 0x%x\n", __func__, __LINE__, *Control)); + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoWrite ( + IN EFI_SERIAL_IO_PROTOCOL *This, + IN OUT UINTN *BufferSize, + IN VOID *Buffer + ) +{ + VIRTIO_SERIAL_IO_PROTOCOL *SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)This; + VIRTIO_SERIAL_PORT *Port = SerialIo->Dev->Ports + SerialIo->PortId; + UINT32 Length; + EFI_TPL OldTpl; + + if (!Port->DeviceOpen) { + *BufferSize = 0; + return EFI_SUCCESS; + } + + VirtioSerialRingClearTx (SerialIo->Dev, PortTx (SerialIo->PortId)); + + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + if (SerialIo->WriteOffset && + (SerialIo->WriteOffset + *BufferSize > PORT_TX_BUFSIZE)) + { + DEBUG ((DEBUG_VERBOSE, "%a:%d: WriteFlush %d\n", __func__, __LINE__, SerialIo->WriteOffset)); + VirtioSerialRingSendBuffer ( + SerialIo->Dev, + PortTx (SerialIo->PortId), + SerialIo->WriteBuffer, + SerialIo->WriteOffset, + TRUE + ); + SerialIo->WriteOffset = 0; + } + + Length = MIN ((UINT32)(*BufferSize), PORT_TX_BUFSIZE - SerialIo->WriteOffset); + CopyMem (SerialIo->WriteBuffer + SerialIo->WriteOffset, Buffer, Length); + SerialIo->WriteOffset += Length; + *BufferSize = Length; + gBS->RestoreTPL (OldTpl); + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoRead ( + IN EFI_SERIAL_IO_PROTOCOL *This, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer + ) +{ + VIRTIO_SERIAL_IO_PROTOCOL *SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)This; + VIRTIO_SERIAL_PORT *Port = SerialIo->Dev->Ports + SerialIo->PortId; + BOOLEAN HasData; + UINT32 Length; + EFI_TPL OldTpl; + + if (!Port->DeviceOpen) { + goto NoData; + } + + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + if (SerialIo->WriteOffset) { + DEBUG ((DEBUG_VERBOSE, "%a:%d: WriteFlush %d\n", __func__, __LINE__, SerialIo->WriteOffset)); + VirtioSerialRingSendBuffer ( + SerialIo->Dev, + PortTx (SerialIo->PortId), + SerialIo->WriteBuffer, + SerialIo->WriteOffset, + TRUE + ); + SerialIo->WriteOffset = 0; + } + + gBS->RestoreTPL (OldTpl); + + if (SerialIo->ReadOffset == SerialIo->ReadSize) { + HasData = VirtioSerialRingGetBuffer ( + SerialIo->Dev, + PortRx (SerialIo->PortId), + &SerialIo->ReadBuffer, + &SerialIo->ReadSize + ); + if (!HasData) { + goto NoData; + } + + SerialIo->ReadOffset = 0; + } + + if (SerialIo->ReadOffset < SerialIo->ReadSize) { + Length = SerialIo->ReadSize - SerialIo->ReadOffset; + if (Length > *BufferSize) { + Length = (UINT32)(*BufferSize); + } + + CopyMem (Buffer, SerialIo->ReadBuffer + SerialIo->ReadOffset, Length); + SerialIo->ReadOffset += Length; + *BufferSize = Length; + return EFI_SUCCESS; + } + +NoData: + *BufferSize = 0; + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioSerialIoInit ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + VIRTIO_SERIAL_IO_PROTOCOL *SerialIo; + EFI_STATUS Status; + + SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)AllocateZeroPool (sizeof *SerialIo); + Port->SerialIo = SerialIo; + + SerialIo->SerialIo.Revision = EFI_SERIAL_IO_PROTOCOL_REVISION; + SerialIo->SerialIo.Reset = VirtioSerialIoReset; + SerialIo->SerialIo.SetAttributes = VirtioSerialIoSetAttributes; + SerialIo->SerialIo.SetControl = VirtioSerialIoSetControl; + SerialIo->SerialIo.GetControl = VirtioSerialIoGetControl; + SerialIo->SerialIo.Write = VirtioSerialIoWrite; + SerialIo->SerialIo.Read = VirtioSerialIoRead; + SerialIo->SerialIo.Mode = &SerialIo->SerialIoMode; + SerialIo->Dev = Dev; + SerialIo->PortId = PortId; + + SerialIo->DevicePath = DuplicateDevicePath (Dev->DevicePath); + mAcpiSerialDevNode.UID = PortId; + SerialIo->DevicePath = AppendDevicePathNode ( + SerialIo->DevicePath, + (EFI_DEVICE_PATH_PROTOCOL *)&mAcpiSerialDevNode + ); + SerialIo->DevicePath = AppendDevicePathNode ( + SerialIo->DevicePath, + (EFI_DEVICE_PATH_PROTOCOL *)&mUartDevNode + ); + + LogDevicePath (DEBUG_INFO, __func__, L"UART", SerialIo->DevicePath); + + Status = gBS->InstallMultipleProtocolInterfaces ( + &SerialIo->DeviceHandle, + &gEfiDevicePathProtocolGuid, + SerialIo->DevicePath, + &gEfiSerialIoProtocolGuid, + &SerialIo->SerialIo, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); + goto FreeSerialIo; + } + + Status = gBS->OpenProtocol ( + Dev->DeviceHandle, + &gVirtioDeviceProtocolGuid, + (VOID **)&Dev->VirtIo, + Dev->DriverBindingHandle, + SerialIo->DeviceHandle, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); + goto UninstallProtocol; + } + + return EFI_SUCCESS; + +UninstallProtocol: + gBS->UninstallMultipleProtocolInterfaces ( + SerialIo->DeviceHandle, + &gEfiDevicePathProtocolGuid, + SerialIo->DevicePath, + &gEfiSerialIoProtocolGuid, + &SerialIo->SerialIo, + NULL + ); + +FreeSerialIo: + FreePool (Port->SerialIo); + Port->SerialIo = NULL; + return Status; +} + +STATIC +VOID +EFIAPI +VirtioSerialIoUninit ( + VIRTIO_SERIAL_IO_PROTOCOL *SerialIo + ) +{ + VIRTIO_SERIAL_DEV *Dev = SerialIo->Dev; + VIRTIO_SERIAL_PORT *Port = Dev->Ports + SerialIo->PortId; + + DEBUG ((DEBUG_INFO, "%a:%d: %s\n", __func__, __LINE__, Port->Name)); + + gBS->CloseProtocol ( + Dev->DeviceHandle, + &gVirtioDeviceProtocolGuid, + Dev->DriverBindingHandle, + SerialIo->DeviceHandle + ); + + gBS->UninstallMultipleProtocolInterfaces ( + SerialIo->DeviceHandle, + &gEfiDevicePathProtocolGuid, + SerialIo->DevicePath, + &gEfiSerialIoProtocolGuid, + &SerialIo->SerialIo, + NULL + ); + + FreePool (SerialIo); + Port->SerialIo = NULL; +} + +EFI_STATUS +EFIAPI +VirtioSerialPortAdd ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + EFI_STATUS Status; + + if (Port->Ready) { + return EFI_SUCCESS; + } + + Status = VirtioSerialInitRing (Dev, PortRx (PortId), PORT_RX_BUFSIZE); + if (EFI_ERROR (Status)) { + goto Failed; + } + + Status = VirtioSerialInitRing (Dev, PortTx (PortId), PORT_TX_BUFSIZE); + if (EFI_ERROR (Status)) { + goto Failed; + } + + UnicodeSPrint (Port->Name, sizeof (Port->Name), L"Port #%d", PortId); + VirtioSerialRingFillRx (Dev, PortRx (PortId)); + Port->Ready = TRUE; + + return EFI_SUCCESS; + +Failed: + VirtioSerialUninitRing (Dev, PortRx (PortId)); + return Status; +} + +VOID +EFIAPI +VirtioSerialPortSetConsole ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + + Port->Console = TRUE; + UnicodeSPrint (Port->Name, sizeof (Port->Name), L"Console #%d", PortId); + VirtioSerialIoInit (Dev, PortId); +} + +VOID +EFIAPI +VirtioSerialPortSetName ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId, + IN UINT8 *Name + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + + DEBUG ((DEBUG_INFO, "%a:%d: \"%a\"\n", __func__, __LINE__, Name)); + UnicodeSPrint (Port->Name, sizeof (Port->Name), L"NamedPort #%d (%a)", PortId, Name); +} + +VOID +EFIAPI +VirtioSerialPortSetDeviceOpen ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId, + IN UINT16 Value + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + + Port->DeviceOpen = (BOOLEAN)Value; + if (Port->DeviceOpen) { + VirtioSerialTxControl (Dev, PortId, VIRTIO_SERIAL_PORT_OPEN, 1); + } +} + +VOID +EFIAPI +VirtioSerialPortRemove ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT32 PortId + ) +{ + VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; + + if (!Port->Ready) { + return; + } + + if (Port->SerialIo) { + VirtioSerialIoUninit (Port->SerialIo); + Port->SerialIo = NULL; + } + + VirtioSerialUninitRing (Dev, PortRx (PortId)); + VirtioSerialUninitRing (Dev, PortTx (PortId)); + Port->Ready = FALSE; +} diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c b/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c new file mode 100644 index 0000000000..936e1507a3 --- /dev/null +++ b/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c @@ -0,0 +1,345 @@ +/** @file + + Driver for virtio-serial devices. + + Helper functions to manage virtio rings. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include + +#include "VirtioSerial.h" + +STATIC +VOID * +BufferPtr ( + IN VIRTIO_SERIAL_RING *Ring, + IN UINT32 BufferNr + ) +{ + return Ring->Buffers + Ring->BufferSize * BufferNr; +} + +STATIC +EFI_PHYSICAL_ADDRESS +BufferAddr ( + IN VIRTIO_SERIAL_RING *Ring, + IN UINT32 BufferNr + ) +{ + return Ring->DeviceAddress + Ring->BufferSize * BufferNr; +} + +STATIC +UINT32 +BufferNext ( + IN VIRTIO_SERIAL_RING *Ring + ) +{ + return Ring->Indices.NextDescIdx % Ring->Ring.QueueSize; +} + +EFI_STATUS +EFIAPI +VirtioSerialInitRing ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + IN UINT32 BufferSize + ) +{ + VIRTIO_SERIAL_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; +} + +VOID +EFIAPI +VirtioSerialUninitRing ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ) +{ + VIRTIO_SERIAL_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)); +} + +VOID +EFIAPI +VirtioSerialRingFillRx ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ) +{ + VIRTIO_SERIAL_RING *Ring = Dev->Rings + Index; + UINT32 BufferNr; + + for (BufferNr = 0; BufferNr < Ring->BufferCount; BufferNr++) { + VirtioSerialRingSendBuffer (Dev, Index, NULL, Ring->BufferSize, FALSE); + } + + Dev->VirtIo->SetQueueNotify (Dev->VirtIo, Index); +} + +VOID +EFIAPI +VirtioSerialRingClearTx ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ) +{ + while (VirtioSerialRingGetBuffer (Dev, Index, NULL, NULL)) { + /* nothing */ } +} + +EFI_STATUS +EFIAPI +VirtioSerialRingSendBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + IN VOID *Data, + IN UINT32 DataSize, + IN BOOLEAN Notify + ) +{ + VIRTIO_SERIAL_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++; + + MemoryFence (); + *Ring->Ring.Avail.Idx = Idx; + MemoryFence (); + + if (Notify) { + Dev->VirtIo->SetQueueNotify (Dev->VirtIo, Index); + } + + return EFI_SUCCESS; +} + +BOOLEAN +EFIAPI +VirtioSerialRingHasBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index + ) +{ + VIRTIO_SERIAL_RING *Ring = Dev->Rings + Index; + UINT16 UsedIdx = *Ring->Ring.Used.Idx; + + if (!Ring->Ready) { + return FALSE; + } + + if (Ring->LastUsedIdx == UsedIdx) { + return FALSE; + } + + return TRUE; +} + +BOOLEAN +EFIAPI +VirtioSerialRingGetBuffer ( + IN OUT VIRTIO_SERIAL_DEV *Dev, + IN UINT16 Index, + OUT VOID *Data, + OUT UINT32 *DataSize + ) +{ + VIRTIO_SERIAL_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 */ + VirtioSerialRingSendBuffer (Dev, Index, NULL, Ring->BufferSize, FALSE); + } + + Ring->LastUsedIdx++; + return TRUE; +}