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