[sw/silicon_creator] Add flash_ctrl_data_erase_verify

Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/sw/device/silicon_creator/lib/drivers/flash_ctrl.c b/sw/device/silicon_creator/lib/drivers/flash_ctrl.c
index 3c15c1e..1141018 100644
--- a/sw/device/silicon_creator/lib/drivers/flash_ctrl.c
+++ b/sw/device/silicon_creator/lib/drivers/flash_ctrl.c
@@ -400,6 +400,50 @@
   return wait_for_done(kErrorFlashCtrlDataErase);
 }
 
+rom_error_t flash_ctrl_data_erase_verify(uint32_t addr,
+                                         flash_ctrl_erase_type_t erase_type) {
+  static_assert(__builtin_popcount(FLASH_CTRL_PARAM_BYTES_PER_BANK) == 1,
+                "Bytes per bank must be a power of two.");
+  static_assert(__builtin_popcount(FLASH_CTRL_PARAM_BYTES_PER_PAGE) == 1,
+                "Bytes per page must be a power of two.");
+
+  size_t byte_count;
+  rom_error_t error = kErrorFlashCtrlDataEraseVerify;
+  switch (launder32(erase_type)) {
+    case kFlashCtrlEraseTypeBank:
+      HARDENED_CHECK_EQ(erase_type, kFlashCtrlEraseTypeBank);
+      byte_count = FLASH_CTRL_PARAM_BYTES_PER_BANK;
+      error = kErrorOk ^ (byte_count - 1);
+      break;
+    case kFlashCtrlEraseTypePage:
+      HARDENED_CHECK_EQ(erase_type, kFlashCtrlEraseTypePage);
+      byte_count = FLASH_CTRL_PARAM_BYTES_PER_PAGE;
+      error = kErrorOk ^ (byte_count - 1);
+      break;
+    default:
+      HARDENED_UNREACHABLE();
+  }
+
+  // Truncate to the closest lower bank/page aligned address.
+  addr &= ~byte_count + 1;
+  uint32_t mask = kFlashCtrlErasedWord;
+  size_t i = 0;
+  for (; launder32(i) < byte_count; i += sizeof(uint32_t)) {
+    uint32_t word =
+        abs_mmio_read32(TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + addr + i);
+    mask &= word;
+    error &= word;
+  }
+  HARDENED_CHECK_EQ(i, byte_count);
+
+  if (launder32(mask) == kFlashCtrlErasedWord) {
+    HARDENED_CHECK_EQ(mask, kFlashCtrlErasedWord);
+    return error ^ (byte_count - 1);
+  }
+
+  return kErrorFlashCtrlDataEraseVerify;
+}
+
 rom_error_t flash_ctrl_info_erase(flash_ctrl_info_page_t info_page,
                                   flash_ctrl_erase_type_t erase_type) {
   const uint32_t addr = info_page_addr(info_page);
diff --git a/sw/device/silicon_creator/lib/drivers/flash_ctrl.h b/sw/device/silicon_creator/lib/drivers/flash_ctrl.h
index b697c07..dea4a41 100644
--- a/sw/device/silicon_creator/lib/drivers/flash_ctrl.h
+++ b/sw/device/silicon_creator/lib/drivers/flash_ctrl.h
@@ -314,6 +314,16 @@
                                   flash_ctrl_erase_type_t erase_type);
 
 /**
+ * Verifies that a data partition page or bank was erased.
+ *
+ * @param addr Address that falls within the bank or page erased.
+ * @param erase_type Whether to verify a page or a bank.
+ * @return Result of the operation.
+ */
+rom_error_t flash_ctrl_data_erase_verify(uint32_t addr,
+                                         flash_ctrl_erase_type_t erase_type);
+
+/**
  * Erases an information partition page or bank.
  *
  * @param info_page Information page to erase for page erases, or a page within
diff --git a/sw/device/silicon_creator/lib/drivers/flash_ctrl_unittest.cc b/sw/device/silicon_creator/lib/drivers/flash_ctrl_unittest.cc
index a03c443..f3acd25 100644
--- a/sw/device/silicon_creator/lib/drivers/flash_ctrl_unittest.cc
+++ b/sw/device/silicon_creator/lib/drivers/flash_ctrl_unittest.cc
@@ -605,5 +605,89 @@
   flash_ctrl_creator_info_pages_lockdown();
 }
 
+struct EraseVerifyCase {
+  /**
+   * Address.
+   */
+  uint32_t addr;
+  /**
+   * Truncated address aligned to closest lower page/bank.
+   */
+  uint32_t aligned_addr;
+  /**
+   * Erase type.
+   */
+  flash_ctrl_erase_type_t erase_type;
+  /**
+   * Value of the last word read from flash (for testing failure cases).
+   */
+  uint32_t last_word_val;
+  /**
+   * Expected return value.
+   */
+  rom_error_t error;
+};
+
+class EraseVerifyTest : public FlashCtrlTest,
+                        public testing::WithParamInterface<EraseVerifyCase> {};
+
+TEST_P(EraseVerifyTest, DataEraseVerify) {
+  size_t byte_count;
+  switch (GetParam().erase_type) {
+    case kFlashCtrlEraseTypeBank:
+      byte_count = FLASH_CTRL_PARAM_BYTES_PER_BANK;
+      break;
+    case kFlashCtrlEraseTypePage:
+      byte_count = FLASH_CTRL_PARAM_BYTES_PER_PAGE;
+      break;
+    default:
+      FAIL();
+  }
+
+  size_t i = 0;
+  for (; i < byte_count - sizeof(uint32_t); i += sizeof(uint32_t)) {
+    EXPECT_ABS_READ32(
+        TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + GetParam().aligned_addr + i,
+        kFlashCtrlErasedWord);
+  }
+  EXPECT_ABS_READ32(
+      TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + GetParam().aligned_addr + i,
+      GetParam().last_word_val);
+
+  EXPECT_EQ(
+      flash_ctrl_data_erase_verify(GetParam().addr, GetParam().erase_type),
+      GetParam().error);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllCases, EraseVerifyTest,
+    testing::Values(
+        // Verify first page.
+        EraseVerifyCase{
+            .addr = 0,
+            .aligned_addr = 0,
+            .erase_type = kFlashCtrlEraseTypePage,
+            .last_word_val = kFlashCtrlErasedWord,
+            .error = kErrorOk,
+        },
+        // Verify 10th page, unaligned address.
+        EraseVerifyCase{
+            .addr = 10 * FLASH_CTRL_PARAM_BYTES_PER_PAGE + 128,
+            .aligned_addr = 10 * FLASH_CTRL_PARAM_BYTES_PER_PAGE,
+            .erase_type = kFlashCtrlEraseTypePage,
+            .last_word_val = kFlashCtrlErasedWord,
+            .error = kErrorOk,
+        },
+        // Fail to verify 10th page, unaligned address.
+        EraseVerifyCase{
+            .addr = 10 * FLASH_CTRL_PARAM_BYTES_PER_PAGE + 128,
+            .aligned_addr = 10 * FLASH_CTRL_PARAM_BYTES_PER_PAGE,
+            .erase_type = kFlashCtrlEraseTypePage,
+            .last_word_val = 0xfffffff0,
+            .error = kErrorFlashCtrlDataEraseVerify,
+        }  // Note: No cases for bank erases since the test times out due to
+           // large number of expectations.
+        ));
+
 }  // namespace
 }  // namespace flash_ctrl_unittest
diff --git a/sw/device/silicon_creator/lib/error.h b/sw/device/silicon_creator/lib/error.h
index 018dfc6..b385dae 100644
--- a/sw/device/silicon_creator/lib/error.h
+++ b/sw/device/silicon_creator/lib/error.h
@@ -100,6 +100,7 @@
   X(kErrorFlashCtrlInfoWrite,         ERROR_(4, kModuleFlashCtrl, kInternal)), \
   X(kErrorFlashCtrlDataErase,         ERROR_(5, kModuleFlashCtrl, kInternal)), \
   X(kErrorFlashCtrlInfoErase,         ERROR_(6, kModuleFlashCtrl, kInternal)), \
+  X(kErrorFlashCtrlDataEraseVerify,   ERROR_(7, kModuleFlashCtrl, kInternal)), \
   X(kErrorSecMmioRegFileSize,         ERROR_(0, kModuleSecMmio, kResourceExhausted)), \
   X(kErrorSecMmioReadFault,           ERROR_(1, kModuleSecMmio, kInternal)), \
   X(kErrorSecMmioWriteFault,          ERROR_(2, kModuleSecMmio, kInternal)), \