Implement a new debug feature to assist in recovering the SPI flash from
broken firmware. This feature is intended to be used with the userspace
tool presently in my repo here:
https://github.com/benjamindoron/early_flash_rescue.

After my planned second project for GSoC 2022 finished early, I chose to
implement this feature too with the remaining time.

This PEIM can load successfully in either CAR or DRAM. Further testing
required to confirm that it works after resolving a bug where `DEBUG()`
seemed to overrun the transport layer's internal buffer, and commands
were lost. The UEFI shell app for testing in my repo works well.

Speed of this feature depends on the SerialPortLib implementation. While
PCDs ensure the PEIM is implementation-agnostic, two `MicroSecondDelay()`
calls are targeted at the Bus Pirate transport for the GMBUS
SerialPortLib.

With that SerialPortLib and transport, time taken ranges from ~5
minutes to flash 70 blocks, to ~30 minutes or more to flash MinPlatform
when code changes modify many blocks. It is estimated that 1h40m is
required to flash all blocks of 6M BIOS region, but this is unlikely:
MinPlatform uses less space. Nonetheless, optimisation is still desired.

Cc: Sai Chaganty <rangasai.v.chaga...@intel.com>
Cc: Isaac Oram <isaac.w.o...@intel.com>
Cc: Nate DeSimone <nathaniel.l.desim...@intel.com>
Cc: Ankit Sinha <ankit.si...@intel.com>
Cc: Liming Gao <gaolim...@byosoft.com.cn>
Cc: Eric Dong <eric.d...@intel.com>
Signed-off-by: Benjamin Doron <benjamin.doro...@gmail.com>
---
 .../AdvancedFeaturePkg/AdvancedFeaturePkg.dsc |   1 +
 .../Include/AdvancedFeatures.dsc              |   4 +
 .../Include/AdvancedFeaturesPcd.dsc           |   2 +
 .../AdvancedFeaturePkg/Include/PostMemory.fdf |   4 +
 .../AdvancedFeaturePkg/Include/PreMemory.fdf  |   4 +
 .../EarlySpiFlashRescueFeaturePkg.dec         |  44 ++++
 .../EarlySpiFlashRescueFeaturePkg.dsc         |  47 ++++
 .../FlashRescueBoardPei/FlashRescueBoard.h    |  84 +++++++
 .../FlashRescueBoardCommon.c                  | 237 ++++++++++++++++++
 .../FlashRescueBoardPei/FlashRescueBoardPei.c | 232 +++++++++++++++++
 .../FlashRescueBoardPei.inf                   |  54 ++++
 .../Include/EarlySpiFlashRescueFeature.dsc    |  34 +++
 .../Include/PostMemory.fdf                    |   8 +
 .../Include/Ppi/FeatureInMemory.h             |  23 ++
 .../Include/PreMemory.fdf                     |   8 +
 .../EarlySpiFlashRescueFeaturePkg/Readme.md   | 119 +++++++++
 16 files changed, 905 insertions(+)
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dsc
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoard.h
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardCommon.c
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.c
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.inf
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/EarlySpiFlashRescueFeature.dsc
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/Ppi/FeatureInMemory.h
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PreMemory.fdf
 create mode 100644 
Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Readme.md

diff --git a/Features/Intel/AdvancedFeaturePkg/AdvancedFeaturePkg.dsc 
b/Features/Intel/AdvancedFeaturePkg/AdvancedFeaturePkg.dsc
index 8235856da9a4..c4f6b9cf3b9c 100644
--- a/Features/Intel/AdvancedFeaturePkg/AdvancedFeaturePkg.dsc
+++ b/Features/Intel/AdvancedFeaturePkg/AdvancedFeaturePkg.dsc
@@ -50,6 +50,7 @@
   gAcpiDebugFeaturePkgTokenSpaceGuid.PcdAcpiDebugFeatureEnable              
|TRUE
   gAcpiDebugFeaturePkgTokenSpaceGuid.PcdUseSmmVersion                       
|FALSE
   gBeepDebugFeaturePkgTokenSpaceGuid.PcdBeepDebugFeatureEnable              
|TRUE
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable  
|TRUE
   gPostCodeDebugFeaturePkgTokenSpaceGuid.PcdPostCodeDebugFeatureEnable      
|TRUE
   gUsb3DebugFeaturePkgTokenSpaceGuid.PcdUsb3DebugFeatureEnable              
|TRUE
 
diff --git a/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeatures.dsc 
b/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeatures.dsc
index 85a067d6a0aa..099f5803f977 100644
--- a/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeatures.dsc
+++ b/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeatures.dsc
@@ -21,6 +21,10 @@
   !include BeepDebugFeaturePkg/Include/BeepDebugFeature.dsc
 !endif
 
+!if gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable 
== TRUE
+  !include EarlySpiFlashRescueFeaturePkg/Include/EarlySpiFlashRescueFeature.dsc
+!endif
+
 !if gPostCodeDebugFeaturePkgTokenSpaceGuid.PcdPostCodeDebugFeatureEnable == 
TRUE
   !include PostCodeDebugFeaturePkg/Include/PostCodeDebugFeature.dsc
 !endif
