This implements a firmware volume block I/O protocol driver on top of the SPI flash that is exposed by the SCP. Since the generic FTW and variable drivers cannot deal with non-memorymapped flash, this driver uses a RuntimeServicesData buffer to expose the varstore slice of the SPI flash, and keeps it in sync when the Write() or Erase() methods are called.
Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Ard Biesheuvel ard.biesheuvel@linaro.org --- Platforms/AMD/Styx/AmdStyx.dec | 6 + Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.c | 503 ++++++++++++++++++++ Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.inf | 63 +++ 3 files changed, 572 insertions(+)
diff --git a/Platforms/AMD/Styx/AmdStyx.dec b/Platforms/AMD/Styx/AmdStyx.dec index 0ce16cb3baff..3e5f5e8a8f2d 100644 --- a/Platforms/AMD/Styx/AmdStyx.dec +++ b/Platforms/AMD/Styx/AmdStyx.dec @@ -112,3 +112,9 @@ gAmdStyxTokenSpaceGuid.PcdParkingProtocolBase|0x8000E80000|UINT64|0x000b0001 gAmdStyxTokenSpaceGuid.PcdParkingProtocolSize|0x0000008000|UINT64|0x000b0002
+ # The original offset in memory of the NV store firmware volume, before + # relocating it to a dynamically allocated buffer. We need this to correlate + # flash accesses to the in-memory copy with LBAs in the actual SPI flash + gAmdStyxTokenSpaceGuid.PcdFlashNvStorageOriginalBase|0|UINT64|0x000c0000 + # block size to use when invoking the ISCP FV methods + gAmdStyxTokenSpaceGuid.PcdFlashNvStorageBlockSize|0x1000|UINT32|0x000c0001 diff --git a/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.c b/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.c new file mode 100644 index 000000000000..e27d9afd6c22 --- /dev/null +++ b/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.c @@ -0,0 +1,503 @@ +/** @file + + FV block I/O protocol driver for Styx SPI flash exposed via ISCP + + Copyright (c) 2016, Linaro Ltd. 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 <PiDxe.h> + +#include <Library/BaseMemoryLib.h> +#include <Library/DebugLib.h> +#include <Library/PcdLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/UefiRuntimeLib.h> + +#include <Protocol/AmdIscpDxeProtocol.h> +#include <Protocol/FirmwareVolumeBlock.h> + +// TODO: replace with FixedPcdGet64 (PcdFdBaseAddress) once we have updated +// the .FDF to let STYX_EFI.fd cover the entire SPI flash +#define SPI_BASE 0x8000C80000UL +#define BLOCK_SIZE (FixedPcdGet32 (PcdFlashNvStorageBlockSize)) + +STATIC AMD_ISCP_DXE_PROTOCOL *mIscpDxeProtocol; +STATIC EFI_HANDLE mStyxSpiFvHandle; + +STATIC EFI_EVENT mVirtualAddressChangeEvent; + +STATIC UINT64 mNvStorageBase; +STATIC UINT64 mNvStorageLbaOffset; + +STATIC CONST UINT64 mNvStorageSize = FixedPcdGet32 (PcdFlashNvStorageVariableSize) + + FixedPcdGet32 (PcdFlashNvStorageFtwWorkingSize) + + FixedPcdGet32 (PcdFlashNvStorageFtwSpareSize); + + +/** + Notification function of EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE. + + This is a notification function registered on EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE event. + It convers pointer to new virtual address. + + @param Event Event whose notification function is being invoked. + @param Context Pointer to the notification function's context. + +**/ +STATIC +VOID +EFIAPI +VariableClassAddressChangeEvent ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EfiConvertPointer (0x0, (VOID **)&mIscpDxeProtocol); + EfiConvertPointer (0x0, (VOID **)&mNvStorageBase); +} + +/** + The GetAttributes() function retrieves the attributes and + current settings of the block. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Attributes Pointer to EFI_FVB_ATTRIBUTES_2 in which the + attributes and current settings are + returned. Type EFI_FVB_ATTRIBUTES_2 is defined + in EFI_FIRMWARE_VOLUME_HEADER. + + @retval EFI_SUCCESS The firmware volume attributes were + returned. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeGetAttributes ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + OUT EFI_FVB_ATTRIBUTES_2 *Attributes + ) +{ + *Attributes = EFI_FVB2_READ_ENABLED_CAP | // Reads may be enabled + EFI_FVB2_READ_STATUS | // Reads are currently enabled + EFI_FVB2_WRITE_STATUS | // Writes are currently enabled + EFI_FVB2_WRITE_ENABLED_CAP | // Writes may be enabled + EFI_FVB2_STICKY_WRITE | // A block erase is required to flip bits into EFI_FVB2_ERASE_POLARITY + EFI_FVB2_MEMORY_MAPPED | // It is memory mapped + EFI_FVB2_ERASE_POLARITY; // After erasure all bits take this value (i.e. '1') + + return EFI_SUCCESS; +} + +/** + The SetAttributes() function sets configurable firmware volume + attributes and returns the new settings of the firmware volume. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Attributes On input, Attributes is a pointer to + EFI_FVB_ATTRIBUTES_2 that contains the + desired firmware volume settings. On + successful return, it contains the new + settings of the firmware volume. Type + EFI_FVB_ATTRIBUTES_2 is defined in + EFI_FIRMWARE_VOLUME_HEADER. + + @retval EFI_SUCCESS The firmware volume attributes were returned. + + @retval EFI_INVALID_PARAMETER The attributes requested are in + conflict with the capabilities + as declared in the firmware + volume header. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeSetAttributes ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + IN OUT EFI_FVB_ATTRIBUTES_2 *Attributes + ) +{ + return EFI_SUCCESS; // ignore for now +} + +/** + The GetPhysicalAddress() function retrieves the base address of + a memory-mapped firmware volume. This function should be called + only for memory-mapped firmware volumes. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Address Pointer to a caller-allocated + EFI_PHYSICAL_ADDRESS that, on successful + return from GetPhysicalAddress(), contains the + base address of the firmware volume. + + @retval EFI_SUCCESS The firmware volume base address was returned. + + @retval EFI_UNSUPPORTED The firmware volume is not memory mapped. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeGetPhysicalAddress ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + OUT EFI_PHYSICAL_ADDRESS *Address + ) +{ + *Address = (EFI_PHYSICAL_ADDRESS)mNvStorageBase; + return EFI_SUCCESS; +} + +/** + The GetBlockSize() function retrieves the size of the requested + block. It also returns the number of additional blocks with + the identical size. The GetBlockSize() function is used to + retrieve the block map (see EFI_FIRMWARE_VOLUME_HEADER). + + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Lba Indicates the block for which to return the size. + + @param BlockSize Pointer to a caller-allocated UINTN in which + the size of the block is returned. + + @param NumberOfBlocks Pointer to a caller-allocated UINTN in + which the number of consecutive blocks, + starting with Lba, is returned. All + blocks in this range have a size of + BlockSize. + + + @retval EFI_SUCCESS The firmware volume base address was returned. + + @retval EFI_INVALID_PARAMETER The requested LBA is out of range. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeGetBlockSize ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + IN EFI_LBA Lba, + OUT UINTN *BlockSize, + OUT UINTN *NumberOfBlocks + ) +{ + *BlockSize = BLOCK_SIZE; + *NumberOfBlocks = mNvStorageSize / BLOCK_SIZE - (UINTN)Lba; + + return EFI_SUCCESS; +} + +/** + Reads the specified number of bytes into a buffer from the specified block. + + The Read() function reads the requested number of bytes from the + requested block and stores them in the provided buffer. + Implementations should be mindful that the firmware volume + might be in the ReadDisabled state. If it is in this state, + the Read() function must return the status code + EFI_ACCESS_DENIED without modifying the contents of the + buffer. The Read() function must also prevent spanning block + boundaries. If a read is requested that would span a block + boundary, the read must read up to the boundary but not + beyond. The output parameter NumBytes must be set to correctly + indicate the number of bytes actually read. The caller must be + aware that a read may be partially completed. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Lba The starting logical block index + from which to read. + + @param Offset Offset into the block at which to begin reading. + + @param NumBytes Pointer to a UINTN. At entry, *NumBytes + contains the total size of the buffer. At + exit, *NumBytes contains the total number of + bytes read. + + @param Buffer Pointer to a caller-allocated buffer that will + be used to hold the data that is read. + + @retval EFI_SUCCESS The firmware volume was read successfully, + and contents are in Buffer. + + @retval EFI_BAD_BUFFER_SIZE Read attempted across an LBA + boundary. On output, NumBytes + contains the total number of bytes + returned in Buffer. + + @retval EFI_ACCESS_DENIED The firmware volume is in the + ReadDisabled state. + + @retval EFI_DEVICE_ERROR The block device is not + functioning correctly and could + not be read. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeRead ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + IN EFI_LBA Lba, + IN UINTN Offset, + IN OUT UINTN *NumBytes, + IN OUT UINT8 *Buffer + ) +{ + VOID *Base; + + if (Offset + *NumBytes > BLOCK_SIZE) { + return EFI_BAD_BUFFER_SIZE; + } + + Base = (VOID *)mNvStorageBase + Lba * BLOCK_SIZE + Offset; + + // + // Copy the data from the in-memory image + // + CopyMem (Buffer, Base, *NumBytes); + + DEBUG_CODE_BEGIN (); + EFI_STATUS Status; + + if (!EfiAtRuntime ()) { + Lba += mNvStorageLbaOffset; + Status = mIscpDxeProtocol->AmdExecuteLoadFvBlockDxe (mIscpDxeProtocol, + Lba * BLOCK_SIZE + Offset, Buffer, *NumBytes); + ASSERT_EFI_ERROR (Status); + + ASSERT (CompareMem (Base, Buffer, *NumBytes) == 0); + } + DEBUG_CODE_END (); + + return EFI_SUCCESS; +} + +/** + Writes the specified number of bytes from the input buffer to the block. + + The Write() function writes the specified number of bytes from + the provided buffer to the specified block and offset. If the + firmware volume is sticky write, the caller must ensure that + all the bits of the specified range to write are in the + EFI_FVB_ERASE_POLARITY state before calling the Write() + function, or else the result will be unpredictable. This + unpredictability arises because, for a sticky-write firmware + volume, a write may negate a bit in the EFI_FVB_ERASE_POLARITY + state but cannot flip it back again. Before calling the + Write() function, it is recommended for the caller to first call + the EraseBlocks() function to erase the specified block to + write. A block erase cycle will transition bits from the + (NOT)EFI_FVB_ERASE_POLARITY state back to the + EFI_FVB_ERASE_POLARITY state. Implementations should be + mindful that the firmware volume might be in the WriteDisabled + state. If it is in this state, the Write() function must + return the status code EFI_ACCESS_DENIED without modifying the + contents of the firmware volume. The Write() function must + also prevent spanning block boundaries. If a write is + requested that spans a block boundary, the write must store up + to the boundary but not beyond. The output parameter NumBytes + must be set to correctly indicate the number of bytes actually + written. The caller must be aware that a write may be + partially completed. All writes, partial or otherwise, must be + fully flushed to the hardware before the Write() service + returns. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. + + @param Lba The starting logical block index to write to. + + @param Offset Offset into the block at which to begin writing. + + @param NumBytes The pointer to a UINTN. At entry, *NumBytes + contains the total size of the buffer. At + exit, *NumBytes contains the total number of + bytes actually written. + + @param Buffer The pointer to a caller-allocated buffer that + contains the source for the write. + + @retval EFI_SUCCESS The firmware volume was written successfully. + + @retval EFI_BAD_BUFFER_SIZE The write was attempted across an + LBA boundary. On output, NumBytes + contains the total number of bytes + actually written. + + @retval EFI_ACCESS_DENIED The firmware volume is in the + WriteDisabled state. + + @retval EFI_DEVICE_ERROR The block device is malfunctioning + and could not be written. + + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeWrite ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + IN EFI_LBA Lba, + IN UINTN Offset, + IN OUT UINTN *NumBytes, + IN UINT8 *Buffer + ) +{ + EFI_STATUS Status; + VOID *Base; + + if (Offset + *NumBytes > BLOCK_SIZE) { + return EFI_BAD_BUFFER_SIZE; + } + + Base = (VOID *)mNvStorageBase + Lba * BLOCK_SIZE + Offset; + + Lba += mNvStorageLbaOffset; + Status = mIscpDxeProtocol->AmdExecuteUpdateFvBlockDxe (mIscpDxeProtocol, + Lba * BLOCK_SIZE + Offset, Buffer, *NumBytes); + if (!EFI_ERROR (Status)) { + // + // Copy the data we just wrote to the in-memory copy of the + // firmware volume + // + CopyMem (Base, Buffer, *NumBytes); + } + return Status; +} + +/** + Erases and initializes a firmware volume block. + + The EraseBlocks() function erases one or more blocks as denoted + by the variable argument list. The entire parameter list of + blocks must be verified before erasing any blocks. If a block is + requested that does not exist within the associated firmware + volume (it has a larger index than the last block of the + firmware volume), the EraseBlocks() function must return the + status code EFI_INVALID_PARAMETER without modifying the contents + of the firmware volume. Implementations should be mindful that + the firmware volume might be in the WriteDisabled state. If it + is in this state, the EraseBlocks() function must return the + status code EFI_ACCESS_DENIED without modifying the contents of + the firmware volume. All calls to EraseBlocks() must be fully + flushed to the hardware before the EraseBlocks() service + returns. + + @param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL + instance. + + @param ... The variable argument list is a list of tuples. + Each tuple describes a range of LBAs to erase + and consists of the following: + - An EFI_LBA that indicates the starting LBA + - A UINTN that indicates the number of blocks to + erase. + + The list is terminated with an + EFI_LBA_LIST_TERMINATOR. For example, the + following indicates that two ranges of blocks + (5-7 and 10-11) are to be erased: EraseBlocks + (This, 5, 3, 10, 2, EFI_LBA_LIST_TERMINATOR); + + @retval EFI_SUCCESS The erase request successfully + completed. + + @retval EFI_ACCESS_DENIED The firmware volume is in the + WriteDisabled state. + @retval EFI_DEVICE_ERROR The block device is not functioning + correctly and could not be written. + The firmware device may have been + partially erased. + @retval EFI_INVALID_PARAMETER One or more of the LBAs listed + in the variable argument list do + not exist in the firmware volume. + +**/ +STATIC +EFI_STATUS +StyxSpiFvDxeErase ( + IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, + ... + ) +{ + VA_LIST Args; + EFI_LBA Start; + UINTN Length; + EFI_STATUS Status; + + VA_START (Args, This); + + for (Start = VA_ARG (Args, EFI_LBA); + Start != EFI_LBA_LIST_TERMINATOR; + Start = VA_ARG (Args, EFI_LBA)) { + Start += mNvStorageLbaOffset; + Length = VA_ARG (Args, UINTN); + Status = mIscpDxeProtocol->AmdExecuteEraseFvBlockDxe (mIscpDxeProtocol, + (Start + mNvStorageLbaOffset) * BLOCK_SIZE, + Length * BLOCK_SIZE); + if (!EFI_ERROR (Status)) { + SetMem64 ((VOID *)mNvStorageBase + Start * BLOCK_SIZE, + Length * BLOCK_SIZE, ~0UL); + } + } + + VA_END (Args); + + return EFI_SUCCESS; +} + +STATIC +EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL mStyxSpiFvProtocol = { + StyxSpiFvDxeGetAttributes, + StyxSpiFvDxeSetAttributes, + StyxSpiFvDxeGetPhysicalAddress, + StyxSpiFvDxeGetBlockSize, + StyxSpiFvDxeRead, + StyxSpiFvDxeWrite, + StyxSpiFvDxeErase +}; + +EFI_STATUS +EFIAPI +StyxSpiFvDxeInitialize ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + mNvStorageBase = PcdGet64 (PcdFlashNvStorageVariableBase64); + mNvStorageLbaOffset = (FixedPcdGet64 (PcdFlashNvStorageOriginalBase) - + SPI_BASE) / BLOCK_SIZE; + + DEBUG ((EFI_D_INFO, + "%a: Using NV store FV in-memory copy at 0x%lx, LBA offset == 0x%lx\n", + __FUNCTION__, mNvStorageBase, mNvStorageLbaOffset)); + + Status = gBS->LocateProtocol (&gAmdIscpDxeProtocolGuid, NULL, + (VOID **)&mIscpDxeProtocol); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_NOTIFY, + VariableClassAddressChangeEvent, NULL, + &gEfiEventVirtualAddressChangeGuid, + &mVirtualAddressChangeEvent); + ASSERT_EFI_ERROR (Status); + + return gBS->InstallMultipleProtocolInterfaces (&mStyxSpiFvHandle, + &gEfiFirmwareVolumeBlockProtocolGuid, &mStyxSpiFvProtocol, + NULL); +} diff --git a/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.inf b/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.inf new file mode 100644 index 000000000000..2c1f243c76e9 --- /dev/null +++ b/Platforms/AMD/Styx/Drivers/StyxSpiFvDxe/StyxSpiFvDxe.inf @@ -0,0 +1,63 @@ +#/** @file +# +# Component description file for StyxSpiFvDxe module +# +# Copyright (c) 2016, Linaro Ltd. 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. +# +#**/ + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = StyxSpiFvDxe + FILE_GUID = F549FC67-C4A6-4E92-B9BA-297E1F82A1A8 + MODULE_TYPE = DXE_RUNTIME_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = StyxSpiFvDxeInitialize + +[Sources] + StyxSpiFvDxe.c + +[Packages] + ArmPkg/ArmPkg.dec + AmdModulePkg/AmdModulePkg.dec + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + OpenPlatformPkg/Platforms/AMD/Styx/AmdStyx.dec + +[LibraryClasses] + BaseLib + DebugLib + UefiLib + UefiDriverEntryPoint + UefiBootServicesTableLib + UefiRuntimeLib + DxeServicesTableLib + +[FixedPcd] + gArmTokenSpaceGuid.PcdFdBaseAddress + + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingSize + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareSize + gAmdStyxTokenSpaceGuid.PcdFlashNvStorageOriginalBase + gAmdStyxTokenSpaceGuid.PcdFlashNvStorageBlockSize + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase64 + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingBase64 + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareBase64 + +[Protocols] + gAmdIscpDxeProtocolGuid ## CONSUMES + gEfiFirmwareVolumeBlockProtocolGuid ## PRODUCES + +[Depex] + gAmdIscpDxeProtocolGuid