There's a small window between - AllocFwCfgDmaAccessBuffer() mapping the new FW_CFG_DMA_ACCESS object for common buffer operation (i.e., decrypting it), and - InternalQemuFwCfgDmaBytes() setting the fields of the object. In this window, earlier garbage in the object is "leaked" to the hypervisor. So zero the object before we decrypt it. (This commit message references AMD SEV directly, because QemuFwCfgDxeLib is not *generally* enabled for IOMMU operation just yet, unlike our goal for the virtio infrastructure. Instead, QemuFwCfgDxeLib uses MemEncryptSevLib explicitly to detect SEV, and then relies on IOMMU protocol behavior that is specific to SEV. At this point, this is by design.) Cc: Brijesh Singh <brijesh.singh@amd.com> Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Tom Lendacky <thomas.lendacky@amd.com> Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Reviewed-by: Jordan Justen <jordan.l.justen@intel.com> Reviewed-by: Brijesh Singh <brijesh.singh@amd.com>
460 lines
12 KiB
C
460 lines
12 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>
|
|
|
|
This program and the accompanying materials are licensed and made available
|
|
under the terms and conditions of the BSD License which accompanies this
|
|
distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
|
|
WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
**/
|
|
|
|
#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 ((EFI_D_INFO, "FW CFG Signature: 0x%x\n", Signature));
|
|
QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion);
|
|
Revision = QemuFwCfgRead32 ();
|
|
DEBUG ((EFI_D_INFO, "FW CFG Revision: 0x%x\n", Revision));
|
|
if ((Signature != SIGNATURE_32 ('Q', 'E', 'M', 'U')) ||
|
|
(Revision < 1)
|
|
) {
|
|
DEBUG ((EFI_D_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
|
|
// BusMasterCommomBuffer64, 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);
|
|
}
|
|
}
|