diff --git a/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeaturesPcd.dsc 
b/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeaturesPcd.dsc
index 54d956a52456..83b8521d1ebc 100644
--- a/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeaturesPcd.dsc
+++ b/Features/Intel/AdvancedFeaturePkg/Include/AdvancedFeaturesPcd.dsc
@@ -22,6 +22,7 @@
   #
   AcpiDebugFeaturePkg/AcpiDebugFeaturePkg.dec
   BeepDebugFeaturePkg/BeepDebugFeaturePkg.dec
+  EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
   PostCodeDebugFeaturePkg/PostCodeDebugFeaturePkg.dec
   Usb3DebugFeaturePkg/Usb3DebugFeaturePkg.dec
 
@@ -67,6 +68,7 @@
   gAcpiDebugFeaturePkgTokenSpaceGuid.PcdAcpiDebugFeatureEnable              
|FALSE
   gAcpiDebugFeaturePkgTokenSpaceGuid.PcdUseSmmVersion                       
|FALSE
   gBeepDebugFeaturePkgTokenSpaceGuid.PcdBeepDebugFeatureEnable              
|FALSE
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable  
|FALSE
   gPostCodeDebugFeaturePkgTokenSpaceGuid.PcdPostCodeDebugFeatureEnable      
|FALSE
   gUsb3DebugFeaturePkgTokenSpaceGuid.PcdUsb3DebugFeatureEnable              
|FALSE
 
diff --git a/Features/Intel/AdvancedFeaturePkg/Include/PostMemory.fdf 
b/Features/Intel/AdvancedFeaturePkg/Include/PostMemory.fdf
index acfbb0e209e8..a710c4012fcf 100644
--- a/Features/Intel/AdvancedFeaturePkg/Include/PostMemory.fdf
+++ b/Features/Intel/AdvancedFeaturePkg/Include/PostMemory.fdf
@@ -22,6 +22,10 @@
   !include BeepDebugFeaturePkg/Include/PostMemory.fdf
 !endif
 
+!if gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable 
== TRUE
+  !include EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf
+!endif
+
 !if gPostCodeDebugFeaturePkgTokenSpaceGuid.PcdPostCodeDebugFeatureEnable == 
TRUE
   !include PostCodeDebugFeaturePkg/Include/PostMemory.fdf
 !endif
diff --git a/Features/Intel/AdvancedFeaturePkg/Include/PreMemory.fdf 
b/Features/Intel/AdvancedFeaturePkg/Include/PreMemory.fdf
index 90b03e61080b..b4c889c4ca68 100644
--- a/Features/Intel/AdvancedFeaturePkg/Include/PreMemory.fdf
+++ b/Features/Intel/AdvancedFeaturePkg/Include/PreMemory.fdf
@@ -22,6 +22,10 @@
   !include BeepDebugFeaturePkg/Include/PreMemory.fdf
 !endif
 
+!if gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable 
== TRUE
+  !include EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf
+!endif
+
 !if gPostCodeDebugFeaturePkgTokenSpaceGuid.PcdPostCodeDebugFeatureEnable == 
