/** @file | |
Implementation of the SNP.Initialize() function and its private helpers if | |
any. | |
Copyright (C) 2013, Red Hat, Inc. | |
Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.<BR> | |
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/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include "VirtioNet.h" | |
/** | |
Initialize a virtio ring for a specific transfer direction of the virtio-net | |
device. | |
This function may only be called by VirtioNetInitialize(). | |
@param[in,out] Dev The VNET_DEV driver instance about to enter the | |
EfiSimpleNetworkInitialized state. | |
@param[in] Selector Identifies the transfer direction (virtio queue) of | |
the network device. | |
@param[out] Ring The virtio-ring inside the VNET_DEV structure, | |
corresponding to Selector. | |
@param[out] Mapping A resulting token to pass to VirtioNetUninitRing() | |
@retval EFI_UNSUPPORTED The queue size reported by the virtio-net device is | |
too small. | |
@return Status codes from VIRTIO_CFG_WRITE(), | |
VIRTIO_CFG_READ(), VirtioRingInit() and | |
VirtioRingMap(). | |
@retval EFI_SUCCESS Ring initialized. | |
*/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
VirtioNetInitRing ( | |
IN OUT VNET_DEV *Dev, | |
IN UINT16 Selector, | |
OUT VRING *Ring, | |
OUT VOID **Mapping | |
) | |
{ | |
EFI_STATUS Status; | |
UINT16 QueueSize; | |
UINT64 RingBaseShift; | |
VOID *MapInfo; | |
// | |
// step 4b -- allocate selected queue | |
// | |
Status = Dev->VirtIo->SetQueueSel (Dev->VirtIo, Selector); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = Dev->VirtIo->GetQueueNumMax (Dev->VirtIo, &QueueSize); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// For each packet (RX and TX alike), we need two descriptors: | |
// one for the virtio-net request header, and another one for the data | |
// | |
if (QueueSize < 2) { | |
return EFI_UNSUPPORTED; | |
} | |
Status = VirtioRingInit (Dev->VirtIo, QueueSize, Ring); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// If anything fails from here on, we must release the ring resources. | |
// | |
Status = VirtioRingMap (Dev->VirtIo, Ring, &RingBaseShift, &MapInfo); | |
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, RingBaseShift); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
*Mapping = MapInfo; | |
return EFI_SUCCESS; | |
UnmapQueue: | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, MapInfo); | |
ReleaseQueue: | |
VirtioRingUninit (Dev->VirtIo, Ring); | |
return Status; | |
} | |
/** | |
Set up static scaffolding for the VirtioNetTransmit() and | |
VirtioNetGetStatus() SNP methods. | |
This function may only be called by VirtioNetInitialize(). | |
The structures laid out and resources configured include: | |
- fully populate the TX queue with a static pattern of virtio descriptor | |
chains, | |
- tracking of heads of free descriptor chains from the above, | |
- one common virtio-net request header (never modified by the host) for all | |
pending TX packets, | |
- select polling over TX interrupt. | |
@param[in,out] Dev The VNET_DEV driver instance about to enter the | |
EfiSimpleNetworkInitialized state. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate the stack to track the heads | |
of free descriptor chains or failed to init | |
TxBufCollection. | |
@return Status codes from VIRTIO_DEVICE_PROTOCOL. | |
AllocateSharedPages() or | |
VirtioMapAllBytesInSharedBuffer() | |
@retval EFI_SUCCESS TX setup successful. | |
*/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
VirtioNetInitTx ( | |
IN OUT VNET_DEV *Dev | |
) | |
{ | |
UINTN TxSharedReqSize; | |
UINTN PktIdx; | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS DeviceAddress; | |
VOID *TxSharedReqBuffer; | |
Dev->TxMaxPending = (UINT16)MIN ( | |
Dev->TxRing.QueueSize / 2, | |
VNET_MAX_PENDING | |
); | |
Dev->TxCurPending = 0; | |
Dev->TxFreeStack = AllocatePool ( | |
Dev->TxMaxPending * | |
sizeof *Dev->TxFreeStack | |
); | |
if (Dev->TxFreeStack == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Dev->TxBufCollection = OrderedCollectionInit ( | |
VirtioNetTxBufMapInfoCompare, | |
VirtioNetTxBufDeviceAddressCompare | |
); | |
if (Dev->TxBufCollection == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeTxFreeStack; | |
} | |
// | |
// Allocate TxSharedReq header and map with BusMasterCommonBuffer so that it | |
// can be accessed equally by both processor and device. | |
// | |
Status = Dev->VirtIo->AllocateSharedPages ( | |
Dev->VirtIo, | |
EFI_SIZE_TO_PAGES (sizeof *Dev->TxSharedReq), | |
&TxSharedReqBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
goto UninitTxBufCollection; | |
} | |
ZeroMem (TxSharedReqBuffer, sizeof *Dev->TxSharedReq); | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterCommonBuffer, | |
TxSharedReqBuffer, | |
sizeof *(Dev->TxSharedReq), | |
&DeviceAddress, | |
&Dev->TxSharedReqMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeTxSharedReqBuffer; | |
} | |
Dev->TxSharedReq = TxSharedReqBuffer; | |
// | |
// In VirtIo 1.0, the NumBuffers field is mandatory. In 0.9.5, it depends on | |
// VIRTIO_NET_F_MRG_RXBUF, which we never negotiate. | |
// | |
TxSharedReqSize = (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) ? | |
sizeof (Dev->TxSharedReq->V0_9_5) : | |
sizeof *Dev->TxSharedReq; | |
for (PktIdx = 0; PktIdx < Dev->TxMaxPending; ++PktIdx) { | |
UINT16 DescIdx; | |
DescIdx = (UINT16)(2 * PktIdx); | |
Dev->TxFreeStack[PktIdx] = DescIdx; | |
// | |
// For each possibly pending packet, lay out the descriptor for the common | |
// (unmodified by the host) virtio-net request header. | |
// | |
Dev->TxRing.Desc[DescIdx].Addr = DeviceAddress; | |
Dev->TxRing.Desc[DescIdx].Len = (UINT32)TxSharedReqSize; | |
Dev->TxRing.Desc[DescIdx].Flags = VRING_DESC_F_NEXT; | |
Dev->TxRing.Desc[DescIdx].Next = (UINT16)(DescIdx + 1); | |
// | |
// The second descriptor of each pending TX packet is updated on the fly, | |
// but it always terminates the descriptor chain of the packet. | |
// | |
Dev->TxRing.Desc[DescIdx + 1].Flags = 0; | |
} | |
// | |
// virtio-0.9.5, Appendix C, Packet Transmission | |
// | |
Dev->TxSharedReq->V0_9_5.Flags = 0; | |
Dev->TxSharedReq->V0_9_5.GsoType = VIRTIO_NET_HDR_GSO_NONE; | |
// | |
// For VirtIo 1.0 only -- the field exists, but it is unused | |
// | |
Dev->TxSharedReq->NumBuffers = 0; | |
// | |
// virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device | |
// | |
MemoryFence (); | |
Dev->TxLastUsed = *Dev->TxRing.Used.Idx; | |
ASSERT (Dev->TxLastUsed == 0); | |
// | |
// want no interrupt when a transmit completes | |
// | |
*Dev->TxRing.Avail.Flags = (UINT16)VRING_AVAIL_F_NO_INTERRUPT; | |
return EFI_SUCCESS; | |
FreeTxSharedReqBuffer: | |
Dev->VirtIo->FreeSharedPages ( | |
Dev->VirtIo, | |
EFI_SIZE_TO_PAGES (sizeof *(Dev->TxSharedReq)), | |
TxSharedReqBuffer | |
); | |
UninitTxBufCollection: | |
OrderedCollectionUninit (Dev->TxBufCollection); | |
FreeTxFreeStack: | |
FreePool (Dev->TxFreeStack); | |
return Status; | |
} | |
/** | |
Set up static scaffolding for the VirtioNetReceive() SNP method and enable | |
live device operation. | |
This function may only be called as VirtioNetInitialize()'s final step. | |
The structures laid out and resources configured include: | |
- destination area for the host to write virtio-net request headers and | |
packet data into, | |
- select polling over RX interrupt, | |
- fully populate the RX queue with a static pattern of virtio descriptor | |
chains. | |
@param[in,out] Dev The VNET_DEV driver instance about to enter the | |
EfiSimpleNetworkInitialized state. | |
@return Status codes from VIRTIO_CFG_WRITE() or | |
VIRTIO_DEVICE_PROTOCOL.AllocateSharedPages or | |
VirtioMapAllBytesInSharedBuffer(). | |
@retval EFI_SUCCESS RX setup successful. The device is live and may | |
already be writing to the receive area. | |
*/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
VirtioNetInitRx ( | |
IN OUT VNET_DEV *Dev | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN VirtioNetReqSize; | |
UINTN RxBufSize; | |
UINT16 RxAlwaysPending; | |
UINTN PktIdx; | |
UINT16 DescIdx; | |
UINTN NumBytes; | |
EFI_PHYSICAL_ADDRESS RxBufDeviceAddress; | |
VOID *RxBuffer; | |
// | |
// In VirtIo 1.0, the NumBuffers field is mandatory. In 0.9.5, it depends on | |
// VIRTIO_NET_F_MRG_RXBUF, which we never negotiate. | |
// | |
VirtioNetReqSize = (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) ? | |
sizeof (VIRTIO_NET_REQ) : | |
sizeof (VIRTIO_1_0_NET_REQ); | |
// | |
// For each incoming packet we must supply two descriptors: | |
// - the recipient for the virtio-net request header, plus | |
// - the recipient for the network data (which consists of Ethernet header | |
// and Ethernet payload). | |
// | |
RxBufSize = VirtioNetReqSize + | |
(Dev->Snm.MediaHeaderSize + Dev->Snm.MaxPacketSize); | |
// | |
// Limit the number of pending RX packets if the queue is big. The division | |
// by two is due to the above "two descriptors per packet" trait. | |
// | |
RxAlwaysPending = (UINT16)MIN (Dev->RxRing.QueueSize / 2, VNET_MAX_PENDING); | |
// | |
// The RxBuf is shared between guest and hypervisor, use | |
// AllocateSharedPages() to allocate this memory region and map it with | |
// BusMasterCommonBuffer so that it can be accessed by both guest and | |
// hypervisor. | |
// | |
NumBytes = RxAlwaysPending * RxBufSize; | |
Dev->RxBufNrPages = EFI_SIZE_TO_PAGES (NumBytes); | |
Status = Dev->VirtIo->AllocateSharedPages ( | |
Dev->VirtIo, | |
Dev->RxBufNrPages, | |
&RxBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
ZeroMem (RxBuffer, NumBytes); | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterCommonBuffer, | |
RxBuffer, | |
NumBytes, | |
&Dev->RxBufDeviceBase, | |
&Dev->RxBufMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeSharedBuffer; | |
} | |
Dev->RxBuf = RxBuffer; | |
// | |
// virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device | |
// | |
MemoryFence (); | |
Dev->RxLastUsed = *Dev->RxRing.Used.Idx; | |
ASSERT (Dev->RxLastUsed == 0); | |
// | |
// virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device: | |
// the host should not send interrupts, we'll poll in VirtioNetReceive() | |
// and VirtioNetIsPacketAvailable(). | |
// | |
*Dev->RxRing.Avail.Flags = (UINT16)VRING_AVAIL_F_NO_INTERRUPT; | |
// | |
// now set up a separate, two-part descriptor chain for each RX packet, and | |
// link each chain into (from) the available ring as well | |
// | |
DescIdx = 0; | |
RxBufDeviceAddress = Dev->RxBufDeviceBase; | |
for (PktIdx = 0; PktIdx < RxAlwaysPending; ++PktIdx) { | |
// | |
// virtio-0.9.5, 2.4.1.2 Updating the Available Ring | |
// invisible to the host until we update the Index Field | |
// | |
Dev->RxRing.Avail.Ring[PktIdx] = DescIdx; | |
// | |
// virtio-0.9.5, 2.4.1.1 Placing Buffers into the Descriptor Table | |
// | |
Dev->RxRing.Desc[DescIdx].Addr = RxBufDeviceAddress; | |
Dev->RxRing.Desc[DescIdx].Len = (UINT32)VirtioNetReqSize; | |
Dev->RxRing.Desc[DescIdx].Flags = VRING_DESC_F_WRITE | VRING_DESC_F_NEXT; | |
Dev->RxRing.Desc[DescIdx].Next = (UINT16)(DescIdx + 1); | |
RxBufDeviceAddress += Dev->RxRing.Desc[DescIdx++].Len; | |
Dev->RxRing.Desc[DescIdx].Addr = RxBufDeviceAddress; | |
Dev->RxRing.Desc[DescIdx].Len = (UINT32)(RxBufSize - VirtioNetReqSize); | |
Dev->RxRing.Desc[DescIdx].Flags = VRING_DESC_F_WRITE; | |
RxBufDeviceAddress += Dev->RxRing.Desc[DescIdx++].Len; | |
} | |
// | |
// virtio-0.9.5, 2.4.1.3 Updating the Index Field | |
// | |
MemoryFence (); | |
*Dev->RxRing.Avail.Idx = RxAlwaysPending; | |
// | |
// At this point reception may already be running. In order to make it sure, | |
// kick the hypervisor. If we fail to kick it, we must first abort reception | |
// before tearing down anything, because reception may have been already | |
// running even without the kick. | |
// | |
// virtio-0.9.5, 2.4.1.4 Notifying the Device | |
// | |
MemoryFence (); | |
Status = Dev->VirtIo->SetQueueNotify (Dev->VirtIo, VIRTIO_NET_Q_RX); | |
if (EFI_ERROR (Status)) { | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); | |
goto UnmapSharedBuffer; | |
} | |
return Status; | |
UnmapSharedBuffer: | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Dev->RxBufMap); | |
FreeSharedBuffer: | |
Dev->VirtIo->FreeSharedPages ( | |
Dev->VirtIo, | |
Dev->RxBufNrPages, | |
RxBuffer | |
); | |
return Status; | |
} | |
/** | |
Resets a network adapter and allocates the transmit and receive buffers | |
required by the network interface; optionally, also requests allocation of | |
additional transmit and receive buffers. | |
@param This The protocol instance pointer. | |
@param ExtraRxBufferSize The size, in bytes, of the extra receive buffer | |
space that the driver should allocate for the | |
network interface. Some network interfaces will not | |
be able to use the extra buffer, and the caller | |
will not know if it is actually being used. | |
@param ExtraTxBufferSize The size, in bytes, of the extra transmit buffer | |
space that the driver should allocate for the | |
network interface. Some network interfaces will not | |
be able to use the extra buffer, and the caller | |
will not know if it is actually being used. | |
@retval EFI_SUCCESS The network interface was initialized. | |
@retval EFI_NOT_STARTED The network interface has not been started. | |
@retval EFI_OUT_OF_RESOURCES There was not enough memory for the transmit | |
and receive buffers. | |
@retval EFI_INVALID_PARAMETER One or more of the parameters has an | |
unsupported value. | |
@retval EFI_DEVICE_ERROR The command could not be sent to the network | |
interface. | |
@retval EFI_UNSUPPORTED This function is not supported by the network | |
interface. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
VirtioNetInitialize ( | |
IN EFI_SIMPLE_NETWORK_PROTOCOL *This, | |
IN UINTN ExtraRxBufferSize OPTIONAL, | |
IN UINTN ExtraTxBufferSize OPTIONAL | |
) | |
{ | |
VNET_DEV *Dev; | |
EFI_TPL OldTpl; | |
EFI_STATUS Status; | |
UINT8 NextDevStat; | |
UINT64 Features; | |
if (This == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((ExtraRxBufferSize > 0) || (ExtraTxBufferSize > 0)) { | |
return EFI_UNSUPPORTED; | |
} | |
Dev = VIRTIO_NET_FROM_SNP (This); | |
OldTpl = gBS->RaiseTPL (TPL_CALLBACK); | |
if (Dev->Snm.State != EfiSimpleNetworkStarted) { | |
Status = EFI_NOT_STARTED; | |
goto InitFailed; | |
} | |
// | |
// In the EfiSimpleNetworkStarted state the virtio-net device has status | |
// value 0 (= reset) -- see the state diagram, the full call chain to | |
// the end of VirtioNetGetFeatures() (considering we're here now), | |
// the DeviceFailed label below, and VirtioNetShutdown(). | |
// | |
// Accordingly, the below is a subsequence of the steps found in the | |
// virtio-0.9.5 spec, 2.2.1 Device Initialization Sequence. | |
// | |
NextDevStat = VSTAT_ACK; // step 2 -- acknowledge device presence | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto InitFailed; | |
} | |
NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto DeviceFailed; | |
} | |
// | |
// Set Page Size - MMIO VirtIo Specific | |
// | |
Status = Dev->VirtIo->SetPageSize (Dev->VirtIo, EFI_PAGE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto DeviceFailed; | |
} | |
// | |
// step 4a -- retrieve features. Note that we're past validating required | |
// features in VirtioNetGetFeatures(). | |
// | |
Status = Dev->VirtIo->GetDeviceFeatures (Dev->VirtIo, &Features); | |
if (EFI_ERROR (Status)) { | |
goto DeviceFailed; | |
} | |
ASSERT (Features & VIRTIO_NET_F_MAC); | |
ASSERT ( | |
Dev->Snm.MediaPresentSupported == | |
!!(Features & VIRTIO_NET_F_STATUS) | |
); | |
Features &= VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS | VIRTIO_F_VERSION_1 | | |
VIRTIO_F_IOMMU_PLATFORM; | |
// | |
// In virtio-1.0, feature negotiation is expected to complete before queue | |
// discovery, and the device can also reject the selected set of features. | |
// | |
if (Dev->VirtIo->Revision >= VIRTIO_SPEC_REVISION (1, 0, 0)) { | |
Status = Virtio10WriteFeatures (Dev->VirtIo, Features, &NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto DeviceFailed; | |
} | |
} | |
// | |
// step 4b, 4c -- allocate and report virtqueues | |
// | |
Status = VirtioNetInitRing ( | |
Dev, | |
VIRTIO_NET_Q_RX, | |
&Dev->RxRing, | |
&Dev->RxRingMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto DeviceFailed; | |
} | |
Status = VirtioNetInitRing ( | |
Dev, | |
VIRTIO_NET_Q_TX, | |
&Dev->TxRing, | |
&Dev->TxRingMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseRxRing; | |
} | |
// | |
// step 5 -- keep only the features we want | |
// | |
if (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) { | |
Features &= ~(UINT64)(VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM); | |
Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, Features); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseTxRing; | |
} | |
} | |
// | |
// step 6 -- virtio-net initialization complete | |
// | |
NextDevStat |= VSTAT_DRIVER_OK; | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseTxRing; | |
} | |
Status = VirtioNetInitTx (Dev); | |
if (EFI_ERROR (Status)) { | |
goto AbortDevice; | |
} | |
// | |
// start receiving | |
// | |
Status = VirtioNetInitRx (Dev); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseTxAux; | |
} | |
Dev->Snm.State = EfiSimpleNetworkInitialized; | |
gBS->RestoreTPL (OldTpl); | |
return EFI_SUCCESS; | |
ReleaseTxAux: | |
VirtioNetShutdownTx (Dev); | |
AbortDevice: | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); | |
ReleaseTxRing: | |
VirtioNetUninitRing (Dev, &Dev->TxRing, Dev->TxRingMap); | |
ReleaseRxRing: | |
VirtioNetUninitRing (Dev, &Dev->RxRing, Dev->RxRingMap); | |
DeviceFailed: | |
// | |
// restore device status invariant for the EfiSimpleNetworkStarted state | |
// | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); | |
InitFailed: | |
gBS->RestoreTPL (OldTpl); | |
return Status; | |
} |