VirtioLib provides an API for simple, synchronous (request/response-style) virtio communication. The guest driver builds one descriptor chain, link for link, with VirtioPrepare() and VirtioAppendDesc(), then submits the chain, and awaits the processing, with VirtioFlush(). The descriptor chain is always built at the beginning of the descriptor area, with the head descriptor having descriptor index 0. In order to submit the descriptor chain to the host, the guest always pushes a new "available element" to the Available Ring, in genuine queue-like fashion, with the new element referencing the head descriptor (which always has index 0, see above). In turn, after processing, the host always pushes a new "used element" to the Used Ring, in genuine queue-like fashion, with the new element referencing the head descriptor of the chain that was just processed. The same element also reports the number of bytes that the host wrote, consecutively across the host-writeable buffers that were linked by the descriptors. (See "OvmfPkg/VirtioNetDxe/TechNotes.txt" for a diagram about the descriptor area and the rings.) Because at most one descriptor chain can be in flight with VirtioLib at any time, - the Available Ring and the Used Ring proceed in lock-step, - and the head descriptor that the new "available" and "used" elements can ever reference has index 0. Based on the above, we can modify VirtioFlush() to return the number of bytes written by the host across the descriptor chain. The virtio-block and virtio-scsi drivers don't care (they have other ways to parse the data produced by the host), while the virtio-net driver doesn't use VirtioFlush() at all (it employs VirtioLib only to set up its rings). However, the virtio entropy device, to be covered in the upcoming patches, reports the amount of randomness produced by the host only through this quantity. Cc: Jordan Justen <jordan.l.justen@intel.com> Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
342 lines
11 KiB
C
342 lines
11 KiB
C
/** @file
|
|
|
|
Utility functions used by virtio device drivers.
|
|
|
|
Copyright (C) 2012-2016, Red Hat, Inc.
|
|
Portion of Copyright (C) 2013, ARM Ltd.
|
|
|
|
This program and the accompanying materials are licensed and made available
|
|
under the terms and conditions of the BSD License which accompanies this
|
|
distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
|
|
WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.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] The number of descriptors to allocate for the
|
|
virtio ring, as requested by the host.
|
|
|
|
@param[out] Ring The virtio ring to set up.
|
|
|
|
@retval EFI_OUT_OF_RESOURCES AllocatePages() failed to allocate contiguous
|
|
pages for the requested QueueSize. Fields of
|
|
Ring have indeterminate value.
|
|
|
|
@retval EFI_SUCCESS Allocation and setup successful. Ring->Base
|
|
(and nothing else) is responsible for
|
|
deallocation.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
VirtioRingInit (
|
|
IN UINT16 QueueSize,
|
|
OUT VRING *Ring
|
|
)
|
|
{
|
|
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);
|
|
|
|
Ring->NumPages = EFI_SIZE_TO_PAGES (RingSize);
|
|
Ring->Base = AllocatePages (Ring->NumPages);
|
|
if (Ring->Base == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
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[out] Ring The virtio ring to clean up.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
VirtioRingUninit (
|
|
IN OUT VRING *Ring
|
|
)
|
|
{
|
|
FreePages (Ring->Base, Ring->NumPages);
|
|
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] BufferPhysAddr (Guest pseudo-physical) 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 UINTN BufferPhysAddr,
|
|
IN UINT32 BufferSize,
|
|
IN UINT16 Flags,
|
|
IN OUT DESC_INDICES *Indices
|
|
)
|
|
{
|
|
volatile VRING_DESC *Desc;
|
|
|
|
Desc = &Ring->Desc[Indices->NextDescIdx++ % Ring->QueueSize];
|
|
Desc->Addr = BufferPhysAddr;
|
|
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;
|
|
}
|