/** @file Stateful and implicitly initialized fw_cfg library implementation. Copyright (C) 2013, Red Hat, Inc. Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.
Copyright (c) 2017, Advanced Micro Devices. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #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); } }