[PATCH 9/9] Platform/RaspberryPi: Add SPI flash variable store.


Jeremy Linton
 

The RPi4 has a 512KB SPI flash, which depending on RPi
and firmware revision has 300-180K free. We can use this
storage to persist variables when the OS is running or
over firmware upgrades. The problem is that the SPI is pin
mux'ed with the PWM audio, so we want to leave the PWM
configured for OS use. And of course there is the problem
of sharing the GPIO block with OS's that are aware of it.
Hence a previous patch set which moves the GPIO and some of
the low level i2c/etc devices into its own SSDT, and
disables them by default.

This patch, adds a few SPI access functions directly to
the variable store rather than creating another runtime
service since the early boot ordering is critical. These
functions are of the form ReadSpi(), WriteSpi(),
DisableSpiWp(), etc all with Spi in the name. On top of that
a few "Flash" routines are created which provide high level
functions for reading/writing and walking the portion of the
SPI flash we use to clone the variable store region.
Importantly WalkFlashVolume() walks the entire SPI flash
region, which has a simple header structure containing
filename+len for each region of the flash, to return how
much of the capacity is being utilized by the existing
bootloader/etc firmware.

So, if this is a RPi4, and there is sufficient space, and
that space doesn't have a valid varstore header we erase
it and flush the RPI's varstore region to the SPI. Then
we note its starting offset in mFvInstance->FlashOffset.
From then on, writes to the EFI_FW_VOL_BLOCK_DEVICE are
written to both the RAM copy as well as the SPI flash. If
the empty region has a valid header we read the entire
region overtop of the one being passed as part of the RPi's
BL33, and continue as above.

At ready to boot we re-enable the LDO, and then during the
dump vars check, we check for DT or the GPIO being enabled
and disable the runtime SPI updates because we can't be sure
of what the OS might be doing with the GPIO. The dual ACPI
and DT mode, leaves it enabled (if GPIO is disabled) so
care should be taken.

Now, one of the problems here is that with the LDO enabled
any SPI accesses can be heard over the speakers as pops,
buzzes, or scratchy tones. This is happening even without
this patch because TFA and/or the rpi low level firmware
doesn't itself assure the LDO is disabled during resets, so
the early SoC startup is quite noisy. We add to this, but
alongside that a couple fairly trivial TFA patchs, to mute
it before reset, and again assure its off before releasing
to us solve a large part of this problem. That said, this
can now happen during runtime as well. Generally the OS's
aren't doing a lot of variable updates, but when they do
its generally barely noticable clicks since we aren't going
through the long process of muting/unmuting the LDO which
itself causes a pop.

So, this patch fixes a whole bunch of bugs on github that
exist because the variable store isn't persisted. It also
fixes a rather large bug in the existing variable store
code caused by the FaultTolerantWriteDxe erasing the entire
variable store region when it garbage collects during startup.
That latter bug is the result of FvbGetLbaAddress reading
recently erased data from the VolumeHeader before its been
recreated, and results in random UEFI crashes.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
.../Drivers/VarBlockServiceDxe/FvbInfo.c | 8 +-
.../Drivers/VarBlockServiceDxe/VarBlockService.c | 654 ++++++++++++++++++++-
.../Drivers/VarBlockServiceDxe/VarBlockService.h | 10 +
.../VarBlockServiceDxe/VarBlockServiceDxe.c | 38 +-
.../VarBlockServiceDxe/VarBlockServiceDxe.inf | 6 +
5 files changed, 694 insertions(+), 22 deletions(-)

diff --git a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/FvbInfo.c b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/FvbInfo.c
index 0e0c108dba..ee18f327e6 100644
--- a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/FvbInfo.c
+++ b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/FvbInfo.c
@@ -11,12 +11,7 @@
#include <Guid/SystemNvDataGuid.h>
#include <Library/BaseLib.h>
#include <Library/PcdLib.h>
-
-typedef struct {
- UINT64 FvLength;
- EFI_FIRMWARE_VOLUME_HEADER FvbInfo;
- EFI_FV_BLOCK_MAP_ENTRY End[1];
-} EFI_FVB_MEDIA_INFO;
+#include "VarBlockService.h"

