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