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