audk/OvmfPkg/VirtioSerialDxe/VirtioSerialRing.c

346 lines
7.7 KiB
C

/** @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;
}