blob: 2324ba851e33a3eab22d0f4d46c21b5d385ce406 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sw/device/lib/testing/test_rom/puppeteer.h"
#include <string.h>
#include "flash_ctrl_regs.h" // NOLINT(build/include_subdir) Generated.
#include "hw/top_matcha/sw/autogen/top_matcha.h" // Generated.
#include "sw/device/lib/base/memory.h"
#include "sw/device/lib/dif/dif_flash_ctrl.h"
#include "sw/device/lib/dif/dif_spi_host.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/base64.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/elf_loader.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/opentitan_uart.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/tar_loader.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/tiny_hmac.h"
#include "sw/device/lib/testing/test_rom/puppeteer_utils/tiny_io.h"
#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
#include "sw/device/silicon_creator/lib/manifest.h"
static dif_flash_ctrl_state_t flash_ctrl;
static dif_spi_host spi_host0;
__attribute__((warn_unused_result)) bool eflash_chip_erase(uintptr_t flash_ctrl_addr) {
flash_ctrl_bank_erase_perms_set(flash_ctrl_addr, kHardenedBoolTrue);
rom_error_t err_0 = flash_ctrl_data_erase(flash_ctrl_addr, 0, kFlashCtrlEraseTypeBank);
rom_error_t err_1 = flash_ctrl_data_erase(flash_ctrl_addr, FLASH_CTRL_PARAM_BYTES_PER_BANK,
kFlashCtrlEraseTypeBank);
flash_ctrl_bank_erase_perms_set(flash_ctrl_addr, kHardenedBoolFalse);
if (err_0 != kErrorOk || err_1 != kErrorOk) {
return false;
}
return true;
}
// Writes 256-bytes from src to dst.
// For now, lets take dst as a memory address, and mask off the top.
#define EFLASH_PAGE_SIZE (256)
__attribute__((warn_unused_result)) bool eflash_write_page(uintptr_t flash_ctrl_addr,
const void* dst,
uint8_t* src) {
uint32_t flash_dst = (uint32_t)dst & ~TOP_MATCHA_EFLASH_BASE_ADDR;
flash_ctrl_data_default_perms_set(flash_ctrl_addr, (flash_ctrl_perms_t){
.read = kMultiBitBool4False,
.write = kMultiBitBool4True,
.erase = kMultiBitBool4False,
});
rom_error_t err = flash_ctrl_data_write(
flash_ctrl_addr, flash_dst, EFLASH_PAGE_SIZE / sizeof(uint32_t), src);
if (err != kErrorOk) {
return false;
}
flash_ctrl_data_default_perms_set(flash_ctrl_addr, (flash_ctrl_perms_t){
.read = kMultiBitBool4True,
.write = kMultiBitBool4False,
.erase = kMultiBitBool4False,
});
return true;
}
void flash_ctrl_testutils_wait_for_init(dif_flash_ctrl_state_t* flash_state) {
dif_flash_ctrl_status_t status;
dif_result_t result;
do {
result = dif_flash_ctrl_get_status(flash_state, &status);
} while (status.controller_init_wip);
}
//-----------------------------------------------------------------------------
// Puppeteer console command table.
Puppeteer::Command Puppeteer::commands[Puppeteer::command_count] = {
{"help", &Puppeteer::on_help, "Print command help"},
{"echo", &Puppeteer::on_echo, "Turn console prompt on or off"},
{"quit", &Puppeteer::on_quit, "Exit console and continue boot"},
{"peekb", &Puppeteer::on_peekb, "Read byte from address"},
{"peekd", &Puppeteer::on_peekd, "Read dword from address"},
{"pokeb", &Puppeteer::on_pokeb, "Write byte to address"},
{"poked", &Puppeteer::on_poked, "Write dword to address"},
{"hexb", &Puppeteer::on_hexb, "Dump address range as 8-bit hex values"},
{"hexd", &Puppeteer::on_hexd, "Dump address range as 32-bit hex values"},
{"dump", &Puppeteer::on_dump, "Dump address range as base64 blob"},
{"boot_ottf", &Puppeteer::on_boot_ottf, "Boots OTTF from flash"},
{"boot_tock", &Puppeteer::on_boot_tock, "Boots Tock from flash"},
{"write", &Puppeteer::on_write, "Write blob to address"},
{"hmac", &Puppeteer::on_test_hmac, "Test HMAC hardware"},
};
//-----------------------------------------------------------------------------
// Tiny console argument parser/matcher.
struct Args {
const char* cursor;
bool error = false;
explicit Args(const char* command) {
cursor = command;
error = false;
}
const char* next() {
while (*cursor && *cursor != ' ') cursor++;
if (*cursor) cursor++;
return cursor;
}
uint32_t get_u32() {
const char* a = cursor;
uint32_t temp = 0;
int scale = 10;
bool valid = false;
bool is_hex = false;
if (a[0] == '0' && (a[1] == 'x' || a[1] == 'X')) {
is_hex = true;
scale = 16;
a += 2;
}
while (*a) {
if (*a >= '0' && *a <= '9') {
temp = (temp * scale) + (*a - '0');
valid = true;
} else if (is_hex && *a >= 'A' && *a <= 'F') {
temp = (temp * scale) + (*a - 'A') + 10;
valid = true;
} else if (is_hex && *a >= 'a' && *a <= 'f') {
temp = (temp * scale) + (*a - 'a') + 10;
valid = true;
} else {
break;
}
a++;
}
if (valid) {
next();
return temp;
} else {
error = true;
return 0;
}
}
bool match(const char* b) {
const char* a = cursor;
if (a == nullptr) return false;
if (b == nullptr) return false;
while (1) {
bool end_a = *a == 0 || *a == ' ';
bool end_b = *b == 0 || *b == ' ';
if (end_a && end_b) return true;
if (*a != *b) return false;
a++;
b++;
}
return (*a == 0) && (*b == 0);
}
};
//-----------------------------------------------------------------------------
int Puppeteer::on_test_hmac(Args /*args*/) {
uint32_t key[8] = {
0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF,
0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF,
};
uint32_t digest[8];
uint8_t message[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
TinyHMAC hmac(io);
hmac.init(key);
hmac.hash(message, 11);
hmac.finish(digest);
io.printf("Digest 0 %p\n", digest[0]);
io.printf("Digest 1 %p\n", digest[1]);
io.printf("Digest 2 %p\n", digest[2]);
io.printf("Digest 3 %p\n", digest[3]);
io.printf("Digest 4 %p\n", digest[4]);
io.printf("Digest 5 %p\n", digest[5]);
io.printf("Digest 6 %p\n", digest[6]);
io.printf("Digest 7 %p\n", digest[7]);
io.printf("test_hmac() done\n");
return 0;
}
//-----------------------------------------------------------------------------
// Prints the command documentation.
int Puppeteer::on_help(Args /*args*/) {
for (int i = 0; i < command_count; i++) {
io.printf("Command '%s' - '%s'\n", Puppeteer::commands[i].name,
Puppeteer::commands[i].help);
}
return 0;
}
//----------
// Turns the BOOTROM> prompt on and off
int Puppeteer::on_echo(Args args) {
if (args.match("on")) {
echo = true;
return 0;
} else if (args.match("off")) {
echo = false;
return 0;
} else {
return -1;
}
}
//----------
// Exits the bootrom console.
int Puppeteer::on_quit(Args /*args*/) {
io.printf("Exiting bootrom console\n");
quit = true;
return 0;
}
//-----------------------------------------------------------------------------
// Prints the value of the byte at the given address.
int Puppeteer::on_peekb(Args args) {
uint32_t address = args.get_u32();
if (args.error) return -1;
io.printf("%p\n", *(volatile uint8_t*)address);
return 0;
}
//----------
// Prints the value of the dword at the given address.
int Puppeteer::on_peekd(Args args) {
uint32_t address = args.get_u32();
if (args.error) return -1;
io.printf("%p\n", *(volatile uint32_t*)address);
return 0;
}
//----------
// Writes the given value as a single byte to the given address.
int Puppeteer::on_pokeb(Args args) {
uint32_t address = args.get_u32();
uint32_t data = args.get_u32();
if (args.error) return -1;
*(volatile uint8_t*)address = data;
io.printf("%p\n", *(volatile uint8_t*)address);
return 0;
}
//----------
// Writes the given value as a single dword to the given address.
int Puppeteer::on_poked(Args args) {
uint32_t address = args.get_u32();
uint32_t data = args.get_u32();
if (args.error) return -1;
*(volatile uint32_t*)address = data;
io.printf("%p\n", *(volatile uint32_t*)address);
return 0;
}
// Reads a 512-byte page from the attached SPI device
// into a user-provided buffer.
#define SPI_PAGE_SIZE (512)
void spi_read_page(dif_spi_host* spi_host, uint32_t page, uint8_t* buf) {
/* clang-format off */
dif_spi_host_segment_t segments[] = {
{
.type = kDifSpiHostSegmentTypeOpcode,
.opcode = 0x13, // Page Read, 4b Addr
},
{.type = kDifSpiHostSegmentTypeAddress,
.address = {
.width = kDifSpiHostWidthStandard,
.mode = kDifSpiHostAddrMode4b,
.address = page,
}},
// Two RX segments, due to the RX fifo only being 256B.
{.type = kDifSpiHostSegmentTypeRx,
.rx = {.width = kDifSpiHostWidthStandard,
.buf = buf,
.length = SPI_PAGE_SIZE >> 1}},
{.type = kDifSpiHostSegmentTypeRx,
.rx = {.width = kDifSpiHostWidthStandard,
.buf = buf + (SPI_PAGE_SIZE >> 1),
.length = SPI_PAGE_SIZE >> 1}},
};
/* clang-format on */
dif_result_t result = dif_spi_host_transaction(spi_host, /*csid=*/0, segments,
ARRAYSIZE(segments));
}
size_t tar_read_data_from_spi(tar_header* buf, size_t offset) {
constexpr size_t kExtflashSize = (1 << 24) - 1;
if (offset + sizeof(tar_header) > kExtflashSize) {
return 0;
}
size_t count = sizeof(tar_header);
size_t remaining = sizeof(tar_header);
while (remaining >= SPI_PAGE_SIZE) {
spi_read_page(&spi_host0, offset + (count - remaining),
reinterpret_cast<uint8_t*>(buf));
remaining -= SPI_PAGE_SIZE;
}
if (remaining != 0) {
return 0;
}
return sizeof(tar_header);
}
int load_bin_from_tar(uintptr_t flash_ctrl_addr, const char* filename, uint8_t* base_addr) {
size_t size = 0;
size_t bin_offset = reinterpret_cast<size_t>(
find_file_in_tar(filename, tar_read_data_from_spi, &size));
if (!bin_offset) {
return 0;
}
uint8_t page[SPI_PAGE_SIZE];
size_t remaining = size;
bool result;
while (remaining >= SPI_PAGE_SIZE) {
size_t offset = size - remaining;
spi_read_page(&spi_host0, bin_offset + offset, page);
result = eflash_write_page(flash_ctrl_addr, base_addr + offset, page);
result = eflash_write_page(flash_ctrl_addr, base_addr + offset + EFLASH_PAGE_SIZE,
page + EFLASH_PAGE_SIZE);
remaining -= SPI_PAGE_SIZE;
}
if (remaining) {
size_t offset = size - remaining;
spi_read_page(&spi_host0, bin_offset + offset, page);
result = eflash_write_page(flash_ctrl_addr, base_addr + offset, page);
result = eflash_write_page(flash_ctrl_addr, base_addr + offset + EFLASH_PAGE_SIZE,
page + EFLASH_PAGE_SIZE);
}
return 1;
// return *reinterpret_cast<generic_call*>(base_addr);
}
int Puppeteer::on_boot_ottf(Args args) { return on_boot_internal(args, false); }
int Puppeteer::on_boot_tock(Args args) { return on_boot_internal(args, true); }
//-----------------------------------------------------------------------------
// write words.
int Puppeteer::on_boot_internal(Args args, bool tock) {
uint8_t* kEflashBase =
reinterpret_cast<uint8_t*>(TOP_MATCHA_EFLASH_BASE_ADDR);
// Initialize and erase the eflash.
dif_flash_ctrl_init_state(
&flash_ctrl, mmio_region_from_addr(TOP_MATCHA_FLASH_CTRL_CORE_BASE_ADDR));
dif_flash_ctrl_start_controller_init(&flash_ctrl);
flash_ctrl_testutils_wait_for_init(&flash_ctrl);
dif_flash_ctrl_set_flash_enablement(&flash_ctrl, kDifToggleEnabled);
dif_flash_ctrl_set_exec_enablement(&flash_ctrl, kDifToggleEnabled);
const uintptr_t flash_ctrl_addr = TOP_MATCHA_FLASH_CTRL_CORE_BASE_ADDR;
if (!eflash_chip_erase(flash_ctrl_addr)) {
io.printf("Erasing eflash failed\n");
io.etx();
return 0;
}
// Initialize the SPI host device.
dif_result_t result = dif_spi_host_init(
mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0);
dif_spi_host_config_t config = {
.spi_clock = 1000000,
.peripheral_clock_freq_hz = 25 * 100 * 1000,
};
result = dif_spi_host_configure(&spi_host0, config);
result = dif_spi_host_output_set_enabled(&spi_host0, /*enabled=*/true);
// Find the tock binary in the external flash.
const char* kScBinary = "matcha-tock-bundle.bin";
int ret = load_bin_from_tar(flash_ctrl_addr, kScBinary, kEflashBase);
if (!ret) {
io.printf("Didn't find %s, don't try to start.\n", kScBinary);
io.etx();
return 0;
}
generic_call entry_point;
if (tock) {
entry_point = *reinterpret_cast<generic_call*>(kEflashBase);
} else {
entry_point = reinterpret_cast<generic_call>(manifest_entry_point_get(
reinterpret_cast<const manifest_t*>(kEflashBase)));
}
// Execute at the returned entry point.
io.printf("Starting %s @ 0x%p on security core\n", entry_point,
tock ? "TockOS" : "OTTF");
io.etx();
entry_point(0, 0, 0, 0, 0, 0, 0, 0);
return 0;
}
//-----------------------------------------------------------------------------
// Dumps 'count' bytes at 'start' in 8-bit hexadecimal chunks
int Puppeteer::on_hexb(Args args) {
auto start = reinterpret_cast<uint8_t*>(args.get_u32());
auto count = args.get_u32();
if (args.error) return -1;
io.printf("Start %p\n", start);
io.printf("Count %d\n", count);
for (uint32_t i = 0; i < count; i++) {
if (i && ((i % 16) == 0)) io.printf("\n");
io.printf("0x%b ", start[i]);
}
io.printf("\n");
return 0;
}
//-----------------------------------------------------------------------------
// Dumps 'count' bytes at 'start' in 32-bit hexadecimal chunks
int Puppeteer::on_hexd(Args args) {
auto start = reinterpret_cast<uint32_t*>(args.get_u32());
auto count = args.get_u32();
if (args.error) return -1;
io.printf("Start %p\n", start);
io.printf("Count %d\n", count);
for (uint32_t i = 0; i < (count + 3) / 4; i++) {
if (i && ((i % 8) == 0)) io.printf("\n");
io.printf("0x%p ", start[i]);
}
io.printf("\n");
return 0;
}
//-----------------------------------------------------------------------------
// Dumps 'count' bytes at 'start' encoded as base64
int Puppeteer::on_dump(Args args) {
auto start = reinterpret_cast<uint8_t*>(args.get_u32());
auto count = args.get_u32();
auto end = start + count;
if (args.error) return -1;
Base64 b64;
for (auto addr = start; addr < end; addr++) {
b64.put_byte(*addr);
while (b64.count >= 6) {
io.putc(b64.get_b64());
}
}
if (b64.count) {
io.putc(b64.get_b64());
}
io.printf("\n");
return 0;
}
//-----------------------------------------------------------------------------
// Writes a base64 blob to 'address'. Issue this command, then paste a base64-
// encoded block of data into the console.
int Puppeteer::on_write(Args args) {
auto start = reinterpret_cast<uint8_t*>(args.get_u32());
if (args.error) return -1;
io.printf("Writing to %p, send a Base64 string\n", start);
io.etx();
Base64 b64;
auto cursor = start;
while (1) {
uint8_t b = io.get();
if (b == '\n') {
break;
} else {
b64.put_b64(b);
while (b64.count >= 8) {
*cursor++ = b64.get_byte();
}
}
}
io.printf("Wrote %d bytes to 0x%p\n", cursor - start, start);
return cursor - start;
}
int Puppeteer::on_err(char* command) {
io.printf("Unknown command '%s'\n", command);
return -1;
}
//-----------------------------------------------------------------------------
int Puppeteer::find_command(char* console_buf) {
for (int i = 0; i < command_count; i++) {
Args args(console_buf);
if (args.match(commands[i].name)) {
return i;
}
}
return -1;
}
//-----------------------------------------------------------------------------
void Puppeteer::console_main() {
io.printf("Puppeteer::console_main()\n");
uint8_t console_cursor = 0;
memset(console_buf, 0, sizeof(console_buf));
io.printf("BOOTROM> ");
// Screen does not echo, sends \r on enter but no \n
// Telnet echos locally, buffers until enter, then sends buffer + \r\n.
// Bootshell sends \n
uint8_t prev = 0;
uint8_t next = 0;
while (!quit) {
prev = next;
next = io.get();
// Ignore LF if it comes immediately after CR
if (prev == '\r' && next == '\n') continue;
if (echo) io.putc(next);
if (next == '\r' || next == '\n') {
if (console_cursor > 0 && console_cursor < 255) {
int command_idx = find_command(console_buf);
if (command_idx != -1) {
Args args(console_buf);
args.next();
(this->*commands[command_idx].handler)(args);
} else {
io.printf("Unknown command '%s'\n", console_buf);
}
}
memset(console_buf, 0, sizeof(console_buf));
io.etx();
io.printf("BOOTROM> ");
console_cursor = 0;
} else {
if (console_cursor < 255) console_buf[console_cursor] = next;
console_cursor++;
}
}
}
//-----------------------------------------------------------------------------