From 2228c2cf57572a952f3036327a0177bfefa6c9fe Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 23 Oct 2025 14:46:17 +0100 Subject: [PATCH 1/9] [ot] hw/opentitan: ot_spi_device: simplify commands and slot matching This commit simplifies command slot definitions and the associated state and control flow. The HW CFG and the HW STA commands have been combined into just HW commands, as in flash mode they are handled in hardware by some way and should therefore share the same data path. For passthrough mode, a subset of these HW commands can be intercepted (all except for WREN and WRDI). As the matched command slot number determines whether the command is a SW or HW command, the `OtSpiFlashCommand` enum has been removed in favour of using `is_sw_command` to return the boolean. Command slot matching has been brought out into its own function, `match_command_slot`, which returns whether an opcode was matched in the command info registers. The decoded slot number is only valid if this returns true. Also a bug is fixed in `ot_spi_device_exec_command` where hardcoded opcodes for read commands were used instead of the opcodes in the associated command slot. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 308 +++++++++++++++-------------------- 1 file changed, 129 insertions(+), 179 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 182637ab34e5b..f8dab8840dc1b 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -317,43 +317,51 @@ REG32(TPM_READ_FIFO, 0x34u) #define SPI_FLASH_BUFFER_SIZE 256u typedef enum { - READ_STATUS1, - READ_STATUS2, - READ_STATUS3, - READ_JEDEC, - READ_SFDP, - READ_NORMAL, - READ_FAST, - READ_DUAL, - READ_QUAD, - READ_DUAL_IO, - READ_QUAD_IO, - HW_COMMAND_COUNT -} SpiDeviceHwCommand; - -#define SPI_DEVICE_CMD_HW_STA_COUNT ((unsigned)HW_COMMAND_COUNT) -#define SPI_DEVICE_CMD_HW_STA_FIRST 0 -#define SPI_DEVICE_CMD_HW_STA_LAST (SPI_DEVICE_CMD_HW_STA_COUNT - 1u) -#define SPI_DEVICE_CMD_HW_CFG_FIRST (R_CMD_INFO_EN4B - R_CMD_INFO_0) -#define SPI_DEVICE_CMD_HW_CFG_LAST (R_CMD_INFO_WRDI - R_CMD_INFO_0) -#define SPI_DEVICE_CMD_HW_CFG_COUNT \ - (SPI_DEVICE_CMD_HW_CFG_LAST - SPI_DEVICE_CMD_HW_CFG_FIRST + 1u) -#define SPI_DEVICE_CMD_SW_FIRST (SPI_DEVICE_CMD_HW_STA_LAST + 1u) -#define SPI_DEVICE_CMD_SW_LAST (SPI_DEVICE_CMD_HW_CFG_FIRST - 1u) -#define SPI_DEVICE_CMD_SW_COUNT \ - (SPI_DEVICE_CMD_SW_LAST - SPI_DEVICE_CMD_SW_FIRST + 1u) + /* Internal HW command slots (0-10) */ + SLOT_HW_READ_STATUS1, /* slot 0, typically 0x05u */ + SLOT_HW_READ_STATUS2, /* slot 1, typically 0x35u */ + SLOT_HW_READ_STATUS3, /* slot 2, typically 0x15u */ + SLOT_HW_READ_JEDEC, /* slot 3, typically 0x9f */ + SLOT_HW_READ_SFDP, /* slot 4, typically 0x5a */ + SLOT_HW_READ_NORMAL, /* slot 5, typically 0x03u */ + SLOT_HW_READ_FAST, /* slot 6, typically 0x0bu */ + SLOT_HW_READ_DUAL, /* slot 7, typically 0x3bu */ + SLOT_HW_READ_QUAD, /* slot 8, typically 0x6bu */ + SLOT_HW_READ_DUAL_IO, /* slot 9, typically 0xbbu */ + SLOT_HW_READ_QUAD_IO, /* slot 10, typically 0xebu */ + /* SW command slots (11-23) */ + SLOT_SW_CMD_11, + SLOT_SW_CMD_12, + SLOT_SW_CMD_13, + SLOT_SW_CMD_14, + SLOT_SW_CMD_15, + SLOT_SW_CMD_16, + SLOT_SW_CMD_17, + SLOT_SW_CMD_18, + SLOT_SW_CMD_19, + SLOT_SW_CMD_20, + SLOT_SW_CMD_21, + SLOT_SW_CMD_22, + SLOT_SW_CMD_23, + /* Configurable HW command slots (EN4B-WRDI, 24-27) */ + SLOT_HW_EN4B, /* slot 24, typically 0xb7u */ + SLOT_HW_EX4B, /* slot 25, typically 0xe9u */ + SLOT_HW_WREN, /* slot 26, typically 0x06u */ + SLOT_HW_WRDI, /* slot 27, typically 0x04u */ + SLOT_COUNT, + SLOT_INVALID = SLOT_COUNT, +} OtSpiDeviceCommandSlot; + +#define SLOT_SW_CMD_FIRST (SLOT_SW_CMD_11) +#define SLOT_SW_CMD_LAST (SLOT_SW_CMD_23) + +static_assert(SLOT_COUNT == 28u, "Invalid command slot count"); + #define TPM_OPCODE_READ_BIT 7u #define TPM_OPCODE_SIZE_MASK 0x3Fu #define TPM_ADDR_HEADER 0xD4u #define TPM_READY 0x01u - -static_assert((SPI_DEVICE_CMD_HW_STA_COUNT + SPI_DEVICE_CMD_SW_COUNT + - SPI_DEVICE_CMD_HW_CFG_COUNT) == 28u, - "Invalid command info definitions"); -static_assert(PARAM_NUM_CMD_INFO == - SPI_DEVICE_CMD_HW_CFG_FIRST - SPI_DEVICE_CMD_HW_STA_FIRST, - "Invalid command info definitions"); static_assert(SPI_SRAM_INGRESS_OFFSET >= (SPI_SRAM_TPM_READ_OFFSET + SPI_SRAM_TPM_READ_SIZE), "SPI SRAM Egress buffers overflow into Ingress buffers"); @@ -381,13 +389,6 @@ typedef enum { SPI_BUS_ERROR, } OtSpiBusState; -typedef enum { - SPI_FLASH_CMD_NONE, /* Not decoded / unknown */ - SPI_FLASH_CMD_HW_STA, /* Hardcoded HW-handled commands */ - SPI_FLASH_CMD_HW_CFG, /* Configurable HW-handled commands */ - SPI_FLASH_CMD_SW, /* Configurable SW-handled commands */ -} OtSpiFlashCommand; - typedef enum { SPI_FLASH_IDLE, /* No command received */ SPI_FLASH_COLLECT, /* Collecting address or additional info after cmd */ @@ -413,10 +414,9 @@ typedef enum { typedef struct { OtSpiFlashState state; - OtSpiFlashCommand type; + OtSpiDeviceCommandSlot slot; /* Command slot */ unsigned pos; /* Current position in data buffer */ unsigned len; /* Meaning depends on command and current state */ - unsigned slot; /* Command slot */ uint32_t address; /* Address tracking */ uint32_t last_read_addr; /* Last address read before increment */ uint32_t cmd_info; /* Selected command info slot */ @@ -631,7 +631,6 @@ static const char *TPM_REG_NAMES[TPM_REGS_COUNT] = { #define COMMAND_OPCODE(_cmd_info_) \ ((uint8_t)((_cmd_info_) & CMD_INFO_OPCODE_MASK)) -#define FLASH_SLOT(_name_) ((R_CMD_INFO_##_name_) - R_CMD_INFO_0) #define STATE_NAME_ENTRY(_st_) [_st_] = stringify(_st_) /* clang-format off */ @@ -719,11 +718,15 @@ static void ot_spi_device_flash_change_state_line( } } -static bool ot_spi_device_flash_is_upload(const SpiDeviceFlash *f) +static bool ot_spi_device_is_sw_command(OtSpiDeviceCommandSlot slot) +{ + return (slot >= SLOT_SW_CMD_FIRST) && (slot <= SLOT_SW_CMD_LAST); +} + +static bool ot_spi_device_flash_command_is_upload(const SpiDeviceFlash *f) { - return (f->cmd_info & CMD_INFO_UPLOAD_MASK) != 0 && - (f->slot >= SPI_DEVICE_CMD_SW_FIRST) && - (f->slot <= SPI_DEVICE_CMD_SW_LAST); + return ot_spi_device_is_sw_command(f->slot) && + ((f->cmd_info & CMD_INFO_UPLOAD_MASK) != 0u); } static bool ot_spi_device_flash_is_readbuf_irq(const OtSPIDeviceState *s) @@ -747,7 +750,6 @@ static void ot_spi_device_clear_modes(OtSPIDeviceState *s) f->cmd_info = UINT32_MAX; f->pos = 0u; f->len = 0u; - f->type = SPI_FLASH_CMD_NONE; g_assert(s->sram); f->payload = &((uint8_t *)s->sram)[SPI_SRAM_PAYLOAD_OFFSET]; memset(f->buffer, 0u, SPI_FLASH_BUFFER_SIZE); @@ -850,23 +852,6 @@ ot_spi_device_is_mailbox_match(const OtSPIDeviceState *s, uint32_t addr) return (addr & R_MAILBOX_ADDR_UPPER_MASK) == mailbox_addr; } -static bool ot_spi_device_is_hw_read_command(const OtSPIDeviceState *s) -{ - const SpiDeviceFlash *f = &s->flash; - - switch (f->slot) { - case READ_NORMAL: - case READ_FAST: - case READ_DUAL: - case READ_QUAD: - case READ_DUAL_IO: - case READ_QUAD_IO: - return true; - default: - return false; - } -} - static void ot_spi_device_release(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; @@ -913,7 +898,7 @@ static void ot_spi_device_release(OtSPIDeviceState *s) * "does not show the commands falling into the mailbox region or * Read SFDP command’s address." */ - if (ot_spi_device_is_hw_read_command(s) && + if (f->slot >= SLOT_HW_READ_NORMAL && f->slot <= SLOT_HW_READ_QUAD_IO && !ot_spi_device_is_mailbox_match(s, f->last_read_addr)) { trace_ot_spi_device_update_last_read_addr(s->ot_id, f->last_read_addr); @@ -946,56 +931,46 @@ static void ot_spi_device_flash_pace_spibus(OtSPIDeviceState *s) timer_mod(f->irq_timer, (int64_t)(now + SPI_BUS_FLASH_READ_DELAY_NS)); } -static void ot_spi_device_flash_decode_command(OtSPIDeviceState *s, uint8_t cmd) +static bool +ot_spi_device_flash_match_command_slot(OtSPIDeviceState *s, uint8_t cmd) { SpiDeviceFlash *f = &s->flash; - if (f->state == SPI_FLASH_IDLE) { - for (unsigned ix = 0; - ix < PARAM_NUM_CMD_INFO + SPI_DEVICE_CMD_HW_CFG_COUNT; ix++) { - uint32_t val32 = s->spi_regs[R_CMD_INFO_0 + ix]; - if (cmd == (uint8_t)SHARED_FIELD_EX32(val32, CMD_INFO_OPCODE)) { - if (SHARED_FIELD_EX32(val32, CMD_INFO_VALID)) { - f->type = - ix < SPI_DEVICE_CMD_HW_STA_COUNT ? - SPI_FLASH_CMD_HW_STA : - (ix < PARAM_NUM_CMD_INFO ? SPI_FLASH_CMD_SW : - SPI_FLASH_CMD_HW_CFG); - f->slot = ix; - f->cmd_info = val32; - trace_ot_spi_device_flash_new_command( - s->ot_id, - f->type == SPI_FLASH_CMD_HW_STA ? - "HW" : - (f->type == SPI_FLASH_CMD_SW ? "SW" : "HW_CFG"), - cmd, f->slot); - break; - } - trace_ot_spi_device_flash_disabled_slot(s->ot_id, cmd, ix); + g_assert(f->state == SPI_FLASH_IDLE); + + /* Find and match the opcode in the CMD_INFO registers */ + for (unsigned ix = 0u; ix < SLOT_COUNT; ix++) { + uint32_t cmd_info = s->spi_regs[R_CMD_INFO_0 + ix]; + if (cmd == (uint8_t)SHARED_FIELD_EX32(cmd_info, CMD_INFO_OPCODE)) { + if (SHARED_FIELD_EX32(cmd_info, CMD_INFO_VALID)) { + f->slot = ix; + f->cmd_info = cmd_info; + const char *type = + ot_spi_device_is_sw_command(f->slot) ? "SW" : "HW"; + trace_ot_spi_device_flash_new_command(s->ot_id, type, cmd, + f->slot); + return true; } + trace_ot_spi_device_flash_disabled_slot(s->ot_id, cmd, ix); } } - if (f->type == SPI_FLASH_CMD_NONE) { - trace_ot_spi_device_flash_ignored_command(s->ot_id, "unmanaged", cmd); - return; - } + trace_ot_spi_device_flash_ignored_command(s->ot_id, "unmatched", cmd); + return false; +} - bool upload = ot_spi_device_flash_is_upload(f); - if (upload) { - if (fifo8_is_full(&f->cmd_fifo)) { - error_setg(&error_warn, "command FIFO overflow\n"); - return; - } +static void ot_spi_device_flash_try_upload(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; - bool set_busy = (bool)(f->cmd_info & CMD_INFO_BUSY_MASK); - if (set_busy) { + if (ot_spi_device_flash_command_is_upload(f)) { + bool busy = (bool)(f->cmd_info & CMD_INFO_BUSY_MASK); + if (busy) { s->spi_regs[R_FLASH_STATUS] |= R_FLASH_STATUS_BUSY_MASK; } - trace_ot_spi_device_flash_upload(s->ot_id, f->slot, f->cmd_info, - set_busy); fifo8_push(&f->cmd_fifo, COMMAND_OPCODE(f->cmd_info)); f->new_cmd = true; + trace_ot_spi_device_flash_upload(s->ot_id, f->slot, f->cmd_info, busy); } } @@ -1035,7 +1010,7 @@ static void ot_spi_device_flash_decode_write_enable(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - bool enable = f->slot == FLASH_SLOT(WREN); + bool enable = f->slot == SLOT_HW_WREN; trace_ot_spi_device_flash_exec(s->ot_id, enable ? "WREN" : "WRDI"); if (enable) { s->spi_regs[R_FLASH_STATUS] |= R_FLASH_STATUS_WEL_MASK; @@ -1049,7 +1024,7 @@ static void ot_spi_device_flash_decode_addr4_enable(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - bool enable = f->slot == FLASH_SLOT(EN4B); + bool enable = f->slot == SLOT_HW_EN4B; trace_ot_spi_device_flash_exec(s->ot_id, enable ? "EN4B" : "EX4B"); if (enable) { @@ -1064,7 +1039,7 @@ static void ot_spi_device_flash_decode_read_status(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - g_assert(f->slot < 3u); + g_assert(f->slot <= SLOT_HW_READ_STATUS3); uint32_t status = s->spi_regs[R_FLASH_STATUS]; f->buffer[0] = (uint8_t)(status >> (f->slot * 8u)); @@ -1094,14 +1069,14 @@ static void ot_spi_device_flash_decode_read_data(OtSPIDeviceState *s) unsigned dummy = 1; switch (f->slot) { - case READ_NORMAL: + case SLOT_HW_READ_NORMAL: dummy = 0; break; - case READ_FAST: - case READ_DUAL: - case READ_QUAD: - case READ_DUAL_IO: - case READ_QUAD_IO: + case SLOT_HW_READ_FAST: + case SLOT_HW_READ_DUAL: + case SLOT_HW_READ_QUAD: + case SLOT_HW_READ_DUAL_IO: + case SLOT_HW_READ_QUAD_IO: dummy = 1u; break; default: @@ -1114,30 +1089,38 @@ static void ot_spi_device_flash_decode_read_data(OtSPIDeviceState *s) f->len = dummy + (ot_spi_device_is_addr4b_en(s) ? 4u : 3u); } -static void ot_spi_device_flash_decode_hw_static_command(OtSPIDeviceState *s) +static void ot_spi_device_flash_decode_hw_command(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - switch ((int)f->slot) { - case READ_STATUS1: - case READ_STATUS2: - case READ_STATUS3: + switch (f->slot) { + case SLOT_HW_READ_STATUS1: + case SLOT_HW_READ_STATUS2: + case SLOT_HW_READ_STATUS3: ot_spi_device_flash_decode_read_status(s); break; - case READ_JEDEC: + case SLOT_HW_READ_JEDEC: ot_spi_device_flash_decode_read_jedec(s); break; - case READ_SFDP: + case SLOT_HW_READ_SFDP: ot_spi_device_flash_decode_read_sfdp(s); break; - case READ_NORMAL: - case READ_FAST: - case READ_DUAL: - case READ_QUAD: - case READ_DUAL_IO: - case READ_QUAD_IO: + case SLOT_HW_READ_NORMAL: + case SLOT_HW_READ_FAST: + case SLOT_HW_READ_DUAL: + case SLOT_HW_READ_QUAD: + case SLOT_HW_READ_DUAL_IO: + case SLOT_HW_READ_QUAD_IO: ot_spi_device_flash_decode_read_data(s); break; + case SLOT_HW_EN4B: + case SLOT_HW_EX4B: + ot_spi_device_flash_decode_addr4_enable(s); + break; + case SLOT_HW_WREN: + case SLOT_HW_WRDI: + ot_spi_device_flash_decode_write_enable(s); + break; default: g_assert_not_reached(); } @@ -1174,20 +1157,20 @@ static void ot_spi_device_flash_exec_read_data(OtSPIDeviceState *s) f->loop = true; } -static void ot_spi_device_exec_command(OtSPIDeviceState *s) +static void ot_spi_device_flash_exec_command(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - switch (COMMAND_OPCODE(f->cmd_info)) { - case 0x5Au: /* READ_SFDP */ + switch (f->slot) { + case SLOT_HW_READ_SFDP: ot_spi_device_flash_exec_read_sfdp(s); break; - case 0x03u: /* READ_NORMAL */ - case 0x0bu: /* READ_FAST */ - case 0x3bu: /* READ_DUAL */ - case 0x6bu: /* READ_QUAD */ - case 0xbbu: /* READ_DUALIO */ - case 0xebu: /* READ_QUADIO */ + case SLOT_HW_READ_NORMAL: + case SLOT_HW_READ_FAST: + case SLOT_HW_READ_DUAL: + case SLOT_HW_READ_QUAD: + case SLOT_HW_READ_DUAL_IO: + case SLOT_HW_READ_QUAD_IO: ot_spi_device_flash_exec_read_data(s); break; default: @@ -1195,31 +1178,6 @@ static void ot_spi_device_exec_command(OtSPIDeviceState *s) } } -static uint8_t ot_spi_device_flash_exec_hw_cfg_command(OtSPIDeviceState *s) -{ - SpiDeviceFlash *f = &s->flash; - - uint8_t tx = SPI_DEFAULT_TX_VALUE; - unsigned cmdinfo = f->slot - (R_CMD_INFO_EN4B - R_CMD_INFO_0); - - switch (cmdinfo) { - case 0: /* EN4B (typ. 0xB7) */ - case 1u: /* EX4B (typ. 0xE9u) */ - ot_spi_device_flash_decode_addr4_enable(s); - break; - case 2u: /* WREN (typ. 0x06u) */ - case 3u: /* WRDI (typ. 0x04u) */ - ot_spi_device_flash_decode_write_enable(s); - break; - default: - error_setg(&error_fatal, "invalid command info %u %u", f->slot, - cmdinfo); - g_assert_not_reached(); - } - - return tx; -} - static bool ot_spi_device_flash_collect(OtSPIDeviceState *s, uint8_t rx) { SpiDeviceFlash *f = &s->flash; @@ -1367,7 +1325,7 @@ static void ot_spi_device_flash_decode_sw_command(OtSPIDeviceState *s) } else if (f->cmd_info & CMD_INFO_DUMMY_EN_MASK) { f->len = 1u; FLASH_CHANGE_STATE(s, UP_DUMMY); - } else if (ot_spi_device_flash_is_upload(f)) { + } else if (ot_spi_device_flash_command_is_upload(f)) { ot_spi_device_flash_init_payload(s); } else { /* @@ -1403,7 +1361,7 @@ static void ot_spi_device_flash_exec_sw_command(OtSPIDeviceState *s, uint8_t rx) if (f->cmd_info & CMD_INFO_DUMMY_EN_MASK) { f->len = 1u; FLASH_CHANGE_STATE(s, UP_DUMMY); - } else if (ot_spi_device_flash_is_upload(f)) { + } else if (ot_spi_device_flash_command_is_upload(f)) { ot_spi_device_flash_init_payload(s); } else { /* @@ -1418,7 +1376,7 @@ static void ot_spi_device_flash_exec_sw_command(OtSPIDeviceState *s, uint8_t rx) case SPI_FLASH_UP_DUMMY: f->pos++; g_assert(f->pos == f->len); - if (ot_spi_device_flash_is_upload(f)) { + if (ot_spi_device_flash_command_is_upload(f)) { ot_spi_device_flash_init_payload(s); } else { /* @@ -1459,36 +1417,28 @@ static uint8_t ot_spi_device_flash_transfer(OtSPIDeviceState *s, uint8_t rx) switch (f->state) { case SPI_FLASH_IDLE: - f->slot = UINT_MAX; + f->slot = SLOT_INVALID; f->pos = 0; f->len = 0; f->src = NULL; f->loop = false; - f->type = SPI_FLASH_CMD_NONE; - ot_spi_device_flash_decode_command(s, rx); - switch (f->type) { - case SPI_FLASH_CMD_HW_STA: - ot_spi_device_flash_decode_hw_static_command(s); - break; - case SPI_FLASH_CMD_HW_CFG: - ot_spi_device_flash_exec_hw_cfg_command(s); - break; - case SPI_FLASH_CMD_SW: - ot_spi_device_flash_decode_sw_command(s); - break; - case SPI_FLASH_CMD_NONE: + if (ot_spi_device_flash_match_command_slot(s, rx)) { + if (ot_spi_device_is_sw_command(f->slot)) { + ot_spi_device_flash_decode_sw_command(s); + ot_spi_device_flash_try_upload(s); + } else { + ot_spi_device_flash_decode_hw_command(s); + } + } else { /* this command cannot be processed, discard all remaining bytes */ trace_ot_spi_device_flash_unknown_command(s->ot_id, rx); FLASH_CHANGE_STATE(s, ERROR); BUS_CHANGE_STATE(s, DISCARD); - break; - default: - g_assert_not_reached(); } break; case SPI_FLASH_COLLECT: if (!ot_spi_device_flash_collect(s, rx)) { - ot_spi_device_exec_command(s); + ot_spi_device_flash_exec_command(s); } break; case SPI_FLASH_BUFFER: From 5b1b46f4ded0a83d7aa68ff0de2634c1c852d14b Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 13:26:38 +0000 Subject: [PATCH 2/9] [ot] hw/opentitan: ot_spi_device: add trace event for flash transfer Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 4 +++- hw/opentitan/trace-events | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index f8dab8840dc1b..7f1f787497c52 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1411,7 +1411,7 @@ static uint8_t ot_spi_device_flash_transfer(OtSPIDeviceState *s, uint8_t rx) { SpiDeviceFlash *f = &s->flash; - (void)rx; + trace_ot_spi_device_flash_transfer(s->ot_id, "->", rx); uint8_t tx = SPI_DEFAULT_TX_VALUE; @@ -1463,6 +1463,8 @@ static uint8_t ot_spi_device_flash_transfer(OtSPIDeviceState *s, uint8_t rx) g_assert_not_reached(); } + trace_ot_spi_device_flash_transfer(s->ot_id, "<-", tx); + return tx; } diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index c3764ccebda1a..6421203cae7d8 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -544,13 +544,14 @@ ot_spi_device_buf_write_in(const char *id, uint32_t addr, unsigned size, uint32_ ot_spi_device_bus_change_state(const char *id, int line, const char *state, int stateval) "%s: @%d: %s [%d]" ot_spi_device_chr_handle_packet(const char *id, unsigned count, unsigned eot, char rx, char tx, const char *st) "%s: 0x%x bytes, eot: %u, rx: %csb, tx: %csb, mode:%s" ot_spi_device_chr_error(const char *id, const char *err) "%s: %s" +ot_spi_device_flash_transfer(const char *id, const char *dir, uint8_t byte) "%s: host %s 0x%02x" ot_spi_device_flash_byte_unexpected(const char *id, uint8_t rx) "%s: unexpected byte 0x%02x after completed command" ot_spi_device_flash_change_state(const char *id, int line, const char *prev, int preval, const char *next, int nextval) "%s: @%d: %s[%d] -> %s[%d]" ot_spi_device_flash_cross_buffer(const char *id, const char *msg, uint32_t addr) "%s: %s 0x%08x" ot_spi_device_flash_disabled_slot(const char *id, uint8_t cmd, unsigned slot) "%s: cmd 0x%02x matched, disabled slot %u" ot_spi_device_flash_exec(const char *id, const char *cmd) "%s: %s" ot_spi_device_flash_ignored_command(const char *id, const char* msg, uint8_t cmd) "%s: %s cmd 0x%02x" -ot_spi_device_flash_new_command(const char *id, const char* type, uint8_t cmd, unsigned slot) "%s: %s CMD 0x%02X slot %u" +ot_spi_device_flash_new_command(const char *id, const char *type, uint8_t cmd, unsigned slot) "%s: %s cmd 0x%02X slot %u" ot_spi_device_flash_overflow(const char *id, const char *msg) "%s: %s" ot_spi_device_flash_pace(const char *id, const char *msg, bool pending) "%s: %s: %u" ot_spi_device_flash_payload(const char *id, unsigned fpos, unsigned idx, unsigned len) "%s: pos:%u idx:%u len:%u" From ff4ec5045a60e3875881a9a3758ad1a76e6f6e1b Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 28 Oct 2025 12:16:25 +0000 Subject: [PATCH 3/9] [ot] hw/opentitan: ot_spi_device: move address size to own function ... and removes `OtSpiDeviceAddrMode`. `ot_spi_device_get_command_address_size` now returns the size of the address field in the current command, using the value in `cmd_info` and the current 4B enable state. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 53 ++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 7f1f787497c52..2271b993b38d1 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -374,13 +374,6 @@ typedef enum { CTRL_MODE_INVALID, } OtSpiDeviceMode; -typedef enum { - ADDR_MODE_ADDRDISABLED, - ADDR_MODE_ADDRCFG, - ADDR_MODE_ADDR3B, - ADDR_MODE_ADDR4B, -} OtSpiDeviceAddrMode; - typedef enum { SPI_BUS_IDLE, SPI_BUS_FLASH, @@ -840,6 +833,25 @@ static bool ot_spi_device_is_mailbox_en(const OtSPIDeviceState *s) return (bool)(s->spi_regs[R_CFG] & R_CFG_MAILBOX_EN_MASK); } +static unsigned +ot_spi_device_get_command_address_size(const OtSPIDeviceState *s) +{ + const SpiDeviceFlash *f = &s->flash; + + switch (SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_ADDR_MODE)) { + case 0x0u: /* AddrDisabled */ + return 0u; + case 0x1u: /* AddrCfg */ + return ot_spi_device_is_addr4b_en(s) ? 4u : 3u; + case 0x2u: /* Addr3B */ + return 3u; + case 0x3u: /* Addr4B */ + return 4u; + default: + g_assert_not_reached(); + } +} + static bool ot_spi_device_is_mailbox_match(const OtSPIDeviceState *s, uint32_t addr) { @@ -1298,29 +1310,10 @@ static void ot_spi_device_flash_decode_sw_command(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - unsigned addr_count; - uint32_t addr_mode = SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_ADDR_MODE); - switch ((int)addr_mode) { - case ADDR_MODE_ADDRDISABLED: - addr_count = 0; - break; - case ADDR_MODE_ADDRCFG: - addr_count = ot_spi_device_is_addr4b_en(s) ? 4u : 3u; - break; - case ADDR_MODE_ADDR3B: - addr_count = 3u; - break; - case ADDR_MODE_ADDR4B: - addr_count = 4u; - break; - default: - g_assert_not_reached(); - break; - } - - f->pos = 0; - if (addr_count != 0) { - f->len = addr_count; + unsigned addr_size = ot_spi_device_get_command_address_size(s); + f->pos = 0u; + if (addr_size != 0u) { + f->len = addr_size; FLASH_CHANGE_STATE(s, UP_ADDR); } else if (f->cmd_info & CMD_INFO_DUMMY_EN_MASK) { f->len = 1u; From e394e00d885ba22a3f9e4c25f6f33dfd7a397b5c Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 30 Oct 2025 11:40:33 +0000 Subject: [PATCH 4/9] [ot] hw/opentitan: ot_spi_device: make logging use ot_id Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 62 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 2271b993b38d1..e055e611a0d1f 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1554,7 +1554,8 @@ ot_spi_device_spi_regs_read(void *opaque, hwaddr addr, unsigned size) if (!fifo8_is_empty(&f->cmd_fifo)) { val32 = (uint32_t)fifo8_pop(&f->cmd_fifo); } else { - qemu_log_mask(LOG_UNIMP, "%s: CMD_FIFO is empty\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: CMD_FIFO is empty\n", __func__, + s->ot_id); val32 = 0; } break; @@ -1562,20 +1563,22 @@ ot_spi_device_spi_regs_read(void *opaque, hwaddr addr, unsigned size) if (!ot_fifo32_is_empty(&f->address_fifo)) { val32 = ot_fifo32_pop(&f->address_fifo); } else { - qemu_log_mask(LOG_UNIMP, "%s: ADDR_FIFO is empty\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: ADDR_FIFO is empty\n", __func__, + s->ot_id); val32 = 0; } break; case R_INTR_TEST: case R_ALERT_TEST: qemu_log_mask(LOG_GUEST_ERROR, - "%s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, SPI_REG_NAME(reg)); + "%s: %s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, SPI_REG_NAME(reg)); val32 = 0; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0; break; } @@ -1647,7 +1650,8 @@ static void ot_spi_device_spi_regs_write(void *opaque, hwaddr addr, case CTRL_MODE_DISABLED: case CTRL_MODE_PASSTHROUGH: default: - qemu_log_mask(LOG_UNIMP, "%s: unsupported mode\n", __func__); + qemu_log_mask(LOG_UNIMP, "%s: %s: unsupported mode\n", __func__, + s->ot_id); break; } break; @@ -1736,12 +1740,13 @@ static void ot_spi_device_spi_regs_write(void *opaque, hwaddr addr, case R_UPLOAD_CMDFIFO: case R_UPLOAD_ADDRFIFO: qemu_log_mask(LOG_GUEST_ERROR, - "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, SPI_REG_NAME(reg)); + "%s: %s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, SPI_REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } }; @@ -1775,19 +1780,20 @@ ot_spi_device_tpm_regs_read(void *opaque, hwaddr addr, unsigned size) case R_TPM_INT_STATUS: case R_TPM_DID_VID: case R_TPM_RID: - qemu_log_mask(LOG_UNIMP, "%s: %s: not supported\n", __func__, - TPM_REG_NAME(reg)); + qemu_log_mask(LOG_UNIMP, "%s: %s: %s: not supported\n", __func__, + s->ot_id, TPM_REG_NAME(reg)); val32 = s->tpm_regs[reg]; break; case R_TPM_READ_FIFO: qemu_log_mask(LOG_GUEST_ERROR, - "%s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, SPI_REG_NAME(reg)); + "%s: %s: W/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, SPI_REG_NAME(reg)); val32 = 0u; break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); val32 = 0u; break; } @@ -1840,12 +1846,13 @@ static void ot_spi_device_tpm_regs_write(void *opaque, hwaddr addr, case R_TPM_CAP: case R_TPM_CMD_ADDR: qemu_log_mask(LOG_GUEST_ERROR, - "%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, - addr, TPM_REG_NAME(reg)); + "%s: %s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", + __func__, s->ot_id, addr, TPM_REG_NAME(reg)); break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", - __func__, addr); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + s->ot_id, addr); break; } }; @@ -1861,8 +1868,8 @@ static MemTxResult ot_spi_device_buf_read_with_attrs( if (addr < SPI_SRAM_INGRESS_OFFSET) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: cannot read egress buffer 0x%" HWADDR_PRIx "\n", - __func__, addr); + "%s: %s: cannot read egress buffer 0x%" HWADDR_PRIx "\n", + __func__, s->ot_id, addr); return MEMTX_DECODE_ERROR; } @@ -1882,9 +1889,9 @@ static MemTxResult ot_spi_device_buf_read_with_attrs( val32 = s->flash.address_fifo.data[addr >> 2u]; } else { qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid ingress buffer access to 0x%" HWADDR_PRIx + "%s: %s: Invalid ingress buffer access to 0x%" HWADDR_PRIx "-0x%" HWADDR_PRIx "\n", - __func__, addr, last); + __func__, s->ot_id, addr, last); val32 = 0; } @@ -1916,8 +1923,9 @@ static MemTxResult ot_spi_device_buf_write_with_attrs( if (last >= SPI_SRAM_INGRESS_OFFSET) { qemu_log_mask(LOG_GUEST_ERROR, - "%s: cannot write ingress buffer 0x%" HWADDR_PRIx "\n", - __func__, addr); + "%s: %s: cannot write ingress buffer 0x%" HWADDR_PRIx + "\n", + __func__, s->ot_id, addr); return MEMTX_DECODE_ERROR; } s->sram[addr >> 2u] = val32; From 5458dd97de0e6355e4ff42108febd78b62ac9980 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 13:59:12 +0000 Subject: [PATCH 5/9] [ot] hw/opentitan: ot_spi_host: passthrough mode method and IRQs This implements a passthrough enable and chip select IRQ and a method to interact with the downstream SPI bus, for use by upstream SPI Device. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_host.c | 87 ++++++++++++++++++++++++++---- hw/opentitan/trace-events | 2 + include/hw/opentitan/ot_spi_host.h | 25 +++++++++ 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index cfc84116d90fa..7a9ce2361b17c 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -257,6 +257,8 @@ typedef struct { } TraceCache; #endif /* DISCARD_REPEATED_STATUS_TRACES */ +#define SPI_DEFAULT_TX_VALUE ((uint8_t)0xffu) + /* ------------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------------ */ @@ -324,12 +326,6 @@ typedef struct { unsigned size; } OtSPIHostCmd; -/* this class is only required to manage on-hold reset */ -struct OtSPIHostClass { - SysBusDeviceClass parent_class; - ResettablePhases parent_phases; -}; - struct OtSPIHostState { SysBusDevice parent_obj; @@ -355,6 +351,7 @@ struct OtSPIHostState { OtSPIHostFsm fsm; bool on_reset; + bool passthrough_en; /* Upstream SPI Device Passthrough enable line */ unsigned pclk; /* Current input clock */ const char *clock_src_name; /* IRQ name once connected */ @@ -829,10 +826,11 @@ static void ot_spi_host_step_fsm(OtSPIHostState *s, const char *cause) ot_spi_host_chip_select(s, s->active.cmd.cs, s->fsm.transaction); } - uint8_t tx = - write ? (uint8_t)txfifo_pop(s->tx_fifo, length == 1u) : 0xffu; + uint8_t tx = write ? (uint8_t)txfifo_pop(s->tx_fifo, length == 1u) : + SPI_DEFAULT_TX_VALUE; - uint8_t rx = s->fsm.output_en ? ssi_transfer(s->ssi, tx) : 0xffu; + uint8_t rx = + s->fsm.output_en ? ssi_transfer(s->ssi, tx) : SPI_DEFAULT_TX_VALUE; if (multi && read && write) { /* invalid command, lets corrupt input data */ @@ -1293,6 +1291,66 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, } } +static uint8_t ot_spi_host_downstream_transfer(OtSPIHostState *s, uint8_t tx) +{ + /* Passthrough is not implemented if SPI Host has more than one CS line */ + if (s->num_cs != 1u) { + trace_ot_spi_host_passthrough_unimplemented(s->ot_id); + return SPI_DEFAULT_TX_VALUE; + } + + if (!s->passthrough_en) { + trace_ot_spi_host_passthrough_disabled(s->ot_id); + return SPI_DEFAULT_TX_VALUE; + } + + /* Forward to downstream flash */ + return (uint8_t)ssi_transfer(s->ssi, (uint32_t)tx); +} + +static void +ot_spi_host_device_passthrough_en_input(void *opaque, int irq, int level) +{ + OtSPIHostState *s = opaque; + g_assert(irq == 0u); + + /* Passthrough is not implemented if SPI Host has more than one CS line */ + if (s->num_cs != 1u) { + trace_ot_spi_host_passthrough_unimplemented(s->ot_id); + return; + } + + s->passthrough_en = (bool)level; + + /* + * Real HW muxes the CS line based on Passthrough Enable, but it doesn't + * make sense for a transfer to be handled by both SPI Host and SPI Device, + * so we just deassert CS on any change. + */ + qemu_irq_raise(s->cs_lines[0]); +} + +static void +ot_spi_host_device_passthrough_cs_input(void *opaque, int irq, int level) +{ + OtSPIHostState *s = opaque; + g_assert(irq == 0u); + + /* Passthrough is not implemented if SPI Host has more than one CS line */ + if (s->num_cs != 1u) { + trace_ot_spi_host_passthrough_unimplemented(s->ot_id); + return; + } + + if (!s->passthrough_en) { + trace_ot_spi_host_passthrough_disabled(s->ot_id); + return; + } + + /* Passthrough enabled, SPI Device is driving this CS */ + qemu_set_irq(s->cs_lines[0], level); +} + /* ------------------------------------------------------------------------ */ /* Device description/instanciation */ /* ------------------------------------------------------------------------ */ @@ -1347,6 +1405,8 @@ static void ot_spi_host_reset_enter(Object *obj, ResetType type) s->on_reset = true; + s->passthrough_en = false; + if (!s->clock_src_name) { IbexClockSrcIfClass *ic = IBEX_CLOCK_SRC_IF_GET_CLASS(s->clock_src); IbexClockSrcIf *ii = IBEX_CLOCK_SRC_IF(s->clock_src); @@ -1413,6 +1473,11 @@ static void ot_spi_host_instance_init(Object *obj) ARRAY_SIZE(s->irqs)); ibex_qdev_init_irq(obj, &s->alert, OT_DEVICE_ALERT); + qdev_init_gpio_in_named(DEVICE(s), &ot_spi_host_device_passthrough_en_input, + OT_SPI_HOST_PASSTHROUGH_EN, 1); + qdev_init_gpio_in_named(DEVICE(s), &ot_spi_host_device_passthrough_cs_input, + OT_SPI_HOST_PASSTHROUGH_CS, 1); + s->regs = g_new0(uint32_t, REGS_COUNT); s->rx_fifo = g_new0(RxFifo, 1u); @@ -1434,8 +1499,10 @@ static void ot_spi_host_class_init(ObjectClass *klass, void *data) dc->realize = ot_spi_host_realize; device_class_set_props(dc, ot_spi_host_properties); - ResettableClass *rc = RESETTABLE_CLASS(klass); OtSPIHostClass *sc = OT_SPI_HOST_CLASS(klass); + sc->ssi_downstream_transfer = &ot_spi_host_downstream_transfer; + + ResettableClass *rc = RESETTABLE_CLASS(klass); resettable_class_set_parent_phases(rc, &ot_spi_host_reset_enter, NULL, &ot_spi_host_reset_exit, &sc->parent_phases); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 6421203cae7d8..5a4bb7858bdc9 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -591,6 +591,8 @@ ot_spi_host_io_read_repeat(const char *id, const char * regname, size_t count) " ot_spi_host_io_write(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr:0x%02x (%s), val:0x%08x, pc:0x%x" ot_spi_host_kick_command(const char *id, unsigned cmdid, bool start) "%s: {%u} start:%u" ot_spi_host_new_command(const char *id, unsigned cmdid, const char *dir, const char *spd, uint32_t csid, bool active, unsigned len) "%s: {%u} d:%s s:%s cs#:%u csa:%u len:%u" +ot_spi_host_passthrough_disabled(const char *id) "%s: passthrough enable is not asserted" +ot_spi_host_passthrough_unimplemented(const char *id) "%s: passthrough mode is not implemented (more than one cs)" ot_spi_host_reset(const char *id) "%s" ot_spi_host_retire_command(const char *id, unsigned cmdid) "%s: {%u}" ot_spi_host_stall(const char *id, const char *msg, uint32_t val) "%s: %s rem %u" diff --git a/include/hw/opentitan/ot_spi_host.h b/include/hw/opentitan/ot_spi_host.h index ced150fa244a6..22088b66f1eab 100644 --- a/include/hw/opentitan/ot_spi_host.h +++ b/include/hw/opentitan/ot_spi_host.h @@ -8,6 +8,7 @@ * Author(s): * Wilfred Mallawa * Emmanuel Blot + * Alice Ziuziakowska * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,10 +33,34 @@ #define HW_OPENTITAN_OT_SPI_HOST_H #include "qom/object.h" +#include "hw/resettable.h" +#include "hw/sysbus.h" #define TYPE_OT_SPI_HOST "ot-spi_host" OBJECT_DECLARE_TYPE(OtSPIHostState, OtSPIHostClass, OT_SPI_HOST) +/* this class is only required to manage on-hold reset */ +struct OtSPIHostClass { + SysBusDeviceClass parent_class; + + /* + * Transfer a byte over this downstream SPI Host's SPI bus. + * If Passthrough Enable is not asserted, or the SPI Host supports more + * than one Chip Select, the transfer does not take place on the bus and a + * default value is returned. + * + * @tx byte to be transferred + * @return received byte from SPI bus, or a default value + */ + uint8_t (*ssi_downstream_transfer)(OtSPIHostState *, uint8_t tx); + + ResettablePhases parent_phases; +}; + +/* IRQ lines from upstream OT SPI Device */ +#define OT_SPI_HOST_PASSTHROUGH_EN (TYPE_OT_SPI_HOST "-passthrough-en") +#define OT_SPI_HOST_PASSTHROUGH_CS (TYPE_OT_SPI_HOST "-passthrough-cs") + /* Supported SPI Host versions */ typedef enum { OT_SPI_HOST_VERSION_EG_1_0_0, From 8be7ea67740a688cd867e1f0bd17a5e5d1903e24 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 14:05:45 +0000 Subject: [PATCH 6/9] [ot] hw/opentitan: ot_spi_device: Add Passthrough IRQs and SPI Host prop This adds the corresponding out IRQs to OT SPI Device, as well as a property that contains the downstream OT SPI Host. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/Kconfig | 1 + hw/opentitan/ot_spi_device.c | 17 +++++++++++++++++ include/hw/opentitan/ot_spi_device.h | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index ad2b5dd61001f..7cee9f246dbbf 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -177,6 +177,7 @@ config OT_SOCDBG_CTRL config OT_SPI_DEVICE bool + select OT_SPI_HOST config OT_SPI_HOST bool diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index e055e611a0d1f..d5c36c033073b 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -36,6 +36,7 @@ #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_fifo32.h" #include "hw/opentitan/ot_spi_device.h" +#include "hw/opentitan/ot_spi_host.h" #include "hw/qdev-properties-system.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" @@ -471,6 +472,11 @@ struct OtSPIDeviceState { SpiDeviceFlash flash; SpiDeviceTpm tpm; + OtSPIHostState *spihost; + /* CS signal for downstream flash in passthrough mode, active low */ + IbexIRQ passthrough_cs; + IbexIRQ passthrough_en; + uint32_t *spi_regs; /* Registers */ uint32_t *tpm_regs; /* Registers */ uint32_t *sram; /* SRAM (DPRAM on EG, E/I on DJ) */ @@ -2320,6 +2326,8 @@ static int ot_spi_device_chr_be_change(void *opaque) static Property ot_spi_device_properties[] = { DEFINE_PROP_STRING(OT_COMMON_DEV_ID, OtSPIDeviceState, ot_id), DEFINE_PROP_CHR("chardev", OtSPIDeviceState, chr), + DEFINE_PROP_LINK("spihost", OtSPIDeviceState, spihost, TYPE_OT_SPI_HOST, + OtSPIHostState *), DEFINE_PROP_END_OF_LIST(), }; @@ -2382,6 +2390,9 @@ static void ot_spi_device_reset_enter(Object *obj, ResetType type) s->tpm_regs[R_TPM_CAP] = 0x660100u; + ibex_irq_lower(&s->passthrough_en); + ibex_irq_raise(&s->passthrough_cs); + ot_spi_device_update_irqs(s); ot_spi_device_update_alerts(s); } @@ -2439,6 +2450,12 @@ static void ot_spi_device_init(Object *obj) ibex_qdev_init_irq(obj, &s->alerts[ix], OT_DEVICE_ALERT); } + /* Passthrough enable is active high, CS is active low */ + ibex_qdev_init_irq_default(OBJECT(s), &s->passthrough_en, + OT_SPI_DEVICE_PASSTHROUGH_EN, 0); + ibex_qdev_init_irq_default(OBJECT(s), &s->passthrough_cs, + OT_SPI_DEVICE_PASSTHROUGH_CS, 1); + /* * This timer is used to hand over to the vCPU whenever a READBUF_* irq is * raised, otherwide the vCPU would not be able to get notified that a diff --git a/include/hw/opentitan/ot_spi_device.h b/include/hw/opentitan/ot_spi_device.h index 9f5b40a713b4d..a6ba18e893b88 100644 --- a/include/hw/opentitan/ot_spi_device.h +++ b/include/hw/opentitan/ot_spi_device.h @@ -33,4 +33,8 @@ #define TYPE_OT_SPI_DEVICE "ot-spi_device" OBJECT_DECLARE_TYPE(OtSPIDeviceState, OtSPIDeviceClass, OT_SPI_DEVICE) +/* IRQ lines to downstream OT SPI Host */ +#define OT_SPI_DEVICE_PASSTHROUGH_EN (TYPE_OT_SPI_DEVICE "-passthrough-en") +#define OT_SPI_DEVICE_PASSTHROUGH_CS (TYPE_OT_SPI_DEVICE "-passthrough-cs") + #endif /* HW_OPENTITAN_OT_SPI_DEVICE_H */ From dcf7f1e547ed2622c45d8c1b93c2a2aae445719e Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 14:08:47 +0000 Subject: [PATCH 7/9] [ot] hw/riscv: ot_earlgrey: connect SPI Device to Host IRQs Signed-off-by: Alice Ziuziakowska --- hw/riscv/ot_earlgrey.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 0fdfc591cefbb..07fd70131d623 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -552,6 +552,9 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { .memmap = MEMMAPENTRIES( { .base = 0x40050000u } ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("spihost", SPI_HOST0) + ), .gpio = IBEXGPIOCONNDEFS( OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 69), OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 70), @@ -561,7 +564,11 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_SYSBUS_IRQ(5, PLIC, 74), OT_EG_SOC_GPIO_SYSBUS_IRQ(6, PLIC, 75), OT_EG_SOC_GPIO_SYSBUS_IRQ(7, PLIC, 76), - OT_EG_SOC_GPIO_ALERT(0, 5) + OT_EG_SOC_GPIO_ALERT(0, 5), + OT_EG_SOC_SIGNAL(OT_SPI_DEVICE_PASSTHROUGH_EN, 0, SPI_HOST0, + OT_SPI_HOST_PASSTHROUGH_EN, 0), + OT_EG_SOC_SIGNAL(OT_SPI_DEVICE_PASSTHROUGH_CS, 0, SPI_HOST0, + OT_SPI_HOST_PASSTHROUGH_CS, 0) ), }, [OT_EG_SOC_DEV_I2C0] = { From 9e03b652c5c1c0d1676401a92c9fa010a2592f5e Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 29 Oct 2025 14:45:50 +0000 Subject: [PATCH 8/9] [ot] hw/opentitan: ot_spi_device: implement Passthrough mode This commit implements the Passthrough mode on SPI Device. Passthrough mode allows the device to act as a proxy for a downstream flash device, optionally intercepting commands to send back its own values, filtering commands sent downstream, or transparently translating address and payload bytes. This also "implements" the Disabled mode, which discards all bytes sent to the device. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 381 +++++++++++++++++++++++++++++++++-- 1 file changed, 365 insertions(+), 16 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index d5c36c033073b..5258ae404f7b3 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -2,9 +2,11 @@ * QEMU OpenTitan SPI Device controller * * Copyright (c) 2023-2025 Rivos, Inc. + * Copyright (c) 2025 lowRISC contributors. * * Author(s): * Emmanuel Blot + * Alice Ziuziakowska * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -391,6 +393,10 @@ typedef enum { SPI_FLASH_UP_ADDR, /* Uploading address (<- SPI host) */ SPI_FLASH_UP_DUMMY, /* Uploading dummy (<- SPI host) */ SPI_FLASH_UP_PAYLOAD, /* Uploading payload (<- SPI host) */ + SPI_FLASH_PASSTHROUGH_UP_ADDR, /* Passthrough mode - Uploading address */ + SPI_FLASH_PASSTHROUGH_UP_DUMMY, /* Passthrough mode - Uploading dummy */ + SPI_FLASH_PASSTHROUGH_UP_PAYLOAD, /* Passthrough mode - Uploading payload */ + SPI_FLASH_PASSTHROUGH_FWD, /* Data passthrough to downstream flash device */ SPI_FLASH_DONE, /* No more clock expected for the current command */ SPI_FLASH_ERROR, /* On error */ } OtSpiFlashState; @@ -423,6 +429,11 @@ typedef struct { bool loop; /* Keep reading the buffer if end is reached */ bool watermark; /* Read watermark hit, used as flip-flop */ bool new_cmd; /* New command has been pushed in current SPI transaction */ + bool cmd_addr_swap_en; /* Passthrough mode - address swap enabled */ + bool cmd_dummy_en; /* Passthrough mode - command has dummy field */ + bool cmd_payload_swap_en; /* Passthrough mode - payload swap enabled */ + bool cmd_payload_dir_out; /* Passthrough mode - payload is from flash */ + uint8_t cmd_dummy_len; /* Passthrough mode - number of dummy cycles */ } SpiDeviceFlash; typedef struct { @@ -492,6 +503,7 @@ struct OtSPIDeviceClass { ResettablePhases parent_phases; }; +#define REG_BITS (8u * sizeof(uint32_t)) #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) #define R_SPI_LAST_REG (R_CMD_INFO_WRDI) @@ -649,6 +661,10 @@ static const char *FLASH_STATE_NAMES[] = { STATE_NAME_ENTRY(SPI_FLASH_UP_ADDR), STATE_NAME_ENTRY(SPI_FLASH_UP_DUMMY), STATE_NAME_ENTRY(SPI_FLASH_UP_PAYLOAD), + STATE_NAME_ENTRY(SPI_FLASH_PASSTHROUGH_FWD), + STATE_NAME_ENTRY(SPI_FLASH_PASSTHROUGH_UP_ADDR), + STATE_NAME_ENTRY(SPI_FLASH_PASSTHROUGH_UP_DUMMY), + STATE_NAME_ENTRY(SPI_FLASH_PASSTHROUGH_UP_PAYLOAD), STATE_NAME_ENTRY(SPI_FLASH_DONE), STATE_NAME_ENTRY(SPI_FLASH_ERROR), }; @@ -744,9 +760,10 @@ static void ot_spi_device_clear_modes(OtSPIDeviceState *s) timer_del(f->irq_timer); FLASH_CHANGE_STATE(s, IDLE); + f->slot = SLOT_INVALID; + f->cmd_info = 0u; f->address = 0u; f->last_read_addr = 0u; - f->cmd_info = UINT32_MAX; f->pos = 0u; f->len = 0u; g_assert(s->sram); @@ -911,21 +928,31 @@ static void ot_spi_device_release(OtSPIDeviceState *s) PAYLOAD_DEPTH, len); trace_ot_spi_device_flash_payload(s->ot_id, f->pos, pos, len); } - /* - * "shows the last address accessed by the host system." - * "does not show the commands falling into the mailbox region or - * Read SFDP command’s address." - */ + FLASH_CHANGE_STATE(s, IDLE); + break; + case CTRL_MODE_PASSTHROUGH: + ibex_irq_raise(&s->passthrough_cs); + FLASH_CHANGE_STATE(s, IDLE); + break; + case CTRL_MODE_DISABLED: + default: + break; + } + + /* + * "shows the last address accessed by the host system." + * "does not show the commands falling into the mailbox region or + * Read SFDP command’s address." + */ + switch (ot_spi_device_get_mode(s)) { + case CTRL_MODE_FLASH: + case CTRL_MODE_PASSTHROUGH: if (f->slot >= SLOT_HW_READ_NORMAL && f->slot <= SLOT_HW_READ_QUAD_IO && !ot_spi_device_is_mailbox_match(s, f->last_read_addr)) { trace_ot_spi_device_update_last_read_addr(s->ot_id, f->last_read_addr); s->spi_regs[R_LAST_READ_ADDR] = f->last_read_addr; } - FLASH_CHANGE_STATE(s, IDLE); - break; - case CTRL_MODE_PASSTHROUGH: - s->spi_regs[R_LAST_READ_ADDR] = f->address; break; default: break; @@ -1467,6 +1494,15 @@ static uint8_t ot_spi_device_flash_transfer(OtSPIDeviceState *s, uint8_t rx) return tx; } +static uint8_t ot_spi_device_flash_spi_transfer(OtSPIDeviceState *s, uint8_t rx) +{ + if (s->spihost) { + OtSPIHostClass *spihostc = OT_SPI_HOST_GET_CLASS(s->spihost); + return spihostc->ssi_downstream_transfer(s->spihost, rx); + } + return SPI_DEFAULT_TX_VALUE; +} + static void ot_spi_device_flash_resume_read(void *opaque) { OtSPIDeviceState *s = opaque; @@ -1476,6 +1512,304 @@ static void ot_spi_device_flash_resume_read(void *opaque) qemu_chr_fe_accept_input(&s->chr); } +static bool +ot_spi_device_flash_passthrough_filter(OtSPIDeviceState *s, uint8_t cmd) +{ + return (bool)(s->spi_regs[R_CMD_FILTER_0 + (cmd / REG_BITS)] & + (1u << (cmd % REG_BITS))); +} + +static uint8_t ot_spi_device_swap_byte_data( + uint8_t byte, unsigned byte_sel, uint32_t swap_mask, uint32_t swap_data) +{ + g_assert(byte_sel < 4u); + uint8_t mask = (uint8_t)((swap_mask >> (byte_sel * 8u)) & 0xffu); + uint8_t data = (uint8_t)((swap_data >> (byte_sel * 8u)) & 0xffu); + return (byte & ~mask) | (data & mask); +} + +static bool ot_spi_device_flash_try_intercept_hw_command(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + uint32_t intercept_val32 = s->spi_regs[R_INTERCEPT_EN]; + switch (f->slot) { + case SLOT_HW_READ_STATUS1: + case SLOT_HW_READ_STATUS2: + case SLOT_HW_READ_STATUS3: + if (FIELD_EX32(intercept_val32, INTERCEPT_EN, STATUS)) { + ot_spi_device_flash_decode_read_status(s); + return true; + } + return false; + case SLOT_HW_READ_JEDEC: + if (FIELD_EX32(intercept_val32, INTERCEPT_EN, JEDEC)) { + ot_spi_device_flash_decode_read_jedec(s); + return true; + } + return false; + case SLOT_HW_READ_SFDP: + if (FIELD_EX32(intercept_val32, INTERCEPT_EN, SFDP)) { + ot_spi_device_flash_decode_read_sfdp(s); + return true; + } + return false; + case SLOT_HW_READ_NORMAL: + case SLOT_HW_READ_FAST: + case SLOT_HW_READ_DUAL: + case SLOT_HW_READ_QUAD: + case SLOT_HW_READ_DUAL_IO: + case SLOT_HW_READ_QUAD_IO: + /* We try to intercept at every read after an address is given */ + return false; + case SLOT_HW_EN4B: + case SLOT_HW_EX4B: + ot_spi_device_flash_decode_addr4_enable(s); + return true; + case SLOT_HW_WREN: + case SLOT_HW_WRDI: + /* These HW commands cannot be intercepted */ + return false; + default: + return false; + } +} + +static void +ot_spi_device_passthrough_unmatched_command_params(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + f->cmd_addr_swap_en = false; + f->cmd_dummy_en = false; + f->cmd_dummy_len = 0u; + f->cmd_payload_swap_en = false; + f->cmd_payload_dir_out = false; +} + +static void ot_spi_device_flash_passthrough_command_params(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + f->cmd_addr_swap_en = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_ADDR_SWAP_EN); + f->cmd_payload_swap_en = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_PAYLOAD_SWAP_EN); + f->cmd_payload_dir_out = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_PAYLOAD_DIR); + f->cmd_dummy_en = (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_DUMMY_EN); + /* field value is one less than the number of cycles */ + f->cmd_dummy_len = + 1u + (uint8_t)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_DUMMY_SIZE); +} + +static void +ot_spi_device_flash_passthrough_addr(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + f->len = ot_spi_device_get_command_address_size(s); + + f->buffer[f->pos] = rx; + if (f->cmd_addr_swap_en) { + unsigned byte_sel = (f->len - f->pos - 1u); + rx = ot_spi_device_swap_byte_data(rx, byte_sel, + s->spi_regs[R_ADDR_SWAP_MASK], + s->spi_regs[R_ADDR_SWAP_DATA]); + } + + (void)ot_spi_device_flash_spi_transfer(s, rx); + + f->pos++; + if (f->pos == f->len) { /* end of address phase */ + f->pos = 0u; + f->address = ldl_be_p(f->buffer); + if (f->cmd_dummy_en) { + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_DUMMY); + } else if (f->cmd_payload_swap_en) { + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); + } else { + FLASH_CHANGE_STATE(s, PASSTHROUGH_FWD); + } + } +} + +static void ot_spi_device_flash_passthrough_dummy(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + g_assert(f->cmd_dummy_en); + + (void)ot_spi_device_flash_spi_transfer(s, 0u); + + f->cmd_dummy_len--; + + if (f->cmd_dummy_len == 0u) { + if (f->cmd_payload_swap_en) { + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); + } else { + FLASH_CHANGE_STATE(s, PASSTHROUGH_FWD); + } + } +} + +static void +ot_spi_device_flash_passthrough_payload(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + g_assert(f->cmd_payload_swap_en); + + rx = ot_spi_device_swap_byte_data(rx, f->pos, + s->spi_regs[R_PAYLOAD_SWAP_MASK], + s->spi_regs[R_PAYLOAD_SWAP_DATA]); + + (void)ot_spi_device_flash_spi_transfer(s, rx); + + f->pos++; + if (f->pos == sizeof(s->spi_regs[R_PAYLOAD_SWAP_DATA])) { + FLASH_CHANGE_STATE(s, PASSTHROUGH_FWD); + } +} + +static uint8_t ot_spi_device_flash_passthrough_read(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + if (FIELD_EX32(s->spi_regs[R_INTERCEPT_EN], INTERCEPT_EN, MBX) != 0u) { + if (f->slot >= SLOT_HW_READ_NORMAL && f->slot <= SLOT_HW_READ_QUAD_IO) { + if (ot_spi_device_get_command_address_size(s) != 0u) { + if (ot_spi_device_is_mailbox_match(s, f->address)) { + uint8_t *mbx = (uint8_t *)&s->sram[SPI_SRAM_MBX_OFFSET]; + uint8_t tx = mbx[f->address & (SPI_SRAM_MBX_SIZE - 1u)]; + f->address += 1u; + return tx; + } + } else { + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: Mailbox read intercept enabled but HW READ " + "CMD %d has no address field", + __func__, s->ot_id, f->slot); + } + } + } + + /* common path: read from downstream flash, update last read address */ + f->last_read_addr = f->address; + f->address += 1u; + return ot_spi_device_flash_spi_transfer(s, 0u); +} + + +static uint8_t +ot_spi_device_flash_transfer_passthrough(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + trace_ot_spi_device_flash_transfer(s->ot_id, "->", rx); + + uint8_t tx = SPI_DEFAULT_TX_VALUE; + + switch (f->state) { + case SPI_FLASH_IDLE: + f->slot = SLOT_INVALID; + f->cmd_info = 0u; + f->pos = 0u; + f->len = 0u; + f->address = 0u; + f->src = NULL; + f->loop = false; + memset(f->buffer, 0u, 4u); + /* + * Unmatched commands are not necessarily erroneous and the HW will + * continue the transfer with these default parameters, unless it + * is filtered later on. + */ + ot_spi_device_passthrough_unmatched_command_params(s); + + if (ot_spi_device_flash_match_command_slot(s, rx)) { + if (ot_spi_device_flash_try_intercept_hw_command(s)) { + break; + } + /* only matched software/not intercepted commands can be uploaded */ + ot_spi_device_flash_try_upload(s); + ot_spi_device_flash_passthrough_command_params(s); + } + + if (ot_spi_device_flash_passthrough_filter(s, rx)) { + /* command opcode is filtered, discard all bytes untl next CS */ + FLASH_CHANGE_STATE(s, IDLE); + BUS_CHANGE_STATE(s, DISCARD); + break; + } + + /* issue the command: assert chip select (active low) */ + ibex_irq_lower(&s->passthrough_cs); + /* pass the opcode through */ + tx = ot_spi_device_flash_spi_transfer(s, rx); + + if (ot_spi_device_get_command_address_size(s) != 0u) { + /* command has address field */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_ADDR); + break; + } + if (f->cmd_dummy_en) { + /* no address field, but has dummy */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_DUMMY); + break; + } + if (f->cmd_payload_swap_en) { + /* no address or dummy field, but payload will be swapped */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); + break; + } + /* no fields or swapping, just passthrough */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_FWD); + break; + case SPI_FLASH_PASSTHROUGH_UP_ADDR: + ot_spi_device_flash_passthrough_addr(s, rx); + break; + case SPI_FLASH_PASSTHROUGH_UP_DUMMY: + ot_spi_device_flash_passthrough_dummy(s); + break; + case SPI_FLASH_PASSTHROUGH_UP_PAYLOAD: + ot_spi_device_flash_passthrough_payload(s, rx); + break; + case SPI_FLASH_PASSTHROUGH_FWD: + if (f->cmd_payload_dir_out) { + tx = ot_spi_device_flash_passthrough_read(s); + } else { + (void)ot_spi_device_flash_spi_transfer(s, rx); + } + break; + /* HW intercepted commands */ + case SPI_FLASH_COLLECT: + if (!ot_spi_device_flash_collect(s, rx)) { + g_assert(f->slot == SLOT_HW_READ_SFDP); + ot_spi_device_flash_exec_read_sfdp(s); + } + break; + case SPI_FLASH_BUFFER: + g_assert(f->slot <= SLOT_HW_READ_JEDEC); + tx = ot_spi_device_flash_read_buffer(s); + break; + case SPI_FLASH_DONE: + FLASH_CHANGE_STATE(s, ERROR); + break; + case SPI_FLASH_ERROR: + break; + default: + error_setg(&error_fatal, "unexpected state %s[%d]", + FLASH_STATE_NAME(f->state), f->state); + g_assert_not_reached(); + } + + trace_ot_spi_device_flash_transfer(s->ot_id, "<-", tx); + + return tx; +} + static uint64_t ot_spi_device_spi_regs_read(void *opaque, hwaddr addr, unsigned size) { @@ -1651,13 +1985,16 @@ static void ot_spi_device_spi_regs_write(void *opaque, hwaddr addr, } s->spi_regs[reg] = val32; switch (ot_spi_device_get_mode(s)) { + case CTRL_MODE_INVALID: + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: invalid mode\n", __func__, + s->ot_id); + /* fallthrough */ case CTRL_MODE_FLASH: - break; case CTRL_MODE_DISABLED: + ibex_irq_lower(&s->passthrough_en); + break; case CTRL_MODE_PASSTHROUGH: - default: - qemu_log_mask(LOG_UNIMP, "%s: %s: unsupported mode\n", __func__, - s->ot_id); + ibex_irq_raise(&s->passthrough_en); break; } break; @@ -1996,10 +2333,11 @@ static void ot_spi_device_chr_handle_header(OtSPIDeviceState *s) switch (ot_spi_device_get_mode(s)) { case CTRL_MODE_FLASH: + case CTRL_MODE_PASSTHROUGH: BUS_CHANGE_STATE(s, FLASH); break; case CTRL_MODE_DISABLED: - case CTRL_MODE_PASSTHROUGH: + case CTRL_MODE_INVALID: default: BUS_CHANGE_STATE(s, DISCARD); break; @@ -2034,7 +2372,18 @@ static void ot_spi_device_chr_recv_flash(OtSPIDeviceState *s, if (bus->rev_rx) { rx = revbit8(rx); } - uint8_t tx = ot_spi_device_flash_transfer(s, rx) ^ bus->mode; + uint8_t tx; + switch (ot_spi_device_get_mode(s)) { + case CTRL_MODE_FLASH: + tx = ot_spi_device_flash_transfer(s, rx); + break; + case CTRL_MODE_PASSTHROUGH: + tx = ot_spi_device_flash_transfer_passthrough(s, rx); + break; + default: + g_assert_not_reached(); + } + tx ^= bus->mode; if (bus->rev_tx) { tx = revbit8(tx); } From a49e9bf3c6400b7cf8c990e5b425253ca78d9e62 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 30 Oct 2025 15:15:36 +0000 Subject: [PATCH 9/9] [ot] hw/opentitan: ot_spi_device: add trace events for Passthrough mode Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 54 ++++++++++++++++++++++++------------ hw/opentitan/trace-events | 10 +++++-- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 5258ae404f7b3..c2585233431e7 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1000,7 +1000,7 @@ ot_spi_device_flash_match_command_slot(OtSPIDeviceState *s, uint8_t cmd) } } - trace_ot_spi_device_flash_ignored_command(s->ot_id, "unmatched", cmd); + trace_ot_spi_device_flash_unmatched_command(s->ot_id, cmd); return false; } @@ -1533,27 +1533,29 @@ static bool ot_spi_device_flash_try_intercept_hw_command(OtSPIDeviceState *s) SpiDeviceFlash *f = &s->flash; uint32_t intercept_val32 = s->spi_regs[R_INTERCEPT_EN]; + bool intercepted = false; + switch (f->slot) { case SLOT_HW_READ_STATUS1: case SLOT_HW_READ_STATUS2: case SLOT_HW_READ_STATUS3: if (FIELD_EX32(intercept_val32, INTERCEPT_EN, STATUS)) { ot_spi_device_flash_decode_read_status(s); - return true; + intercepted = true; } - return false; + break; case SLOT_HW_READ_JEDEC: if (FIELD_EX32(intercept_val32, INTERCEPT_EN, JEDEC)) { ot_spi_device_flash_decode_read_jedec(s); - return true; + intercepted = true; } - return false; + break; case SLOT_HW_READ_SFDP: if (FIELD_EX32(intercept_val32, INTERCEPT_EN, SFDP)) { ot_spi_device_flash_decode_read_sfdp(s); - return true; + intercepted = true; } - return false; + break; case SLOT_HW_READ_NORMAL: case SLOT_HW_READ_FAST: case SLOT_HW_READ_DUAL: @@ -1561,18 +1563,25 @@ static bool ot_spi_device_flash_try_intercept_hw_command(OtSPIDeviceState *s) case SLOT_HW_READ_DUAL_IO: case SLOT_HW_READ_QUAD_IO: /* We try to intercept at every read after an address is given */ - return false; + break; case SLOT_HW_EN4B: case SLOT_HW_EX4B: ot_spi_device_flash_decode_addr4_enable(s); - return true; + intercepted = true; + break; case SLOT_HW_WREN: case SLOT_HW_WRDI: /* These HW commands cannot be intercepted */ - return false; + break; default: - return false; + break; } + + if (intercepted) { + trace_ot_spi_device_flash_intercepted_command(s->ot_id, f->slot); + } + + return intercepted; } static void @@ -1613,9 +1622,13 @@ ot_spi_device_flash_passthrough_addr(OtSPIDeviceState *s, uint8_t rx) f->buffer[f->pos] = rx; if (f->cmd_addr_swap_en) { unsigned byte_sel = (f->len - f->pos - 1u); - rx = ot_spi_device_swap_byte_data(rx, byte_sel, - s->spi_regs[R_ADDR_SWAP_MASK], - s->spi_regs[R_ADDR_SWAP_DATA]); + uint8_t swapped_rx = + ot_spi_device_swap_byte_data(rx, byte_sel, + s->spi_regs[R_ADDR_SWAP_MASK], + s->spi_regs[R_ADDR_SWAP_DATA]); + trace_ot_spi_device_flash_swap_byte(s->ot_id, "address", byte_sel, rx, + swapped_rx); + rx = swapped_rx; } (void)ot_spi_device_flash_spi_transfer(s, rx); @@ -1660,9 +1673,13 @@ ot_spi_device_flash_passthrough_payload(OtSPIDeviceState *s, uint8_t rx) g_assert(f->cmd_payload_swap_en); - rx = ot_spi_device_swap_byte_data(rx, f->pos, - s->spi_regs[R_PAYLOAD_SWAP_MASK], - s->spi_regs[R_PAYLOAD_SWAP_DATA]); + uint8_t swapped_rx = + ot_spi_device_swap_byte_data(rx, f->pos, + s->spi_regs[R_PAYLOAD_SWAP_MASK], + s->spi_regs[R_PAYLOAD_SWAP_DATA]); + trace_ot_spi_device_flash_swap_byte(s->ot_id, "payload", f->pos, rx, + swapped_rx); + rx = swapped_rx; (void)ot_spi_device_flash_spi_transfer(s, rx); @@ -1680,6 +1697,8 @@ static uint8_t ot_spi_device_flash_passthrough_read(OtSPIDeviceState *s) if (f->slot >= SLOT_HW_READ_NORMAL && f->slot <= SLOT_HW_READ_QUAD_IO) { if (ot_spi_device_get_command_address_size(s) != 0u) { if (ot_spi_device_is_mailbox_match(s, f->address)) { + trace_ot_spi_device_flash_intercept_mailbox(s->ot_id, + f->address); uint8_t *mbx = (uint8_t *)&s->sram[SPI_SRAM_MBX_OFFSET]; uint8_t tx = mbx[f->address & (SPI_SRAM_MBX_SIZE - 1u)]; f->address += 1u; @@ -1739,6 +1758,7 @@ ot_spi_device_flash_transfer_passthrough(OtSPIDeviceState *s, uint8_t rx) if (ot_spi_device_flash_passthrough_filter(s, rx)) { /* command opcode is filtered, discard all bytes untl next CS */ + trace_ot_spi_device_flash_filtered_command(s->ot_id, rx); FLASH_CHANGE_STATE(s, IDLE); BUS_CHANGE_STATE(s, DISCARD); break; diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 5a4bb7858bdc9..0f9bbb325b82e 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -548,18 +548,22 @@ ot_spi_device_flash_transfer(const char *id, const char *dir, uint8_t byte) "%s: ot_spi_device_flash_byte_unexpected(const char *id, uint8_t rx) "%s: unexpected byte 0x%02x after completed command" ot_spi_device_flash_change_state(const char *id, int line, const char *prev, int preval, const char *next, int nextval) "%s: @%d: %s[%d] -> %s[%d]" ot_spi_device_flash_cross_buffer(const char *id, const char *msg, uint32_t addr) "%s: %s 0x%08x" -ot_spi_device_flash_disabled_slot(const char *id, uint8_t cmd, unsigned slot) "%s: cmd 0x%02x matched, disabled slot %u" +ot_spi_device_flash_disabled_slot(const char *id, uint8_t cmd, unsigned slot) "%s: cmd 0x%02x matched disabled slot %u" ot_spi_device_flash_exec(const char *id, const char *cmd) "%s: %s" -ot_spi_device_flash_ignored_command(const char *id, const char* msg, uint8_t cmd) "%s: %s cmd 0x%02x" -ot_spi_device_flash_new_command(const char *id, const char *type, uint8_t cmd, unsigned slot) "%s: %s cmd 0x%02X slot %u" +ot_spi_device_flash_filtered_command(const char *id, uint8_t cmd) "%s: filtered cmd %02x" +ot_spi_device_flash_intercepted_command(const char *id, unsigned slot) "%s: intercepted HW cmd slot %u" +ot_spi_device_flash_intercept_mailbox(const char *id, uint32_t addr) "%s: intercepted read addr:0x%08x to mailbox region" +ot_spi_device_flash_new_command(const char *id, const char *type, uint8_t cmd, unsigned slot) "%s: %s cmd 0x%02x slot %u" ot_spi_device_flash_overflow(const char *id, const char *msg) "%s: %s" ot_spi_device_flash_pace(const char *id, const char *msg, bool pending) "%s: %s: %u" ot_spi_device_flash_payload(const char *id, unsigned fpos, unsigned idx, unsigned len) "%s: pos:%u idx:%u len:%u" ot_spi_device_flash_push_address(const char *id, uint32_t address) "%s: 0x%08x" ot_spi_device_flash_set_read_addr(const char *id, uint32_t addr) "%s: 0x%08x" +ot_spi_device_flash_swap_byte(const char *id, const char *type, unsigned byte_num, uint8_t prev, uint8_t cur) "%s: swapped %s byte %u 0x%02x -> 0x%02x" ot_spi_device_flash_read_status(const char *id, unsigned slot, uint8_t status) "%s: sr[%u] 0x%02x" ot_spi_device_flash_read_threshold(const char *id, uint32_t addr, uint32_t threshold) "%s: 0x%08x @ 0x%08x" ot_spi_device_flash_unknown_command(const char *id, uint8_t opcode) "%s: 0x%02x" +ot_spi_device_flash_unmatched_command(const char *id, uint8_t cmd) "%s: unmatched cmd 0x%02x" ot_spi_device_flash_upload(const char *id, unsigned slot, uint32_t cmd_info, bool busy) "%s: slot:%d info:0x%08x busy:%u" ot_spi_device_gen_fifo_error(const char *id, const char *msg) "%s: %s" ot_spi_device_gen_phase(const char *id, const char *func, unsigned off, unsigned lim, bool phase) "%s: %s off:0x%03x lim:0x%03x ph:%u"