aboutsummaryrefslogtreecommitdiffstats
path: root/roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c')
-rw-r--r--roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c b/roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c
new file mode 100644
index 000000000..e09324813
--- /dev/null
+++ b/roms/edk2/OvmfPkg/Library/VirtioLib/VirtioLib.c
@@ -0,0 +1,552 @@
+/** @file
+
+ Utility functions used by virtio device drivers.
+
+ Copyright (C) 2012-2016, Red Hat, Inc.
+ Portion of Copyright (C) 2013, ARM Ltd.
+ Copyright (C) 2017, AMD Inc, All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+
+#include <Library/VirtioLib.h>
+
+
+/**
+
+ Configure a virtio ring.
+
+ This function sets up internal storage (the guest-host communication area)
+ and lays out several "navigation" (ie. no-ownership) pointers to parts of
+ that storage.
+
+ Relevant sections from the virtio-0.9.5 spec:
+ - 1.1 Virtqueues,
+ - 2.3 Virtqueue Configuration.
+
+ @param[in] VirtIo The virtio device which will use the ring.
+
+ @param[in] The number of descriptors to allocate for the
+ virtio ring, as requested by the host.
+
+ @param[out] Ring The virtio ring to set up.
+
+ @return Status codes propagated from
+ VirtIo->AllocateSharedPages().
+
+ @retval EFI_SUCCESS Allocation and setup successful. Ring->Base
+ (and nothing else) is responsible for
+ deallocation.
+
+**/
+EFI_STATUS
+EFIAPI
+VirtioRingInit (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN UINT16 QueueSize,
+ OUT VRING *Ring
+ )
+{
+ EFI_STATUS Status;
+ UINTN RingSize;
+ volatile UINT8 *RingPagesPtr;
+
+ RingSize = ALIGN_VALUE (
+ sizeof *Ring->Desc * QueueSize +
+ sizeof *Ring->Avail.Flags +
+ sizeof *Ring->Avail.Idx +
+ sizeof *Ring->Avail.Ring * QueueSize +
+ sizeof *Ring->Avail.UsedEvent,
+ EFI_PAGE_SIZE);
+
+ RingSize += ALIGN_VALUE (
+ sizeof *Ring->Used.Flags +
+ sizeof *Ring->Used.Idx +
+ sizeof *Ring->Used.UsedElem * QueueSize +
+ sizeof *Ring->Used.AvailEvent,
+ EFI_PAGE_SIZE);
+
+ //
+ // Allocate a shared ring buffer
+ //
+ Ring->NumPages = EFI_SIZE_TO_PAGES (RingSize);
+ Status = VirtIo->AllocateSharedPages (
+ VirtIo,
+ Ring->NumPages,
+ &Ring->Base
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ SetMem (Ring->Base, RingSize, 0x00);
+ RingPagesPtr = Ring->Base;
+
+ Ring->Desc = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Desc * QueueSize;
+
+ Ring->Avail.Flags = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Avail.Flags;
+
+ Ring->Avail.Idx = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Avail.Idx;
+
+ Ring->Avail.Ring = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Avail.Ring * QueueSize;
+
+ Ring->Avail.UsedEvent = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Avail.UsedEvent;
+
+ RingPagesPtr = (volatile UINT8 *) Ring->Base +
+ ALIGN_VALUE (RingPagesPtr - (volatile UINT8 *) Ring->Base,
+ EFI_PAGE_SIZE);
+
+ Ring->Used.Flags = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Used.Flags;
+
+ Ring->Used.Idx = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Used.Idx;
+
+ Ring->Used.UsedElem = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Used.UsedElem * QueueSize;
+
+ Ring->Used.AvailEvent = (volatile VOID *) RingPagesPtr;
+ RingPagesPtr += sizeof *Ring->Used.AvailEvent;
+
+ Ring->QueueSize = QueueSize;
+ return EFI_SUCCESS;
+}
+
+
+/**
+
+ Tear down the internal resources of a configured virtio ring.
+
+ The caller is responsible to stop the host from using this ring before
+ invoking this function: the VSTAT_DRIVER_OK bit must be clear in
+ VhdrDeviceStatus.
+
+ @param[in] VirtIo The virtio device which was using the ring.
+
+ @param[out] Ring The virtio ring to clean up.
+
+**/
+VOID
+EFIAPI
+VirtioRingUninit (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN OUT VRING *Ring
+ )
+{
+ VirtIo->FreeSharedPages (VirtIo, Ring->NumPages, Ring->Base);
+ SetMem (Ring, sizeof *Ring, 0x00);
+}
+
+
+/**
+
+ Turn off interrupt notifications from the host, and prepare for appending
+ multiple descriptors to the virtio ring.
+
+ The calling driver must be in VSTAT_DRIVER_OK state.
+
+ @param[in,out] Ring The virtio ring we intend to append descriptors to.
+
+ @param[out] Indices The DESC_INDICES structure to initialize.
+
+**/
+VOID
+EFIAPI
+VirtioPrepare (
+ IN OUT VRING *Ring,
+ OUT DESC_INDICES *Indices
+ )
+{
+ //
+ // Prepare for virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device.
+ // We're going to poll the answer, the host should not send an interrupt.
+ //
+ *Ring->Avail.Flags = (UINT16) VRING_AVAIL_F_NO_INTERRUPT;
+
+ //
+ // Prepare for virtio-0.9.5, 2.4.1 Supplying Buffers to the Device.
+ //
+ // Since we support only one in-flight descriptor chain, we can always build
+ // that chain starting at entry #0 of the descriptor table.
+ //
+ Indices->HeadDescIdx = 0;
+ Indices->NextDescIdx = Indices->HeadDescIdx;
+}
+
+/**
+
+ Append a contiguous buffer for transmission / reception via the virtio ring.
+
+ This function implements the following section from virtio-0.9.5:
+ - 2.4.1.1 Placing Buffers into the Descriptor Table
+
+ Free space is taken as granted, since the individual drivers support only
+ synchronous requests and host side status is processed in lock-step with
+ request submission. It is the calling driver's responsibility to verify the
+ ring size in advance.
+
+ The caller is responsible for initializing *Indices with VirtioPrepare()
+ first.
+
+ @param[in,out] Ring The virtio ring to append the buffer to,
+ as a descriptor.
+
+ @param[in] BufferDeviceAddress (Bus master device) start address of the
+ transmit / receive buffer.
+
+ @param[in] BufferSize Number of bytes to transmit or receive.
+
+ @param[in] Flags A bitmask of VRING_DESC_F_* flags. The
+ caller computes this mask dependent on
+ further buffers to append and transfer
+ direction. VRING_DESC_F_INDIRECT is
+ unsupported. The VRING_DESC.Next field is
+ always set, but the host only interprets
+ it dependent on VRING_DESC_F_NEXT.
+
+ @param[in,out] Indices Indices->HeadDescIdx is not accessed.
+ On input, Indices->NextDescIdx identifies
+ the next descriptor to carry the buffer.
+ On output, Indices->NextDescIdx is
+ incremented by one, modulo 2^16.
+
+**/
+VOID
+EFIAPI
+VirtioAppendDesc (
+ IN OUT VRING *Ring,
+ IN UINT64 BufferDeviceAddress,
+ IN UINT32 BufferSize,
+ IN UINT16 Flags,
+ IN OUT DESC_INDICES *Indices
+ )
+{
+ volatile VRING_DESC *Desc;
+
+ Desc = &Ring->Desc[Indices->NextDescIdx++ % Ring->QueueSize];
+ Desc->Addr = BufferDeviceAddress;
+ Desc->Len = BufferSize;
+ Desc->Flags = Flags;
+ Desc->Next = Indices->NextDescIdx % Ring->QueueSize;
+}
+
+
+/**
+
+ Notify the host about the descriptor chain just built, and wait until the
+ host processes it.
+
+ @param[in] VirtIo The target virtio device to notify.
+
+ @param[in] VirtQueueId Identifies the queue for the target device.
+
+ @param[in,out] Ring The virtio ring with descriptors to submit.
+
+ @param[in] Indices Indices->NextDescIdx is not accessed.
+ Indices->HeadDescIdx identifies the head descriptor
+ of the descriptor chain.
+
+ @param[out] UsedLen On success, the total number of bytes, consecutively
+ across the buffers linked by the descriptor chain,
+ that the host wrote. May be NULL if the caller
+ doesn't care, or can compute the same information
+ from device-specific request structures linked by the
+ descriptor chain.
+
+ @return Error code from VirtIo->SetQueueNotify() if it fails.
+
+ @retval EFI_SUCCESS Otherwise, the host processed all descriptors.
+
+**/
+EFI_STATUS
+EFIAPI
+VirtioFlush (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN UINT16 VirtQueueId,
+ IN OUT VRING *Ring,
+ IN DESC_INDICES *Indices,
+ OUT UINT32 *UsedLen OPTIONAL
+ )
+{
+ UINT16 NextAvailIdx;
+ UINT16 LastUsedIdx;
+ EFI_STATUS Status;
+ UINTN PollPeriodUsecs;
+
+ //
+ // virtio-0.9.5, 2.4.1.2 Updating the Available Ring
+ //
+ // It is not exactly clear from the wording of the virtio-0.9.5
+ // specification, but each entry in the Available Ring references only the
+ // head descriptor of any given descriptor chain.
+ //
+ NextAvailIdx = *Ring->Avail.Idx;
+ //
+ // (Due to our lock-step progress, this is where the host will produce the
+ // used element with the head descriptor's index in it.)
+ //
+ LastUsedIdx = NextAvailIdx;
+ Ring->Avail.Ring[NextAvailIdx++ % Ring->QueueSize] =
+ Indices->HeadDescIdx % Ring->QueueSize;
+
+ //
+ // virtio-0.9.5, 2.4.1.3 Updating the Index Field
+ //
+ MemoryFence();
+ *Ring->Avail.Idx = NextAvailIdx;
+
+ //
+ // virtio-0.9.5, 2.4.1.4 Notifying the Device -- gratuitous notifications are
+ // OK.
+ //
+ MemoryFence();
+ Status = VirtIo->SetQueueNotify (VirtIo, VirtQueueId);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device
+ // Wait until the host processes and acknowledges our descriptor chain. The
+ // condition we use for polling is greatly simplified and relies on the
+ // synchronous, lock-step progress.
+ //
+ // Keep slowing down until we reach a poll period of slightly above 1 ms.
+ //
+ PollPeriodUsecs = 1;
+ MemoryFence();
+ while (*Ring->Used.Idx != NextAvailIdx) {
+ gBS->Stall (PollPeriodUsecs); // calls AcpiTimerLib::MicroSecondDelay
+
+ if (PollPeriodUsecs < 1024) {
+ PollPeriodUsecs *= 2;
+ }
+ MemoryFence();
+ }
+
+ MemoryFence();
+
+ if (UsedLen != NULL) {
+ volatile CONST VRING_USED_ELEM *UsedElem;
+
+ UsedElem = &Ring->Used.UsedElem[LastUsedIdx % Ring->QueueSize];
+ ASSERT (UsedElem->Id == Indices->HeadDescIdx);
+ *UsedLen = UsedElem->Len;
+ }
+
+ return EFI_SUCCESS;
+}
+
+
+/**
+
+ Report the feature bits to the VirtIo 1.0 device that the VirtIo 1.0 driver
+ understands.
+
+ In VirtIo 1.0, a device can reject a self-inconsistent feature bitmap through
+ the new VSTAT_FEATURES_OK status bit. (For example if the driver requests a
+ higher level feature but clears a prerequisite feature.) This function is a
+ small wrapper around VIRTIO_DEVICE_PROTOCOL.SetGuestFeatures() that also
+ verifies if the VirtIo 1.0 device accepts the feature bitmap.
+
+ @param[in] VirtIo Report feature bits to this device.
+
+ @param[in] Features The set of feature bits that the driver wishes
+ to report. The caller is responsible to perform
+ any masking before calling this function; the
+ value is directly written with
+ VIRTIO_DEVICE_PROTOCOL.SetGuestFeatures().
+
+ @param[in,out] DeviceStatus On input, the status byte most recently written
+ to the device's status register. On output (even
+ on error), DeviceStatus will be updated so that
+ it is suitable for further status bit
+ manipulation and writing to the device's status
+ register.
+
+ @retval EFI_SUCCESS The device accepted the configuration in Features.
+
+ @return EFI_UNSUPPORTED The device rejected the configuration in Features.
+
+ @retval EFI_UNSUPPORTED VirtIo->Revision is smaller than 1.0.0.
+
+ @return Error codes from the SetGuestFeatures(),
+ SetDeviceStatus(), GetDeviceStatus() member
+ functions.
+
+**/
+EFI_STATUS
+EFIAPI
+Virtio10WriteFeatures (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN UINT64 Features,
+ IN OUT UINT8 *DeviceStatus
+ )
+{
+ EFI_STATUS Status;
+
+ if (VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ Status = VirtIo->SetGuestFeatures (VirtIo, Features);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ *DeviceStatus |= VSTAT_FEATURES_OK;
+ Status = VirtIo->SetDeviceStatus (VirtIo, *DeviceStatus);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = VirtIo->GetDeviceStatus (VirtIo, DeviceStatus);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if ((*DeviceStatus & VSTAT_FEATURES_OK) == 0) {
+ Status = EFI_UNSUPPORTED;
+ }
+
+ return Status;
+}
+
+/**
+ Provides the virtio device address required to access system memory from a
+ DMA bus master.
+
+ The interface follows the same usage pattern as defined in UEFI spec 2.6
+ (Section 13.2 PCI Root Bridge I/O Protocol)
+
+ The VirtioMapAllBytesInSharedBuffer() is similar to VIRTIO_MAP_SHARED
+ with exception that NumberOfBytes is IN-only parameter. The function
+ maps all the bytes specified in NumberOfBytes param in one consecutive
+ range.
+
+ @param[in] VirtIo The virtio device for which the mapping is
+ requested.
+
+ @param[in] Operation Indicates if the bus master is going to
+ read or write to system memory.
+
+ @param[in] HostAddress The system memory address to map to shared
+ buffer address.
+
+ @param[in] NumberOfBytes Number of bytes to map.
+
+ @param[out] DeviceAddress The resulting shared map address for the
+ bus master to access the hosts HostAddress.
+
+ @param[out] Mapping A resulting token to pass to
+ VIRTIO_UNMAP_SHARED.
+
+
+ @retval EFI_SUCCESS The NumberOfBytes is successfully mapped.
+ @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a
+ common buffer.
+ @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
+ @retval EFI_OUT_OF_RESOURCES The request could not be completed due to
+ a lack of resources. This includes the case
+ when NumberOfBytes bytes cannot be mapped
+ in one consecutive range.
+ @retval EFI_DEVICE_ERROR The system hardware could not map the
+ requested address.
+**/
+EFI_STATUS
+EFIAPI
+VirtioMapAllBytesInSharedBuffer (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN VIRTIO_MAP_OPERATION Operation,
+ IN VOID *HostAddress,
+ IN UINTN NumberOfBytes,
+ OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
+ OUT VOID **Mapping
+ )
+{
+ EFI_STATUS Status;
+ VOID *MapInfo;
+ UINTN Size;
+ EFI_PHYSICAL_ADDRESS PhysicalAddress;
+
+ Size = NumberOfBytes;
+ Status = VirtIo->MapSharedBuffer (
+ VirtIo,
+ Operation,
+ HostAddress,
+ &Size,
+ &PhysicalAddress,
+ &MapInfo
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if (Size < NumberOfBytes) {
+ goto Failed;
+ }
+
+ *Mapping = MapInfo;
+ *DeviceAddress = PhysicalAddress;
+
+ return EFI_SUCCESS;
+
+Failed:
+ VirtIo->UnmapSharedBuffer (VirtIo, MapInfo);
+ return EFI_OUT_OF_RESOURCES;
+}
+
+/**
+
+ Map the ring buffer so that it can be accessed equally by both guest
+ and hypervisor.
+
+ @param[in] VirtIo The virtio device instance.
+
+ @param[in] Ring The virtio ring to map.
+
+ @param[out] RingBaseShift A resulting translation offset, to be
+ passed to VirtIo->SetQueueAddress().
+
+ @param[out] Mapping A resulting token to pass to
+ VirtIo->UnmapSharedBuffer().
+
+ @return Status code from VirtIo->MapSharedBuffer()
+**/
+EFI_STATUS
+EFIAPI
+VirtioRingMap (
+ IN VIRTIO_DEVICE_PROTOCOL *VirtIo,
+ IN VRING *Ring,
+ OUT UINT64 *RingBaseShift,
+ OUT VOID **Mapping
+ )
+{
+ EFI_STATUS Status;
+ EFI_PHYSICAL_ADDRESS DeviceAddress;
+
+ Status = VirtioMapAllBytesInSharedBuffer (
+ VirtIo,
+ VirtioOperationBusMasterCommonBuffer,
+ Ring->Base,
+ EFI_PAGES_TO_SIZE (Ring->NumPages),
+ &DeviceAddress,
+ Mapping
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ *RingBaseShift = DeviceAddress - (UINT64)(UINTN)Ring->Base;
+ return EFI_SUCCESS;
+}