/** @file | |
Stateful and implicitly initialized fw_cfg library implementation. | |
Copyright (C) 2013, Red Hat, Inc. | |
Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) 2017, Advanced Micro Devices. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Uefi.h> | |
#include <Protocol/IoMmu.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/IoLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/QemuFwCfgLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/MemEncryptSevLib.h> | |
#include "QemuFwCfgLibInternal.h" | |
STATIC BOOLEAN mQemuFwCfgSupported = FALSE; | |
STATIC BOOLEAN mQemuFwCfgDmaSupported; | |
STATIC EDKII_IOMMU_PROTOCOL *mIoMmuProtocol; | |
/** | |
Returns a boolean indicating if the firmware configuration interface | |
is available or not. | |
This function may change fw_cfg state. | |
@retval TRUE The interface is available | |
@retval FALSE The interface is not available | |
**/ | |
BOOLEAN | |
EFIAPI | |
QemuFwCfgIsAvailable ( | |
VOID | |
) | |
{ | |
return InternalQemuFwCfgIsAvailable (); | |
} | |
RETURN_STATUS | |
EFIAPI | |
QemuFwCfgInitialize ( | |
VOID | |
) | |
{ | |
UINT32 Signature; | |
UINT32 Revision; | |
// | |
// Enable the access routines while probing to see if it is supported. | |
// For probing we always use the IO Port (IoReadFifo8()) access method. | |
// | |
mQemuFwCfgSupported = TRUE; | |
mQemuFwCfgDmaSupported = FALSE; | |
QemuFwCfgSelectItem (QemuFwCfgItemSignature); | |
Signature = QemuFwCfgRead32 (); | |
DEBUG ((DEBUG_INFO, "FW CFG Signature: 0x%x\n", Signature)); | |
QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion); | |
Revision = QemuFwCfgRead32 (); | |
DEBUG ((DEBUG_INFO, "FW CFG Revision: 0x%x\n", Revision)); | |
if ((Signature != SIGNATURE_32 ('Q', 'E', 'M', 'U')) || | |
(Revision < 1) | |
) { | |
DEBUG ((DEBUG_INFO, "QemuFwCfg interface not supported.\n")); | |
mQemuFwCfgSupported = FALSE; | |
return RETURN_SUCCESS; | |
} | |
if ((Revision & FW_CFG_F_DMA) == 0) { | |
DEBUG ((DEBUG_INFO, "QemuFwCfg interface (IO Port) is supported.\n")); | |
} else { | |
mQemuFwCfgDmaSupported = TRUE; | |
DEBUG ((DEBUG_INFO, "QemuFwCfg interface (DMA) is supported.\n")); | |
} | |
if (mQemuFwCfgDmaSupported && MemEncryptSevIsEnabled ()) { | |
EFI_STATUS Status; | |
// | |
// IoMmuDxe driver must have installed the IOMMU protocol. If we are not | |
// able to locate the protocol then something must have gone wrong. | |
// | |
Status = gBS->LocateProtocol (&gEdkiiIoMmuProtocolGuid, NULL, | |
(VOID **)&mIoMmuProtocol); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"QemuFwCfgSevDma %a:%a Failed to locate IOMMU protocol.\n", | |
gEfiCallerBaseName, __FUNCTION__)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
} | |
return RETURN_SUCCESS; | |
} | |
/** | |
Returns a boolean indicating if the firmware configuration interface is | |
available for library-internal purposes. | |
This function never changes fw_cfg state. | |
@retval TRUE The interface is available internally. | |
@retval FALSE The interface is not available internally. | |
**/ | |
BOOLEAN | |
InternalQemuFwCfgIsAvailable ( | |
VOID | |
) | |
{ | |
return mQemuFwCfgSupported; | |
} | |
/** | |
Returns a boolean indicating whether QEMU provides the DMA-like access method | |
for fw_cfg. | |
@retval TRUE The DMA-like access method is available. | |
@retval FALSE The DMA-like access method is unavailable. | |
**/ | |
BOOLEAN | |
InternalQemuFwCfgDmaIsAvailable ( | |
VOID | |
) | |
{ | |
return mQemuFwCfgDmaSupported; | |
} | |
/** | |
Function is used for allocating a bi-directional FW_CFG_DMA_ACCESS used | |
between Host and device to exchange the information. The buffer must be free'd | |
using FreeFwCfgDmaAccessBuffer (). | |
**/ | |
STATIC | |
VOID | |
AllocFwCfgDmaAccessBuffer ( | |
OUT VOID **Access, | |
OUT VOID **MapInfo | |
) | |
{ | |
UINTN Size; | |
UINTN NumPages; | |
EFI_STATUS Status; | |
VOID *HostAddress; | |
EFI_PHYSICAL_ADDRESS DmaAddress; | |
VOID *Mapping; | |
Size = sizeof (FW_CFG_DMA_ACCESS); | |
NumPages = EFI_SIZE_TO_PAGES (Size); | |
// | |
// As per UEFI spec, in order to map a host address with | |
// BusMasterCommonBuffer64, the buffer must be allocated using the IOMMU | |
// AllocateBuffer() | |
// | |
Status = mIoMmuProtocol->AllocateBuffer ( | |
mIoMmuProtocol, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
NumPages, | |
&HostAddress, | |
EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to allocate FW_CFG_DMA_ACCESS\n", gEfiCallerBaseName, | |
__FUNCTION__)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
// | |
// Avoid exposing stale data even temporarily: zero the area before mapping | |
// it. | |
// | |
ZeroMem (HostAddress, Size); | |
// | |
// Map the host buffer with BusMasterCommonBuffer64 | |
// | |
Status = mIoMmuProtocol->Map ( | |
mIoMmuProtocol, | |
EdkiiIoMmuOperationBusMasterCommonBuffer64, | |
HostAddress, | |
&Size, | |
&DmaAddress, | |
&Mapping | |
); | |
if (EFI_ERROR (Status)) { | |
mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, HostAddress); | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to Map() FW_CFG_DMA_ACCESS\n", gEfiCallerBaseName, | |
__FUNCTION__)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
if (Size < sizeof (FW_CFG_DMA_ACCESS)) { | |
mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping); | |
mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, HostAddress); | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to Map() - requested 0x%Lx got 0x%Lx\n", gEfiCallerBaseName, | |
__FUNCTION__, (UINT64)sizeof (FW_CFG_DMA_ACCESS), (UINT64)Size)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
*Access = HostAddress; | |
*MapInfo = Mapping; | |
} | |
/** | |
Function is to used for freeing the Access buffer allocated using | |
AllocFwCfgDmaAccessBuffer() | |
**/ | |
STATIC | |
VOID | |
FreeFwCfgDmaAccessBuffer ( | |
IN VOID *Access, | |
IN VOID *Mapping | |
) | |
{ | |
UINTN NumPages; | |
EFI_STATUS Status; | |
NumPages = EFI_SIZE_TO_PAGES (sizeof (FW_CFG_DMA_ACCESS)); | |
Status = mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to UnMap() Mapping 0x%Lx\n", gEfiCallerBaseName, | |
__FUNCTION__, (UINT64)(UINTN)Mapping)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
Status = mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, Access); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to Free() 0x%Lx\n", gEfiCallerBaseName, __FUNCTION__, | |
(UINT64)(UINTN)Access)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
} | |
/** | |
Function is used for mapping host address to device address. The buffer must | |
be unmapped with UnmapDmaDataBuffer (). | |
**/ | |
STATIC | |
VOID | |
MapFwCfgDmaDataBuffer ( | |
IN BOOLEAN IsWrite, | |
IN VOID *HostAddress, | |
IN UINT32 Size, | |
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, | |
OUT VOID **MapInfo | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN NumberOfBytes; | |
VOID *Mapping; | |
EFI_PHYSICAL_ADDRESS PhysicalAddress; | |
NumberOfBytes = Size; | |
Status = mIoMmuProtocol->Map ( | |
mIoMmuProtocol, | |
(IsWrite ? | |
EdkiiIoMmuOperationBusMasterRead64 : | |
EdkiiIoMmuOperationBusMasterWrite64), | |
HostAddress, | |
&NumberOfBytes, | |
&PhysicalAddress, | |
&Mapping | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to Map() Address 0x%Lx Size 0x%Lx\n", gEfiCallerBaseName, | |
__FUNCTION__, (UINT64)(UINTN)HostAddress, (UINT64)Size)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
if (NumberOfBytes < Size) { | |
mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping); | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to Map() - requested 0x%x got 0x%Lx\n", gEfiCallerBaseName, | |
__FUNCTION__, Size, (UINT64)NumberOfBytes)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
*DeviceAddress = PhysicalAddress; | |
*MapInfo = Mapping; | |
} | |
STATIC | |
VOID | |
UnmapFwCfgDmaDataBuffer ( | |
IN VOID *Mapping | |
) | |
{ | |
EFI_STATUS Status; | |
Status = mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, | |
"%a:%a failed to UnMap() Mapping 0x%Lx\n", gEfiCallerBaseName, | |
__FUNCTION__, (UINT64)(UINTN)Mapping)); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
} | |
/** | |
Transfer an array of bytes, or skip a number of bytes, using the DMA | |
interface. | |
@param[in] Size Size in bytes to transfer or skip. | |
@param[in,out] Buffer Buffer to read data into or write data from. Ignored, | |
and may be NULL, if Size is zero, or Control is | |
FW_CFG_DMA_CTL_SKIP. | |
@param[in] Control One of the following: | |
FW_CFG_DMA_CTL_WRITE - write to fw_cfg from Buffer. | |
FW_CFG_DMA_CTL_READ - read from fw_cfg into Buffer. | |
FW_CFG_DMA_CTL_SKIP - skip bytes in fw_cfg. | |
**/ | |
VOID | |
InternalQemuFwCfgDmaBytes ( | |
IN UINT32 Size, | |
IN OUT VOID *Buffer OPTIONAL, | |
IN UINT32 Control | |
) | |
{ | |
volatile FW_CFG_DMA_ACCESS LocalAccess; | |
volatile FW_CFG_DMA_ACCESS *Access; | |
UINT32 AccessHigh, AccessLow; | |
UINT32 Status; | |
VOID *AccessMapping, *DataMapping; | |
VOID *DataBuffer; | |
ASSERT (Control == FW_CFG_DMA_CTL_WRITE || Control == FW_CFG_DMA_CTL_READ || | |
Control == FW_CFG_DMA_CTL_SKIP); | |
if (Size == 0) { | |
return; | |
} | |
Access = &LocalAccess; | |
AccessMapping = NULL; | |
DataMapping = NULL; | |
DataBuffer = Buffer; | |
// | |
// When SEV is enabled, map Buffer to DMA address before issuing the DMA | |
// request | |
// | |
if (MemEncryptSevIsEnabled ()) { | |
VOID *AccessBuffer; | |
EFI_PHYSICAL_ADDRESS DataBufferAddress; | |
// | |
// Allocate DMA Access buffer | |
// | |
AllocFwCfgDmaAccessBuffer (&AccessBuffer, &AccessMapping); | |
Access = AccessBuffer; | |
// | |
// Map actual data buffer | |
// | |
if (Control != FW_CFG_DMA_CTL_SKIP) { | |
MapFwCfgDmaDataBuffer ( | |
Control == FW_CFG_DMA_CTL_WRITE, | |
Buffer, | |
Size, | |
&DataBufferAddress, | |
&DataMapping | |
); | |
DataBuffer = (VOID *) (UINTN) DataBufferAddress; | |
} | |
} | |
Access->Control = SwapBytes32 (Control); | |
Access->Length = SwapBytes32 (Size); | |
Access->Address = SwapBytes64 ((UINTN)DataBuffer); | |
// | |
// Delimit the transfer from (a) modifications to Access, (b) in case of a | |
// write, from writes to Buffer by the caller. | |
// | |
MemoryFence (); | |
// | |
// Start the transfer. | |
// | |
AccessHigh = (UINT32)RShiftU64 ((UINTN)Access, 32); | |
AccessLow = (UINT32)(UINTN)Access; | |
IoWrite32 (FW_CFG_IO_DMA_ADDRESS, SwapBytes32 (AccessHigh)); | |
IoWrite32 (FW_CFG_IO_DMA_ADDRESS + 4, SwapBytes32 (AccessLow)); | |
// | |
// Don't look at Access.Control before starting the transfer. | |
// | |
MemoryFence (); | |
// | |
// Wait for the transfer to complete. | |
// | |
do { | |
Status = SwapBytes32 (Access->Control); | |
ASSERT ((Status & FW_CFG_DMA_CTL_ERROR) == 0); | |
} while (Status != 0); | |
// | |
// After a read, the caller will want to use Buffer. | |
// | |
MemoryFence (); | |
// | |
// If Access buffer was dynamically allocated then free it. | |
// | |
if (AccessMapping != NULL) { | |
FreeFwCfgDmaAccessBuffer ((VOID *)Access, AccessMapping); | |
} | |
// | |
// If DataBuffer was mapped then unmap it. | |
// | |
if (DataMapping != NULL) { | |
UnmapFwCfgDmaDataBuffer (DataMapping); | |
} | |
} |