summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c
diff options
context:
space:
mode:
Diffstat (limited to 'OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c')
-rw-r--r--OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c345
1 files changed, 345 insertions, 0 deletions
diff --git a/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c b/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c
new file mode 100644
index 0000000000..936e1507a3
--- /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;
+}