diff options
-rw-r--r-- | OvmfPkg/VirtioSerialDxe/VirtioSerial.c | 808 | ||||
-rw-r--r-- | OvmfPkg/VirtioSerialDxe/VirtioSerial.h | 226 | ||||
-rw-r--r-- | OvmfPkg/VirtioSerialDxe/VirtioSerial.inf | 40 | ||||
-rw-r--r-- | OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c | 465 | ||||
-rw-r--r-- | OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c | 345 |
5 files changed, 1884 insertions, 0 deletions
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 <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/DevicePathLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Library/VirtioLib.h>
+
+#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 <Protocol/ComponentName.h>
+#include <Protocol/DriverBinding.h>
+#include <Protocol/SerialIo.h>
+
+#include <IndustryStandard/Virtio.h>
+#include <IndustryStandard/VirtioSerial.h>
+
+#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 <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/DevicePathLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/PrintLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Library/VirtioLib.h>
+
+#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 <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Library/VirtioLib.h>
+
+#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;
+}
|