TRUE
   !include PostCodeDebugFeaturePkg/Include/PreMemory.fdf
 !endif
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
new file mode 100644
index 000000000000..d22a5983b8a3
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
@@ -0,0 +1,44 @@
+## @file
+# This package provides advanced feature functionality for rescuing SPI flash.
+# This package should only depend on EDK II Core packages, IntelSiliconPkg, 
and MinPlatformPkg.
+#
+# The DEC files are used by the utilities that parse DSC and
+# INF files to generate AutoGen.c and AutoGen.h files
+# for the build infrastructure.
+#
+# Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+  DEC_SPECIFICATION = 0x00010017
+  PACKAGE_NAME      = EarlySpiFlashRescueFeaturePkg
+  PACKAGE_GUID      = 605FCBD8-1D5A-4B72-9874-8FD7DD4FDABD
+  PACKAGE_VERSION   = 0.1
+
+[Includes]
+  Include
+
+[Guids]
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid  =  {0x3e9700b8, 0x98e2, 
0x4db4, {0x8a, 0x46, 0xfe, 0x10, 0x77, 0x64, 0xe8, 0xd0}}
+
+[PcdsFeatureFlag]
+  ## This PCD specifies whether early rescue PEIM is built.
+  
gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable|FALSE|BOOLEAN|0xB0000001
+
+[PcdsFixedAtBuild]
+  ## This PCD specifies the wait timeout value in millisecond to await a 
userspace.
+  #  Default timeout value is 15000 milliseconds.
+  #  If user does not want system stall for long time, it can be set to small 
value.
+  #  Delays in 250ms increments between sending HELLO. Less than this 
deactivates the feature.
+  
gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdUserspaceHostWaitTimeout|15000|UINT32|0xB0000002
+
+  ## This PCD specifies the size in bytes of data packets.
+  ## Dependent on implementation layer. Synchronise with userspace, user must 
not change without support.
+  
gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdDataXferPacketSize|64|UINT16|0xB0000003
+
+[Ppis]
+  ## Include/Ppi/FeatureInMemory.h
+  gPeiFlashRescueReadyInMemoryPpiGuid = {0xe5147285, 0x4d34, 0x415e, {0x8e, 
0xa8, 0x85, 0xbd, 0xd8, 0xc6, 0x5b, 0xde }}
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dsc
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dsc
new file mode 100644
index 000000000000..5c4857a885d4
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dsc
@@ -0,0 +1,47 @@
+## @file
+# This package provides advanced feature functionality for rescuing SPI flash.
+# This package should only depend on EDK II Core packages, IntelSiliconPkg, 
and MinPlatformPkg.
+#
+# The DEC files are used by the utilities that parse DSC and
+# INF files to generate AutoGen.c and AutoGen.h files
+# for the build infrastructure.
+#
+# Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+  PLATFORM_NAME                  = EarlySpiFlashRescueFeaturePkg
+  PLATFORM_GUID                  = E703420D-AEEF-41C5-B8CA-237BE36C64EC
+  PLATFORM_VERSION               = 0.1
+  DSC_SPECIFICATION              = 0x00010005
+  OUTPUT_DIRECTORY               = Build/$(PLATFORM_NAME)
+  SUPPORTED_ARCHITECTURES        = IA32|X64
+  BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
+  SKUID_IDENTIFIER               = DEFAULT
+  PEI_ARCH                       = IA32
+  DXE_ARCH                       = X64
+
+[Packages]
+  MinPlatformPkg/MinPlatformPkg.dec
+
+#
+# MinPlatform common include for required feature PCD
+# These PCD must be set before the core include files, CoreCommonLib,
+# CorePeiLib, and CoreDxeLib.
+#
+!include MinPlatformPkg/Include/Dsc/MinPlatformFeaturesPcd.dsc.inc
+
+#
+# Include common libraries
+#
+!include MinPlatformPkg/Include/Dsc/CoreCommonLib.dsc
+!include MinPlatformPkg/Include/Dsc/CorePeiLib.dsc
+!include MinPlatformPkg/Include/Dsc/CoreDxeLib.dsc
+
+#
+# This package always builds the feature.
+#
+!include Include/EarlySpiFlashRescueFeature.dsc
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoard.h
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoard.h
new file mode 100644
index 000000000000..f874c3c0b3bd
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoard.h
@@ -0,0 +1,84 @@
+/** @file
+  Early SPI flash rescue protocol - board implementation.
+
+  Copyright (c) 2022, Baruch Binyamin Doron.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#ifndef FLASH_RESCUE_BOARD_H
+#define FLASH_RESCUE_BOARD_H
+
+#include <Base.h>
+#include <Protocol/Spi2.h>
+
+#define SIZE_BLOCK     4096
+#define MS_IN_SECOND   1000
+#define NS_IN_SECOND   (1000 * 1000 * 1000)
+
+#define EARLY_FLASH_RESCUE_PROTOCOL_VERSION    0.50
+#define EARLY_FLASH_RESCUE_COMMAND_HELLO       0x10
+#define EARLY_FLASH_RESCUE_COMMAND_CHECKSUM    0x11
+#define EARLY_FLASH_RESCUE_COMMAND_READ                0x12
+#define EARLY_FLASH_RESCUE_COMMAND_WRITE       0x13
+#define EARLY_FLASH_RESCUE_COMMAND_RESET       0x14
+#define EARLY_FLASH_RESCUE_COMMAND_EXIT                0x15
+
+#pragma pack(push, 1)
+typedef struct {
+       UINT8   Command;
+       UINT16  BlockNumber;  // This 4K block in BIOS region
+} EARLY_FLASH_RESCUE_COMMAND;
+
+typedef struct {
+       UINT8   Acknowledge;  // Usually, ACK == 0x01
+       UINT16  Size;         // OPTIONAL?
+} EARLY_FLASH_RESCUE_RESPONSE;
+#pragma pack(pop)
+
+/**
+  Returns a pointer to the PCH SPI PPI.
+
+  @return Pointer to PCH_SPI2_PPI   If an instance of the PCH SPI PPI is found
+  @return NULL                      If an instance of the PCH SPI PPI is not 
found
+
+**/
+PCH_SPI2_PROTOCOL *
+GetSpiPpi (
+  VOID
+  );
+
+/**
+ * Perform system reset to start this firmware.
+**/
+VOID
+EFIAPI
+PerformSystemReset (
+  VOID
+  );
+
+/**
+ * Send HELLO command to an awaiting userspace.
+ *
+ * @return EFI_SUCCESS  Command acknowledged.
+ * @return EFI_TIMEOUT  Command timed-out.
+**/
+EFI_STATUS
+EFIAPI
+SendHelloPacket (
+  VOID
+  );
+
+/**
+ * Perform flash.
+ *
+ * @return EFI_SUCCESS       Successful flash.
+ * @return EFI_DEVICE_ERROR  Successful flash.
+ * @return EFI_TIMEOUT       Await command timed-out.
+**/
+EFI_STATUS
+EFIAPI
+PerformFlash (
+  VOID
+  );
+
+#endif
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardCommon.c
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardCommon.c
new file mode 100644
index 000000000000..0e340ed77676
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardCommon.c
@@ -0,0 +1,237 @@
+/** @file
+  Early SPI flash rescue protocol - board implementation.
+
+  Copyright (c) 2022, Baruch Binyamin Doron.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/PcdLib.h>
+#include <Library/SerialPortLib.h>
+#include <Library/SpiLib.h>
+#include <Library/TimerLib.h>
+#include <Protocol/Spi2.h>
+#include "FlashRescueBoard.h"
+
+// TODO: Appropriate size
+static UINT16 XferBlockSize = FixedPcdGet16 (PcdDataXferPacketSize);
+
+/**
+ * Send HELLO command to an awaiting userspace.
+ *
+ * @return EFI_SUCCESS  Command acknowledged.
+ * @return EFI_TIMEOUT  Command timed-out.
+**/
+EFI_STATUS
+EFIAPI
+SendHelloPacket (
+  VOID
+  )
+{
+  UINT32                       WaitTimeout;
+  EARLY_FLASH_RESCUE_COMMAND   CommandPacket;
+  UINTN                        TimeCounter;
+  EARLY_FLASH_RESCUE_RESPONSE  ResponsePacket;
+
+  WaitTimeout = FixedPcdGet32 (PcdUserspaceHostWaitTimeout);
+
+  // TODO: Consider sending a total `BlockNumber`?
+  CommandPacket.Command = EARLY_FLASH_RESCUE_COMMAND_HELLO;
+  CommandPacket.BlockNumber = 0;
+
+  for (TimeCounter = 0; TimeCounter < WaitTimeout; TimeCounter += 250) {
+    // Maybe packet was not in FIFO
+    SerialPortWrite ((UINT8 *)&CommandPacket, sizeof (CommandPacket));
+
+    SerialPortRead ((UINT8 *)&ResponsePacket, sizeof (ResponsePacket));
+    if (ResponsePacket.Acknowledge == 1) {
+      return EFI_SUCCESS;
+    }
+
+    MicroSecondDelay (250 * MS_IN_SECOND);
+  }
+
+  return EFI_TIMEOUT;
+}
+
+/**
+ * Send the requested block CRC to an awaiting userspace.
+ * - TODO: NACK blocks as necessary
+**/
+VOID
+EFIAPI
+SendBlockChecksum (
+  UINTN  BlockNumber
+  )
+{
+  PCH_SPI2_PROTOCOL            *Spi2Ppi;
+  UINTN                        Address;
+  UINT8                        BlockData[SIZE_BLOCK];
+  EFI_STATUS                   Status;
+  UINT32                       Crc;
+  EARLY_FLASH_RESCUE_RESPONSE  ResponsePacket;
+
+  Spi2Ppi = GetSpiPpi ();
+  if (Spi2Ppi == NULL) {
+    return;
+  }
+
+  // `BlockNumber` starting in BIOS region
+  Address = BlockNumber * SIZE_BLOCK;
+
+  Status = Spi2Ppi->FlashRead (
+             Spi2Ppi,
+             &gFlashRegionBiosGuid,
+             Address,
+             SIZE_BLOCK,
+             BlockData
+             );
+  if (EFI_ERROR (Status)) {
+    return;
+  }
+
+  Crc = CalculateCrc32 (BlockData, SIZE_BLOCK);
+
+  // Now, acknowledge userspace request and send block CRC
+  ResponsePacket.Acknowledge = 1;
+  SerialPortWrite ((UINT8 *)&ResponsePacket, sizeof (ResponsePacket));
+  SerialPortWrite ((UINT8 *)&Crc, sizeof (Crc));
+}
+
+/**
+ * Write the requested SPI flash block.
+ * - TODO: NACK blocks as necessary
+**/
+VOID
+EFIAPI
+WriteBlock (
+  UINTN  BlockNumber
+  )
+{
+  PCH_SPI2_PROTOCOL            *Spi2Ppi;
+  UINTN                        Address;
+  UINT8                        BlockData[SIZE_BLOCK];
+  EARLY_FLASH_RESCUE_RESPONSE  ResponsePacket;
+  VOID                         *XferBlock;
+  UINTN                        Index;
+  EFI_STATUS                   Status;
+
+  Spi2Ppi = GetSpiPpi ();
+  if (Spi2Ppi == NULL) {
+    return;
+  }
+
+  // `BlockNumber` starting in BIOS region
+  Address = BlockNumber * SIZE_BLOCK;
+
+  // Acknowledge userspace command and retrieve block
+  ResponsePacket.Acknowledge = 1;
+  SerialPortWrite ((UINT8 *)&ResponsePacket, sizeof (ResponsePacket));
+
+  // Start streaming block
+  XferBlock = BlockData;
+  for (Index = 0; Index < SIZE_BLOCK; Index += XferBlockSize) {
+    // FIXME: This will incur some penalty, but we must wait
+    // - Still debugging timing parameters, especially at higher baudrate
+    // - Possible optimisation: Shorter stall if SerialPortPoll()
+    MicroSecondDelay (33 * MS_IN_SECOND);
+
+    SerialPortRead (XferBlock, XferBlockSize);
+    XferBlock += XferBlockSize;
+
+    // FIXME: This will incur some penalty, but userspace must wait
+    ResponsePacket.Acknowledge = 1;
+    SerialPortWrite ((UINT8 *)&ResponsePacket, sizeof (ResponsePacket));
+  }
+
+  // SPI flash is is fairly durable, but determine when erase is necessary.
+  Status = Spi2Ppi->FlashErase (
+             Spi2Ppi,
+             &gFlashRegionBiosGuid,
+             Address,
+             SIZE_BLOCK
+             );
+  if (EFI_ERROR (Status)) {
+    return;
+  }
+
+  Status = Spi2Ppi->FlashWrite (
+             Spi2Ppi,
+             &gFlashRegionBiosGuid,
+             Address,
+             SIZE_BLOCK,
+             BlockData
+             );
+}
+
+/**
+ * Perform flash.
+ *
+ * @return EFI_SUCCESS       Successful flash.
+ * @return EFI_DEVICE_ERROR  Initialise SPI service failed.
+ * @return EFI_TIMEOUT       Await command timed-out.
+**/
+EFI_STATUS
+EFIAPI
+PerformFlash (
+  VOID
+  )
+{
+  EFI_STATUS                  Status;
+  UINT8                       NoUserspaceExit;
+  UINT64                      LastServicedTimeNs;
+  EARLY_FLASH_RESCUE_COMMAND  CommandPacket;
+
+  //
+  // TODO: Library must reinstall its PPI, backed by NEM/DRAM
+  //
+  Status = SpiServiceInit ();
+  if (EFI_ERROR (Status)) {
+    return EFI_DEVICE_ERROR;
+  }
+
+  // Userspace-side orchestrates procedure, so no looping over blocks
+  NoUserspaceExit = 1;
+
+  LastServicedTimeNs = GetTimeInNanoSecond (GetPerformanceCounter ());
+  while (NoUserspaceExit) {
+    // Check if there is command waiting for us
+    if (SerialPortPoll ()) {
+      // Stall a tiny bit, in-case the remainder of the packet is flushing
+      MicroSecondDelay (10 * MS_IN_SECOND);
+
+      SerialPortRead ((UINT8 *)&CommandPacket, sizeof (CommandPacket));
+      switch (CommandPacket.Command) {
+        case EARLY_FLASH_RESCUE_COMMAND_CHECKSUM:
+          SendBlockChecksum (CommandPacket.BlockNumber);
+          break;
+        case EARLY_FLASH_RESCUE_COMMAND_WRITE:
+          WriteBlock (CommandPacket.BlockNumber);
+          break;
+        case EARLY_FLASH_RESCUE_COMMAND_RESET:
+          PerformSystemReset ();
+          // Permit fallthrough
+          NoUserspaceExit = 0;
+          break;
+        case EARLY_FLASH_RESCUE_COMMAND_EXIT:
+          NoUserspaceExit = 0;
+          break;
+        default:
+          break;
+      }
+
+      LastServicedTimeNs = GetTimeInNanoSecond (GetPerformanceCounter ());
+    }
+
+    if ((GetTimeInNanoSecond (GetPerformanceCounter ()) - LastServicedTimeNs) 
>=
+        (10ULL * NS_IN_SECOND)) {
+      // This is very bad. SPI flash could be inconsistent
+      // - In CAR there's likely too little memory to stash a backup
+      return EFI_TIMEOUT;
+    }
+  }
+
+  return EFI_SUCCESS;
+}
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.c
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.c
new file mode 100644
index 000000000000..74d7d9229554
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.c
@@ -0,0 +1,232 @@
+/** @file
+  Early SPI flash rescue protocol - board implementation.
+
+  Copyright (c) 2022, Baruch Binyamin Doron.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <PiPei.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/PeCoffLib.h>
+#include <Library/PcdLib.h>
+#include <Library/PeiServicesLib.h>
+#include <Library/ResetSystemLib.h>
+#include <Library/SpiLib.h>
+#include <Library/TimerLib.h>
+#include <Ppi/FeatureInMemory.h>
+#include <Ppi/Spi2.h>
+#include "FlashRescueBoard.h"
+
+STATIC CONST EFI_PEI_PPI_DESCRIPTOR  mFlashRescueReadyInMemoryPpiList = {
+  EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
+  &gPeiFlashRescueReadyInMemoryPpiGuid,
+  NULL
+};
+
+/**
+  Returns a pointer to the PCH SPI PPI.
+
+  @return Pointer to PCH_SPI2_PPI   If an instance of the PCH SPI PPI is found
+  @return NULL                      If an instance of the PCH SPI PPI is not 
found
+
+**/
+PCH_SPI2_PPI *
+GetSpiPpi (
+  VOID
+  )
+{
+  EFI_STATUS    Status;
+  PCH_SPI2_PPI  *PchSpi2Ppi;
+
+  Status =  PeiServicesLocatePpi (
+              &gPchSpi2PpiGuid,
+              0,
+              NULL,
+              (VOID **) &PchSpi2Ppi
+              );
+  if (EFI_ERROR (Status)) {
+    return NULL;
+  }
+
+  return PchSpi2Ppi;
+}
+
+/**
+ * Perform system reset to start this firmware.
+**/
+VOID
+EFIAPI
+PerformSystemReset (
+  VOID
+  )
+{
+  //
+  // PPI may be unavailable, but no PPI can point here. Use silicon variant of 
reset
+  //
+  ResetCold ();
+}
+
+/**
+ * During no-evict mode, cache coherency is by definition not maintained.
+ * Consequently, it's quite plausible that L1i and L3 (CAR) become out of sync.
+ * This results in the CPU throwing #UD for valid code and instructions loaded
+ * into CAR.
+ * As has been explained to me, this is because uOps in L1i do not match
+ * instructions in CAR.
+ * Therefore, attempt to saturate the L1 caches so that prior contents are 
demoted
+ * to L2. Then any access to CAR will cache-miss L1.
+ *
+ * This issue is provably the case since there is no issue in DRAM.
+**/
+STATIC
+UINT32
+EFIAPI
+FlushBiosHack (
+  VOID
+  )
+{
+  UINT32           temp;
+  UINT32           codeRegionStartAddr = 0xFFF00000;
+  UINT32           codeSizeInDWords = 640 * 1024 / 4;  // 640K / 4; larger 
caches
+  volatile UINT32  *codeAddr;
+  UINT32           DWordIdx;
+  UINT32           repeatCnt = 2;
+  UINT32           repeatIdx;
+
+  temp = 0;
+  for (repeatIdx = 0; repeatIdx < repeatCnt; repeatIdx++) {
+    for (DWordIdx = 0; DWordIdx < codeSizeInDWords; DWordIdx++) {
+      codeAddr = (volatile UINT32 *) (codeRegionStartAddr + DWordIdx * 4);
+      temp |= *codeAddr;
+    }
+  }
+
+  return temp;
+}
+
+/**
+  Entry Point function
+
+  @param[in] FileHandle  Handle of the file being invoked.
+  @param[in] PeiServices Describes the list of possible PEI Services.
+
+  @retval EFI_SUCCESS          if it completed successfully.
+  @retval EFI_OUT_OF_RESOURCES if PEIM cannot be reloaded.
+**/
+EFI_STATUS
+EFIAPI
+FlashRescueBoardPeiEntryPoint (
+  IN       EFI_PEI_FILE_HANDLE  FileHandle,
+  IN CONST EFI_PEI_SERVICES     **PeiServices
+  )
+{
+  EFI_STATUS                    Status;
+  VOID                          *FlashRescueReady;
+  VOID                          *ThisPeimData;
+  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;
+  EFI_PHYSICAL_ADDRESS          PeimCopy;
+  EFI_PEIM_ENTRY_POINT2         PeimEntryPoint;
+
+  //
+  // Second entry: Enter flash loop
+  //
+  Status = PeiServicesLocatePpi (
+             &gPeiFlashRescueReadyInMemoryPpiGuid,
+             0,
+             NULL,
+             (VOID **)&FlashRescueReady
+             );
+  if (!EFI_ERROR (Status)) {
+    Status = PerformFlash ();
+    ASSERT_EFI_ERROR (Status);
+
+    return EFI_SUCCESS;
+  }
+
+  //
+  // First entry: Establish communication with board or don't reload
+  //
+  DEBUG ((DEBUG_INFO, "HELLO begins. Re-connect with userspace-side now\n"));
+  MicroSecondDelay (3000 * MS_IN_SECOND);
+
+  Status = SendHelloPacket ();
+  if (EFI_ERROR (Status)) {
+    return EFI_SUCCESS;
+  }
+
+  //
+  // For a DEBUG() stack using the same transport as our SerialPortLib,
+  // it seems like commands are lost when DEBUG() fills the FIFO.
+  // Therefore, disable DEBUG().
+  // - However, this still requires testing.
+  //
+  PatchPcdSet32 (PcdDebugPrintErrorLevel, 0x0);
+
+  //
+  // Find this PEIM, then obtain a PE context with its data handle
+  //
+  Status = PeiServicesFfsFindSectionData (
+             EFI_SECTION_PE32,
+             FileHandle,
+             &ThisPeimData
+             );
+  ASSERT_EFI_ERROR (Status);
+
+  ZeroMem (&ImageContext, sizeof (ImageContext));
+
+  ImageContext.Handle    = ThisPeimData;
+  ImageContext.ImageRead = PeCoffLoaderImageReadFromMemory;
+
+  Status = PeCoffLoaderGetImageInfo (&ImageContext);
+  ASSERT_EFI_ERROR (Status);
+
+  //
+  // Allocate memory from NEM or DRAM
+  // - RegisterForShadow() is simpler for DRAM, but simplify code paths
+  //
+  Status = PeiServicesAllocatePages (
+               EfiBootServicesCode,
+               EFI_SIZE_TO_PAGES ((UINT32)ImageContext.ImageSize),
+               &PeimCopy
+               );
+  if (EFI_ERROR (Status)) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  //
+  // Load and relocate into the new buffer
+  //
+  ImageContext.ImageAddress = PeimCopy;
+  Status = PeCoffLoaderLoadImage (&ImageContext);
+  ASSERT_EFI_ERROR (Status);
+
+  Status = PeCoffLoaderRelocateImage (&ImageContext);
+  ASSERT_EFI_ERROR (Status);
+
+  FlushBiosHack ();
+
+  //
+  // Install flag PPI and call entrypoint
+  //
+  PeiServicesInstallPpi (&mFlashRescueReadyInMemoryPpiList);
+  ASSERT_EFI_ERROR (Status);
+
+  PeimEntryPoint = (EFI_PEIM_ENTRY_POINT2)(UINTN)ImageContext.EntryPoint;
+  Status = PeimEntryPoint (FileHandle, PeiServices);
+  ASSERT_EFI_ERROR (Status);
+
+  //
+  // Cleanup. It is important that PPIs do not point here
+  //
+  Status = SpiServiceInit ();
+  ASSERT_EFI_ERROR (Status);
+
+  Status = PeiServicesFreePages (
+             PeimCopy,
+             EFI_SIZE_TO_PAGES ((UINT32)ImageContext.ImageSize)
+             );
+  ASSERT_EFI_ERROR (Status);
+
+  return EFI_SUCCESS;
+}
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.inf
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.inf
new file mode 100644
index 000000000000..0937600c5ef8
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.inf
@@ -0,0 +1,54 @@
+##  @file
+#  Early SPI flash rescue protocol - board implementation.
+#
+#  Copyright (c) 2022, Baruch Binyamin Doron.<BR>
+#  SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+  INF_VERSION                    = 0x0001001B
+  BASE_NAME                      = FlashRescueBoardPei
+  FILE_GUID                      = D1B51B35-E01A-4633-991F-E1B639B8D3AA
+  MODULE_TYPE                    = PEIM
+  VERSION_STRING                 = 0.50
+  ENTRY_POINT                    = FlashRescueBoardPeiEntryPoint
+
+[Sources]
+  FlashRescueBoardPei.c
+  FlashRescueBoardCommon.c
+
+[Packages]
+  MdePkg/MdePkg.dec
+  MdeModulePkg/MdeModulePkg.dec
+  EarlySpiFlashRescueFeaturePkg/EarlySpiFlashRescueFeaturePkg.dec
+  IntelSiliconPkg/IntelSiliconPkg.dec
+  KabylakeSiliconPkg/SiPkg.dec
+
+[LibraryClasses]
+  PeimEntryPoint
+  PeiServicesLib
+  BaseLib
+  BaseMemoryLib
+  DebugLib
+  MemoryAllocationLib
+  PcdLib
+  PeCoffLib
+  ResetSystemLib
+  SerialPortLib
+  SpiLib
+  TimerLib
+
+[Ppis]
+  gPeiFlashRescueReadyInMemoryPpiGuid
+
+[Guids]
+  gFlashRegionBiosGuid
+
+[Pcd]
+  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdUserspaceHostWaitTimeout
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdDataXferPacketSize
+
+[Depex]
+  TRUE
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/EarlySpiFlashRescueFeature.dsc
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/EarlySpiFlashRescueFeature.dsc
new file mode 100644
index 000000000000..938c0740c7ce
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/EarlySpiFlashRescueFeature.dsc
@@ -0,0 +1,34 @@
+## @file
+# This is a build description file for the SPI flash rescue advanced feature.
+# This file should be included into another package DSC file to build this 
feature.
+#
+# The DEC files are used by the utilities that parse DSC and
+# INF files to generate AutoGen.c and AutoGen.h files
+# for the build infrastructure.
+#
+# Copyright (c) 2019 - 2021, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+################################################################################
+#
+# Defines Section - statements that will be processed to create a Makefile.
+#
+################################################################################
+[Defines]
+!ifndef $(PEI_ARCH)
+  !error "PEI_ARCH must be specified to build this feature!"
+!endif
+!ifndef $(DXE_ARCH)
+  !error "DXE_ARCH must be specified to build this feature!"
+!endif
+
+################################################################################
+#
+# Components section - list of all components needed by this feature.
+#
+################################################################################
+[Components.IA32]
+  EarlySpiFlashRescueFeaturePkg/FlashRescueBoardPei/FlashRescueBoardPei.inf
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf
new file mode 100644
index 000000000000..4a201157994a
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PostMemory.fdf
@@ -0,0 +1,8 @@
+## @file
+#  FDF file for post-memory modules that enable the SPI flash rescue feature.
+#
+# Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/Ppi/FeatureInMemory.h
 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/Ppi/FeatureInMemory.h
