diff --git a/docs/opentitan/ot_darjeeling.md b/docs/opentitan/ot_darjeeling.md index ff0b74c2e56d..783ce9cdc229 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 63337e9539b4..04e2ee88ce06 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 b7232850df3a..ceafe9e37002 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 diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index d38dc28224af..d5bd6b5dc068 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 28e5efe12259..792c04fe09ad 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 @@ -36,6 +38,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" @@ -92,23 +95,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) @@ -151,10 +138,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 +242,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 +271,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 +285,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)) @@ -313,47 +308,55 @@ 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, - 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"); @@ -366,13 +369,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, @@ -381,13 +377,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 */ @@ -396,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; @@ -411,12 +403,20 @@ 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; - OtSpiFlashCommand type; + 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 */ - 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 */ @@ -446,12 +446,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 */ @@ -479,12 +473,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; /* SRAM (DPRAM on EG, E/I on DJ) */ + 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 */ }; @@ -494,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) @@ -616,23 +616,12 @@ 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)) -#define FLASH_SLOT(_name_) ((R_CMD_INFO_##_name_) - R_CMD_INFO_0) #define STATE_NAME_ENTRY(_st_) [_st_] = stringify(_st_) /* clang-format off */ @@ -652,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), }; @@ -720,11 +712,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) @@ -743,12 +739,12 @@ 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; - 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); @@ -839,6 +835,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) { @@ -851,23 +866,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; @@ -880,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." - */ - if (ot_spi_device_is_hw_read_command(s) && + /* 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; @@ -948,56 +963,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); } } @@ -1027,8 +1047,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); } @@ -1037,7 +1058,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; @@ -1051,7 +1072,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) { @@ -1066,7 +1087,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)); @@ -1096,14 +1117,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: @@ -1116,30 +1137,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(); } @@ -1149,8 +1178,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]; @@ -1176,20 +1204,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: @@ -1197,31 +1225,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; @@ -1239,7 +1242,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) { @@ -1342,34 +1345,15 @@ 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; 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 { /* @@ -1405,7 +1389,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 { /* @@ -1420,7 +1404,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 { /* @@ -1455,42 +1439,34 @@ 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; + uint8_t tx = SPI_DEFAULT_TX_RX_VALUE; 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: @@ -1515,9 +1491,17 @@ 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; } +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; @@ -1527,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) { @@ -1611,7 +1935,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; @@ -1619,20 +1944,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; } @@ -1696,15 +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: unsupported mode\n", __func__); + ibex_irq_raise(&s->passthrough_en); break; } break; @@ -1792,13 +2134,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; } }; @@ -1832,19 +2173,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; } @@ -1896,13 +2236,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; } }; @@ -1918,8 +2257,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; } @@ -1939,9 +2278,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; } @@ -1973,8 +2311,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; @@ -2045,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; @@ -2057,7 +2396,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)) { @@ -2084,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); } @@ -2378,6 +2728,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(), }; @@ -2420,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 */ @@ -2434,12 +2786,17 @@ 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); + ibex_irq_raise(&s->passthrough_cs); + ot_spi_device_update_irqs(s); ot_spi_device_update_alerts(s); } @@ -2451,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, @@ -2497,6 +2856,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/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index cfc84116d90f..5beac1ae03d2 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,18 +324,12 @@ 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; 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) */ @@ -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"); - qemu_set_irq(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 = s->fsm.output_en ? ssi_transfer(s->ssi, tx) : 0xffu; + uint8_t rx = SPI_DEFAULT_TX_RX_VALUE; + + 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); @@ -1384,10 +1515,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]; @@ -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 17957c7d9271..0a9fdb940a24 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -547,18 +547,25 @@ 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_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_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" 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" @@ -590,6 +597,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/hw/riscv/ot_darjeeling.c b/hw/riscv/ot_darjeeling.c index 523692f11a74..35458c5eac23 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 38d2a830e0c1..6418d22fe368 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] = { diff --git a/include/hw/opentitan/ot_spi_device.h b/include/hw/opentitan/ot_spi_device.h index 9f5b40a713b4..ff4146058b91 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 */ diff --git a/include/hw/opentitan/ot_spi_host.h b/include/hw/opentitan/ot_spi_host.h index ced150fa244a..22088b66f1ea 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,