"Empty" OTP and support in test_rom - On our planned test chip, the OTP will just be a ram, and thus empty. This commit adds a modified bootstrap library with an OTP_IS_RAM define, and targets for a test rom with that version of the library. - Add splicing targets to get a bitstream with both the OTP_IS_RAM version of the rom, and an empty OTP -- and add this to the smoketest tarball so we get it from CI. Change-Id: I2315ad69553a0e24227a545993281159869275e8
diff --git a/hw/bitstream/vivado/BUILD b/hw/bitstream/vivado/BUILD index acc80f9..b192368 100644 --- a/hw/bitstream/vivado/BUILD +++ b/hw/bitstream/vivado/BUILD
@@ -121,11 +121,30 @@ tags = ["manual"], ) +bitstream_splice( + name = "fpga_nexus_mask_test_rom_no_otp", + testonly = True, + src = ":fpga_nexus_test_rom", + data = "//sw/device/lib/testing/test_rom:test_rom_no_otp_fpga_nexus_scr_vmem", + meminfo = ":rom_mmi", + tags = ["manual"], +) + +bitstream_splice( + name = "fpga_nexus_mask_test_rom_no_otp_empty", + testonly = True, + src = ":fpga_nexus_mask_test_rom_no_otp", + data = "//hw/top_matcha/data:otp_img_empty", + meminfo = ":otp_mmi", + tags = ["manual"], +) + pkg_tar( name = "fpga_nexus_smoketest", testonly = True, srcs = [ ":fpga_nexus_mask_test_rom", + ":fpga_nexus_mask_test_rom_no_otp_empty", ":logs", ":otp_mmi", ":rom_mmi",
diff --git a/hw/top_matcha/data/BUILD b/hw/top_matcha/data/BUILD index 6fd346d..ee7aa69 100644 --- a/hw/top_matcha/data/BUILD +++ b/hw/top_matcha/data/BUILD
@@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 -load("@lowrisc_opentitan//rules:otp.bzl", "otp_image") +load("@lowrisc_opentitan//rules:otp.bzl", "otp_image", "otp_json", "otp_partition") package(default_visibility = ["//visibility:public"]) @@ -30,3 +30,199 @@ "@lowrisc_opentitan//hw/ip/otp_ctrl/data:otp_json_hw_cfg", ], ) + +################ + +otp_json( + name = "otp_json_empty", + partitions = [ + otp_partition( + name = "SECRET0", + items = { + "TEST_UNLOCK_TOKEN": "0", + "TEST_EXIT_TOKEN": "0", + }, + lock = False, + ), + otp_partition( + name = "SECRET1", + items = { + "FLASH_ADDR_KEY_SEED": "0", + "FLASH_DATA_KEY_SEED": "0", + "SRAM_DATA_KEY_SEED": "0", + }, + lock = False, + ), + otp_partition( + name = "SECRET2", + items = { + "RMA_TOKEN": "0", + "CREATOR_ROOT_KEY_SHARE0": "0", + "CREATOR_ROOT_KEY_SHARE1": "0", + }, + lock = False, + ), + otp_partition( + name = "LIFE_CYCLE", + count = 8, + # Should we set this to TEST_UNLOCKED0? + state = "TEST_UNLOCKED0", + ), + ], + seed = "0", +) + +otp_json( + name = "otp_json_creator_sw_cfg", + partitions = [ + otp_partition( + name = "CREATOR_SW_CFG", + items = { + "CREATOR_SW_CFG_DIGEST": "0x0", + # Use software mod_exp implementation for signature + # verification. See the definition of `hardened_bool_t` in + # sw/device/lib/base/hardened.h. + "CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN": "0", + # Mark the first three keys as valid and remaining as invalid + # since we currently have only three keys. See the definition of + # `hardened_byte_bool_t` in sw/device/lib/base/hardened.h. + "CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN": "0", + # Enable use of entropy for countermeasures. See the definition + # of `hardened_bool_t` in sw/device/lib/base/hardened.h. + "CREATOR_SW_CFG_RNG_EN": "0", + # ROM execution is enabled if this item is set to a non-zero + # value. + "CREATOR_SW_CFG_ROM_EXEC_EN": "0", + # Value to write to the cpuctrl CSR in `rom_init()`. + # See: + # https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html#cpu-control-register-cpuctrl + "CREATOR_SW_CFG_CPUCTRL": "0", + "CREATOR_SW_CFG_JITTER_EN": "0", + # Value of the min_security_version_rom_ext field of the + # default boot data. + "CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT": "0x0", + # Value of the min_security_version_bl0 field of the default + # boot data. + "CREATOR_SW_CFG_MIN_SEC_VER_BL0": "0x0", + # Enable the default boot data in PROD and PROD_END life cycle + # states. See the definition of `hardened_bool_t` in + # sw/device/lib/base/hardened.h. + "CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN": "0", + # Enable AST initialization. + "CREATOR_SW_CFG_AST_INIT_EN": "0", + # TODO: This enables a busyloop in the ROM to give time to + # trigger an RMA lifecycle transition via JTAG. The current + # value of 10 cycles is useful for test code which verifies + # the path through the ROM. This value is not useful for a + # real chip. + "CREATOR_SW_CFG_RMA_SPIN_EN": "0", + "CREATOR_SW_CFG_RMA_SPIN_CYCLES": "0", + # Entropy source health check default values. This needs to be + # populated when `CREATOR_SW_CFG_RNG_EN` is set to true. + "CREATOR_SW_CFG_RNG_REPCNT_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_REPCNTS_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_ADAPTP_HI_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_ADAPTP_LO_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_BUCKET_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_MARKOV_HI_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_MARKOV_LO_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_EXTHT_HI_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_EXTHT_LO_THRESHOLDS": "0", + "CREATOR_SW_CFG_RNG_ALERT_THRESHOLD": "0", + "CREATOR_SW_CFG_RNG_HEALTH_CONFIG_DIGEST": "0", + }, + ), + ], +) + +otp_json( + name = "otp_json_owner_sw_cfg", + partitions = [ + otp_partition( + name = "OWNER_SW_CFG", + items = { + "OWNER_SW_CFG_DIGEST": "0x0", + # Enable bootstrap. See `hardened_bool_t` in + # sw/device/lib/base/hardened.h. + "OWNER_SW_CFG_ROM_BOOTSTRAP_EN": "0", + # Set to 0x739 to use the ROM_EXT hash measurement as the key + # manager attestation binding value. + "OWNER_SW_CFG_ROM_KEYMGR_ROM_EXT_MEAS_EN": "0x0", + # Report errors without any redaction. + "OWNER_SW_CFG_ROM_ERROR_REPORTING": "0", + # Set the enables to kAlertEnableNone. + # See `alert_enable_t` in + # sw/device/silicon_creator/lib/drivers/alert.h + "OWNER_SW_CFG_ROM_ALERT_CLASS_EN": "0", + # Set the esclation policies to kAlertEscalateNone. + # See `alert_escalate_t` in + # sw/device/silicon_creator/lib/drivers/alert.h + "OWNER_SW_CFG_ROM_ALERT_ESCALATION": "0", + # Set the classifiactions to kAlertClassX. + # See `alert_class_t` in + # sw/device/silicon_creator/lib/drivers/alert.h + "OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION": ["0"] * 80, + # Set the classifiactions to kAlertClassX. See `alert_class_t` in + # sw/device/silicon_creator/lib/drivers/alert.h + "OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION": ["0"] * 16, + # Set the alert accumulation thresholds to 0 per class. + "OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH": ["0x00000000"] * 4, + # Set the alert timeout cycles to 0 per class. + "OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES": ["0x00000000"] * 4, + # Set the alert phase cycles to 0,10,10,0xFFFFFFFF for classes + # A and B, and to all zeros for classes C and D. + "OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES": [ + "0x0", + "0", + "0", + "0", + "0x0", + "0", + "0", + "0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + ], + }, + ), + ], +) + +otp_json( + name = "otp_json_hw_cfg", + partitions = [ + otp_partition( + name = "HW_CFG", + items = { + "DEVICE_ID": "0", + # Cryptolib and chip-level tests require access to the CSRNG + # software interfaces. + "EN_CSRNG_SW_APP_READ": False, + "EN_SRAM_IFETCH": False, + # Cryptolib and chip-level tests require access to the + # entropy_src FW data interface. + "EN_ENTROPY_SRC_FW_READ": False, + # Cryptolib and chip-level tests require access to the + # entropy_src FW override interface. + "EN_ENTROPY_SRC_FW_OVER": False, + }, + lock = False, + ), + ], +) + +otp_image( + name = "otp_img_empty", + src = ":otp_json_empty", + overlays = [ + ":otp_json_creator_sw_cfg", + ":otp_json_owner_sw_cfg", + ":otp_json_hw_cfg", + ], +)
diff --git a/sw/device/lib/testing/test_rom/BUILD b/sw/device/lib/testing/test_rom/BUILD index 31ef4bc..bce31d3 100644 --- a/sw/device/lib/testing/test_rom/BUILD +++ b/sw/device/lib/testing/test_rom/BUILD
@@ -26,17 +26,34 @@ opentitan_rom_binary( name = "test_rom", + srcs = [ + "test_rom.c", + "test_rom_start.S", + ], deps = [ ":linker_script", ":test_rom_lib", + "//sw/device/silicon_creator/rom:bootstrap", ], ) +opentitan_rom_binary( + name = "test_rom_no_otp", + srcs = [ + "test_rom.c", + "test_rom_start.S", + ], + defines = ["OTP_IS_RAM"], + deps = [ + ":linker_script", + ":test_rom_lib", + "//sw/device/silicon_creator/rom:bootstrap_no_otp", + ] +) + cc_library( name = "test_rom_lib", srcs = [ - "test_rom.c", - "test_rom_start.S", ], target_compatible_with = [OPENTITAN_CPU], deps = [ @@ -79,7 +96,6 @@ "@lowrisc_opentitan//sw/device/silicon_creator/lib/base:static_critical_sec_mmio", "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:flash_ctrl", "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:retention_sram", - "@lowrisc_opentitan//sw/device/silicon_creator/rom:bootstrap", ], )
diff --git a/sw/device/lib/testing/test_rom/test_rom.c b/sw/device/lib/testing/test_rom/test_rom.c index 02303a0..0f2ed0e 100644 --- a/sw/device/lib/testing/test_rom/test_rom.c +++ b/sw/device/lib/testing/test_rom/test_rom.c
@@ -73,9 +73,14 @@ OT_WEAK bool rom_test_main(void) { // Check the otp to see if execute should start + // val: 0xFFFFFFFF +#if defined(OTP_IS_RAM) + uint32_t otp_val = 0xffffffff; +#else uint32_t otp_val = abs_mmio_read32( TOP_MATCHA_OTP_CTRL_CORE_BASE_ADDR + OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + OTP_CTRL_PARAM_CREATOR_SW_CFG_ROM_EXEC_EN_OFFSET); +#endif if (otp_val == 0) { test_status_set(kTestStatusInBootRomHalt); @@ -86,9 +91,14 @@ // Initialize Ibex cpuctrl (contains icache / security feature enablements). uint32_t cpuctrl_csr; CSR_READ(CSR_REG_CPUCTRL, &cpuctrl_csr); + // val: 0x1 +#if defined(OTP_IS_RAM) + uint32_t cpuctrl_otp_val = 0x1; +#else uint32_t cpuctrl_otp_val = abs_mmio_read32( TOP_MATCHA_OTP_CTRL_CORE_BASE_ADDR + OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + OTP_CTRL_PARAM_CREATOR_SW_CFG_CPUCTRL_OFFSET); +#endif cpuctrl_csr = bitfield_field32_write( cpuctrl_csr, (bitfield_field32_t){.mask = 0x3f, .index = 0}, cpuctrl_otp_val); @@ -170,9 +180,14 @@ } // Check the otp to see if flash scramble should be enabled. + // val: 0x0 +#if defined(OTP_IS_RAM) + otp_val = 0; +#else otp_val = abs_mmio_read32( TOP_MATCHA_OTP_CTRL_CORE_BASE_ADDR + OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_DATA_DEFAULT_CFG_OFFSET); +#endif if (otp_val != 0) { dif_flash_ctrl_region_properties_t default_properties;
diff --git a/sw/device/lib/testing/test_rom/test_rom_start.S b/sw/device/lib/testing/test_rom/test_rom_start.S index 8db5211..a326517 100644 --- a/sw/device/lib/testing/test_rom/test_rom_start.S +++ b/sw/device/lib/testing/test_rom/test_rom_start.S
@@ -219,6 +219,7 @@ #endif _start: #if !OT_IS_ENGLISH_BREAKFAST +#if !OTP_IS_RAM // Check if AST initialization should be skipped. li a0, (TOP_MATCHA_OTP_CTRL_CORE_BASE_ADDR + \ OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET) @@ -233,6 +234,9 @@ OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + \ OTP_CTRL_PARAM_CREATOR_SW_CFG_AST_CFG_OFFSET) call crt_section_copy +#else + j .L_ast_init_skip +#endif // Wait for AST initialization to complete. li a0, TOP_MATCHA_SENSOR_CTRL_BASE_ADDR
diff --git a/sw/device/silicon_creator/BUILD b/sw/device/silicon_creator/BUILD new file mode 100644 index 0000000..34910b0 --- /dev/null +++ b/sw/device/silicon_creator/BUILD
@@ -0,0 +1,6 @@ +# Copyright 2022 Google LLC. +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"])
diff --git a/sw/device/silicon_creator/rom/BUILD b/sw/device/silicon_creator/rom/BUILD new file mode 100644 index 0000000..9e1d2ff --- /dev/null +++ b/sw/device/silicon_creator/rom/BUILD
@@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC. +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +BOOTSTRAP_DEPS = [ + "//hw/top_matcha/sw/autogen:top_matcha", + "//hw/top_matcha/ip/flash_ctrl/data/autogen:flash_ctrl_regs", + "@lowrisc_opentitan//hw/ip/gpio/data:gpio_regs", + "@lowrisc_opentitan//hw/ip/otp_ctrl/data:otp_ctrl_regs", + "@lowrisc_opentitan//sw/device/lib/base:abs_mmio", + "@lowrisc_opentitan//sw/device/lib/base:bitfield", + "@lowrisc_opentitan//sw/device/lib/base:hardened", + "@lowrisc_opentitan//sw/device/silicon_creator/lib:error", + "@lowrisc_opentitan//sw/device/silicon_creator/lib/base:chip", + "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:flash_ctrl", + "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:otp", + "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:rstmgr", + "@lowrisc_opentitan//sw/device/silicon_creator/lib/drivers:spi_device", + ] + +cc_library( + name = "bootstrap", + srcs = ["bootstrap.c"], + hdrs = ["bootstrap.h"], + deps = BOOTSTRAP_DEPS, +) + +cc_library( + name = "bootstrap_no_otp", + srcs = ["bootstrap.c"], + hdrs = ["bootstrap.h"], + defines = ["OTP_IS_RAM"], + deps = BOOTSTRAP_DEPS, +)
diff --git a/sw/device/silicon_creator/rom/bootstrap.c b/sw/device/silicon_creator/rom/bootstrap.c new file mode 100644 index 0000000..f77c592 --- /dev/null +++ b/sw/device/silicon_creator/rom/bootstrap.c
@@ -0,0 +1,392 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/silicon_creator/rom/bootstrap.h" + +#include <stdalign.h> + +#include "sw/device/lib/base/abs_mmio.h" +#include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/hardened.h" +#include "sw/device/silicon_creator/lib/base/chip.h" +#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h" +#include "sw/device/silicon_creator/lib/drivers/otp.h" +#include "sw/device/silicon_creator/lib/drivers/rstmgr.h" +#include "sw/device/silicon_creator/lib/drivers/spi_device.h" +#include "sw/device/silicon_creator/lib/error.h" + +#include "flash_ctrl_regs.h" +#include "gpio_regs.h" +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "otp_ctrl_regs.h" + +enum { + /* + * Maximum flash address, exclusive. + */ + kMaxAddress = + FLASH_CTRL_PARAM_BYTES_PER_BANK * FLASH_CTRL_PARAM_REG_NUM_BANKS, +}; + +static_assert(FLASH_CTRL_PARAM_REG_NUM_BANKS == 2, "Flash must have 2 banks"); + +/** + * Bootstrap states. + * + * OpenTitan bootstrap consists of three states between which the chip + * transitions sequentially. + * + * Encoding generated with + * $ ./util/design/sparse-fsm-encode.py -d 5 -m 3 -n 32 \ + * -s 375382971 --language=c + * + * Minimum Hamming distance: 17 + * Maximum Hamming distance: 19 + * Minimum Hamming weight: 16 + * Maximum Hamming weight: 19 + */ +typedef enum bootstrap_state { + /** + * Initial bootstrap state where the chip waits for a SECTOR_ERASE or + * CHIP_ERASE command. + */ + kBootstrapStateErase = 0xd4576543, + /** + * Second bootstrap state where the chip verifies that all data banks have + * been erased. + */ + kBootstrapStateEraseVerify = 0xf3c71bac, + /** + * Final bootstrap state. This is the main program loop where the chip handles + * erase, program, and reset commands. + */ + kBootstrapStateProgram = 0xbdd8ca60, +} bootstrap_state_t; + +/** + * Handles access permissions and erases both data banks of the embedded flash. + * + * @return Result of the operation. + */ +static rom_error_t bootstrap_chip_erase(void) { + flash_ctrl_bank_erase_perms_set(kHardenedBoolTrue); + rom_error_t err_0 = flash_ctrl_data_erase(0, kFlashCtrlEraseTypeBank); + rom_error_t err_1 = flash_ctrl_data_erase(FLASH_CTRL_PARAM_BYTES_PER_BANK, + kFlashCtrlEraseTypeBank); + flash_ctrl_bank_erase_perms_set(kHardenedBoolFalse); + + HARDENED_RETURN_IF_ERROR(err_0); + return err_1; +} + +/** + * Handles access permissions and erases a 4 KiB region in the data partition of + * the embedded flash. + * + * Since OpenTitan's flash page size is 2 KiB, this function erases two + * consecutive pages. + * + * @param addr Address that falls within the 4 KiB region being deleted. + * @return Result of the operation. + */ +static rom_error_t bootstrap_sector_erase(uint32_t addr) { + static_assert(FLASH_CTRL_PARAM_BYTES_PER_PAGE == 2048, + "Page size must be 2 KiB"); + enum { + /** + * Mask for truncating `addr` to the lower 4 KiB aligned address. + */ + kPageAddrMask = ~UINT32_C(4096) + 1, + }; + + if (addr >= kMaxAddress) { + return kErrorBootstrapEraseAddress; + } + addr &= kPageAddrMask; + + flash_ctrl_data_default_perms_set((flash_ctrl_perms_t){ + .read = kMultiBitBool4False, + .write = kMultiBitBool4False, + .erase = kMultiBitBool4True, + }); + rom_error_t err_0 = flash_ctrl_data_erase(addr, kFlashCtrlEraseTypePage); + rom_error_t err_1 = flash_ctrl_data_erase( + addr + FLASH_CTRL_PARAM_BYTES_PER_PAGE, kFlashCtrlEraseTypePage); + flash_ctrl_data_default_perms_set((flash_ctrl_perms_t){ + .read = kMultiBitBool4False, + .write = kMultiBitBool4False, + .erase = kMultiBitBool4False, + }); + + HARDENED_RETURN_IF_ERROR(err_0); + return err_1; +} + +/** + * Handles access permissions and programs up to 256 bytes of flash memory + * starting at `addr`. + * + * If `byte_count` is not a multiple of flash word size, it's rounded up to next + * flash word and missing bytes in `data` are set to `0xff`. + * + * @param addr Address to write to, must be flash word aligned. + * @param byte_count Number of bytes to write. Rounded up to next flash word if + * not a multiple of flash word size. Missing bytes in `data` are set to `0xff`. + * @param data Data to write, must be word aligned. If `byte_count` is not a + * multiple of flash word size, `data` must have enough space until the next + * flash word. + * @return Result of the operation. + */ +static rom_error_t bootstrap_page_program(uint32_t addr, size_t byte_count, + uint8_t *data) { + static_assert(__builtin_popcount(FLASH_CTRL_PARAM_BYTES_PER_WORD) == 1, + "Bytes per flash word must be a power of two."); + enum { + /** + * Mask for checking that `addr` is flash word aligned. + */ + kFlashWordMask = FLASH_CTRL_PARAM_BYTES_PER_WORD - 1, + /** + * SPI flash programming page size in bytes. + */ + kFlashProgPageSize = 256, + /** + * Mask for checking whether `addr` is flash programming page aligned. + * + * Flash programming page size is 256 bytes, writes that start at an `addr` + * with a non-zero LSB wrap to the start of the 256 byte region. + */ + kFlashProgPageMask = kFlashProgPageSize - 1, + }; + + if (addr & kFlashWordMask || addr >= kMaxAddress) { + return kErrorBootstrapProgramAddress; + } + + // Round up to next flash word and fill missing bytes with `0xff`. + size_t flash_word_misalignment = byte_count & kFlashWordMask; + if (flash_word_misalignment > 0) { + size_t padding_byte_count = + FLASH_CTRL_PARAM_BYTES_PER_WORD - flash_word_misalignment; + for (size_t i = 0; i < padding_byte_count; ++i) { + data[byte_count++] = 0xff; + } + } + size_t rem_word_count = byte_count / sizeof(uint32_t); + + flash_ctrl_data_default_perms_set((flash_ctrl_perms_t){ + .read = kMultiBitBool4False, + .write = kMultiBitBool4True, + .erase = kMultiBitBool4False, + }); + // Perform two writes if the start address is not page-aligned (256 bytes). + // Note: Address is flash-word-aligned (8 bytes) due to the check above. + rom_error_t err_0 = kErrorOk; + size_t prog_page_misalignment = addr & kFlashProgPageMask; + if (prog_page_misalignment > 0) { + size_t word_count = + (kFlashProgPageSize - prog_page_misalignment) / sizeof(uint32_t); + if (word_count > rem_word_count) { + word_count = rem_word_count; + } + err_0 = flash_ctrl_data_write(addr, word_count, data); + rem_word_count -= word_count; + data += word_count * sizeof(uint32_t); + // Wrap to the beginning of the current page since PAGE_PROGRAM modifies + // a single page only. + addr &= ~kFlashProgPageMask; + } + rom_error_t err_1 = kErrorOk; + if (rem_word_count > 0) { + err_1 = flash_ctrl_data_write(addr, rem_word_count, data); + } + flash_ctrl_data_default_perms_set((flash_ctrl_perms_t){ + .read = kMultiBitBool4False, + .write = kMultiBitBool4False, + .erase = kMultiBitBool4False, + }); + + HARDENED_RETURN_IF_ERROR(err_0); + return err_1; +} + +/** + * Bootstrap state 1: Wait for an erase command and erase the data + * partition. + * + * This function erases both data banks of the flash regardless of the type of + * the erase command (CHIP_ERASE or SECTOR_ERASE). + * + * @param state Bootstrap state. + * @return Result of the operation. + */ +static rom_error_t bootstrap_handle_erase(bootstrap_state_t *state) { + HARDENED_CHECK_EQ(*state, kBootstrapStateErase); + + spi_device_cmd_t cmd; + RETURN_IF_ERROR(spi_device_cmd_get(&cmd)); + // Erase requires WREN, ignore if WEL is not set. + if (!bitfield_bit32_read(spi_device_flash_status_get(), kSpiDeviceWelBit)) { + return kErrorOk; + } + + rom_error_t error = kErrorUnknown; + switch (cmd.opcode) { + case kSpiDeviceOpcodeChipErase: + case kSpiDeviceOpcodeSectorErase: + error = bootstrap_chip_erase(); + HARDENED_RETURN_IF_ERROR(error); + *state = kBootstrapStateEraseVerify; + // Note: We clear WIP and WEN bits in `bootstrap_handle_erase_verify()` + // after checking that both data banks have been erased. + break; + default: + // Ignore any other command, e.g. PAGE_PROGRAM, RESET, and clear WIP and + // WEN bits right away. + spi_device_flash_status_clear(); + error = kErrorOk; + } + + return error; +} + +/** + * Bootstrap state 2: Verify that all data banks have been erased. + * + * This function also clears the WIP and WEN bits of the flash status register. + * + * @param state Bootstrap state. + * @return Result of the operation. + */ +static rom_error_t bootstrap_handle_erase_verify(bootstrap_state_t *state) { + HARDENED_CHECK_EQ(*state, kBootstrapStateEraseVerify); + + rom_error_t err_0 = flash_ctrl_data_erase_verify(0, kFlashCtrlEraseTypeBank); + rom_error_t err_1 = flash_ctrl_data_erase_verify( + FLASH_CTRL_PARAM_BYTES_PER_BANK, kFlashCtrlEraseTypeBank); + HARDENED_RETURN_IF_ERROR(err_0); + HARDENED_RETURN_IF_ERROR(err_1); + + *state = kBootstrapStateProgram; + spi_device_flash_status_clear(); + return err_0; +} + +/** + * Bootstrap state 3: (Erase/)Program loop. + * + * @param state Bootstrap state. + * @return Result of the operation. + */ +static rom_error_t bootstrap_handle_program(bootstrap_state_t *state) { + static_assert(alignof(spi_device_cmd_t) >= sizeof(uint32_t) && + offsetof(spi_device_cmd_t, payload) >= sizeof(uint32_t), + "Payload must be word aligned."); + static_assert( + sizeof((spi_device_cmd_t){0}.payload) % FLASH_CTRL_PARAM_BYTES_PER_WORD == + 0, + "Payload size must be a multiple of flash word size."); + + HARDENED_CHECK_EQ(*state, kBootstrapStateProgram); + + spi_device_cmd_t cmd; + RETURN_IF_ERROR(spi_device_cmd_get(&cmd)); + // Erase and program require WREN, ignore if WEL is not set. + if (cmd.opcode != kSpiDeviceOpcodeReset && + !bitfield_bit32_read(spi_device_flash_status_get(), kSpiDeviceWelBit)) { + return kErrorOk; + } + + rom_error_t error = kErrorUnknown; + switch (cmd.opcode) { + case kSpiDeviceOpcodeChipErase: + error = bootstrap_chip_erase(); + break; + case kSpiDeviceOpcodeSectorErase: + error = bootstrap_sector_erase(cmd.address); + break; + case kSpiDeviceOpcodePageProgram: + error = bootstrap_page_program(cmd.address, cmd.payload_byte_count, + cmd.payload); + break; + case kSpiDeviceOpcodeReset: + rstmgr_reset(); +#ifdef OT_PLATFORM_RV32 + HARDENED_UNREACHABLE(); +#else + // If this is an off-target test, return `kErrorUnknown` to be able to + // test without requiring EXPECT_DEATH. + error = kErrorUnknown; +#endif + break; + default: + // We don't expect any other commands but we can potentially end up + // here with a 0x0 opcode due to glitches on SPI or strap lines (see + // #11871). + error = kErrorOk; + } + HARDENED_RETURN_IF_ERROR(error); + + spi_device_flash_status_clear(); + return error; +} + +hardened_bool_t bootstrap_requested(void) { +#if defined(OTP_IS_RAM) + uint32_t res = kHardenedBoolTrue; +#else + uint32_t res = + otp_read32(OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_BOOTSTRAP_EN_OFFSET); +#endif + if (launder32(res) != kHardenedBoolTrue) { + return kHardenedBoolFalse; + } + HARDENED_CHECK_EQ(res, kHardenedBoolTrue); + + // A single read is sufficient since we expect strong pull-ups on the strap + // pins. + res ^= SW_STRAP_BOOTSTRAP; + res ^= + abs_mmio_read32(TOP_MATCHA_GPIO_BASE_ADDR + GPIO_DATA_IN_REG_OFFSET) & + SW_STRAP_MASK; + if (launder32(res) != kHardenedBoolTrue) { + return kHardenedBoolFalse; + } + HARDENED_CHECK_EQ(res, kHardenedBoolTrue); + return res; +} + +rom_error_t bootstrap(void) { + hardened_bool_t requested = bootstrap_requested(); + if (launder32(requested) != kHardenedBoolTrue) { + return kErrorBootstrapNotRequested; + } + HARDENED_CHECK_EQ(requested, kHardenedBoolTrue); + + spi_device_init(); + + // Bootstrap event loop. + bootstrap_state_t state = kBootstrapStateErase; + rom_error_t error = kErrorUnknown; + while (true) { + switch (launder32(state)) { + case kBootstrapStateErase: + HARDENED_CHECK_EQ(state, kBootstrapStateErase); + error = bootstrap_handle_erase(&state); + break; + case kBootstrapStateEraseVerify: + HARDENED_CHECK_EQ(state, kBootstrapStateEraseVerify); + error = bootstrap_handle_erase_verify(&state); + break; + case kBootstrapStateProgram: + HARDENED_CHECK_EQ(state, kBootstrapStateProgram); + error = bootstrap_handle_program(&state); + break; + default: + error = kErrorBootstrapInvalidState; + } + HARDENED_RETURN_IF_ERROR(error); + } + HARDENED_UNREACHABLE(); +}
diff --git a/sw/device/silicon_creator/rom/bootstrap.h b/sw/device/silicon_creator/rom/bootstrap.h new file mode 100644 index 0000000..fd4c725 --- /dev/null +++ b/sw/device/silicon_creator/rom/bootstrap.h
@@ -0,0 +1,48 @@ +// Copyright 2023 Google LLC. +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SW_DEVICE_SILICON_CREATOR_ROM_BOOTSTRAP_H_ +#define SW_DEVICE_SILICON_CREATOR_ROM_BOOTSTRAP_H_ + +#include "sw/device/lib/base/hardened.h" +#include "sw/device/silicon_creator/lib/error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Checks whether bootstrap is requested. + * + * The return value of this function also depends on the `ROM_BOOTSTRAP_EN` OTP + * item. + * + * @return Whether bootstrap is requested. + */ +hardened_bool_t bootstrap_requested(void); + +/** + * Bootstraps the data partition of the embedded flash with data received by the + * spi_device. + * + * OpenTitan bootstrap uses the typical SPI flash EEPROM commands. A typical + * bootstrap session involves: + * - Asserting bootstrap pins to enter bootstrap mode, + * - Erasing the chip (WREN, CHIP_ERASE, busy loop ...), + * - Programming the chip (WREN, PAGE_PROGRAM, busy loop ...), and + * - Resetting the chip (RESET). + * + * This function only returns on error since a successful bootstrap ends with a + * chip reset. + * + * @return Result of the operation. + */ +rom_error_t bootstrap(void); + +#ifdef __cplusplus +} +#endif + +#endif // SW_DEVICE_SILICON_CREATOR_ROM_BOOTSTRAP_H_