mirror of https://github.com/acidanthera/audk.git
346 lines
7.7 KiB
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;
|
|
}
|