public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Gerd Hoffmann" <kraxel@redhat.com>
To: devel@edk2.groups.io
Cc: "Stefan Berger" <stefanb@linux.ibm.com>,
	"Julien Grall" <julien@xen.org>,
	"Anthony Perard" <anthony.perard@citrix.com>,
	"Ard Biesheuvel" <ardb+tianocore@kernel.org>,
	"Jordan Justen" <jordan.l.justen@intel.com>,
	"Pawel Polawski" <ppolawsk@redhat.com>,
	"Oliver Steffen" <osteffen@redhat.com>,
	"Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Jiewen Yao" <jiewen.yao@intel.com>,
	"Gerd Hoffmann" <kraxel@redhat.com>
Subject: [PATCH 2/4] OvmfPkg/VirtioSerialDxe: add driver
Date: Mon, 17 Apr 2023 11:03:18 +0200	[thread overview]
Message-ID: <20230417090320.225046-3-kraxel@redhat.com> (raw)
In-Reply-To: <20230417090320.225046-1-kraxel@redhat.com>

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>
---
 OvmfPkg/VirtioSerialDxe/VirtioSerial.inf   |  40 ++
 OvmfPkg/VirtioSerialDxe/VirtioSerial.h     | 226 ++++++
 OvmfPkg/VirtioSerialDxe/VirtioSerial.c     | 789 +++++++++++++++++++++
 OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c | 443 ++++++++++++
 OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c | 345 +++++++++
 5 files changed, 1843 insertions(+)
 create mode 100644 OvmfPkg/VirtioSerialDxe/VirtioSerial.inf
 create mode 100644 OvmfPkg/VirtioSerialDxe/VirtioSerial.h
 create mode 100644 OvmfPkg/VirtioSerialDxe/VirtioSerial.c
 create mode 100644 OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c
 create mode 100644 OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c

diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerial.inf b/OvmfPkg/VirtioSerialDxe/VirtioSerial.inf
new file mode 100644
index 000000000000..d63a08b928c0
--- /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/VirtioSerial.h b/OvmfPkg/VirtioSerialDxe/VirtioSerial.h
new file mode 100644
index 000000000000..e626fdf43095
--- /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.c b/OvmfPkg/VirtioSerialDxe/VirtioSerial.c
new file mode 100644
index 000000000000..314140bfc314
--- /dev/null
+++ b/OvmfPkg/VirtioSerialDxe/VirtioSerial.c
@@ -0,0 +1,789 @@
+/** @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", 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;
+
+  for (Entry = GetFirstNode (&mVirtioSerialList);
+       Entry != NULL;
+       Entry = GetNextNode (&mVirtioSerialList, Entry))
+  {
+    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 = VirtioSerialFind (DeviceHandle);
+
+  DEBUG ((DEBUG_INFO, "%a:%d: Dev %p\n", __func__, __LINE__, Dev));
+
+  if (!Dev) {
+    return EFI_SUCCESS;
+  }
+
+  RemoveEntryList (&(Dev->Link));
+
+  gBS->CloseEvent (Dev->ExitBoot);
+
+  VirtioSerialUninit (Dev);
+
+  gBS->CloseProtocol (
+         DeviceHandle,
+         &gVirtioDeviceProtocolGuid,
+         This->DriverBindingHandle,
+         DeviceHandle
+         );
+
+  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;
+
+  if (ChildHandle) {
+    Dev = VirtioSerialFind (DeviceHandle);
+    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/VirtioSerialPort.c b/OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c
new file mode 100644
index 000000000000..a10566e046c6
--- /dev/null
+++ b/OvmfPkg/VirtioSerialDxe/VirtioSerialPort.c
@@ -0,0 +1,443 @@
+/** @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_HIGH_LEVEL);
+  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_HIGH_LEVEL);
+  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
+  )
+{
+  DEBUG ((DEBUG_INFO, "%a:%d: FIXME (#%d)\n", __func__, __LINE__, SerialIo->PortId));
+}
+
+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 000000000000..936e1507a3b6
--- /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;
+}
-- 
2.39.2


  parent reply	other threads:[~2023-04-17  9:03 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-04-17  9:03 [PATCH 0/4] OvmfPkg: add virtio serial driver Gerd Hoffmann
2023-04-17  9:03 ` [PATCH 1/4] OvmfPkg: add IndustryStandard/VirtioSerial.h Gerd Hoffmann
2023-04-17  9:03 ` Gerd Hoffmann [this message]
2023-04-17  9:03 ` [PATCH 3/4] OvmfPkg/VirtioSerialDxe: wire up in OvmfPkg* Gerd Hoffmann
2023-04-17  9:03 ` [PATCH 4/4] OvmfPkg/PlatformBootManagerLib: setup virtio serial console Gerd Hoffmann
2023-04-18 15:46 ` [PATCH 0/4] OvmfPkg: add virtio serial driver Ard Biesheuvel
2023-04-19  6:18   ` Gerd Hoffmann
2023-04-20  9:52     ` [edk2-devel] " Gupta, Pankaj
2023-04-20 11:46       ` Gerd Hoffmann
2023-04-20 13:31         ` Gupta, Pankaj

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230417090320.225046-3-kraxel@redhat.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox