blob: fd34ad75a9a079ce52f144ca90152a8165192542 [file] [log] [blame]
/** @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;
Control.Id = Id;
Control.Event = Event;
Control.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;
}
//
// 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
);
}