text: // Copyright lowRISC contributors.
  // Licensed under the Apache License, Version 2.0, see LICENSE for details.
  // SPDX-License-Identifier: Apache-2.0
  
  #include "sw/device/lib/base/macros.h"
  #include "sw/device/lib/runtime/ibex.h"
  #include "sw/device/lib/testing/test_framework/check.h"
  #include "sw/device/silicon_creator/lib/base/sec_mmio.h"
  #include "sw/device/silicon_creator/lib/boot_data.h"
  #include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
  #include "sw/device/silicon_creator/lib/test_main.h"
  
  #include "flash_ctrl_regs.h"  // Generated.
  #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
  
  const test_config_t kTestConfig;
  
  /**
   * Boot data flash info pages.
   */
  static const flash_ctrl_info_page_t kPages[2] = {
      kFlashCtrlInfoPageBootData0,
      kFlashCtrlInfoPageBootData1,
  };
  
  /**
   * Boot data entry used in tests.
   */
  boot_data_t kTestBootData = (boot_data_t){
      .digest = {{0x00f0046c, 0x34e7a3d5, 0x93b15c2e, 0x77cbd502, 0x3d0530f6,
                  0xa58d38b2, 0x60693f97, 0x67e132d9}},
      .identifier = kBootDataIdentifier,
      .is_valid = kBootDataValidEntry,
      // `kBootDataDefault.counter` + 1 for consistency.
      .counter = 6,
      .min_security_version_rom_ext = 0,
  };
  
  /**
   * Sets read, write, and erase permissions for boot data pages.
   *
   * This function is intended for backdoor access during tests, e.g. to set the
   * contents of a page before a read test or check the contents after a write
   * test.
   *
   * @param enable New read, write, and erase permissions.
   */
  static void boot_data_pages_mp_set(hardened_bool_t perm) {
    multi_bit_bool_t mubi_perm =
        perm == kHardenedBoolTrue ? kMultiBitBool4True : kMultiBitBool4False;
    for (size_t i = 0; i < ARRAYSIZE(kPages); ++i) {
      flash_ctrl_info_perms_set(kPages[i], (flash_ctrl_perms_t){
                                               .read = mubi_perm,
                                               .write = mubi_perm,
                                               .erase = mubi_perm,
                                           });
    }
  }
  
  /**
   * Erases boot data info pages.
   */
  static void erase_boot_data_pages(void) {
    boot_data_pages_mp_set(kHardenedBoolTrue);
    for (size_t i = 0; i < ARRAYSIZE(kPages); ++i) {
      CHECK(flash_ctrl_info_erase(kPages[i], kFlashCtrlEraseTypePage) == kErrorOk,
            "Flash page erase failed.");
    }
    boot_data_pages_mp_set(kHardenedBoolFalse);
  }
  
  /**
   * Writes a boot data entry at the given page and index.
   *
   * This function also checks that the entry was written correctly by reading it
   * back from the flash.
   *
   * @param page Flash info page.
   * @param index Index of the entry to write in the given page.
   * @param boot_data A boot data entry.
   */
  static void write_boot_data(flash_ctrl_info_page_t page, size_t index,
                              const boot_data_t *boot_data) {
    const uint32_t offset = index * sizeof(boot_data_t);
    uint32_t buf[kBootDataNumWords];
    memcpy(buf, boot_data, sizeof(boot_data_t));
    boot_data_pages_mp_set(kHardenedBoolTrue);
    CHECK(flash_ctrl_info_write(page, offset, kBootDataNumWords, buf) == kErrorOk,
          "Flash write failed.");
    CHECK(flash_ctrl_info_read(page, offset, kBootDataNumWords, buf) == kErrorOk,
          "Flash read failed.");
    boot_data_pages_mp_set(kHardenedBoolFalse);
    CHECK(memcmp(buf, boot_data, sizeof(boot_data_t)) == 0,
          "Flash write failed.");
  }
  
  /**
   * Reads the boot data entry at the given page and index.
   *
   * @param page Flash info page.
   * @param index Index of the entry to read in the given page.
   * @param boot_data A boot data entry.
   */
  static void read_boot_data(flash_ctrl_info_page_t page, size_t index,
                             boot_data_t *boot_data) {
    const uint32_t offset = index * sizeof(boot_data_t);
    uint32_t buf[kBootDataNumWords];
    boot_data_pages_mp_set(kHardenedBoolTrue);
    CHECK(flash_ctrl_info_read(page, offset, kBootDataNumWords, buf) == kErrorOk,
          "Flash read failed.");
    boot_data_pages_mp_set(kHardenedBoolFalse);
    memcpy(boot_data, buf, sizeof(boot_data_t));
  }
  
  /**
   * Writes the given number of invalidated boot data entries to a page.
   *
   * This function invalidates the given boot data entry by setting its
   * `is_valid` field to `kBootDataInvalidEntry` before writing it to the
   * flash.
   *
   * @param page Flash info page.
   * @param num_entries Number of entries to write.
   * @param boot_data A boot data entry.
   */
  static void fill_with_invalidated_boot_data(flash_ctrl_info_page_t page,
                                              size_t num_entries,
                                              const boot_data_t *boot_data) {
    boot_data_t invalidated = *boot_data;
    invalidated.identifier = kBootDataIdentifier;
    invalidated.is_valid = kBootDataInvalidEntry;
    for (size_t i = 0; i < num_entries; ++i) {
      write_boot_data(page, i, &invalidated);
    }
  }
  
  /**
   * Compares two `boot_data_t` structs.
   *
   * @param lhs LHS of the comparison.
   * @param rhs RHS of the comparison.
   * @return The result of the operation.
   */
  static rom_error_t compare_boot_data(const boot_data_t *lhs,
                                       const boot_data_t *rhs) {
    if (memcmp(lhs, rhs, sizeof(boot_data_t)) != 0) {
      return kErrorUnknown;
    }
    return kErrorOk;
  }
  
  /**
   * Checks whether a boot data entry is valid.
   *
   * This function checks the `identifier`, `digest`, and counter fields of a boot
   * data entry.
   *
   * @param boot_data A boot data entry.
   * @return The result of the operation.
   */
  static rom_error_t check_boot_data(const boot_data_t *boot_data,
                                     uint32_t counter) {
    enum {
      kDigestRegionOffset = sizeof(boot_data->digest),
      kDigestRegionSize = sizeof(boot_data_t) - sizeof(boot_data->digest),
    };
  
    if (boot_data->identifier != kBootDataIdentifier) {
      return kErrorUnknown;
    }
  
    if (boot_data->counter != counter) {
      return kErrorUnknown;
    }
  
    hmac_digest_t act_digest;
    hmac_sha256_init();
    hmac_sha256_update((const char *)boot_data + kDigestRegionOffset,
                       kDigestRegionSize);
    hmac_sha256_final(&act_digest);
    if (memcmp(&act_digest, &boot_data->digest, sizeof(act_digest)) != 0) {
      return kErrorUnknown;
    }
    return kErrorOk;
  }
  
  rom_error_t check_test_data_test(void) {
    RETURN_IF_ERROR(check_boot_data(&kTestBootData, kTestBootData.counter));
    return kErrorOk;
  }
  
  rom_error_t read_empty_default_allowed_test(void) {
    erase_boot_data_pages();
  
    boot_data_t boot_data;
    RETURN_IF_ERROR(boot_data_read(kLcStateTest, &boot_data));
    RETURN_IF_ERROR(check_boot_data(&boot_data, 5));
    return kErrorOk;
  }
  
  rom_error_t read_empty_default_not_allowed_test(void) {
    erase_boot_data_pages();
  
    boot_data_t boot_data;
    if (boot_data_read(kLcStateProd, &boot_data) == kErrorBootDataNotFound) {
      return kErrorOk;
    }
    return kErrorUnknown;
  }
  
  rom_error_t read_single_page_0_test(void) {
    erase_boot_data_pages();
    write_boot_data(kFlashCtrlInfoPageBootData0, 0, &kTestBootData);
  
    boot_data_t boot_data;
    RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data));
    RETURN_IF_ERROR(compare_boot_data(&boot_data, &kTestBootData));
    return kErrorOk;
  }
  
  rom_error_t read_single_page_1_test(void) {
    erase_boot_data_pages();
    write_boot_data(kFlashCtrlInfoPageBootData1, 0, &kTestBootData);
  
    boot_data_t boot_data;
    uint64_t start = ibex_mcycle_read();
    RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data));
    uint64_t end = ibex_mcycle_read();
    RETURN_IF_ERROR(compare_boot_data(&boot_data, &kTestBootData));
    uint32_t cycles = end - start;
    LOG_INFO("boot_data_read() took %u cycles", cycles);
    return kErrorOk;
  }
  
  rom_error_t read_full_page_0_test(void) {
    erase_boot_data_pages();
    fill_with_invalidated_boot_data(kFlashCtrlInfoPageBootData0,
                                    kBootDataEntriesPerPage - 1, &kTestBootData);
    write_boot_data(kFlashCtrlInfoPageBootData0, kBootDataEntriesPerPage - 1,
                    &kTestBootData);
    fill_with_invalidated_boot_data(kFlashCtrlInfoPageBootData1,
                                    kBootDataEntriesPerPage, &kTestBootData);
  
    boot_data_t boot_data;
    RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data));
    RETURN_IF_ERROR(compare_boot_data(&boot_data, &kTestBootData));
    return kErrorOk;
  }
  
  rom_error_t read_full_page_1_test(void) {
    erase_boot_data_pages();
    fill_with_invalidated_boot_data(kFlashCtrlInfoPageBootData0,
                                    kBootDataEntriesPerPage, &kTestBootData);
    fill_with_invalidated_boot_data(kFlashCtrlInfoPageBootData1,
                                    kBootDataEntriesPerPage - 1, &kTestBootData);
    write_boot_data(kFlashCtrlInfoPageBootData1, kBootDataEntriesPerPage - 1,
                    &kTestBootData);
  
    boot_data_t boot_data;
    uint64_t start = ibex_mcycle_read();
    RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data));
    uint64_t end = ibex_mcycle_read();
    RETURN_IF_ERROR(compare_boot_data(&boot_data, &kTestBootData));
    uint32_t cycles = end - start;
    LOG_INFO("boot_data_read() took %u cycles", cycles);
    return kErrorOk;
  }
  
  rom_error_t write_empty_test(void) {
    erase_boot_data_pages();
    RETURN_IF_ERROR(boot_data_write(&kTestBootData));
    boot_data_t boot_data;
    RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data));
    RETURN_IF_ERROR(compare_boot_data(&kTestBootData, &boot_data));
    return kErrorOk;
  }
  
  rom_error_t write_page_switch_test(void) {
    erase_boot_data_pages();
    boot_data_t boot_data_act;
    boot_data_t boot_data_exp;
    // Counter value starts from `kBootDataDefault.counter` defined in
    // `boot_data.c` and is incremented before each write.
    uint32_t counter_exp = 5;
  
    // Write `kBootDataEntriesPerPage` + 1 entries to test the switch from page 0
    // to page 1.
    for (size_t i = 0; i < kBootDataEntriesPerPage + 1; ++i) {
      RETURN_IF_ERROR(boot_data_write(&kTestBootData));
      // Check `identifier`, `digest`, and `counter` fields.
      RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data_act));
      RETURN_IF_ERROR(check_boot_data(&boot_data_act, ++counter_exp));
      if (i > 0) {
        // Previous entry must be invalidated.
        boot_data_t prev_entry;
        read_boot_data(kFlashCtrlInfoPageBootData0, i - 1, &prev_entry);
        if (prev_entry.is_valid != kBootDataInvalidEntry) {
          LOG_ERROR("Previous entry was not invalidated");
          return kErrorUnknown;
        }
      }
    }
    // Last written entry must be at entry 0 in page 1.
    read_boot_data(kFlashCtrlInfoPageBootData1, 0, &boot_data_exp);
    if (memcmp(&boot_data_act, &boot_data_exp, sizeof(boot_data_t)) != 0) {
      LOG_ERROR("Page 0 -> 1 switch failed.");
      return kErrorUnknown;
    }
  
    // Write `kBootDataEntriesPerPage` entries to test the switch from page 1 to
    // page 0.
    for (size_t i = 1; i < kBootDataEntriesPerPage + 1; ++i) {
      RETURN_IF_ERROR(boot_data_write(&kTestBootData));
      // Check `identifier`, `digest`, and `counter` fields.
      RETURN_IF_ERROR(boot_data_read(kLcStateProd, &boot_data_act));
      RETURN_IF_ERROR(check_boot_data(&boot_data_act, ++counter_exp));
      // Previous entry must be invalidated.
      boot_data_t prev_entry;
      read_boot_data(kFlashCtrlInfoPageBootData1, i - 1, &prev_entry);
      if (prev_entry.is_valid != kBootDataInvalidEntry) {
        LOG_ERROR("Previous entry was not invalidated");
        return kErrorUnknown;
      }
    }
    // Last written entry must be at entry 0 in page 0.
    read_boot_data(kFlashCtrlInfoPageBootData0, 0, &boot_data_exp);
    if (memcmp(&boot_data_act, &boot_data_exp, sizeof(boot_data_t)) != 0) {
      LOG_ERROR("Page 1 -> 0 switch failed.");
      return kErrorUnknown;
    }
  
    return kErrorOk;
  }
  
  bool test_main(void) {
    rom_error_t result = kErrorOk;
  
    flash_ctrl_init();
    SEC_MMIO_WRITE_INCREMENT(kFlashCtrlSecMmioInit);
    sec_mmio_check_counters(/*expected_check_count=*/0);
  
    EXECUTE_TEST(result, check_test_data_test);
    EXECUTE_TEST(result, read_empty_default_allowed_test);
    EXECUTE_TEST(result, read_empty_default_not_allowed_test);
    EXECUTE_TEST(result, read_single_page_0_test);
    EXECUTE_TEST(result, read_single_page_1_test);
    EXECUTE_TEST(result, read_full_page_0_test);
    EXECUTE_TEST(result, read_full_page_1_test);
    EXECUTE_TEST(result, write_empty_test);
    EXECUTE_TEST(result, write_page_switch_test);
  
    return result == kErrorOk;
  }
  