audk/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c

495 lines
13 KiB
C

/** @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/MemEncryptTdxLib.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 () || (MemEncryptTdxIsEnabled ()))) {
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,
"QemuFwCfgDma %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 or TDX is enabled, map Buffer to DMA address before issuing the DMA
// request
//
if (MemEncryptSevIsEnabled () || MemEncryptTdxIsEnabled ()) {
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);
}
}