blob: b6c843e79b0184601350e6ddaef904aa0278556c [file] [log] [blame]
// 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();
}