blob: 41e507c89aa7913ed7fa68917e6a757cc528f848 [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/boot_rom/bootstrap.h"
#include <stddef.h>
#include "sw/device/boot_rom/spiflash_frame.h"
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/log.h"
#include "sw/device/lib/base/memory.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_gpio.h"
#include "sw/device/lib/dif/dif_spi_device.h"
#include "sw/device/lib/flash_ctrl.h"
#include "sw/device/lib/hw_sha256.h"
#include "sw/device/lib/runtime/check.h"
#include "sw/device/lib/runtime/hart.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#define GPIO_BOOTSTRAP_BIT_MASK 0x00020000u
/**
* Check if flash is blank to determine if bootstrap is needed.
*
* TODO: Update this to check bootstrap pin instead in Verilator.
*/
static bool bootstrap_requested(void) {
// The following flash empty-sniff-check is done this way due to the lack of
// clear eflash reset in SIM environments.
if (kDeviceType == kDeviceSimVerilator) {
mmio_region_t flash_region = mmio_region_from_addr(FLASH_MEM_BASE_ADDR);
uint32_t value = mmio_region_read32(flash_region, 0x0);
return value == 0 || value == UINT32_MAX;
}
// Initialize GPIO device.
dif_gpio_t gpio;
dif_gpio_params_t gpio_params = {
.base_addr = mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR)};
CHECK(dif_gpio_init(gpio_params, &gpio) == kDifGpioOk);
dif_gpio_state_t gpio_in;
CHECK(dif_gpio_read_all(&gpio, &gpio_in) == kDifGpioOk);
return (gpio_in & GPIO_BOOTSTRAP_BIT_MASK) != 0;
}
/**
* Erase all flash, and verify blank.
*/
static int erase_flash(void) {
if (flash_bank_erase(FLASH_BANK_0) != 0) {
return E_BS_ERASE;
}
if (flash_bank_erase(FLASH_BANK_1) != 0) {
return E_BS_ERASE;
}
if (flash_check_empty() == 0) {
return E_BS_NOTEMPTY;
}
return 0;
}
/**
* Compares the SHA256 hash of the recieved data with the recieved hash.
*
* Returns true if the hashes match.
*/
static bool check_frame_hash(const spiflash_frame_t *frame) {
uint8_t hash[sizeof(frame->header.hash)];
uint8_t *data = ((uint8_t *)frame) + sizeof(hash);
hw_SHA256_hash(data, sizeof(spiflash_frame_t) - sizeof(hash), hash);
return memcmp(hash, frame->header.hash, sizeof(hash)) == 0;
}
/**
* Load spiflash frames from the SPI interface.
*
* This function checks that the sequence numbers and hashes of the frames are
* correct before programming them into flash.
*/
static int bootstrap_flash(dif_spi_device_t *spi) {
uint8_t ack[SHA256_DIGEST_SIZE] = {0};
uint32_t expected_frame_num = 0;
while (true) {
size_t bytes_available;
CHECK(dif_spi_device_rx_pending(spi, &bytes_available) ==
kDifSpiDeviceResultOk,
"Failed to check pending bytes.");
if (bytes_available >= sizeof(spiflash_frame_t)) {
spiflash_frame_t frame;
CHECK(
dif_spi_device_recv(spi, &frame, sizeof(spiflash_frame_t),
/*bytes_received=*/NULL) == kDifSpiDeviceResultOk,
"Failed to recieve bytes from SPI.");
uint32_t frame_num = SPIFLASH_FRAME_NUM(frame.header.frame_num);
LOG_INFO("Processing frame #%d, expecting #%d", frame_num,
expected_frame_num);
if (frame_num == expected_frame_num) {
if (!check_frame_hash(&frame)) {
LOG_ERROR("Detected hash mismatch on frame #%d", frame_num);
CHECK(dif_spi_device_send(spi, ack, sizeof(ack),
/*bytes_received=*/NULL) ==
kDifSpiDeviceResultOk,
"Failed to send bytes to SPI.");
continue;
}
hw_SHA256_hash(&frame, sizeof(spiflash_frame_t), ack);
CHECK(dif_spi_device_send(spi, ack, sizeof(ack),
/*bytes_received=*/NULL) ==
kDifSpiDeviceResultOk,
"Failed to send bytes to SPI.");
if (expected_frame_num == 0) {
flash_default_region_access(/*rd_en=*/true, /*prog_en=*/true,
/*erase_en=*/true);
int flash_error = erase_flash();
if (flash_error != 0) {
return flash_error;
}
LOG_INFO("Flash erase successful");
}
if (flash_write(frame.header.flash_offset, kDataPartition, frame.data,
SPIFLASH_FRAME_DATA_WORDS) != 0) {
return E_BS_WRITE;
}
++expected_frame_num;
if (SPIFLASH_FRAME_IS_EOF(frame.header.frame_num)) {
LOG_INFO("Bootstrap: DONE!");
return 0;
}
} else {
// Send previous ack if unable to verify current frame.
CHECK(dif_spi_device_send(spi, ack, sizeof(ack),
/*bytes_received=*/NULL) ==
kDifSpiDeviceResultOk,
"Failed to send bytes to SPI.");
}
}
}
}
int bootstrap(void) {
if (!bootstrap_requested()) {
return 0;
}
// SPI device is only initialized in bootstrap mode.
LOG_INFO("Bootstrap requested, initialising HW...");
flash_init_block();
mmio_region_t spi_reg = mmio_region_from_addr(0x40020000);
dif_spi_device_config_t config = {
.clock_polarity = kDifSpiDeviceEdgePositive,
.data_phase = kDifSpiDeviceEdgeNegative,
.tx_order = kDifSpiDeviceBitOrderMsbToLsb,
.rx_order = kDifSpiDeviceBitOrderMsbToLsb,
.rx_fifo_timeout = 63,
.rx_fifo_len = kDifSpiDeviceBufferLen / 2,
.tx_fifo_len = kDifSpiDeviceBufferLen / 2,
};
dif_spi_device_t spi;
CHECK(dif_spi_device_init(spi_reg, &config, &spi) == kDifSpiDeviceResultOk,
"Failed to initialize SPI.");
LOG_INFO("HW initialisation completed, waiting for SPI input...");
int error = bootstrap_flash(&spi);
if (error != 0) {
error |= erase_flash();
LOG_ERROR("Bootstrap error: 0x%x", error);
}
// Always make sure to revert flash_ctrl access to default settings.
// bootstrap_flash enables access to flash to perform update.
flash_default_region_access(/*rd_en=*/false, /*prog_en=*/false,
/*erase_en=*/false);
return error;
}