| /** @file | |
| Stateful and implicitly initialized fw_cfg library implementation. | |
| Copyright (C) 2013 - 2014, Red Hat, Inc. | |
| Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR> | |
| (C) Copyright 2021 Hewlett Packard Enterprise Development LP<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Uefi.h> | |
| #include <Library/BaseLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/IoLib.h> | |
| #include <Library/QemuFwCfgLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Protocol/FdtClient.h> | |
| STATIC UINTN mFwCfgSelectorAddress; | |
| STATIC UINTN mFwCfgDataAddress; | |
| STATIC UINTN mFwCfgDmaAddress; | |
| /** | |
| Reads firmware configuration bytes into a buffer | |
| @param[in] Size Size in bytes to read | |
| @param[in] Buffer Buffer to store data into (OPTIONAL if Size is 0) | |
| **/ | |
| typedef | |
| VOID(EFIAPI READ_BYTES_FUNCTION)( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ); | |
| /** | |
| Writes bytes from a buffer to firmware configuration | |
| @param[in] Size Size in bytes to write | |
| @param[in] Buffer Buffer to transfer data from (OPTIONAL if Size is 0) | |
| **/ | |
| typedef | |
| VOID(EFIAPI WRITE_BYTES_FUNCTION)( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ); | |
| /** | |
| Skips bytes in firmware configuration | |
| @param[in] Size Size in bytes to skip | |
| **/ | |
| typedef | |
| VOID(EFIAPI SKIP_BYTES_FUNCTION)( | |
| IN UINTN Size | |
| ); | |
| // | |
| // Forward declaration of the two implementations we have. | |
| // | |
| STATIC READ_BYTES_FUNCTION MmioReadBytes; | |
| STATIC WRITE_BYTES_FUNCTION MmioWriteBytes; | |
| STATIC SKIP_BYTES_FUNCTION MmioSkipBytes; | |
| STATIC READ_BYTES_FUNCTION DmaReadBytes; | |
| STATIC WRITE_BYTES_FUNCTION DmaWriteBytes; | |
| STATIC SKIP_BYTES_FUNCTION DmaSkipBytes; | |
| // | |
| // These correspond to the implementation we detect at runtime. | |
| // | |
| STATIC READ_BYTES_FUNCTION *InternalQemuFwCfgReadBytes = MmioReadBytes; | |
| STATIC WRITE_BYTES_FUNCTION *InternalQemuFwCfgWriteBytes = MmioWriteBytes; | |
| STATIC SKIP_BYTES_FUNCTION *InternalQemuFwCfgSkipBytes = MmioSkipBytes; | |
| /** | |
| 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 (BOOLEAN)(mFwCfgSelectorAddress != 0 && mFwCfgDataAddress != 0); | |
| } | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgInitialize ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| FDT_CLIENT_PROTOCOL *FdtClient; | |
| CONST UINT64 *Reg; | |
| UINT32 RegSize; | |
| UINTN AddressCells, SizeCells; | |
| UINT64 FwCfgSelectorAddress; | |
| UINT64 FwCfgSelectorSize; | |
| UINT64 FwCfgDataAddress; | |
| UINT64 FwCfgDataSize; | |
| UINT64 FwCfgDmaAddress; | |
| UINT64 FwCfgDmaSize; | |
| Status = gBS->LocateProtocol ( | |
| &gFdtClientProtocolGuid, | |
| NULL, | |
| (VOID **)&FdtClient | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| Status = FdtClient->FindCompatibleNodeReg ( | |
| FdtClient, | |
| "qemu,fw-cfg-mmio", | |
| (CONST VOID **)&Reg, | |
| &AddressCells, | |
| &SizeCells, | |
| &RegSize | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_WARN, | |
| "%a: No 'qemu,fw-cfg-mmio' compatible DT node found (Status == %r)\n", | |
| __func__, | |
| Status | |
| )); | |
| return EFI_SUCCESS; | |
| } | |
| ASSERT (AddressCells == 2); | |
| ASSERT (SizeCells == 2); | |
| ASSERT (RegSize == 2 * sizeof (UINT64)); | |
| FwCfgDataAddress = SwapBytes64 (Reg[0]); | |
| FwCfgDataSize = 8; | |
| FwCfgSelectorAddress = FwCfgDataAddress + FwCfgDataSize; | |
| FwCfgSelectorSize = 2; | |
| // | |
| // The following ASSERT()s express | |
| // | |
| // Address + Size - 1 <= MAX_UINTN | |
| // | |
| // for both registers, that is, that the last byte in each MMIO range is | |
| // expressible as a MAX_UINTN. The form below is mathematically | |
| // equivalent, and it also prevents any unsigned overflow before the | |
| // comparison. | |
| // | |
| ASSERT (FwCfgSelectorAddress <= MAX_UINTN - FwCfgSelectorSize + 1); | |
| ASSERT (FwCfgDataAddress <= MAX_UINTN - FwCfgDataSize + 1); | |
| mFwCfgSelectorAddress = FwCfgSelectorAddress; | |
| mFwCfgDataAddress = FwCfgDataAddress; | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "Found FwCfg @ 0x%Lx/0x%Lx\n", | |
| FwCfgSelectorAddress, | |
| FwCfgDataAddress | |
| )); | |
| if (SwapBytes64 (Reg[1]) >= 0x18) { | |
| FwCfgDmaAddress = FwCfgDataAddress + 0x10; | |
| FwCfgDmaSize = 0x08; | |
| // | |
| // See explanation above. | |
| // | |
| ASSERT (FwCfgDmaAddress <= MAX_UINTN - FwCfgDmaSize + 1); | |
| DEBUG ((DEBUG_INFO, "Found FwCfg DMA @ 0x%Lx\n", FwCfgDmaAddress)); | |
| } else { | |
| FwCfgDmaAddress = 0; | |
| } | |
| if (QemuFwCfgIsAvailable ()) { | |
| UINT32 Signature; | |
| QemuFwCfgSelectItem (QemuFwCfgItemSignature); | |
| Signature = QemuFwCfgRead32 (); | |
| if (Signature == SIGNATURE_32 ('Q', 'E', 'M', 'U')) { | |
| // | |
| // For DMA support, we require the DTB to advertise the register, and the | |
| // feature bitmap (which we read without DMA) to confirm the feature. | |
| // | |
| if (FwCfgDmaAddress != 0) { | |
| UINT32 Features; | |
| QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion); | |
| Features = QemuFwCfgRead32 (); | |
| if ((Features & FW_CFG_F_DMA) != 0) { | |
| mFwCfgDmaAddress = FwCfgDmaAddress; | |
| InternalQemuFwCfgReadBytes = DmaReadBytes; | |
| InternalQemuFwCfgWriteBytes = DmaWriteBytes; | |
| InternalQemuFwCfgSkipBytes = DmaSkipBytes; | |
| } | |
| } | |
| } else { | |
| mFwCfgSelectorAddress = 0; | |
| mFwCfgDataAddress = 0; | |
| } | |
| } | |
| return RETURN_SUCCESS; | |
| } | |
| /** | |
| Selects a firmware configuration item for reading. | |
| Following this call, any data read from this item will start from the | |
| beginning of the configuration item's data. | |
| @param[in] QemuFwCfgItem Firmware Configuration item to read | |
| **/ | |
| VOID | |
| EFIAPI | |
| QemuFwCfgSelectItem ( | |
| IN FIRMWARE_CONFIG_ITEM QemuFwCfgItem | |
| ) | |
| { | |
| if (QemuFwCfgIsAvailable ()) { | |
| MmioWrite16 (mFwCfgSelectorAddress, SwapBytes16 ((UINT16)QemuFwCfgItem)); | |
| } | |
| } | |
| /** | |
| Slow READ_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| MmioReadBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ) | |
| { | |
| UINTN Left; | |
| UINT8 *Ptr; | |
| UINT8 *End; | |
| #if defined (MDE_CPU_AARCH64) || defined (MDE_CPU_RISCV64) | |
| Left = Size & 7; | |
| #else | |
| Left = Size & 3; | |
| #endif | |
| Size -= Left; | |
| Ptr = Buffer; | |
| End = Ptr + Size; | |
| #if defined (MDE_CPU_AARCH64) || defined (MDE_CPU_RISCV64) | |
| while (Ptr < End) { | |
| *(UINT64 *)Ptr = MmioRead64 (mFwCfgDataAddress); | |
| Ptr += 8; | |
| } | |
| if (Left & 4) { | |
| *(UINT32 *)Ptr = MmioRead32 (mFwCfgDataAddress); | |
| Ptr += 4; | |
| } | |
| #else | |
| while (Ptr < End) { | |
| *(UINT32 *)Ptr = MmioRead32 (mFwCfgDataAddress); | |
| Ptr += 4; | |
| } | |
| #endif | |
| if (Left & 2) { | |
| *(UINT16 *)Ptr = MmioRead16 (mFwCfgDataAddress); | |
| Ptr += 2; | |
| } | |
| if (Left & 1) { | |
| *Ptr = MmioRead8 (mFwCfgDataAddress); | |
| } | |
| } | |
| /** | |
| 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. | |
| **/ | |
| STATIC | |
| VOID | |
| DmaTransferBytes ( | |
| IN UINTN Size, | |
| IN OUT VOID *Buffer OPTIONAL, | |
| IN UINT32 Control | |
| ) | |
| { | |
| volatile FW_CFG_DMA_ACCESS Access; | |
| UINT32 Status; | |
| ASSERT ( | |
| Control == FW_CFG_DMA_CTL_WRITE || Control == FW_CFG_DMA_CTL_READ || | |
| Control == FW_CFG_DMA_CTL_SKIP | |
| ); | |
| if (Size == 0) { | |
| return; | |
| } | |
| ASSERT (Size <= MAX_UINT32); | |
| Access.Control = SwapBytes32 (Control); | |
| Access.Length = SwapBytes32 ((UINT32)Size); | |
| Access.Address = SwapBytes64 ((UINT64)(UINTN)Buffer); | |
| // | |
| // We shouldn't start the transfer before setting up Access. | |
| // | |
| MemoryFence (); | |
| // | |
| // This will fire off the transfer. | |
| // | |
| #if defined (MDE_CPU_AARCH64) || defined (MDE_CPU_RISCV64) | |
| MmioWrite64 (mFwCfgDmaAddress, SwapBytes64 ((UINT64)&Access)); | |
| #else | |
| MmioWrite32 ((UINT32)(mFwCfgDmaAddress + 4), SwapBytes32 ((UINT32)&Access)); | |
| #endif | |
| // | |
| // We shouldn't look at Access.Control before starting the transfer. | |
| // | |
| MemoryFence (); | |
| do { | |
| Status = SwapBytes32 (Access.Control); | |
| ASSERT ((Status & FW_CFG_DMA_CTL_ERROR) == 0); | |
| } while (Status != 0); | |
| // | |
| // The caller will want to access the transferred data. | |
| // | |
| MemoryFence (); | |
| } | |
| /** | |
| Fast READ_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| DmaReadBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ) | |
| { | |
| DmaTransferBytes (Size, Buffer, FW_CFG_DMA_CTL_READ); | |
| } | |
| /** | |
| Reads firmware configuration bytes into a buffer | |
| If called multiple times, then the data read will continue at the offset of | |
| the firmware configuration item where the previous read ended. | |
| @param[in] Size Size in bytes to read | |
| @param[in] Buffer Buffer to store data into | |
| **/ | |
| VOID | |
| EFIAPI | |
| QemuFwCfgReadBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| if (QemuFwCfgIsAvailable ()) { | |
| InternalQemuFwCfgReadBytes (Size, Buffer); | |
| } else { | |
| ZeroMem (Buffer, Size); | |
| } | |
| } | |
| /** | |
| Slow WRITE_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| MmioWriteBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ) | |
| { | |
| UINTN Idx; | |
| for (Idx = 0; Idx < Size; ++Idx) { | |
| MmioWrite8 (mFwCfgDataAddress, ((UINT8 *)Buffer)[Idx]); | |
| } | |
| } | |
| /** | |
| Fast WRITE_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| DmaWriteBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer OPTIONAL | |
| ) | |
| { | |
| DmaTransferBytes (Size, Buffer, FW_CFG_DMA_CTL_WRITE); | |
| } | |
| /** | |
| Write firmware configuration bytes from a buffer | |
| If called multiple times, then the data written will continue at the offset | |
| of the firmware configuration item where the previous write ended. | |
| @param[in] Size Size in bytes to write | |
| @param[in] Buffer Buffer to read data from | |
| **/ | |
| VOID | |
| EFIAPI | |
| QemuFwCfgWriteBytes ( | |
| IN UINTN Size, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| if (QemuFwCfgIsAvailable ()) { | |
| InternalQemuFwCfgWriteBytes (Size, Buffer); | |
| } | |
| } | |
| /** | |
| Slow SKIP_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| MmioSkipBytes ( | |
| IN UINTN Size | |
| ) | |
| { | |
| UINTN ChunkSize; | |
| UINT8 SkipBuffer[256]; | |
| // | |
| // Emulate the skip by reading data in chunks, and throwing it away. The | |
| // implementation below doesn't affect the static data footprint for client | |
| // modules. Large skips are not expected, therefore this fallback is not | |
| // performance critical. The size of SkipBuffer is thought not to exert a | |
| // large pressure on the stack. | |
| // | |
| while (Size > 0) { | |
| ChunkSize = MIN (Size, sizeof SkipBuffer); | |
| MmioReadBytes (ChunkSize, SkipBuffer); | |
| Size -= ChunkSize; | |
| } | |
| } | |
| /** | |
| Fast SKIP_BYTES_FUNCTION. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| DmaSkipBytes ( | |
| IN UINTN Size | |
| ) | |
| { | |
| DmaTransferBytes (Size, NULL, FW_CFG_DMA_CTL_SKIP); | |
| } | |
| /** | |
| Skip bytes in the firmware configuration item. | |
| Increase the offset of the firmware configuration item without transferring | |
| bytes between the item and a caller-provided buffer. Subsequent read, write | |
| or skip operations will commence at the increased offset. | |
| @param[in] Size Number of bytes to skip. | |
| **/ | |
| VOID | |
| EFIAPI | |
| QemuFwCfgSkipBytes ( | |
| IN UINTN Size | |
| ) | |
| { | |
| if (QemuFwCfgIsAvailable ()) { | |
| InternalQemuFwCfgSkipBytes (Size); | |
| } | |
| } | |
| /** | |
| Reads a UINT8 firmware configuration value | |
| @return Value of Firmware Configuration item read | |
| **/ | |
| UINT8 | |
| EFIAPI | |
| QemuFwCfgRead8 ( | |
| VOID | |
| ) | |
| { | |
| UINT8 Result; | |
| QemuFwCfgReadBytes (sizeof Result, &Result); | |
| return Result; | |
| } | |
| /** | |
| Reads a UINT16 firmware configuration value | |
| @return Value of Firmware Configuration item read | |
| **/ | |
| UINT16 | |
| EFIAPI | |
| QemuFwCfgRead16 ( | |
| VOID | |
| ) | |
| { | |
| UINT16 Result; | |
| QemuFwCfgReadBytes (sizeof Result, &Result); | |
| return Result; | |
| } | |
| /** | |
| Reads a UINT32 firmware configuration value | |
| @return Value of Firmware Configuration item read | |
| **/ | |
| UINT32 | |
| EFIAPI | |
| QemuFwCfgRead32 ( | |
| VOID | |
| ) | |
| { | |
| UINT32 Result; | |
| QemuFwCfgReadBytes (sizeof Result, &Result); | |
| return Result; | |
| } | |
| /** | |
| Reads a UINT64 firmware configuration value | |
| @return Value of Firmware Configuration item read | |
| **/ | |
| UINT64 | |
| EFIAPI | |
| QemuFwCfgRead64 ( | |
| VOID | |
| ) | |
| { | |
| UINT64 Result; | |
| QemuFwCfgReadBytes (sizeof Result, &Result); | |
| return Result; | |
| } | |
| /** | |
| Find the configuration item corresponding to the firmware configuration file. | |
| @param[in] Name Name of file to look up. | |
| @param[out] Item Configuration item corresponding to the file, to be passed | |
| to QemuFwCfgSelectItem (). | |
| @param[out] Size Number of bytes in the file. | |
| @retval RETURN_SUCCESS If file is found. | |
| @retval RETURN_NOT_FOUND If file is not found. | |
| @retval RETURN_UNSUPPORTED If firmware configuration is unavailable. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgFindFile ( | |
| IN CONST CHAR8 *Name, | |
| OUT FIRMWARE_CONFIG_ITEM *Item, | |
| OUT UINTN *Size | |
| ) | |
| { | |
| UINT32 Count; | |
| UINT32 Idx; | |
| if (!QemuFwCfgIsAvailable ()) { | |
| return RETURN_UNSUPPORTED; | |
| } | |
| QemuFwCfgSelectItem (QemuFwCfgItemFileDir); | |
| Count = SwapBytes32 (QemuFwCfgRead32 ()); | |
| for (Idx = 0; Idx < Count; ++Idx) { | |
| UINT32 FileSize; | |
| UINT16 FileSelect; | |
| CHAR8 FName[QEMU_FW_CFG_FNAME_SIZE]; | |
| FileSize = QemuFwCfgRead32 (); | |
| FileSelect = QemuFwCfgRead16 (); | |
| QemuFwCfgRead16 (); // skip the field called "reserved" | |
| InternalQemuFwCfgReadBytes (sizeof (FName), FName); | |
| if (AsciiStrCmp (Name, FName) == 0) { | |
| *Item = (FIRMWARE_CONFIG_ITEM)SwapBytes16 (FileSelect); | |
| *Size = SwapBytes32 (FileSize); | |
| return RETURN_SUCCESS; | |
| } | |
| } | |
| return RETURN_NOT_FOUND; | |
| } |