Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sw/silicon_creator] Refactor ROM bootstrap into a common lib #19155

Merged
merged 3 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions sw/device/silicon_creator/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,25 @@ opentitan_functest(
"//sw/device/lib/testing/test_framework:ottf_test_config",
],
)

cc_library(
name = "bootstrap",
srcs = ["bootstrap.c"],
hdrs = ["bootstrap.h"],
deps = [
"//sw/device/lib/base:bitfield",
"//sw/device/lib/base:hardened",
"//sw/device/silicon_creator/lib:error",
"//sw/device/silicon_creator/lib/drivers:flash_ctrl",
"//sw/device/silicon_creator/lib/drivers:rstmgr",
"//sw/device/silicon_creator/lib/drivers:spi_device",
],
)

filegroup(
name = "bootstrap_srcs",
srcs = [
"bootstrap.c",
"bootstrap.h",
],
)
365 changes: 365 additions & 0 deletions sw/device/silicon_creator/lib/bootstrap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
// 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/lib/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/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"

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.
*/
OT_WARN_UNUSED_RESULT
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.
*/
OT_WARN_UNUSED_RESULT
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.
*/
OT_WARN_UNUSED_RESULT
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 &= ~(uint32_t)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.
*/
OT_WARN_UNUSED_RESULT
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.
*/
OT_WARN_UNUSED_RESULT
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.
*/
OT_WARN_UNUSED_RESULT
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_TRAP();
#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;
}

rom_error_t enter_bootstrap(hardened_bool_t protect_rom_ext) {
// TODO(lowRISC/opentitan#19151): Implement `protect_rom_ext` behavior.
HARDENED_CHECK_EQ(protect_rom_ext, kHardenedBoolFalse);

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_TRAP();
}