new file mode 100644
index 000000000000..1f68c4abd423
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/Ppi/FeatureInMemory.h
@@ -0,0 +1,23 @@
+/** @file
+  This file declares that this feature is relocated into memory from XIP.
+
+  This PPI is published by the feature PEIM after relocation. There is no
+  interface, it is used as a global variable. This is necessary to mitigate
+  possible bugs from flashing over XIP code.
+
+  Copyright (c) 2022, Baruch Binyamin Doron.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef __FEATURE_IN_MEMORY_H__
+#define __FEATURE_IN_MEMORY_H__
+
+#define PEI_FLASH_RESCUE_READY_IN_MEMORY_PPI_GUID \
+  { \
+    0xe5147285, 0x4d34, 0x415e, {0x8e, 0xa8, 0x85, 0xbd, 0xd8, 0xc6, 0x5b, 
0xde } \
+  }
+
+extern EFI_GUID  gPeiFlashRescueReadyInMemoryPpiGuid;
+
+#endif
diff --git 
a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PreMemory.fdf 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PreMemory.fdf
new file mode 100644
index 000000000000..89fec0096e95
--- /dev/null
+++ 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Include/PreMemory.fdf
@@ -0,0 +1,8 @@
+## @file
+#  FDF file for pre-memory modules that enable the SPI flash rescue feature.
+#
+# Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
diff --git a/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Readme.md 
b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Readme.md
new file mode 100644
index 000000000000..72d399f4ed40
--- /dev/null
+++ b/Features/Intel/Debugging/EarlySpiFlashRescueFeaturePkg/Readme.md
@@ -0,0 +1,119 @@
+# Overview
+* **Feature Name:** Early SPI Flash Rescue
+* **PI Phase(s) Supported:** PEI
+* **SMM Required?** No
+
+More Information:
+* [GitHub repo](https://github.com/benjamindoron/early_flash_rescue)
+
+## Purpose
+The Early SPI Flash Rescue feature enables the use of a serial port to
+rescue systems from broken firmware by delivering an update image to flash.
+With an appropriate serial port library, this is an important capability in
+firmware development, as debugging broken images on laptops is inefficient
+and wastes the developer's time on tedium. Now, simply flash the image under
+test, retrieve the broken debug log, then reflash another image. Even before
+permanent memory, it is assumed that the right serial port could complete
+this task in a few minutes. Presently, optimisation is underway to reduce the
+time for even slow serial ports down from 30 minutes and make this less a
+mere proof of concept.
+
+The feature is intended to be used with the userspace-side 
[here](https://github.com/benjamindoron/early_flash_rescue/tree/main/flash_rescue_userspace)
+presently implemented for POSIX Linux.
+
+# High-Level Theory of Operation
+*_TODO_*
+A description of how the device works at a high-level.
+
+The description should not be constrained to implementation details but 
provide a simple mental model of how the
+feature is supposed to work.
+
+## Firmware Volumes
+It is the user's responsibility to configure where the module is located.
+PEI APRIORI (MinPlatform PreMem BootFV) is recommended, but post-mem FVs
+are also expected.
+
+## Modules
+* EarlyFlashRescueBoardPei
+
+## <Module Name>
+*_TODO_*
+Each module in the feature should have a section that describes the module in 
a level of detail that is useful
+to better understand the module source code.
+
+## <Library Name>
+*_TODO_*
+Each library in the feature should have a section that describes the library 
in a level of detail that is useful
+to better understand the library source code.
+
+## Key Functions
+*_TODO_*
+A bulleted list of key functions for interacting with the feature.
+
+Not all features need to be listed. Only functions exposed through external 
interfaces that are important for feature
+users to be aware of.
+
+## Configuration
+*_TODO_*
+Information that is useful for configuring the feature.
+
+Not all configuration options need to be listed. This section is used to 
provide more background on configuration
+options than possible elsewhere.
+
+## Data Flows
+*_TODO_*
+Architecturally defined data structures and flows for the feature.
+
+## Control Flows
+*_TODO_*
+Key control flows for the feature.
+
+## Build Flows
+*_TODO_*
+Any special build flows should be described in this section.
+
+This is particularly useful for features that use custom build tools or 
require non-standard tool configuration. If the
+standard flow in the feature package template is used, this section may be 
empty.
+
+## Test Point Results
+No test points implemented
+
+## Functional Exit Criteria
+*_TODO_*
+The testable functionality for the feature.
+
+This section should provide an ordered list of criteria that a board 
integrator can reference to ensure the feature is
+functional on their board.
+
+## TODO: Feature Enabling Checklist
+* In the board DSC file, enable the feature
+```
+[PcdsFeatureFlag]
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdFlashRescueFeatureEnable|TRUE
+```
+* In the board FDF file, insert the module into FV (and APRIORI)
+```
+APRIORI PEI {
+  # Optionally, other modules prepended/appended...
+  INF  
EarlySpiFlashRescueFeaturePkg/EarlyFlashRescueBoardPei/EarlyFlashRescueBoardPei.inf
+}
+
+INF  
EarlySpiFlashRescueFeaturePkg/EarlyFlashRescueBoardPei/EarlyFlashRescueBoardPei.inf
+```
+
+
+## Performance Impact
+A general expectation for the impact on overall boot performance due to using 
this feature.
+
+This section is expected to provide guidance on:
+* How to estimate performance impact due to the feature
+* How to measure performance impact of the feature
+* How to manage performance impact of the feature
+
+## Common Optimizations
+* In the board DSC file, tune the timeout value and packet size
+```
+[PcdsFixedAtBuild]
+  
gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdUserspaceHostWaitTimeout|15000
+  gEarlySpiFlashRescueFeaturePkgTokenSpaceGuid.PcdDataXferPacketSize|64
+```
-- 
2.37.3



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#93918): https://edk2.groups.io/g/devel/message/93918
Mute This Topic: https://groups.io/mt/93757729/21656
Group Owner: devel+ow...@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com]
-=-=-=-=-=-=-=-=-=-=-=-



Reply via email to