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