OvmfPkg/VirtioSerialDxe: add driver

Add a driver for the virtio serial device.

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.

The driver supports tty ports only, they are registered as SerialIo
UART in the system.

Named ports are detected and logged, but not exposed as devices.  They
are usually used by guest agents to communicate with the host.  It's not
clear whenever it makes sense for the firmware to run such agents and if
so which efi protocol could be to expose the ports.  So leaving that for
another day.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Gerd Hoffmann 2023-05-04 15:11:59 +02:00 committed by mergify[bot]
parent 1694b00511
commit 4d1452c599
5 changed files with 1884 additions and 0 deletions

View File

@ -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
);
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}