From b8650184bab3b8e0c5ed17f46e86d10d9fc01b5a Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 6 Nov 2025 15:38:18 +0000 Subject: [PATCH 01/17] [ot] hw/opentitan: ot_spi_device: remove comment about different sram Both EG and DJ use E/I SRAM now Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 28e5efe122599..febf379d9e2ec 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -481,7 +481,7 @@ struct OtSPIDeviceState { uint32_t *spi_regs; /* Registers */ uint32_t *tpm_regs; /* Registers */ - uint32_t *sram; /* SRAM (DPRAM on EG, E/I on DJ) */ + uint32_t *sram; /* Properties */ char *ot_id; From d4eefba9033e1608634f3b73f56ad49bc2285cff Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Fri, 7 Nov 2025 15:56:10 +0000 Subject: [PATCH 02/17] [ot] hw/opentitan: ot_spi_device: format macros manually Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index febf379d9e2ec..f378effc77d95 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -151,10 +151,13 @@ REG32(CMD_INFO_0, 0x7cu) /* ReadStatus1 */ SHARED_FIELD(CMD_INFO_ADDR_MODE, 8u, 2u) SHARED_FIELD(CMD_INFO_ADDR_SWAP_EN, 10u, 1u) /* not used in Flash mode */ SHARED_FIELD(CMD_INFO_MBYTE_EN, 11u, 1u) - SHARED_FIELD(CMD_INFO_DUMMY_SIZE, 12u, 3u) /* limited to bits, ignore in QEMU */ - SHARED_FIELD(CMD_INFO_DUMMY_EN, 15u, 1u) /* only use this bit for dummy cfg */ + /* limited to bits, ignore in QEMU */ + SHARED_FIELD(CMD_INFO_DUMMY_SIZE, 12u, 3u) + /* only use this bit for dummy cfg */ + SHARED_FIELD(CMD_INFO_DUMMY_EN, 15u, 1u) SHARED_FIELD(CMD_INFO_PAYLOAD_EN, 16u, 4u) - SHARED_FIELD(CMD_INFO_PAYLOAD_DIR, 20u, 1u) /* not used in Flash mode (guess) */ + /* not used in Flash mode (guess) */ + SHARED_FIELD(CMD_INFO_PAYLOAD_DIR, 20u, 1u) SHARED_FIELD(CMD_INFO_PAYLOAD_SWAP_EN, 21u, 1u) /* not used in Flash mode */ SHARED_FIELD(CMD_INFO_READ_PIPELINE_MODE, 22u, 2u) SHARED_FIELD(CMD_INFO_UPLOAD, 24u, 1u) @@ -252,7 +255,8 @@ REG32(TPM_READ_FIFO, 0x34u) /* * Memory layout extracted from the documentation: - * opentitan.org/book/hw/ip/spi_device/doc/programmers_guide.html#dual-port-sram-layout + * opentitan.org/book/hw/ip/spi_device/doc/programmers_guide.html + * #dual-port-sram-layout * * New scheme (Egress + Ingress) Old Scheme (DPSRAM) * +--------------------------------+ +-----------------------+ @@ -280,6 +284,7 @@ REG32(TPM_READ_FIFO, 0x34u) * 0xfc0 -+-------------------+------+-----+ * */ +/* clang-format off */ #define SPI_SRAM_READ0_OFFSET 0x0 #define SPI_SRAM_READ_SIZE 0x400u #define SPI_SRAM_READ1_OFFSET (SPI_SRAM_READ0_OFFSET + SPI_SRAM_READ_SIZE) @@ -293,18 +298,21 @@ REG32(TPM_READ_FIFO, 0x34u) #define SPI_SRAM_INGRESS_OFFSET 0xE00u #define SPI_SRAM_PAYLOAD_OFFSET SPI_SRAM_INGRESS_OFFSET #define SPI_SRAM_PAYLOAD_SIZE 0x100u -#define SPI_SRAM_CMD_OFFSET (SPI_SRAM_PAYLOAD_OFFSET + SPI_SRAM_PAYLOAD_SIZE) +#define SPI_SRAM_CMD_OFFSET \ + (SPI_SRAM_PAYLOAD_OFFSET + SPI_SRAM_PAYLOAD_SIZE) #define SPI_SRAM_CMD_SIZE 0x40u #define SPI_SRAM_ADDR_OFFSET (SPI_SRAM_CMD_OFFSET + SPI_SRAM_CMD_SIZE) #define SPI_SRAM_ADDR_SIZE 0x40u #define SPI_SRAM_TPM_WRITE_OFFSET (SPI_SRAM_ADDR_OFFSET + SPI_SRAM_ADDR_SIZE) #define SPI_SRAM_TPM_WRITE_SIZE 0x40u -#define SPI_SRAM_ADDR_END (SPI_SRAM_TPM_WRITE_OFFSET + SPI_SRAM_TPM_WRITE_SIZE) +#define SPI_SRAM_ADDR_END \ + (SPI_SRAM_TPM_WRITE_OFFSET + SPI_SRAM_TPM_WRITE_SIZE) #define SPI_SRAM_END_OFFSET (SPI_SRAM_ADDR_END) #define SPI_DEVICE_SIZE 0x2000u #define SPI_DEVICE_SPI_REGS_OFFSET 0u #define SPI_DEVICE_TPM_REGS_OFFSET 0x800u #define SPI_DEVICE_SRAM_OFFSET 0x1000u +/* clang-format on */ #define SRAM_SIZE (PARAM_SRAM_DEPTH * sizeof(uint32_t)) #define EGRESS_BUFFER_SIZE_BYTES (PARAM_SRAM_EGRESS_DEPTH * sizeof(uint32_t)) From 31d3905cb551c2ffa40a53db3dc5b45687b210e9 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 5 Nov 2025 10:09:51 +0000 Subject: [PATCH 03/17] [ot] hw/opentitan: ot_spi_device: remove unused struct Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index f378effc77d95..651a8d2e57332 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -454,12 +454,6 @@ typedef struct { bool should_sw_handle; } SpiDeviceTpm; -typedef struct { - uint32_t *buf; - uint32_t *ptr; - uint32_t *addr; -} SpiFifo; - typedef struct { OtSpiBusState state; unsigned byte_count; /* Count of SPI payload to receive */ From 7f42e0867e103d93a6b508f6906a63036068440d Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 5 Nov 2025 10:12:35 +0000 Subject: [PATCH 04/17] [ot] hw/opentitan: ot_spi_device: update FLASH_STATUS register The bit-fields previously used here are just advisory/convention, the whole 22-bit field is RW and software maintained. This is also expected behaviour for spi_passthru_test. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 651a8d2e57332..e603653ffaddb 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -92,23 +92,7 @@ REG32(LAST_READ_ADDR, 0x24u) REG32(FLASH_STATUS, 0x28u) FIELD(FLASH_STATUS, BUSY, 0u, 1u) FIELD(FLASH_STATUS, WEL, 1u, 1u) - FIELD(FLASH_STATUS, BP0, 2u, 1u) - FIELD(FLASH_STATUS, BP1, 3u, 1u) - FIELD(FLASH_STATUS, BP2, 4u, 1u) - FIELD(FLASH_STATUS, TB, 5u, 1u) /* beware actual bits depend on emulated dev. */ - FIELD(FLASH_STATUS, SEC, 6u, 1u) - FIELD(FLASH_STATUS, SRP0, 7u, 1u) - FIELD(FLASH_STATUS, SRP1, 8u, 1u) - FIELD(FLASH_STATUS, QE, 9u, 1u) - FIELD(FLASH_STATUS, LB1, 11u, 1u) - FIELD(FLASH_STATUS, LB2, 12u, 1u) - FIELD(FLASH_STATUS, LB3, 13u, 1u) - FIELD(FLASH_STATUS, CMP, 14u, 1u) - FIELD(FLASH_STATUS, SUS, 15u, 1u) - FIELD(FLASH_STATUS, WPS, 18u, 1u) - FIELD(FLASH_STATUS, DRV0, 21u, 1u) - FIELD(FLASH_STATUS, DRV1, 22u, 1u) - FIELD(FLASH_STATUS, HOLD_NRST, 23u, 1u) + FIELD(FLASH_STATUS, STATUS, 2u, 22u) REG32(JEDEC_CC, 0x2cu) FIELD(JEDEC_CC, CC, 0u, 8u) FIELD(JEDEC_CC, NUM_CC, 8u, 8u) @@ -618,19 +602,9 @@ static const char *TPM_REG_NAMES[TPM_REGS_COUNT] = { R_INTERCEPT_EN_SFDP_MASK | R_INTERCEPT_EN_MBX_MASK) #define FLASH_STATUS_RW0C_MASK \ (R_FLASH_STATUS_BUSY_MASK | R_FLASH_STATUS_WEL_MASK) -#define FLASH_STATUS_RW_MASK \ - (R_FLASH_STATUS_BP0_MASK | R_FLASH_STATUS_BP1_MASK | \ - R_FLASH_STATUS_BP2_MASK | R_FLASH_STATUS_TB_MASK | \ - R_FLASH_STATUS_SEC_MASK | R_FLASH_STATUS_SRP0_MASK | \ - R_FLASH_STATUS_SRP1_MASK | R_FLASH_STATUS_QE_MASK | \ - R_FLASH_STATUS_LB1_MASK | R_FLASH_STATUS_LB2_MASK | \ - R_FLASH_STATUS_LB3_MASK | R_FLASH_STATUS_CMP_MASK | \ - R_FLASH_STATUS_SUS_MASK | R_FLASH_STATUS_WPS_MASK | \ - R_FLASH_STATUS_DRV0_MASK | R_FLASH_STATUS_DRV1_MASK | \ - R_FLASH_STATUS_HOLD_NRST_MASK) -#define FLASH_STATUS_MASK (FLASH_STATUS_RW0C_MASK | FLASH_STATUS_RW_MASK) -#define JEDEC_CC_MASK (R_JEDEC_CC_CC_MASK | R_JEDEC_CC_NUM_CC_MASK) -#define JEDEC_ID_MASK (R_JEDEC_ID_DEVICE_MASK | R_JEDEC_ID_MF_MASK) +#define FLASH_STATUS_RW_MASK (R_FLASH_STATUS_STATUS_MASK) +#define JEDEC_CC_MASK (R_JEDEC_CC_CC_MASK | R_JEDEC_CC_NUM_CC_MASK) +#define JEDEC_ID_MASK (R_JEDEC_ID_DEVICE_MASK | R_JEDEC_ID_MF_MASK) #define COMMAND_OPCODE(_cmd_info_) \ ((uint8_t)((_cmd_info_) & CMD_INFO_OPCODE_MASK)) From 30da5a7998b69d66b7477f76d2f9d7173455dd05 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 5 Nov 2025 10:21:20 +0000 Subject: [PATCH 05/17] [ot] hw/opentitan: ot_spi_device: fix address parsing in read SFDP The bytes are received over the wire and stored in the buffer most-significant byte first (big-endian), so the dummy byte is the last byte not the first. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index e603653ffaddb..64043e45e1cc0 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1125,8 +1125,7 @@ static void ot_spi_device_flash_exec_read_sfdp(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - unsigned address = ldl_be_p(f->src); - address &= (1u << 24u) - 1u; /* discard dummy byte */ + unsigned address = ldl_be_p(f->src) >> 8u; /* discard dummy byte */ f->pos = address % SPI_SRAM_SFDP_SIZE; f->len = SPI_SRAM_SFDP_SIZE; f->src = &((uint8_t *)s->sram)[SPI_SRAM_SFDP_OFFSET]; From 4ca708edffe40c6dcf14c0526b41bc2c9c3ba779 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 5 Nov 2025 11:56:55 +0000 Subject: [PATCH 06/17] [ot] hw/opentitan: ot_spi_device: change end of JEDEC ID buffer to 0s Expected by spi_passthru_test, see: https://github.com/lowRISC/opentitan/blob/master/sw/host/tests/chip/spi_device/src/spi_passthru.rs#L70-L80 Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index 64043e45e1cc0..b167b325781aa 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1003,8 +1003,9 @@ static void ot_spi_device_flash_decode_read_jedec(OtSPIDeviceState *s) */ f->buffer[f->len++] = (uint8_t)(jedec_device >> 0u); f->buffer[f->len++] = (uint8_t)(jedec_device >> 8u); - memset(&f->buffer[f->len], (int)SPI_DEFAULT_TX_VALUE, - SPI_FLASH_BUFFER_SIZE - f->len); + /* after the end of JEDEC ID is 0s on OpenTitan */ + memset(&f->buffer[f->len], (int)0u, SPI_FLASH_BUFFER_SIZE - f->len); + f->len = SPI_FLASH_BUFFER_SIZE; f->src = f->buffer; FLASH_CHANGE_STATE(s, BUFFER); } From 552ce7514ef1abafd7fd01e41e256ab339d426c0 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 5 Nov 2025 16:48:30 +0000 Subject: [PATCH 07/17] [ot] hw/opentitan: ot_spi_device: DEFAULT_TX_VALUE -> DEFAULT_TX_RX_VALUE Name is more verbose but matches the usage better, as in `ot_spi_host` Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index b167b325781aa..b4653f487be29 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -305,8 +305,8 @@ REG32(TPM_READ_FIFO, 0x34u) #define INGRESS_BUFFER_SIZE_WORDS PARAM_SRAM_INGRESS_DEPTH #define FLASH_READ_BUFFER_SIZE (2u * SPI_SRAM_READ_SIZE) -#define SPI_DEFAULT_TX_VALUE ((uint8_t)0xffu) -#define SPI_FLASH_BUFFER_SIZE 256u +#define SPI_DEFAULT_TX_RX_VALUE ((uint8_t)0xffu) +#define SPI_FLASH_BUFFER_SIZE 256u typedef enum { READ_STATUS1, @@ -1177,7 +1177,7 @@ static uint8_t ot_spi_device_flash_exec_hw_cfg_command(OtSPIDeviceState *s) { SpiDeviceFlash *f = &s->flash; - uint8_t tx = SPI_DEFAULT_TX_VALUE; + uint8_t tx = SPI_DEFAULT_TX_RX_VALUE; unsigned cmdinfo = f->slot - (R_CMD_INFO_EN4B - R_CMD_INFO_0); switch (cmdinfo) { @@ -1215,7 +1215,7 @@ static uint8_t ot_spi_device_flash_read_buffer(OtSPIDeviceState *s) g_assert(f->src); - uint8_t tx = (f->pos < f->len) ? f->src[f->pos] : SPI_DEFAULT_TX_VALUE; + uint8_t tx = (f->pos < f->len) ? f->src[f->pos] : SPI_DEFAULT_TX_RX_VALUE; f->pos++; if (f->pos >= f->len) { @@ -1433,7 +1433,7 @@ static uint8_t ot_spi_device_flash_transfer(OtSPIDeviceState *s, uint8_t rx) (void)rx; - uint8_t tx = SPI_DEFAULT_TX_VALUE; + uint8_t tx = SPI_DEFAULT_TX_RX_VALUE; switch (f->state) { case SPI_FLASH_IDLE: @@ -2033,7 +2033,7 @@ static void ot_spi_device_chr_handle_header(OtSPIDeviceState *s) static void ot_spi_device_chr_send_discard(OtSPIDeviceState *s, unsigned count) { - const uint8_t buf[1u] = { SPI_DEFAULT_TX_VALUE }; + const uint8_t buf[1u] = { SPI_DEFAULT_TX_RX_VALUE }; while (count--) { if (qemu_chr_fe_backend_connected(&s->chr)) { From 26ac5d0938e95baf368ca61d0f6911390029b853 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 23 Oct 2025 14:46:17 +0100 Subject: [PATCH 08/17] [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 | 321 ++++++++++++++++------------------- hw/opentitan/trace-events | 5 +- 2 files changed, 146 insertions(+), 180 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index b4653f487be29..eb03c9b71e82b 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -309,43 +309,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"); @@ -373,13 +381,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 */ @@ -405,10 +406,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 */ @@ -608,7 +608,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 */ @@ -696,11 +695,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 (f->cmd_info & CMD_INFO_UPLOAD_MASK) != 0 && - (f->slot >= SPI_DEVICE_CMD_SW_FIRST) && - (f->slot <= SPI_DEVICE_CMD_SW_LAST); + return (slot >= SLOT_SW_CMD_FIRST) && (slot <= SLOT_SW_CMD_LAST); +} + +static bool ot_spi_device_flash_command_is_upload(const SpiDeviceFlash *f) +{ + 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) @@ -724,7 +727,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); @@ -827,23 +829,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; @@ -891,7 +876,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); @@ -924,56 +909,61 @@ 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; - } + g_assert(f->state == SPI_FLASH_IDLE); + + /* + * Find and match the opcode in the CMD_INFO registers. In case of + * multiple matching entries, the last one is used. + */ + bool matched = false; + 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; + matched = true; + const char *type = + ot_spi_device_is_sw_command(f->slot) ? "SW" : "HW"; + trace_ot_spi_device_flash_match(s->ot_id, type, cmd, f->slot); + } else { 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; + if (matched) { + 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; } - 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; - } + trace_ot_spi_device_flash_unmatched_command(s->ot_id, cmd); + return false; +} - bool set_busy = (bool)(f->cmd_info & CMD_INFO_BUSY_MASK); - if (set_busy) { +static void ot_spi_device_flash_try_upload(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + 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)); + if (fifo8_is_full(&f->cmd_fifo)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: cmd fifo overflow", + __func__, s->ot_id); + } else { + 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); } } @@ -1014,7 +1004,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; @@ -1028,7 +1018,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) { @@ -1043,7 +1033,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)); @@ -1073,14 +1063,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: @@ -1093,30 +1083,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(); } @@ -1152,20 +1150,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: @@ -1173,31 +1171,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_RX_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; @@ -1345,7 +1318,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 { /* @@ -1381,7 +1354,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 { /* @@ -1396,7 +1369,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 { /* @@ -1437,36 +1410,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: diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 17957c7d92710..7feedd2f615a8 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -549,8 +549,8 @@ ot_spi_device_flash_change_state(const char *id, int line, const char *prev, int 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_match(const char *id, const char *type, uint8_t cmd, unsigned slot) "%s: %s matched 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" @@ -559,6 +559,7 @@ ot_spi_device_flash_set_read_addr(const char *id, uint32_t addr) "%s: 0x%08x" 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" From e7326469297641c7023715d983a1563fcf100b83 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 13:26:38 +0000 Subject: [PATCH 09/17] [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 | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index eb03c9b71e82b..d127fe797435e 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1404,7 +1404,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_RX_VALUE; @@ -1456,6 +1456,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 7feedd2f615a8..1402da0e8d8ba 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -558,6 +558,7 @@ 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_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_transfer(const char *id, const char *dir, uint8_t byte) "%s: host %s 0x%02x" 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" From 95c38d166cda91aabfe9bb4e5f1fa1ebc65181d5 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 28 Oct 2025 12:16:25 +0000 Subject: [PATCH 10/17] [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 d127fe797435e..ecd4855dd901e 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -366,13 +366,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, @@ -817,6 +810,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) { @@ -1291,29 +1303,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 9071602ec40e333c89fa245a1de22f0d150284c3 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 30 Oct 2025 11:40:33 +0000 Subject: [PATCH 11/17] [ot] hw/opentitan: ot_spi_device: change logging Log the `ot_id`, and remove usages of `HWADDR_PRIx` Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_device.c | 62 +++++++++++++++++------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index ecd4855dd901e..4026b2cd454a5 100644 --- a/hw/opentitan/ot_spi_device.c +++ b/hw/opentitan/ot_spi_device.c @@ -1547,7 +1547,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; @@ -1555,20 +1556,20 @@ 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)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: W/O register 0x%02x (%s)\n", + __func__, s->ot_id, (uint32_t)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%x\n", __func__, + s->ot_id, (uint32_t)addr); val32 = 0; break; } @@ -1640,7 +1641,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; @@ -1728,13 +1730,12 @@ static void ot_spi_device_spi_regs_write(void *opaque, hwaddr addr, case R_UPLOAD_STATUS2: 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)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: R/O register 0x%02x (%s)\n", + __func__, s->ot_id, (uint32_t)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%x\n", __func__, + s->ot_id, (uint32_t)addr); break; } }; @@ -1768,19 +1769,18 @@ 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)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: W/O register 0x%02x (%s)\n", + __func__, s->ot_id, (uint32_t)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%x\n", __func__, + s->ot_id, (uint32_t)addr); val32 = 0u; break; } @@ -1832,13 +1832,12 @@ static void ot_spi_device_tpm_regs_write(void *opaque, hwaddr addr, break; 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)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: R/O register 0x%02x (%s)\n", + __func__, s->ot_id, (uint32_t)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%x\n", __func__, + s->ot_id, (uint32_t)addr); break; } }; @@ -1854,8 +1853,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%x\n", __func__, + s->ot_id, (uint32_t)addr); return MEMTX_DECODE_ERROR; } @@ -1875,9 +1874,8 @@ 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 - "-0x%" HWADDR_PRIx "\n", - __func__, addr, last); + "%s: %s: Invalid ingress buffer access to 0x%x-0x%x\n", + __func__, s->ot_id, (uint32_t)addr, (uint32_t)last); val32 = 0; } @@ -1909,8 +1907,8 @@ 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%x\n", __func__, + s->ot_id, (uint32_t)addr); return MEMTX_DECODE_ERROR; } s->sram[addr >> 2u] = val32; From 5bf30dd1cf792db0810e61d7a70d9250b4dca2bc Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Thu, 6 Nov 2025 11:16:09 +0000 Subject: [PATCH 12/17] [ot] hw/opentitan: ot_spi_host: change CS lines into IbexIRQs Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_spi_host.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index cfc84116d90fa..4b4992bcc18fe 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -335,7 +335,7 @@ struct OtSPIHostState { MemoryRegion mmio; - qemu_irq *cs_lines; /* CS output lines */ + IbexIRQ *cs_lines; /* CS output lines */ SSIBus *ssi; /* SPI bus */ uint32_t *regs; /* Registers (except. fifos) */ @@ -529,7 +529,7 @@ static void ot_spi_host_chip_select(OtSPIHostState *s, unsigned csid, { if (csid < s->num_cs) { trace_ot_spi_host_cs(s->ot_id, csid, activate ? "" : "de"); - qemu_set_irq(s->cs_lines[csid], !activate); + ibex_irq_set(&s->cs_lines[csid], !activate); } } @@ -1384,10 +1384,10 @@ static void ot_spi_host_realize(DeviceState *dev, Error **errp) g_assert(s->version < OT_SPI_HOST_VERSION_COUNT); - s->cs_lines = g_new0(qemu_irq, (size_t)s->num_cs); + s->cs_lines = g_new0(IbexIRQ, (size_t)s->num_cs); + + ibex_qdev_init_irqs(OBJECT(dev), &s->cs_lines[0], SSI_GPIO_CS, s->num_cs); - qdev_init_gpio_out_named(DEVICE(s), s->cs_lines, SSI_GPIO_CS, - (int)s->num_cs); qdev_init_gpio_in_named(DEVICE(s), &ot_spi_host_clock_input, "clock-in", 1); char busname[16u]; From b02a9153704b2cfe06b5da4c76c32463957cad22 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 13:59:12 +0000 Subject: [PATCH 13/17] [ot] hw/opentitan: ot_spi_host: SPI Host support for passthrough mode 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 | 164 ++++++++++++++++++++++++++--- hw/opentitan/trace-events | 1 + include/hw/opentitan/ot_spi_host.h | 25 +++++ 3 files changed, 177 insertions(+), 13 deletions(-) diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index 4b4992bcc18fe..5beac1ae03d27 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -35,11 +35,9 @@ #include "qemu/bswap.h" #include "qemu/fifo8.h" #include "qemu/log.h" -#include "qemu/main-loop.h" #include "qemu/module.h" #include "qemu/timer.h" #include "qapi/error.h" -#include "hw/irq.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_spi_host.h" @@ -257,6 +255,8 @@ typedef struct { } TraceCache; #endif /* DISCARD_REPEATED_STATUS_TRACES */ +#define SPI_DEFAULT_TX_RX_VALUE ((uint8_t)0xffu) + /* ------------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------------ */ @@ -324,12 +324,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 +349,19 @@ struct OtSPIHostState { OtSPIHostFsm fsm; bool on_reset; + + /* + * Upstream SPI Device Passthrough enable and chip select. If we support + * passthrough mode (only one CS), The SPI Host CS is muxed with the + * passthrough CS, controlled by the passthrough enable signal. + */ + bool passthrough_en; /* Upstream SPI Device Passthrough enable line */ + bool passthrough_cs; /* Upstream SPI Device Chip Select */ + bool host_cs0; /* Our Chip Select 0 */ + + /* Already emitted a guest error on first byte of invalid transfer */ + bool transfer_error_emitted; + unsigned pclk; /* Current input clock */ const char *clock_src_name; /* IRQ name once connected */ @@ -524,15 +531,41 @@ static bool ot_spi_host_is_ready(const OtSPIHostState *s) return !cmdfifo_is_full(s->cmd_fifo); } +/* Passthrough functionality is only implemented if there is one CS */ +static inline bool ot_spi_host_supports_passthrough(const OtSPIHostState *s) +{ + return s->num_cs == 1u; +} + +static void ot_spi_host_update_muxed_cs0(OtSPIHostState *s) +{ + if (ot_spi_host_supports_passthrough(s)) { + ibex_irq_set(&s->cs_lines[0], + s->passthrough_en ? s->passthrough_cs : s->host_cs0); + } else { + ibex_irq_set(&s->cs_lines[0], s->host_cs0); + } +} + static void ot_spi_host_chip_select(OtSPIHostState *s, unsigned csid, bool activate) { if (csid < s->num_cs) { trace_ot_spi_host_cs(s->ot_id, csid, activate ? "" : "de"); - ibex_irq_set(&s->cs_lines[csid], !activate); + if (csid == 0u) { + s->host_cs0 = !activate; + ot_spi_host_update_muxed_cs0(s); + } else { + ibex_irq_set(&s->cs_lines[csid], !activate); + } } } +static inline bool ot_spi_host_passthrough_active(const OtSPIHostState *s) +{ + return ot_spi_host_supports_passthrough(s) && s->passthrough_en; +} + static bool ot_spi_host_update_stall(OtSPIHostState *s) { g_assert(s->active.state != CMD_NONE); @@ -826,13 +859,33 @@ static void ot_spi_host_step_fsm(OtSPIHostState *s, const char *cause) if (!s->fsm.transaction) { s->fsm.transaction = true; + if (ot_spi_host_passthrough_active(s)) { + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: SPI Host active CS while Passthrough is active\n", + __func__, s->ot_id); + } 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_RX_VALUE; + + uint8_t rx = SPI_DEFAULT_TX_RX_VALUE; - uint8_t rx = s->fsm.output_en ? ssi_transfer(s->ssi, tx) : 0xffu; + if (s->fsm.output_en) { + if (ot_spi_host_passthrough_active(s)) { + if (!s->transfer_error_emitted) { + s->transfer_error_emitted = true; + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: Host transfer while Passthrough is active\n", + __func__, s->ot_id); + } + } else { + rx = (uint8_t)ssi_transfer(s->ssi, tx); + } + } if (multi && read && write) { /* invalid command, lets corrupt input data */ @@ -1293,6 +1346,78 @@ 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) +{ + if (!ot_spi_host_supports_passthrough(s)) { + if (!s->transfer_error_emitted) { + s->transfer_error_emitted = true; + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: SPI Host does not support passthrough: more " + "than one CS\n", + __func__, s->ot_id); + } + return SPI_DEFAULT_TX_RX_VALUE; + } + + if (!s->passthrough_en) { + trace_ot_spi_host_passthrough_disabled(s->ot_id); + return SPI_DEFAULT_TX_RX_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); + + s->transfer_error_emitted = false; + + if (!ot_spi_host_supports_passthrough(s)) { + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: SPI Host does not support passthrough: more than one CS\n", + __func__, s->ot_id); + return; + } + + if ((bool)level && !s->host_cs0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: Passthrough enabled while Host CS is active\n", + __func__, s->ot_id); + } + + s->passthrough_en = (bool)level; + + ot_spi_host_update_muxed_cs0(s); +} + +static void +ot_spi_host_device_passthrough_cs_input(void *opaque, int irq, int level) +{ + OtSPIHostState *s = opaque; + g_assert(irq == 0u); + + if (!ot_spi_host_supports_passthrough(s)) { + qemu_log_mask( + LOG_GUEST_ERROR, + "%s: %s: SPI Host does not support passthrough: more than one CS\n", + __func__, s->ot_id); + return; + } + + if (!s->passthrough_en) { + trace_ot_spi_host_passthrough_disabled(s->ot_id); + } + + s->passthrough_cs = (bool)level; + + ot_spi_host_update_muxed_cs0(s); +} + /* ------------------------------------------------------------------------ */ /* Device description/instanciation */ /* ------------------------------------------------------------------------ */ @@ -1347,6 +1472,12 @@ static void ot_spi_host_reset_enter(Object *obj, ResetType type) s->on_reset = true; + s->passthrough_en = false; + s->passthrough_cs = true; + s->host_cs0 = true; + + s->transfer_error_emitted = 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 +1544,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 +1570,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 1402da0e8d8ba..421944a62807a 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -592,6 +592,7 @@ 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_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 7df6e095dcd323868cfb379f9651aaf49ff03a63 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 14:05:45 +0000 Subject: [PATCH 14/17] [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 | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index d38dc28224af0..d5bd6b5dc068a 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -196,6 +196,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 4026b2cd454a5..fb377fe6f4424 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" @@ -458,12 +459,17 @@ struct OtSPIDeviceState { SpiDeviceFlash flash; SpiDeviceTpm tpm; + /* 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; /* Properties */ char *ot_id; + OtSPIHostState *spi_host; /* downstream SPI Host */ CharBackend chr; /* communication device */ guint watch_tag; /* tracker for comm device change */ }; @@ -2312,6 +2318,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("spi-host", OtSPIDeviceState, spi_host, TYPE_OT_SPI_HOST, + OtSPIHostState *), DEFINE_PROP_END_OF_LIST(), }; @@ -2374,6 +2382,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); } @@ -2431,6 +2442,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..ff4146058b91f 100644 --- a/include/hw/opentitan/ot_spi_device.h +++ b/include/hw/opentitan/ot_spi_device.h @@ -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 @@ -33,4 +35,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 293f1535bb5f6845407c809a3e22047409c208b1 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Mon, 3 Nov 2025 14:08:47 +0000 Subject: [PATCH 15/17] [ot] hw/riscv: ot_earlgrey,ot_darjeeling: connect SPI Device to Host IRQs Signed-off-by: Alice Ziuziakowska --- hw/riscv/ot_darjeeling.c | 10 +++++++++- hw/riscv/ot_earlgrey.c | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 523692f11a74a..35458c5eac23c 100644 --- a/hw/riscv/ot_darjeeling.c +++ b/hw/riscv/ot_darjeeling.c @@ -2,6 +2,7 @@ * QEMU RISC-V Board Compatible with OpenTitan Darjeeling platform * * Copyright (c) 2023-2025 Rivos, Inc. + * Copyright (c) 2025 lowRISC contributors. * * Author(s): * Emmanuel Blot @@ -1325,6 +1326,9 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { .memmap = MEMMAPENTRIES( { .base = 0x30310000u } ), + .link = IBEXDEVICELINKDEFS( + OT_DJ_SOC_DEVLINK("spi-host", SPI_HOST0) + ), .gpio = IBEXGPIOCONNDEFS( OT_DJ_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 42), OT_DJ_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 43), @@ -1334,7 +1338,11 @@ static const IbexDeviceDef ot_dj_soc_devices[] = { OT_DJ_SOC_GPIO_SYSBUS_IRQ(5, PLIC, 47), OT_DJ_SOC_GPIO_SYSBUS_IRQ(6, PLIC, 48), OT_DJ_SOC_GPIO_SYSBUS_IRQ(7, PLIC, 49), - OT_DJ_SOC_GPIO_ALERT(0, 2) + OT_DJ_SOC_GPIO_ALERT(0, 2), + OT_DJ_SOC_SIGNAL(OT_SPI_DEVICE_PASSTHROUGH_EN, 0, SPI_HOST0, + OT_SPI_HOST_PASSTHROUGH_EN, 0), + OT_DJ_SOC_SIGNAL(OT_SPI_DEVICE_PASSTHROUGH_CS, 0, SPI_HOST0, + OT_SPI_HOST_PASSTHROUGH_CS, 0) ), }, [OT_DJ_SOC_DEV_PWRMGR] = { diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 38d2a830e0c12..6418d22fe3685 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -553,6 +553,9 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { .memmap = MEMMAPENTRIES( { .base = 0x40050000u } ), + .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("spi-host", SPI_HOST0) + ), .gpio = IBEXGPIOCONNDEFS( OT_EG_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 69), OT_EG_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 70), @@ -562,7 +565,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 c9f9a964162890628cbf3cea61dadbdbfcad3146 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Wed, 29 Oct 2025 14:45:50 +0000 Subject: [PATCH 16/17] [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 | 484 ++++++++++++++++++++++++++++++++--- hw/opentitan/trace-events | 9 +- 2 files changed, 456 insertions(+), 37 deletions(-) diff --git a/hw/opentitan/ot_spi_device.c b/hw/opentitan/ot_spi_device.c index fb377fe6f4424..792c04fe09ad6 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 @@ -383,6 +385,9 @@ 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_DONE, /* No more clock expected for the current command */ SPI_FLASH_ERROR, /* On error */ } OtSpiFlashState; @@ -398,9 +403,18 @@ typedef enum { SPI_TPM_END, /* Finished the spi transaction.*/ } OtSpiTpmState; +typedef struct { + unsigned address_size; /* Length of address field for current command */ + bool cmd_addr_swap; /* Address byte swapping is enabled */ + bool cmd_dummy; /* Command has dummy field */ + bool cmd_payload_swap; /* Payload byte swapping is enabled */ + bool cmd_payload_dir_out; /* Payload is from flash to host */ +} OtSpiCommandParams; + typedef struct { OtSpiFlashState state; OtSpiDeviceCommandSlot slot; /* Command slot */ + OtSpiCommandParams cmd_params; /* Parameters for current command */ unsigned pos; /* Current position in data buffer */ unsigned len; /* Meaning depends on command and current state */ uint32_t address; /* Address tracking */ @@ -479,6 +493,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) @@ -626,6 +641,9 @@ 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_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), }; @@ -721,9 +739,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); @@ -859,51 +878,68 @@ static void ot_spi_device_release(OtSPIDeviceState *s) bus->failed_transaction = false; bool update_irq = false; - switch (ot_spi_device_get_mode(s)) { + + OtSpiDeviceMode mode = ot_spi_device_get_mode(s); + switch (mode) { case CTRL_MODE_FLASH: + case CTRL_MODE_PASSTHROUGH: + /* new uploaded command */ if (!fifo8_is_empty(&f->cmd_fifo) && f->new_cmd) { s->spi_regs[R_INTR_STATE] |= INTR_UPLOAD_CMDFIFO_NOT_EMPTY_MASK; update_irq = true; } - if (f->state == SPI_FLASH_UP_PAYLOAD) { - unsigned pos; - unsigned len; - if (f->pos) { - s->spi_regs[R_INTR_STATE] |= INTR_UPLOAD_PAYLOAD_NOT_EMPTY_MASK; - update_irq = true; - } - if (f->pos > f->len) { - pos = f->pos % SPI_SRAM_PAYLOAD_SIZE; - len = SPI_SRAM_PAYLOAD_SIZE; + /* uploaded payload */ + if (f->pos) { + s->spi_regs[R_INTR_STATE] |= INTR_UPLOAD_PAYLOAD_NOT_EMPTY_MASK; + update_irq = true; + } + /* update upload status register with payload information */ + if ((mode == CTRL_MODE_FLASH && f->state == SPI_FLASH_UP_PAYLOAD) || + (mode == CTRL_MODE_PASSTHROUGH && + f->state == SPI_FLASH_PASSTHROUGH_UP_PAYLOAD)) { + unsigned payload_start, payload_len; + if (f->pos > SPI_SRAM_PAYLOAD_SIZE) { + payload_start = f->pos % SPI_SRAM_PAYLOAD_SIZE; + payload_len = SPI_SRAM_PAYLOAD_SIZE; s->spi_regs[R_INTR_STATE] |= INTR_UPLOAD_PAYLOAD_OVERFLOW_MASK; update_irq = true; trace_ot_spi_device_flash_overflow(s->ot_id, "payload"); } else { - pos = 0; - len = f->pos; + payload_start = 0u; + payload_len = f->pos; } s->spi_regs[R_UPLOAD_STATUS2] = - FIELD_DP32(0, UPLOAD_STATUS2, PAYLOAD_START_IDX, pos); + FIELD_DP32(0, UPLOAD_STATUS2, PAYLOAD_START_IDX, payload_start); s->spi_regs[R_UPLOAD_STATUS2] = FIELD_DP32(s->spi_regs[R_UPLOAD_STATUS2], UPLOAD_STATUS2, - PAYLOAD_DEPTH, len); - trace_ot_spi_device_flash_payload(s->ot_id, f->pos, pos, len); + PAYLOAD_DEPTH, payload_len); + trace_ot_spi_device_flash_payload(s->ot_id, f->pos, payload_start, + payload_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." - */ + /* passthrough mode: release CS */ + if (mode == CTRL_MODE_PASSTHROUGH) { + ibex_irq_raise(&s->passthrough_cs); + } + FLASH_CHANGE_STATE(s, IDLE); + break; + 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 (mode) { + 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; @@ -1460,6 +1496,12 @@ 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) +{ + OtSPIHostClass *spihostc = OT_SPI_HOST_GET_CLASS(s->spi_host); + return spihostc->ssi_downstream_transfer(s->spi_host, rx); +} + static void ot_spi_device_flash_resume_read(void *opaque) { OtSPIDeviceState *s = opaque; @@ -1469,6 +1511,346 @@ static void ot_spi_device_flash_resume_read(void *opaque) qemu_chr_fe_accept_input(&s->chr); } +static bool +ot_spi_device_flash_command_is_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)); + uint8_t data = (uint8_t)(swap_data >> (byte_sel * 8u)); + 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]; + 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); + intercepted = true; + } + break; + case SLOT_HW_READ_JEDEC: + if (FIELD_EX32(intercept_val32, INTERCEPT_EN, JEDEC)) { + ot_spi_device_flash_decode_read_jedec(s); + intercepted = true; + } + break; + case SLOT_HW_READ_SFDP: + if (FIELD_EX32(intercept_val32, INTERCEPT_EN, SFDP)) { + ot_spi_device_flash_decode_read_sfdp(s); + intercepted = true; + } + break; + 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 these at every read after an address is given */ + break; + case SLOT_HW_EN4B: + case SLOT_HW_EX4B: + /* Always intercepted */ + ot_spi_device_flash_decode_addr4_enable(s); + intercepted = true; + break; + case SLOT_HW_WREN: + case SLOT_HW_WRDI: + /* Always intercepted */ + ot_spi_device_flash_decode_write_enable(s); + intercepted = true; + break; + default: + break; + } + + if (intercepted) { + trace_ot_spi_device_flash_intercepted_command(s->ot_id, f->slot); + } + + return intercepted; +} + +static void ot_spi_device_flash_passthrough_command_params(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + OtSpiCommandParams *p = &f->cmd_params; + + p->address_size = ot_spi_device_get_command_address_size(s); + p->cmd_addr_swap = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_ADDR_SWAP_EN); + p->cmd_payload_swap = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_PAYLOAD_SWAP_EN); + p->cmd_payload_dir_out = + (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_PAYLOAD_DIR); + + /* + * SPI transfers in QEMU are modelled at the granularity of bytes, + * therefore we can only support either 0 dummy cycles (dummy_en = 0), or + * 8 dummy cycles (dummy_en = 1, dummy_size = 7). Anything in between we + * round up to 1 dummy byte, so we only check dummy_en. + */ + p->cmd_dummy = (bool)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_DUMMY_EN); + + if (p->cmd_dummy && + ((uint8_t)SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_DUMMY_SIZE) != 7u)) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: slot %d: set non-zero dummy cycle count is " + "unsupported, using 8 cycles (1 byte)", + __func__, s->ot_id, f->slot); + } + + if (SHARED_FIELD_EX32(f->cmd_info, CMD_INFO_READ_PIPELINE_MODE) != 0u) { + qemu_log_mask(LOG_UNIMP, + "%s: %s: slot %d: 2-stage read pipeline is unsupported", + __func__, s->ot_id, f->slot); + } + + trace_ot_spi_device_flash_command_params(s->ot_id, p->address_size, + p->cmd_addr_swap, p->cmd_dummy, + p->cmd_payload_swap); +} + +static void +ot_spi_device_flash_passthrough_address_phase(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + OtSpiCommandParams *p = &f->cmd_params; + + f->len = 4u; + + f->buffer[f->pos] = rx; + if (p->cmd_addr_swap) { + unsigned byte_sel = (f->len - f->pos - 1u); + 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); + + f->pos++; + if (f->pos == f->len) { /* end of address phase */ + f->pos = 0u; + f->address = ldl_be_p(f->buffer); + + /* if upload command, push address FIFO */ + if (ot_spi_device_flash_command_is_upload(f)) { + if (!ot_fifo32_is_full(&f->address_fifo)) { + ot_fifo32_push(&f->address_fifo, f->address); + } else { + g_assert_not_reached(); + } + } + + if (p->cmd_dummy) { + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_DUMMY); + } else { + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); + } + } +} + +static void ot_spi_device_flash_passthrough_dummy_phase(OtSPIDeviceState *s) +{ + SpiDeviceFlash *f = &s->flash; + + OtSpiCommandParams *p = &f->cmd_params; + + g_assert(p->cmd_dummy); + + (void)ot_spi_device_flash_spi_transfer(s, SPI_DEFAULT_TX_RX_VALUE); + + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); +} + +static uint8_t +ot_spi_device_flash_passthrough_payload_phase(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + OtSpiCommandParams *p = &f->cmd_params; + + if (p->cmd_payload_dir_out) { /* flash -> SPI Host */ + + /* try intercept read to mailbox region */ + 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 (p->address_size != 0u) { + if (ot_spi_device_is_mailbox_match(s, f->address)) { + trace_ot_spi_device_flash_intercept_mailbox(s->ot_id, + f->address); + unsigned word_idx = + (f->address >> 2u) & (SPI_SRAM_MBX_SIZE - 1u); + unsigned byte_idx = f->address % 4u; + uint32_t word = + s->sram[(SPI_SRAM_MBX_OFFSET >> 2u) + word_idx]; + uint8_t tx = (uint8_t)(word >> (8u * byte_idx)); + + 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 out path: read from flash, update last read address */ + f->last_read_addr = f->address; + f->address += 1u; + return ot_spi_device_flash_spi_transfer(s, SPI_DEFAULT_TX_RX_VALUE); + + } /* SPI Host -> flash */ + + /* if this command is to be uploaded, upload payload */ + if (ot_spi_device_flash_command_is_upload(f)) { + f->payload[f->pos % SPI_SRAM_PAYLOAD_SIZE] = rx; + } + + /* if payload swap is enabled, swap the first 4 bytes of payload */ + if (f->pos < 4u && p->cmd_payload_swap) { + 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; + } + + f->pos++; + + (void)ot_spi_device_flash_spi_transfer(s, rx); + + return SPI_DEFAULT_TX_RX_VALUE; +} + + +static uint8_t +ot_spi_device_flash_transfer_passthrough(OtSPIDeviceState *s, uint8_t rx) +{ + SpiDeviceFlash *f = &s->flash; + + OtSpiCommandParams *p = &f->cmd_params; + + trace_ot_spi_device_flash_transfer(s->ot_id, "->", rx); + + uint8_t tx = SPI_DEFAULT_TX_RX_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. + */ + memset(&f->cmd_params, 0u, sizeof(OtSpiCommandParams)); + + 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_command_is_filter(s, rx)) { + /* command opcode is filtered, do not send to downstream */ + trace_ot_spi_device_flash_filtered_command(s->ot_id, rx); + ibex_irq_raise(&s->passthrough_cs); + } else { + /* 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 (p->address_size != 0u) { + /* command has address field */ + f->pos = 4u - p->address_size; + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_ADDR); + break; + } + if (p->cmd_dummy) { + /* no address field, but has dummy */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_DUMMY); + break; + } + /* no address or dummy field, rest is payload */ + FLASH_CHANGE_STATE(s, PASSTHROUGH_UP_PAYLOAD); + break; + case SPI_FLASH_PASSTHROUGH_UP_ADDR: + ot_spi_device_flash_passthrough_address_phase(s, rx); + break; + case SPI_FLASH_PASSTHROUGH_UP_DUMMY: + ot_spi_device_flash_passthrough_dummy_phase(s); + break; + case SPI_FLASH_PASSTHROUGH_UP_PAYLOAD: + tx = ot_spi_device_flash_passthrough_payload_phase(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_SFDP); + 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) { @@ -1639,16 +2021,32 @@ static void ot_spi_device_spi_regs_write(void *opaque, hwaddr addr, if ((val32 & R_CONTROL_MODE_MASK) != (s->spi_regs[reg] & R_CONTROL_MODE_MASK)) { ot_spi_device_clear_modes(s); + if (s->bus.state == SPI_BUS_FLASH) { + /* + * Hardware assumes that control mode does not change during a + * transaction, so the behaviour is undefined if that happens. + * We choose to cancel any ongoing SPI transfer until the next + * CS. + */ + qemu_log_mask(LOG_TRACE, + "%s: %s: Flash mode changed during transfer, " + "discarding rest of transfer\n", + __func__, s->ot_id); + BUS_CHANGE_STATE(s, DISCARD); + } } 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; @@ -1985,10 +2383,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; @@ -2024,7 +2423,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); } @@ -2362,8 +2772,8 @@ static void ot_spi_device_reset_enter(Object *obj, ResetType type) ot_spi_device_clear_modes(s); - memset(s->spi_regs, 0, SPI_REGS_SIZE); - memset(s->tpm_regs, 0, TPM_REGS_SIZE); + memset(s->spi_regs, 0u, SPI_REGS_SIZE); + memset(s->tpm_regs, 0u, TPM_REGS_SIZE); fifo8_reset(&bus->chr_fifo); /* not sure if the following FIFOs should be reset on clear_modes instead */ @@ -2376,10 +2786,12 @@ static void ot_spi_device_reset_enter(Object *obj, ResetType type) s->spi_regs[R_CONTROL] = 0x10u; s->spi_regs[R_STATUS] = 0x60u; s->spi_regs[R_JEDEC_CC] = 0x7fu; - for (unsigned ix = 0; ix < PARAM_NUM_CMD_INFO; ix++) { + for (unsigned ix = 0u; ix < PARAM_NUM_CMD_INFO; ix++) { s->spi_regs[R_CMD_INFO_0 + ix] = 0x7000u; } + memset(&f->cmd_params, 0u, sizeof(OtSpiCommandParams)); + s->tpm_regs[R_TPM_CAP] = 0x660100u; ibex_irq_lower(&s->passthrough_en); @@ -2396,6 +2808,8 @@ static void ot_spi_device_realize(DeviceState *dev, Error **errp) g_assert(s->ot_id); + g_assert(s->spi_host); + qemu_chr_fe_set_handlers(&s->chr, &ot_spi_device_chr_can_receive, &ot_spi_device_chr_receive, &ot_spi_device_chr_event_hander, diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 421944a62807a..0a9fdb940a248 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -547,17 +547,22 @@ ot_spi_device_chr_error(const char *id, const char *err) "%s: %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_command_params(const char *id, unsigned addr_size, bool addr_swap_en, bool dummy_en, bool payload_swap_en) "%s: addrsize=%u addrswap=%u dummy=%u payloadswap=%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_match(const char *id, const char *type, uint8_t cmd, unsigned slot) "%s: %s matched 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_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_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_transfer(const char *id, const char *dir, uint8_t byte) "%s: host %s 0x%02x" 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" From 5059dc50221f72fca1d519ab400d26b5def154af Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Fri, 7 Nov 2025 11:40:35 +0000 Subject: [PATCH 17/17] [ot] docs/opentitan: ot_spi_device: Update SPI Device documentation Signed-off-by: Alice Ziuziakowska --- docs/opentitan/ot_darjeeling.md | 13 +++++-------- docs/opentitan/ot_earlgrey.md | 17 +++++++---------- docs/opentitan/ot_spi_device.md | 16 +++++++--------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/docs/opentitan/ot_darjeeling.md b/docs/opentitan/ot_darjeeling.md index ff0b74c2e56d5..783ce9cdc229e 100644 --- a/docs/opentitan/ot_darjeeling.md +++ b/docs/opentitan/ot_darjeeling.md @@ -34,6 +34,7 @@ Please check out `hw/opentitan/ot_ref.log` * SPI data flash (from QEMU upstream w/ fixes) * [SPI Host controller](ot_spi_host.md) * HW bus config is ignored (SPI mode, speed, ...) +* [SPI Device](ot_spi_device.md) * Timer * [UART](ot_uart.md) * missing RX timeout, TX break not supported @@ -67,10 +68,6 @@ Devices in this group implement subset(s) of the real HW. * [LC controller](lc_ctrl_dmi.md) can be accessed through JTAG using a DM-TL bridge * Escalation is not supported * [ROM controller](ot_rom_ctrl.md) -* [SPI device](ot_spi_device.md) - * Flash mode supported - * TPM mode supported, but shares a CS with flash/passthrough mode and so cannot be used together - * Passthrough mode not supported * SRAM controller * Initialization and scrambling from OTP key supported * Wait for init completion (bus stall) emulated @@ -136,10 +133,10 @@ generate the `.raw` image files. Darjeeling emulation supports the following buses: -| **Type** | **Num** | **Usage** | -| -------- | ------- | ----------------------------------| -| `mtd` | 0 | [SPI host](ot_spi_host.md) | -| `pflash` | 0 | [OTP](ot_otp.md) | +| **Type** | **Num** | **Usage** | +| -------- | ------- | -----------------------------------------------------------| +| `mtd` | 0 | [SPI host](ot_spi_host.md), [SPI device](ot_spi_device.md) | +| `pflash` | 0 | [OTP](ot_otp.md) | ## Tools diff --git a/docs/opentitan/ot_earlgrey.md b/docs/opentitan/ot_earlgrey.md index 63337e9539b45..04e2ee88ce069 100644 --- a/docs/opentitan/ot_earlgrey.md +++ b/docs/opentitan/ot_earlgrey.md @@ -32,6 +32,7 @@ * SPI data flash (from QEMU upstream w/ fixes) * [SPI Host controller](ot_spi_host.md) * HW bus config is ignored (SPI mode, speed, ...) +* [SPI Device](ot_spi_device.md) * Timer * [UART](ot_uart.md) * missing RX timeout, TX break not supported @@ -59,10 +60,6 @@ Devices in this group implement subset(s) of the real HW. * Masking is not supported * Lifecycle controller * [ROM controller](ot_rom_ctrl.md) -* [SPI device](ot_spi_device.md) - * Flash mode supported - * TPM mode supported, but shares a CS with flash/passthrough mode and so cannot be used together - * Passthrough mode not supported * SRAM controller * Initialization and scrambling from OTP key supported * Wait for init completion (bus stall) emulated @@ -127,12 +124,12 @@ generate the `.raw` image files. EarlGrey emulation supports the following buses: -| **Type** | **Num** | **Usage** | -| -------- | ------- | ----------------------------------| -| `mtd` | 0 | [SPI host 0](ot_spi_host.md) | -| `mtd` | 1 | [SPI host 1](ot_spi_host.md) | -| `mtd` | 2 | [Embedded Flash](ot_flash.md) | -| `pflash` | 0 | [OTP](ot_otp.md) | +| **Type** | **Num** | **Usage** | +| -------- | ------- | -------------------------------------------------------------| +| `mtd` | 0 | [SPI host 0](ot_spi_host.md), [SPI device](ot_spi_device.md) | +| `mtd` | 1 | [SPI host 1](ot_spi_host.md) | +| `mtd` | 2 | [Embedded Flash](ot_flash.md) | +| `pflash` | 0 | [OTP](ot_otp.md) | ## Tools diff --git a/docs/opentitan/ot_spi_device.md b/docs/opentitan/ot_spi_device.md index b7232850df3a5..ceafe9e370027 100644 --- a/docs/opentitan/ot_spi_device.md +++ b/docs/opentitan/ot_spi_device.md @@ -2,24 +2,22 @@ ## Supported modes -### FW/Generic mode - -This mode is only partially supported, and is being deprecated in real HW, so no further work is -expected for this mode. - ### Flash mode -This mode is fully supported (to the extend of the understanding of the HW...). +This mode is fully supported (to the extent of the understanding of the HW...). ### Passthrough mode -This mode is not yet supported. +This mode is supported, with minor deficiencies: The two-stage read pipeline, and dummy cycle +counts of 1 to 7 are not supported as SPI transfers are modelled with byte-granularity (see the +[CharDev protocol](#spi-device-chardev-protocol)). For commands with 1 to 7 dummy cycles specified, +the maximum 8 dummy cycles (1 dummy byte) will be used. ### TPM This mode is partially supported, TPM commands handled by hw are not supported yet. The CharDev protocol doesn't support a distinct chip select for TPM, therefore it is sharing the same CS with -the other modes. If CS is asserted and TPM is enabled, then it will have priority. +the other modes. If CS is asserted and TPM is enabled, the TPM will have priority. ## Connection with a SPI Host @@ -61,7 +59,7 @@ Commands: IT is also possible to use [`spidevflash.py`](spidevflash.md) tool to upload a binary using the same protocol. -### SPI device CharDev protocol +### SPI Device CharDev protocol SPI clock is not emulated, but each byte exchanged over the communication channel represent 8-bit SPI data. Dual and Quad lines are not emulated, all communications happen over a regular byte