| // 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/mask_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/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_earlgrey/sw/autogen/top_earlgrey.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, |
| }); |
| // Wrap to the start of the flash programming page if `addr` is not aligned. |
| 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); |
| err_0 = flash_ctrl_data_write(addr, word_count, data); |
| rem_word_count -= word_count; |
| data += word_count * sizeof(uint32_t); |
| 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; |
| break; |
| default: |
| // Ignore any other command, e.g. PAGE_PROGRAM, RESET. |
| error = kErrorOk; |
| } |
| |
| // Note: We clear WIP and WEN bits in `bootstrap_handle_erase_verify()` after |
| // checking that both data banks have been erased. |
| 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) { |
| uint32_t res = otp_read32(OTP_CTRL_PARAM_ROM_BOOTSTRAP_EN_OFFSET); |
| 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 ^= kBootstrapPinMask; |
| res ^= |
| abs_mmio_read32(TOP_EARLGREY_GPIO_BASE_ADDR + GPIO_DATA_IN_REG_OFFSET) & |
| kBootstrapPinMask; |
| 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(); |
| } |