/** @file
|
|
FV block I/O protocol driver for Styx SPI flash exposed via ISCP
|
|
Copyright (c) 2016, Linaro Ltd. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
**/
|
|
#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>
|
|
#define SPI_BASE (FixedPcdGet64 (PcdFdBaseAddress))
|
#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)) {
|
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);
|
}
|