EFI_FVB_MEDIA_INFO mPlatformFvbMediaInfo[] = {
//
@@ -38,6 +33,7 @@ EFI_FVB_MEDIA_INFO mPlatformFvbMediaInfo[] = {
FixedPcdGet32 (PcdNvStorageEventLogSize),
EFI_FVH_SIGNATURE,
EFI_FVB2_MEMORY_MAPPED |
+ EFI_FVB2_STICKY_WRITE |
EFI_FVB2_READ_ENABLED_CAP |
EFI_FVB2_READ_STATUS |
EFI_FVB2_WRITE_ENABLED_CAP |
diff --git a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.c b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.c
index 572309439a..03ccf799b6 100644
--- a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.c
+++ b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.c
@@ -7,17 +7,28 @@
*
**/

+#include <Base.h>
+
+#include <IndustryStandard/Bcm2836.h>
+#include <IndustryStandard/Bcm2836Gpio.h>
+
#include <Protocol/DevicePath.h>
#include <Protocol/FirmwareVolumeBlock.h>
+#include <Protocol/RpiFirmware.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/DxeServicesTableLib.h>
+#include <Library/GpioLib.h>
+#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
+#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>

+#include <Guid/VariableFormat.h>
+
#include "VarBlockService.h"

#define EFI_FVB2_STATUS \
@@ -85,6 +96,467 @@ EFI_FW_VOL_BLOCK_DEVICE mFvbDeviceTemplate = {
}
};

+/*
+ * This is a derived approximation for the number of BCM2835_SPI_CS
+ * register reads that can be accomplished in 1US on a bcm2711.
+ */
+#define SPI_CS_READS_PER_US 25
+
+STATIC
+VOID
+SpiDelay (
+ IN UINTN micro_sec
+ )
+/*++
+
+ Routine Description:
+ Delay loop based on how fast we can read SPI controller
+ state rather than a hardcoded delay since we are a runtime
+ service.
+
+ Arguments:
+ micro_sec - Approximate number of micro seconds to delay.
+
+ Returns:
+--*/
+{
+ UINT32 looping;
+ for (looping = 0; looping < micro_sec; looping++) {
+ UINT32 looping2;
+ //
+ // RPi4 does about 25 reg reads per micro second
+ //
+ for (looping2 = 0; looping2 < SPI_CS_READS_PER_US; looping2++) {
+ MmioRead32 (mFvInstance->SpiBase+BCM2835_SPI_CS);
+ }
+ }
+}
+
+STATIC
+INT32
+DoSpiCommand (
+ UINT8 *Buffer,
+ UINTN in_len,
+ UINTN out_len)
+/*++
+
+ Routine Description:
+ Read and/or Write data on the SPI bus. A buffer with
+ data to write/read is passed and this routine then writes
+ out_len data on the SPI bus, and reads in_len data back
+ into the buffer starting at the first location. Obviously
+ this means that the buffer must be greater than the largest
+ of in_len or out_len
+
+ Arguments:
+ in_len - bytes to read from the SPI to buffer
+ out_len - bytes to write on the SPI from buffer
+
+ Returns:
+ number of bytes read into buffer.
+--*/
+{
+ int cur_byte;
+ UINT32 ret = 0;
+
+ MmioWrite32 (mFvInstance->SpiBase + BCM2835_SPI_CS, BCM2835_SPI_CS_TA);
+
+ for (cur_byte = 0 ;cur_byte < in_len + out_len; cur_byte++)
+ {
+ int loop = 10000*SPI_CS_READS_PER_US;
+
+ while ((MmioRead32 (mFvInstance->SpiBase + BCM2835_SPI_CS) & BCM2835_SPI_CS_TXD) == 0) {
+ loop--;
+ if (loop == 0) {
+ DEBUG ((DEBUG_ERROR, "Write timeout %X\n", MmioRead32 (mFvInstance->SpiBase+BCM2835_SPI_CS)));
+ ret = -1;
+ break;
+ }
+ }
+
+ if (cur_byte < out_len) {
+ MmioWrite32 (mFvInstance->SpiBase + BCM2835_SPI_FIFO, Buffer[cur_byte]);
+ } else {
+ MmioWrite32 (mFvInstance->SpiBase + BCM2835_SPI_FIFO, 0);
+ }
+
+ loop = 10000 * SPI_CS_READS_PER_US;
+ while ((MmioRead32 (mFvInstance->SpiBase + BCM2835_SPI_CS) & BCM2835_SPI_CS_RXD) == 0) {
+ loop--;
+ if (loop == 0) {
+ DEBUG ((DEBUG_ERROR, "Read timeout %X\n", MmioRead32 (mFvInstance->SpiBase+BCM2835_SPI_CS)));
+ ret = -1;
+ break;
+ }
+ }
+
+ if (cur_byte < out_len) {
+ MmioRead32 (mFvInstance->SpiBase + BCM2835_SPI_FIFO);
+ } else {
+ ret++;
+ Buffer[cur_byte - out_len] = MmioRead32 (mFvInstance->SpiBase + BCM2835_SPI_FIFO);
+ }
+ }
+
+ MmioWrite32 (mFvInstance->SpiBase + BCM2835_SPI_CS, 0);
+
+ SpiDelay (1); //wait for /CS to settle
+
+ return ret;
+}
+
+STATIC
+INT32
+ReadDeviceId(VOID)
+/*++
+
+ Routine Description:
+ Sends the SPI device identification command then checks to see
+ if its a winbond we recognize, and returns the expected capacity.
+
+ Arguments:
+
+ Returns:
+ Capacity of attached device
+--*/
+{
+ UINT8 Buffer[32];
+ Buffer[0] = 0x9F;
+
+ DoSpiCommand(Buffer, 3, 1); //EF 30 31 is the winbond W25X40CL on the base rpi4
+ if (Buffer[0] != 0xEF) {
+ DEBUG ((DEBUG_INFO, "ReadDeviceId %02X %02X %02X\n",
+ Buffer[0], Buffer[1], Buffer[2]));
+ }
+ // Lets assume we understand JEDEC type 0x30
+ if (Buffer[1] == 0x30) {
+ // it should be 512K
+ return 1 << Buffer[2]; //not really standard...
+ }
+
+ return 0;
+}
+
+
+STATIC
+INT32
+ReadSpi(
+ UINT32 Addr,
+ UINT8 *Buffer,
+ UINT32 Len)
+/*++
+
+ Routine Description:
+ Reads Len bytes of data at flash Addr into Buffer
+
+ Arguments:
+ Addr - Flash device address
+ Buffer - Buffer where data is returned, this
+ buffer must be at least 5 bytes long to
+ hold the command.
+ Len - Number of bytes to read into Buffer
+
+ Returns:
+ Number of bytes read
+--*/
+{
+ INT32 ret;
+ Buffer[0] = 0x0B; //send read data
+ Buffer[1] = (Addr >> 16) & 0xFF; // address MSB
+ Buffer[2] = (Addr >> 8) & 0xFF; //
+ Buffer[3] = Addr & 0xFF; // address LSB
+ Buffer[4] = 0;
+
+ ret = DoSpiCommand (Buffer, Len, 5);
+ return ret;
+}
+
+STATIC
+UINT32
+WalkFlashVolume(VOID)
+/*++
+
+ Routine Description:
+ Walk the RPi's SPI flash volume to determine if there is
+ free space we may consume as the backing store for a UEFI
+ variable store volume. This is fairly safe as the entire volume
+ can be recovered using the Raspberry Pi OS image tool to create
+ an EEPROM update disk. We aren't going to bother to
+ attempt to contain it in their volume format, rather hiding in
+ the free/unclaimed space. If this space is corrupted via an update
+ done outside of our control, we will fallback to the original
+ RPI_EFI.FD variables. AKA we should never really be worse off.
+
+
+ Arguments:
+ None
+
+ Returns:
+ A value that can be assigned to mFvInstance->FlashOffset
+ as the location we may right, otherwise 0.
+--*/
+{
+ UINT32 total_data;
+ UINT32 device_size;
+ UINT8 buffer[32];
+
+ device_size = ReadDeviceId();
+ // newer write location 00051100
+
+ for (total_data = 0; total_data < device_size; ) {
+ UINT32 len;
+ if (ReadSpi (total_data, buffer, 24) == 24) {
+ len = 0;
+ len += (*(UINT8 *)&buffer[5]) << 16;
+ len += (*(UINT8 *)&buffer[6]) << 8;
+ len += (*(UINT8 *)&buffer[7]);
+
+ // round up to nearest 8 byte align
+ len += 7;
+ len &= 0xFFFFF8;
+
+ buffer[24]=0;
+ DEBUG ((DEBUG_INFO, "%X len=%d filename=%a \n", *(UINT32 *)buffer,
+ len, (char *)&buffer[8]));
+ if (*(UINT32 *)buffer == 0xFFFFFFFF)
+ break;
+ total_data += 8 + len;
+ } else {
+ DEBUG ((DEBUG_ERROR, "Didn't get correct amount of data from SPI, abort its use\n"));
+ return 0;
+ }
+ }
+
+ DEBUG ((DEBUG_INFO, "First free sector at %X free space remaining %dK \n", total_data,(device_size-total_data)/1024));
+ if ((device_size - total_data) > SIZE_128KB) {
+ //start at the next 4k page
+ total_data = (total_data + SIZE_4KB) & 0xFFFFE000;
+ DEBUG ((DEBUG_INFO, "Start of Fv at %X\n", total_data));
+ } else {
+ total_data = 0;
+ }
+
+ return total_data;
+}
+
+
+STATIC
+INT32
+FlashRead(
+ UINT32 Addr,
+ UINT8 *Buffer,
+ UINT32 Len)
+/*++
+
+ Routine Description:
+ Reads Len data from variable storage area into Buffer
+
+ Arguments:
+ Addr - Offset into variable store region
+ Buffer - Buffer where data is returned, this
+ buffer must be at least 5 bytes long to
+ hold the command.
+ Len - Number of bytes to read into Buffer
+
+ Returns:
+ Number of bytes read
+--*/
+{
+ return ReadSpi (mFvInstance->FlashOffset + Addr, Buffer, Len);
+}
+
+STATIC
+VOID
+DisableSpiWp(VOID)
+/*++
+
+ Routine Description:
+ Sends SPI flash command to disable write protection
+
+ Arguments:
+
+ Returns:
+
+--*/
+{
+ UINT8 Buffer[32];
+ Buffer[0] = 0x06;
+
+ DoSpiCommand (Buffer, 0, 1);
+}
+
+STATIC
+INT32
+ReadSpiStatus(VOID)
+/*++
+
+ Routine Description:
+ Sends SPI get status command
+ Arguments:
+
+ Returns:
+ Status of flash device
+--*/
+{
+ UINT8 Buffer[32];
+ Buffer[0] = 0x05;
+
+ DoSpiCommand (Buffer, 1, 1);
+
+ return Buffer[0];
+}
+
+
+STATIC
+VOID
+WriteSpi(
+ UINT32 Addr,
+ UINT8 *SrcBuffer,
+ UINT32 Len)
+/*++
+
+ Routine Description:
+ Writes Len bytes of SrcBuffer to SPI flash. The max write len is a single
+ 256 byte block, but this routine deals with the case where its misaligned
+ across two flash blocks.
+
+ Arguments:
+ Addr - Flash device address
+ SrcBuffer - Buffer from which data is written to the SPI flash
+ Len - Number of bytes to write
+
+ Returns:
+ Nothing
+--*/
+{
+ UINT8 Buffer[280];
+ UINTN loop;
+ int additional = 0;
+
+ if (Len > 256) Len = 256;
+
+ // check if request crosses boundary
+ if (((Addr + Len - 1) & 0xFFFFFF00) != (Addr & 0xFFFFFF00)) {
+ additional = (Addr + Len) & 0xFF;
+ Len -= additional;
+ }
+
+ do {
+
+ DisableSpiWp ();
+
+ while (ReadSpiStatus () != 2) {
+ DEBUG ((DEBUG_INFO, "Spi status %X \n", ReadSpiStatus ()));
+ }
+
+ Buffer[0] = 0x02; //write len
+ Buffer[1] = (Addr >> 16) & 0xFF;
+ Buffer[2] = (Addr >> 8) & 0xFF;
+ Buffer[3] = Addr & 0xFF;
+
+ CopyMem (&Buffer[4], SrcBuffer, Len);
+
+ DoSpiCommand (Buffer, 0, 4+Len);
+
+ loop = Len * 30000 * SPI_CS_READS_PER_US;
+ while (ReadSpiStatus () & 0x3) {
+ loop--;
+ if (loop == 0) {
+ DEBUG ((DEBUG_ERROR, "Write still busy, continue\n"));
+ break;
+ }
+ }
+
+ // deal with second block
+ if (additional) {
+ Addr += Len;
+ SrcBuffer += Len;
+ Len = additional;
+ additional = 0;
+ } else {
+ Len = 0;
+ }
+
+ } while (Len);
+}
+
+
+STATIC
+VOID
+Erase4kSpi(
+ UINT32 Addr)
+/*++
+
+ Routine Description:
+ Erases a complete SPI flash page, which in this
+ case is 4k at the given address.
+
+ Arguments:
+ Addr - Flash device address
+
+ Returns:
+ Nothing
+--*/
+{
+ UINT8 Buffer[32];
+ int loop = 300000 * SPI_CS_READS_PER_US;
+
+ DisableSpiWp ();
+
+ Buffer[0] = 0x20; //erase 4k
+ Buffer[1] = (Addr >> 16) & 0xFF;
+ Buffer[2] = (Addr >> 8) & 0xFF;
+ Buffer[3] = Addr & 0xFF;
+
+ DoSpiCommand (Buffer, 0, 4);
+
+ while (ReadSpiStatus () & 0x3) {
+ loop--;
+ if (loop == 0) {
+ DEBUG ((DEBUG_ERROR, "Erase still busy \n"));
+ break;
+ }
+ }
+}
+
+STATIC
+VOID
+FlashWrite (
+ IN UINTN Address,
+ IN UINT8 *Buffer,
+ IN UINTN NumBytes
+ )
+/*++
+
+ Routine Description:
+ Writes Len bytes of SrcBuffer to flash variable storage. This routine breaks
+ the writes into blocks <= 256 bytes, which is the max that can be written with
+ the SPI flash commands we are using.
+
+ Arguments:
+ Address - Variable store offset
+ Buffer - data buffer to write
+ NumBytes - bytes in buffer to write
+
+ Returns:
+
+--*/
+{
+ UINTN Off=Address;
+
+ while (NumBytes>0) {
+ int write_bytes = NumBytes;
+ if (write_bytes > 256) {
+ write_bytes = 256;
+ }
+ WriteSpi (mFvInstance->FlashOffset + Off, Buffer, write_bytes);
+
+ Off += write_bytes;
+ Buffer += write_bytes;
+ NumBytes -= write_bytes;
+ }
+}
+

EFI_STATUS
VarStoreWrite (
@@ -93,8 +565,47 @@ VarStoreWrite (
IN UINT8 *Buffer
)
{
+
+ if (Address<mFvInstance->FvBase) {
+ return EFI_INVALID_PARAMETER;
+ }
+
CopyMem ((VOID*)Address, Buffer, *NumBytes);
- mFvInstance->Dirty = TRUE;
+
+ if (mFvInstance->FlashOffset) {
+ GpioPinFuncSet (40, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT4);
+
+ FlashWrite (Address-mFvInstance->FvBase, Buffer, *NumBytes);
+
+ GpioPinFuncSet (40, GPIO_FSEL_ALT0);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT0);
+ } else {
+ mFvInstance->Dirty = TRUE;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+FlashErase (
+ IN UINTN Address,
+ IN UINTN LbaLength
+ )
+
+{
+ UINTN Off=Address;
+
+ while (LbaLength>0) {
+ int erase_bytes = LbaLength;
+ if (erase_bytes > 4096) {
+ erase_bytes = 4096;
+ }
+ Erase4kSpi (mFvInstance->FlashOffset + Off);
+
+ Off += erase_bytes;
+ LbaLength -= erase_bytes;
+ }

return EFI_SUCCESS;
}
@@ -106,8 +617,22 @@ VarStoreErase (
IN UINTN LbaLength
)
{
+ if (Address<mFvInstance->FvBase) {
+ return EFI_INVALID_PARAMETER;
+ }
SetMem ((VOID*)Address, LbaLength, 0xff);
- mFvInstance->Dirty = TRUE;
+
+ if (mFvInstance->FlashOffset) {
+ GpioPinFuncSet (40, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT4);
+
+ FlashErase (Address-mFvInstance->FvBase, LbaLength);
+
+ GpioPinFuncSet (40, GPIO_FSEL_ALT0);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT0);
+ } else {
+ mFvInstance->Dirty = TRUE;
+ }

return EFI_SUCCESS;
}
@@ -151,6 +676,7 @@ FvbGetLbaAddress (

--*/
{
+
UINT32 NumBlocks;
UINT32 BlockLength;
UINTN Offset;
@@ -166,8 +692,16 @@ FvbGetLbaAddress (
// Parse the blockmap of the FV to find which map entry the Lba belongs to.
//
while (TRUE) {
- NumBlocks = BlockMap->NumBlocks;
- BlockLength = BlockMap->Length;
+ if (BlockMap->NumBlocks==0xFFFFFFFF) {
+ NumBlocks = mFvInstance->NumOfBlocks;
+ } else {
+ NumBlocks = BlockMap->NumBlocks;
+ }
+ if (BlockMap->Length==0xFFFFFFFF) {
+ BlockLength = mFvInstance->BlockSize;
+ } else {
+ BlockLength = BlockMap->Length;
+ }

if (NumBlocks == 0 || BlockLength == 0) {
return EFI_INVALID_PARAMETER;
@@ -199,6 +733,7 @@ FvbGetLbaAddress (
Offset = Offset + NumBlocks * BlockLength;
BlockMap++;
}
+
}


@@ -371,6 +906,19 @@ FvbSetVolumeAttributes (
*AttribPtr = (*AttribPtr) | NewStatus;
*Attributes = *AttribPtr;

+ if (mFvInstance->FlashOffset) {
+ GpioPinFuncSet (40, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT4);
+
+ FlashErase (0, 0x1000);
+ FlashWrite (0, (UINT8*)mFvInstance->VolumeHeader, 0x1000);
+
+ GpioPinFuncSet (40, GPIO_FSEL_ALT0);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT0);
+
+ }
+
+
return EFI_SUCCESS;
}

@@ -416,12 +964,16 @@ FvbProtocolGetBlockSize (

--*/
{
- return FvbGetLbaAddress (
- Lba,
- NULL,
- BlockSize,
- NumOfBlocks
- );
+ EFI_STATUS Status;
+
+ Status = FvbGetLbaAddress (
+ Lba,
+ NULL,
+ BlockSize,
+ NumOfBlocks
+ );
+
+ return Status;
}


@@ -602,7 +1154,7 @@ FvbProtocolWrite (
EFI_FVB_ATTRIBUTES_2 Attributes;
UINTN LbaAddress;
UINTN LbaLength;
- EFI_STATUS Status;
+ EFI_STATUS Status = EFI_SUCCESS;
EFI_STATUS ReturnStatus;

//
@@ -637,6 +1189,7 @@ FvbProtocolWrite (
return EFI_INVALID_PARAMETER;
}

+ // forces this write to split
if (LbaLength < (*NumBytes + Offset)) {
*NumBytes = (UINT32)(LbaLength - Offset);
Status = EFI_BAD_BUFFER_SIZE;
@@ -789,8 +1342,6 @@ ValidateFvHeader (
Expected =
(UINT16)(((UINTN)FwVolHeader->Checksum + 0x10000 - Checksum) & 0xffff);

- DEBUG ((DEBUG_INFO, "FV@%p Checksum is 0x%x, expected 0x%x\n",
- FwVolHeader, FwVolHeader->Checksum, Expected));
return EFI_NOT_FOUND;
}

@@ -798,6 +1349,8 @@ ValidateFvHeader (
}


+
+
EFI_STATUS
EFIAPI
FvbInitialize (
@@ -825,6 +1378,7 @@ FvbInitialize (
UINTN NumOfBlocks;
RETURN_STATUS PcdStatus;
UINTN StartOffset;
+ EFI_FIRMWARE_VOLUME_HEADER SpiBuffer[2];

BaseAddress = PcdGet32 (PcdNvStorageVariableBase);
Length = (FixedPcdGet32 (PcdFlashNvStorageVariableSize) +
@@ -833,6 +1387,7 @@ FvbInitialize (
FixedPcdGet32 (PcdNvStorageEventLogSize));
StartOffset = BaseAddress - FixedPcdGet64 (PcdFdBaseAddress);

+
BufferSize = sizeof (EFI_FW_VOL_INSTANCE);

mFvInstance = AllocateRuntimeZeroPool (BufferSize);
@@ -842,11 +1397,78 @@ FvbInitialize (

mFvInstance->FvBase = (UINTN)BaseAddress;
mFvInstance->FvLength = (UINTN)Length;
- mFvInstance->Offset = StartOffset;
+ mFvInstance->BlockSize = FixedPcdGet32 (PcdFirmwareBlockSize);
+ mFvInstance->Offset = StartOffset; // Start offset of RPI_EFI.FD file
/*
* Should I parse config.txt instead and find the real name?
*/
mFvInstance->MappedFile = L"RPI_EFI.FD";
+ /*
+ * SPI Control
+ */
+ mFvInstance->SpiBase = BCM2836_SPI0_BASE_ADDRESS;
+ mFvInstance->FlashOffset = 0;
+ mFvInstance->DisableRuntime = 0;
+
+
+#if (RPI_MODEL == 4)
+ /*
+ * On the RPI4 there is a 512KB flash chip
+ * used to store the low level bcm2711 bootstrap code
+ * and the XHCI firmware on newer devices. It has between
+ * ~330K and ~180K available depending on model/version
+ * Possibly less as its consumed for other purposes.
+ * It shares a GPIO pin with the 3.5mm audio port (pwm)
+ * so accessing it causes pops, shzzzz and bzzz
+ * noices that are sometimes audible even without us.
+ * During reboot for example. Newer TFA's will presumably
+ * help us out and disable the audio amp at shutdown
+ * and leave it disabled during startup. That means
+ * that this code can bzzzz if TFA hasn't assured the
+ * audio is off for us.
+ *
+ * Runtime audio pops are audible although barely noticable
+ * in Windows and DT based linux boots. Although the larger
+ * problem is configuring the GPIO pins. As such we disable
+ * runtime persistance if either DT boot mode, or ACPI GPIO
+ * is enabled.
+ */
+
+ GpioPinFuncSet (40, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (42, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (43, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (44, GPIO_FSEL_ALT4);
+ GpioPinFuncSet (45, GPIO_FSEL_ALT4);
+
+ GpioSetPull (43, GPIO_PULL_DOWN);
+ GpioSetPull (44, GPIO_PULL_DOWN);
+ GpioSetPull (45, GPIO_PULL_DOWN);
+
+ mFvInstance->FlashOffset = WalkFlashVolume ();
+
+ if (mFvInstance->FlashOffset) {
+ if (FlashRead (0, (UINT8*)SpiBuffer, sizeof(EFI_FIRMWARE_VOLUME_HEADER)*2)
+ != sizeof(EFI_FIRMWARE_VOLUME_HEADER)*2) {
+ DEBUG ((DEBUG_ERROR, "Unable to read data from SPI\n"));
+ mFvInstance->FlashOffset = 0;
+ } else {
+ Status = ValidateFvHeader (SpiBuffer);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_INFO, "Invalid header on SPI, recreate volume\n"));
+ FlashErase (0, Length);
+ FlashWrite (0, (UINT8*)BaseAddress, Length);
+ }
+
+ // read the entire varstore...
+ if (FlashRead (0, (UINT8*)BaseAddress, Length) != Length) {
+ DEBUG ((DEBUG_ERROR, "Failed to read entire flash region\n"));
+ }
+ }
+ }
+ GpioPinFuncSet (40, GPIO_FSEL_ALT0);
+ GpioPinFuncSet (41, GPIO_FSEL_ALT0);
+#endif

Status = ValidateFvHeader (mFvInstance->VolumeHeader);
if (!EFI_ERROR (Status)) {
@@ -903,7 +1525,9 @@ FvbInitialize (
}

//
- // The total number of blocks in the FV.
+ // The total number of blocks in the FV. This should match:
+ // PcdFlashNvStorageVariableSize + PcdFlashNvStorageFtwWorkingSize +
+ // PcdFlashNvStorageFtwSpareSize + PcdNvStorageEventLogSize / PcdFirmwareBlockSize
//
mFvInstance->NumOfBlocks = NumOfBlocks;

diff --git a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.h b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.h
index b65c26453d..1b4f6bd877 100644
--- a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.h
+++ b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockService.h
@@ -30,9 +30,13 @@ typedef struct {
UINTN FvLength;
UINTN Offset;
UINTN NumOfBlocks;
+ UINTN BlockSize;
EFI_DEVICE_PATH_PROTOCOL *Device;
CHAR16 *MappedFile;
BOOLEAN Dirty;
+ UINTN SpiBase;
+ UINTN FlashOffset;
+ UINTN DisableRuntime;
} EFI_FW_VOL_INSTANCE;

extern EFI_FW_VOL_INSTANCE *mFvInstance;
@@ -208,4 +212,10 @@ FileClose (
IN EFI_FILE_PROTOCOL *File
);

+typedef struct {
+ UINT64 FvLength;
+ EFI_FIRMWARE_VOLUME_HEADER FvbInfo;
+ EFI_FV_BLOCK_MAP_ENTRY End[1];
+} EFI_FVB_MEDIA_INFO;
+
#endif
diff --git a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.c b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.c
index 4071a3fca4..351cb59368 100644
--- a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.c
+++ b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.c
@@ -8,8 +8,10 @@
*
**/

+#include <Library/GpioLib.h>
+#include <Protocol/RpiFirmware.h>
#include "VarBlockService.h"
-
+#include "ConfigVars.h"
//
// Minimum delay to enact before reset, when variables are dirty (in μs).
// Needed to ensure that SSD-based USB 3.0 devices have time to flush their
@@ -104,9 +106,14 @@ FvbVirtualAddressChangeEvent (

--*/
{
+ if (mFvInstance->DisableRuntime) {
+ mFvInstance->FlashOffset = 0; //disable flash writes
+ }
+ EfiConvertPointer (0x0, (VOID**)&mFvInstance->SpiBase);
EfiConvertPointer (0x0, (VOID**)&mFvInstance->FvBase);
EfiConvertPointer (0x0, (VOID**)&mFvInstance->VolumeHeader);
EfiConvertPointer (0x0, (VOID**)&mFvInstance);
+ GpioSetupRuntime ();
}


@@ -175,6 +182,15 @@ DumpVars (

if (!mFvInstance->Dirty) {
DEBUG ((DEBUG_INFO, "Variables not dirty, not dumping!\n"));
+ // if there is a valid SPI flash volume in use, don't delay the reset
+ if (mFvInstance->FlashOffset) {
+ PcdSet32S (PcdPlatformResetDelay, 0);
+ }
+ if ((PcdGet32 (PcdSystemTableMode) == SYSTEM_TABLE_MODE_DT) ||
+ PcdGet32 (PcdEnableGpio)) {
+ mFvInstance->DisableRuntime = TRUE;
+ }
+
return;
}

@@ -200,6 +216,24 @@ DumpVars (
mFvInstance->Dirty = FALSE;
}

+STATIC
+VOID
+EnableAudioLdo(void)
+{
+ EFI_STATUS Status;
+ RASPBERRY_PI_FIRMWARE_PROTOCOL *mFwProtocol;
+
+ Status = gBS->LocateProtocol (&gRaspberryPiFirmwareProtocolGuid,
+ NULL, (VOID**)&mFwProtocol);
+ ASSERT_EFI_ERROR (Status);
+ if (EFI_ERROR (Status)) {
+ return;
+ }
+
+ mFwProtocol->SetLdoRegState (1);
+ ASSERT_EFI_ERROR (Status);
+}
+

VOID
ReadyToBootHandler (
@@ -230,6 +264,8 @@ ReadyToBootHandler (
DumpVars (NULL, NULL);
Status = gBS->CloseEvent (Event);
ASSERT_EFI_ERROR (Status);
+
+ EnableAudioLdo();
}


diff --git a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.inf b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.inf
index c2edb25bd4..6fe5a22dd3 100644
--- a/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.inf
+++ b/Platform/RaspberryPi/Drivers/VarBlockServiceDxe/VarBlockServiceDxe.inf
@@ -37,6 +37,7 @@
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
Platform/RaspberryPi/RaspberryPi.dec
+ Silicon/Broadcom/Bcm283x/Bcm283x.dec

[LibraryClasses]
BaseLib
@@ -44,6 +45,7 @@
DebugLib
DevicePathLib
DxeServicesTableLib
+ GpioLib
MemoryAllocationLib
PcdLib
UefiBootServicesTableLib
@@ -61,6 +63,7 @@
gEfiBlockIoProtocolGuid
gEfiFirmwareVolumeBlockProtocolGuid # PROTOCOL SOMETIMES_PRODUCED
gEfiDevicePathProtocolGuid # PROTOCOL SOMETIMES_PRODUCED
+ gRaspberryPiFirmwareProtocolGuid ## CONSUMES

[FixedPcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableSize
@@ -75,12 +78,15 @@
gArmTokenSpaceGuid.PcdFdSize

[Pcd]
+ gBcm283xTokenSpaceGuid.PcdBcm283xRegistersAddress
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingBase
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareBase
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase
gRaspberryPiTokenSpaceGuid.PcdNvStorageEventLogBase
gRaspberryPiTokenSpaceGuid.PcdPlatformResetDelay
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase64
+ gRaspberryPiTokenSpaceGuid.PcdSystemTableMode
+ gRaspberryPiTokenSpaceGuid.PcdEnableGpio

[FeaturePcd]

--
2.13.7