diff --git a/docs/opentitan/earlgrey.md b/docs/opentitan/earlgrey.md index f919919efc3c0..8c4f73c45cdf8 100644 --- a/docs/opentitan/earlgrey.md +++ b/docs/opentitan/earlgrey.md @@ -96,7 +96,7 @@ See the section "Useful execution options" for documentation about the `no_epmp_ qemu-system-riscv32 -M ot-earlgrey -display none -serial mon:stdio \ -object ot-rom_img,id=rom,file=rom_with_fake_keys_fpga_cw310.elf \ -drive if=pflash,file=otp-rma.raw,format=raw \ - -drive if=mtd,bus=1,file=flash.raw,format=raw + -drive if=mtd,bus=2,file=flash.raw,format=raw ```` where `otp-rma.raw` contains the RMA OTP image and `flash.raw` contains the signed binary file of the @@ -145,14 +145,14 @@ See [`tools.md`](tools.md) * `-display none` can be used to prevent QEMU to open a semi-graphical windows as the default console, and use the current shell instead. -### Flash +### Embedded Flash -* `-drive if=mtd,bus=1,file=,format=raw` should be used to specify a path to a QEMU RAW - image file used as the OpenTitan internal flash controller image. This _RAW_ file should have - been generated with the [`flashgen.py`](flashgen.md) tool. +* `-drive if=mtd,id=eflash,bus=2,file=,format=raw` should be used to specify a path to a + QEMU RAW image file used as the OpenTitan internal flash controller image. This _RAW_ file should + have been generated with the [`flashgen.py`](flashgen.md) tool. - Note: for now, bus 1 is assigned to the internal controller with the embedded flash storage. See - also SPI Host section. + Note: MTD bus 2 is assigned to the internal controller with the embedded flash storage. See also + the SPI Host section. ### OTBN @@ -168,17 +168,21 @@ See [`tools.md`](tools.md) ### SPI Host +* `-global ot-earlgrey-board.spiflash=` should be used to instanciate a SPI + dataflash device of the specified type to the first device (/CS0) of the specified bus. + Any SPI dataflash device supported by QEMU can be used. To list the supported devices, use + `grep -F 'INFO("' hw/block/m25p80.c | cut -d'"' -f2` + * `-drive if=mtd,bus=0,file=,format=raw` should be used to specify a path to a QEMU RAW - image file used as the ISSP IS25WP128 SPI data flash backend file. This _RAW_ file should have - been created with the qemu-img tool. There is no dedicated tool to populate this image file for - now. + image file used as the SPI data flash backend file. This _RAW_ file should have been created with + the qemu-img tool. There is no dedicated tool to populate this image file for now. ````sh qemu-img create -f raw spi.raw 16M ```` - For now, bus 0 is assigned to the SPI Host controller with an external flash storage. See also - Flash controller section. + MTD bus 0 is assigned to the SPI0 Host controller and MTD bus 1 is assigned to the SPI1 Host + controller. See also Embedded Flash controller section. ### UART diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index d5e2238b74a21..5a03689fb881e 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -358,6 +358,8 @@ static const FlashPartInfo known_devices[] = { .sfdp_read = m25p80_sfdp_w25q512jv }, { INFO("w25q01jvq", 0xef4021, 0, 64 << 10, 2048, ER_4K), .sfdp_read = m25p80_sfdp_w25q01jvq }, + { INFO("w25q512nw", 0xef6020, 0, 64 << 10, 1024, ER_4K | ER_32K), + .sfdp_read = m25p80_sfdp_w25q512nw }, /* Microchip */ { INFO("25csm04", 0x29cc00, 0x100, 64 << 10, 8, 0) }, @@ -397,6 +399,7 @@ typedef enum { DPP = 0xa2, QPP = 0x32, QPP_4 = 0x34, + PP_4 = 0x38, RDID_90 = 0x90, RDID_AB = 0xab, AAI_WP = 0xad, @@ -751,6 +754,7 @@ static void complete_collecting_data(Flash *s) case PP: case PP4: case PP4_4: + case PP_4: s->state = STATE_PAGE_PROGRAM; break; case AAI_WP: @@ -1150,6 +1154,7 @@ static void decode_new_cmd(Flash *s, uint32_t value) case ERASE4_SECTOR: case PP: case PP4: + case PP_4: case DIE_ERASE: case RDID_90: case RDID_AB: diff --git a/hw/block/m25p80_sfdp.c b/hw/block/m25p80_sfdp.c index 922096d190b18..d2f274d3d29a1 100644 --- a/hw/block/m25p80_sfdp.c +++ b/hw/block/m25p80_sfdp.c @@ -367,6 +367,42 @@ static const uint8_t sfdp_w25q01jvq[] = { }; define_sfdp_read(w25q01jvq); +static const uint8_t sfdp_w25q512nw[] = { + 0x53, 0x46, 0x44, 0x50, 0x06, 0x01, 0x01, 0xff, + 0x00, 0x06, 0x01, 0x10, 0x80, 0x00, 0x00, 0xff, + 0x84, 0x00, 0x01, 0x02, 0xd0, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe5, 0x20, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0x44, 0xeb, 0x08, 0x6b, 0x08, 0x3b, 0x42, 0xbb, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0x40, 0xeb, 0x0c, 0x20, 0x0f, 0x52, + 0x10, 0xd8, 0x00, 0x00, 0x33, 0x02, 0xa6, 0x00, + 0x81, 0xe7, 0x14, 0xd9, 0xe9, 0x63, 0x76, 0x33, + 0x7a, 0x75, 0x7a, 0x75, 0xf7, 0xbd, 0xd5, 0x5c, + 0x19, 0xf7, 0x5d, 0xff, 0xe9, 0x70, 0xf9, 0xa5, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0a, 0xf0, 0xff, 0x21, 0xff, 0xdc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; +define_sfdp_read(w25q512nw); + /* * Integrated Silicon Solution (ISSI) */ diff --git a/hw/block/m25p80_sfdp.h b/hw/block/m25p80_sfdp.h index b82af94bedc56..d747b791baa91 100644 --- a/hw/block/m25p80_sfdp.h +++ b/hw/block/m25p80_sfdp.h @@ -26,6 +26,7 @@ uint8_t m25p80_sfdp_w25q256(uint32_t addr); uint8_t m25p80_sfdp_w25q512jv(uint32_t addr); uint8_t m25p80_sfdp_w25q01jvq(uint32_t addr); +uint8_t m25p80_sfdp_w25q512nw(uint32_t addr); uint8_t m25p80_sfdp_is25wp128(uint32_t addr); uint8_t m25p80_sfdp_is25wp256(uint32_t addr); diff --git a/hw/opentitan/ot_spi_host.c b/hw/opentitan/ot_spi_host.c index 0d20bd33e3da0..1208ce279b1e6 100644 --- a/hw/opentitan/ot_spi_host.c +++ b/hw/opentitan/ot_spi_host.c @@ -3,6 +3,7 @@ * * Copyright (C) 2022 Western Digital * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2025 lowRISC contributors. * * Author(s): * Wilfred Mallawa @@ -28,10 +29,6 @@ * * Known limitations: * - BigEndian devices are not supported - * - TX FIFO TXWD/TXEMPTY behavior documented in - * https://github.com/lowRISC/opentitan/issues/17644 is not emulated: there - * is no special case for first write: TXEMPTY is reset whenever one packet - * has been pushed and not yet sent over the SPI bus. */ #include "qemu/osdep.h" @@ -58,10 +55,10 @@ /* ------------------------------------------------------------------------ */ /* undef to get all the repeated, identical status query traces */ -#define DISCARD_REPEATED_STATUS_TRACES +#undef DISCARD_REPEATED_STATUS_TRACES /* fake delayed completion of HW commands */ -#define FSM_TRIGGER_DELAY_NS 100U /* nanoseconds */ +#define FSM_COMPLETION_DELAY_NS 100U /* nanoseconds */ #define TXFIFO_LEN 288U /* bytes */ #define RXFIFO_LEN 256U /* bytes */ @@ -101,20 +98,20 @@ REG32(STATUS, 0x14u) FIELD(STATUS, ACTIVE, 30u, 1u) FIELD(STATUS, READY, 31u, 1u) REG32(CONFIGOPTS, 0x18u) - FIELD(CONFIGOPTS, CLKDIV_0, 0u, 16u) - FIELD(CONFIGOPTS, CSNIDLE_0, 16u, 4u) - FIELD(CONFIGOPTS, CSNTRAIL_0, 20u, 4u) - FIELD(CONFIGOPTS, CSNLEAD_0, 24u, 4u) - FIELD(CONFIGOPTS, FULLCYC_0, 29u, 1u) - FIELD(CONFIGOPTS, CPHA_0, 30u, 1u) - FIELD(CONFIGOPTS, CPOL_0, 31u, 1u) + FIELD(CONFIGOPTS, CLKDIV, 0u, 16u) + FIELD(CONFIGOPTS, CSNIDLE, 16u, 4u) + FIELD(CONFIGOPTS, CSNTRAIL, 20u, 4u) + FIELD(CONFIGOPTS, CSNLEAD, 24u, 4u) + FIELD(CONFIGOPTS, FULLCYC, 29u, 1u) + FIELD(CONFIGOPTS, CPHA, 30u, 1u) + FIELD(CONFIGOPTS, CPOL, 31u, 1u) REG32(CSID, 0x1cu) FIELD(CSID, CSID, 0u, 32u) REG32(COMMAND, 0x20u) - FIELD(COMMAND, LEN, 0u, 9u) - FIELD(COMMAND, CSAAT, 9u, 1u) - FIELD(COMMAND, SPEED, 10u, 2u) - FIELD(COMMAND, DIRECTION, 12u, 2u) + FIELD(COMMAND, CSAAT, 0u, 1u) + FIELD(COMMAND, SPEED, 1u, 2u) + FIELD(COMMAND, DIRECTION, 3u, 2u) + FIELD(COMMAND, LEN, 5u, 20u) REG32(RXDATA, 0x24u) REG32(TXDATA, 0x28u) REG32(ERROR_ENABLE, 0x2cu) @@ -170,13 +167,13 @@ REG32(EVENT_ENABLE, 0x34u) R_ERROR_STATUS_ACCESSINVAL_MASK) #define R_CONFIGOPTS_MASK \ - (R_CONFIGOPTS_CLKDIV_0_MASK | \ - R_CONFIGOPTS_CSNIDLE_0_MASK | \ - R_CONFIGOPTS_CSNTRAIL_0_MASK | \ - R_CONFIGOPTS_CSNLEAD_0_MASK | \ - R_CONFIGOPTS_FULLCYC_0_MASK | \ - R_CONFIGOPTS_CPHA_0_MASK | \ - R_CONFIGOPTS_CPOL_0_MASK) + (R_CONFIGOPTS_CLKDIV_MASK | \ + R_CONFIGOPTS_CSNIDLE_MASK | \ + R_CONFIGOPTS_CSNTRAIL_MASK | \ + R_CONFIGOPTS_CSNLEAD_MASK | \ + R_CONFIGOPTS_FULLCYC_MASK | \ + R_CONFIGOPTS_CPHA_MASK | \ + R_CONFIGOPTS_CPOL_MASK) #define R_EVENT_ENABLE_MASK \ (R_EVENT_ENABLE_RXFULL_MASK | \ @@ -249,17 +246,21 @@ static void ot_spi_host_trace_status(const char *ot_id, const char *msg, unsigned rxd = FIELD_EX32(status, STATUS, RXQD); unsigned txd = FIELD_EX32(status, STATUS, TXQD); char str[64u]; - (void)snprintf(str, sizeof(str), "%s%s%s%s%s%s%s%s%s%s", - FIELD_EX32(status, STATUS, RXWM) ? "RXM|" : "", - FIELD_EX32(status, STATUS, RXSTALL) ? "RXS|" : "", - FIELD_EX32(status, STATUS, RXEMPTY) ? "RXE|" : "", - FIELD_EX32(status, STATUS, RXFULL) ? "RXF|" : "", - FIELD_EX32(status, STATUS, TXWM) ? "TXM|" : "", - FIELD_EX32(status, STATUS, TXSTALL) ? "TXS|" : "", - FIELD_EX32(status, STATUS, TXEMPTY) ? "TXE|" : "", - FIELD_EX32(status, STATUS, TXFULL) ? "TXF|" : "", - FIELD_EX32(status, STATUS, ACTIVE) ? "ACT|" : "", - FIELD_EX32(status, STATUS, READY) ? "RDY|" : ""); + int last = + snprintf(str, sizeof(str), "%s%s%s%s%s%s%s%s%s%s", + FIELD_EX32(status, STATUS, RXWM) ? "RXM|" : "", + FIELD_EX32(status, STATUS, RXSTALL) ? "RXS|" : "", + FIELD_EX32(status, STATUS, RXEMPTY) ? "RXE|" : "", + FIELD_EX32(status, STATUS, RXFULL) ? "RXF|" : "", + FIELD_EX32(status, STATUS, TXWM) ? "TXM|" : "", + FIELD_EX32(status, STATUS, TXSTALL) ? "TXS|" : "", + FIELD_EX32(status, STATUS, TXEMPTY) ? "TXE|" : "", + FIELD_EX32(status, STATUS, TXFULL) ? "TXF|" : "", + FIELD_EX32(status, STATUS, ACTIVE) ? "ACT|" : "", + FIELD_EX32(status, STATUS, READY) ? "RDY|" : ""); + if (str[last - 1] == '|') { + str[last - 1] = '\0'; + } trace_ot_spi_host_status(ot_id, msg, status, str, cmd, rxd, txd); } @@ -323,17 +324,17 @@ struct OtSPIHostState { IbexIRQ irqs[2u]; /**< System bus IRQs */ IbexIRQ alert; /**< OpenTitan alert */ - uint32_t events; /**< Active events */ - uint32_t last_events; /**< Last detected events */ + uint64_t total_transfer; /**< Transfered bytes since reset */ + uint16_t last_command_id; /**< Command tracker (for debug purpose) */ OtSPIHostFsm fsm; bool on_reset; /* properties */ char *ot_id; + uint32_t completion_delay_ns; /** completion delay/pacing */ uint32_t bus_num; /**< SPI host port number */ uint8_t num_cs; /**< Supported CS line count */ - bool initbug; /**< Whether to ignore first TX request */ }; /* ------------------------------------------------------------------------ */ @@ -364,20 +365,23 @@ struct TxFifo { uint32_t num; }; +enum CmdState { + CMD_SCHEDULED, /* command scheduled for execution, not yet handled */ + CMD_ONGOING, /* command is being executed, not yet completed */ + CMD_EXECUTED, /* commmand has been executed, need to be popped out */ +}; + /** - * Command FIFO stores commands alongs with SPI device configuration. - * To fit into 64-bit word, limit supported CS lines down to 64K rather than 4G. + * Command FIFO stores commands along with SPI device configuration. */ typedef struct { uint32_t opts; /* configopts */ - uint16_t command; /* command[15:0] */ + uint32_t command; /* command */ uint8_t csid; /* csid[7:0] */ - bool ongoing; /* command is being processed */ + int8_t state; /* enum CmdState */ + uint16_t cmdid; /* for debug/tracking */ } CmdFifoSlot; -static_assert(sizeof(TxFifoSlot) == sizeof(uint64_t), - "Invalid CmdFifoSlot size"); - struct CmdFifo { CmdFifoSlot *data; uint32_t capacity; @@ -461,20 +465,23 @@ static void cmdfifo_create(CmdFifo *fifo, uint32_t capacity) fifo->num = 0u; } -static void cmdfifo_push(CmdFifo *fifo, CmdFifoSlot cmd) +static void cmdfifo_push(CmdFifo *fifo, const CmdFifoSlot *cmd) { g_assert(fifo->num < fifo->capacity); - fifo->data[(fifo->head + fifo->num) % fifo->capacity] = cmd; + memcpy(&fifo->data[(fifo->head + fifo->num) % fifo->capacity], cmd, + sizeof(*cmd)); fifo->num++; } -static CmdFifoSlot cmdfifo_pop(CmdFifo *fifo) +static void cmdfifo_pop(CmdFifo *fifo, CmdFifoSlot *cmd) { g_assert(fifo->num > 0u); - CmdFifoSlot ret = fifo->data[fifo->head++]; + if (cmd) { + memcpy(cmd, &fifo->data[fifo->head], sizeof(*cmd)); + } + fifo->head++; fifo->head %= fifo->capacity; fifo->num--; - return ret; } static CmdFifoSlot *cmdfifo_peek(CmdFifo *fifo) @@ -614,29 +621,12 @@ enum OtSPIHostIrq { static bool ot_spi_host_update_event(OtSPIHostState *s) { - /* new events' state */ uint32_t events = ot_spi_host_build_event_bits(s); + uint32_t eff_events = events & s->regs[R_EVENT_ENABLE]; + trace_ot_spi_host_events(s->ot_id, events, events, events, + s->regs[R_EVENT_ENABLE], eff_events); - /* events that have changed since last call (detect rising/falling edges) */ - uint32_t changes = s->last_events ^ events; - /* RXWM/TXWM are not edge events, but level ones */ - changes |= R_EVENT_ENABLE_RXWM_MASK | R_EVENT_ENABLE_TXWM_MASK; - s->last_events = events; - - /* pick up changes */ - events &= changes; - - /* accumulate events */ - s->events |= events; - - /* mask disabled events to get the spi event state */ - bool event = (bool)(s->events & s->regs[R_EVENT_ENABLE]); - trace_ot_spi_host_debug1(s->ot_id, "event", event); - - /* - * if the spi event test has been enabled, force event and clear its bit - * right away - */ + bool event = (bool)eff_events; event |= (bool)(s->regs[R_INTR_TEST] & INTR_SPI_EVENT_MASK); s->regs[R_INTR_TEST] &= ~INTR_SPI_EVENT_MASK; if (event) { @@ -645,7 +635,6 @@ static bool ot_spi_host_update_event(OtSPIHostState *s) s->regs[R_INTR_STATE] &= ~INTR_SPI_EVENT_MASK; } - /* now update the IRQ signal (event could have been already signalled) */ bool event_level = (bool)(s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE] & INTR_SPI_EVENT_MASK); if (event_level != (bool)ibex_irq_get_level(&s->irqs[IRQ_SPI_EVENT])) { @@ -721,9 +710,6 @@ static void ot_spi_host_reset(OtSPIHostState *s) txfifo_reset(s->tx_fifo); cmdfifo_reset(s->cmd_fifo); - s->events = 0u; - s->last_events = FIELD_DP32(0u, EVENT_ENABLE, TXEMPTY, 1u); - memset(&s->fsm, 0, sizeof(s->fsm)); for (unsigned csid = 0u; csid < s->num_cs; csid++) { @@ -737,6 +723,9 @@ static void ot_spi_host_reset(OtSPIHostState *s) ot_spi_host_update_regs(s); ot_spi_host_update_alert(s); + + s->total_transfer = 0; + s->last_command_id = 0; } /** @@ -745,14 +734,18 @@ static void ot_spi_host_reset(OtSPIHostState *s) */ static void ot_spi_host_step_fsm(OtSPIHostState *s, const char *cause) { - trace_ot_spi_host_fsm(s->ot_id, cause); - CmdFifoSlot *headcmd = cmdfifo_peek(s->cmd_fifo); + trace_ot_spi_host_fsm(s->ot_id, headcmd->cmdid, cause); + s->fsm.active = true; ot_spi_host_update_event(s); - uint32_t command = (uint32_t)headcmd->command; + if (headcmd->state == CMD_EXECUTED) { + goto post; + } + + uint32_t command = headcmd->command; bool read = ot_spi_host_is_rx(command); bool write = ot_spi_host_is_tx(command); unsigned speed = FIELD_EX32(command, COMMAND, SPEED); @@ -771,8 +764,9 @@ static void ot_spi_host_step_fsm(OtSPIHostState *s, const char *cause) ot_spi_host_trace_status(s->ot_id, "S>", ot_spi_host_get_status(s)); - trace_ot_spi_host_command( - s->ot_id, F_COMMAND_DIRECTION[FIELD_EX32(command, COMMAND, DIRECTION)], + trace_ot_spi_host_exec_command( + s->ot_id, headcmd->cmdid, + F_COMMAND_DIRECTION[FIELD_EX32(command, COMMAND, DIRECTION)], F_COMMAND_SPEED[FIELD_EX32(command, COMMAND, SPEED)], (uint32_t)headcmd->csid, (bool)FIELD_EX32(command, COMMAND, CSAAT), length, s->fsm.transaction); @@ -807,35 +801,39 @@ static void ot_spi_host_step_fsm(OtSPIHostState *s, const char *cause) fifo8_push(s->rx_fifo, rx); } - trace_ot_spi_host_transfer(s->ot_id, tx, rx); + trace_ot_spi_host_transfer(s->ot_id, s->total_transfer, tx, rx); + s->total_transfer += 1u; length--; } - bool ongoing; + int8_t cmd_state; if (length) { /* if the transfer early ended, a stall condition has been detected */ if (write && txfifo_is_empty(s->tx_fifo)) { - trace_ot_spi_host_debug(s->ot_id, "Tx stall"); + trace_ot_spi_host_stall(s->ot_id, "TX", length); s->fsm.tx_stall = true; } if (read && fifo8_is_full(s->rx_fifo)) { - trace_ot_spi_host_debug(s->ot_id, "Rx stall"); + trace_ot_spi_host_stall(s->ot_id, "RX", length); s->fsm.rx_stall = true; } command = FIELD_DP32(command, COMMAND, LEN, length - 1); - ongoing = true; + cmd_state = CMD_ONGOING; } else { command = FIELD_DP32(command, COMMAND, LEN, 0); - ongoing = false; + cmd_state = CMD_EXECUTED; } - headcmd->command = (uint16_t)command; - headcmd->ongoing = ongoing; + headcmd->command = command; + headcmd->state = cmd_state; - timer_mod(s->fsm_delay, - qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + FSM_TRIGGER_DELAY_NS); +post: + ot_spi_host_update_regs(s); + + timer_mod(s->fsm_delay, qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + + (int64_t)s->completion_delay_ns); ot_spi_host_trace_status(s->ot_id, "S<", ot_spi_host_get_status(s)); } @@ -857,15 +855,17 @@ static void ot_spi_host_post_fsm(void *opaque) { OtSPIHostState *s = opaque; - trace_ot_spi_host_fsm(s->ot_id, "post"); - CmdFifoSlot *headcmd = cmdfifo_peek(s->cmd_fifo); - uint32_t command = (uint32_t)headcmd->command; - bool ongoing = headcmd->ongoing; + trace_ot_spi_host_fsm(s->ot_id, headcmd->cmdid, "post"); + + uint32_t command = headcmd->command; + bool retire = headcmd->state == CMD_EXECUTED; ot_spi_host_trace_status(s->ot_id, "P>", ot_spi_host_get_status(s)); - if (!ongoing) { + if (retire) { + trace_ot_spi_host_retire_command(s->ot_id, headcmd->cmdid); + if (ot_spi_host_is_rx(command)) { /* * transfer has been completed, RX FIFO may need padding up to a @@ -885,7 +885,7 @@ static void ot_spi_host_post_fsm(void *opaque) } /* retire command */ - cmdfifo_pop(s->cmd_fifo); + cmdfifo_pop(s->cmd_fifo, NULL); /* "the command is complete when STATUS.ACTIVE goes low." */ s->fsm.active = false; @@ -895,13 +895,13 @@ static void ot_spi_host_post_fsm(void *opaque) ot_spi_host_trace_status(s->ot_id, "P<", ot_spi_host_get_status(s)); - if (!ongoing) { + if (retire) { /* last command has completed */ if (!cmdfifo_is_empty(s->cmd_fifo)) { /* more commands have been scheduled */ - trace_ot_spi_host_debug(s->ot_id, "Next cmd"); + trace_ot_spi_host_debug(s->ot_id, "next cmd"); if (!ot_spi_host_is_on_error(s)) { - qemu_bh_schedule(s->fsm_bh); + ot_spi_host_step_fsm(s, "post"); } else { trace_ot_spi_host_debug(s->ot_id, "no resched: on err"); } @@ -962,6 +962,8 @@ static uint64_t ot_spi_host_io_read(void *opaque, hwaddr addr, case R_RXDATA: { /* here, size != 4 is illegal, what to do in this case? */ if (fifo8_num_used(s->rx_fifo) < sizeof(uint32_t)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: Read underflow: %u\n", + __func__, s->ot_id, fifo8_num_used(s->rx_fifo)); REG_UPDATE(s, ERROR_STATUS, UNDERFLOW, 1); ot_spi_host_update_regs(s); val32 = 0u; @@ -972,9 +974,21 @@ static uint64_t ot_spi_host_io_read(void *opaque, hwaddr addr, val32 <<= 8u; val32 |= (uint32_t)fifo8_pop(s->rx_fifo); } + ot_spi_host_trace_status(s->ot_id, "rxd", ot_spi_host_get_status(s)); val32 = bswap32(val32); - bool resume = !cmdfifo_is_empty(s->cmd_fifo) && s->fsm.rx_stall && - !s->fsm.tx_stall; + bool resume = false; + if (!cmdfifo_is_empty(s->cmd_fifo)) { + if (!s->fsm.tx_stall) { + uint32_t rxwm = REG_GET(s, CONTROL, RX_WATERMARK); + if (rxwm < sizeof(uint32_t)) { + rxwm = sizeof(uint32_t); + } + uint32_t inlen = fifo8_num_used(s->rx_fifo) / sizeof(uint32_t); + if (inlen <= rxwm) { + resume = true; + } + } + } s->fsm.rx_stall = false; if (resume) { ot_spi_host_step_fsm(s, "rx"); @@ -998,7 +1012,9 @@ static uint64_t ot_spi_host_io_read(void *opaque, hwaddr addr, if (trace_cache.pc != pc || trace_cache.addr != addr || trace_cache.value != val32) { if (trace_cache.count > 1u) { - trace_ot_spi_host_io_read_repeat(s->ot_id, trace_cache.count); + hwaddr rreg = R32_OFF(trace_cache.addr); + trace_ot_spi_host_io_read_repeat(s->ot_id, REG_NAME(rreg), + trace_cache.count); } #endif /* DISCARD_REPEATED_STATUS_TRACES */ trace_ot_spi_host_io_read(s->ot_id, (uint32_t)addr, REG_NAME(reg), @@ -1040,15 +1056,9 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, switch (reg) { /* Skipping any R/O registers */ case R_INTR_STATE: - /* rw1c register */ - val32 &= INTR_MASK; + val32 &= INTR_ERROR_MASK; /* rw1c bit */ s->regs[R_INTR_STATE] &= ~val32; - if (val32 & INTR_SPI_EVENT_MASK) { - /* store current state */ - s->last_events = ot_spi_host_build_event_bits(s); - /* clear up all signalled events */ - s->events = 0u; - } + /* this call also regenerates all raised events */ ot_spi_host_update_regs(s); break; case R_INTR_ENABLE: @@ -1073,6 +1083,9 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, ot_spi_host_reset(s); } s->fsm.output_en = FIELD_EX32(val32, CONTROL, OUTPUT_EN); + if (!cmdfifo_is_empty(s->cmd_fifo)) { + ot_spi_host_step_fsm(s, "ctrl"); + } break; case R_CONFIGOPTS: /* Update the respective config-opts register based on CSIDth index */ @@ -1090,7 +1103,7 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, break; case R_COMMAND: { if (cmdfifo_is_full(s->cmd_fifo)) { - trace_ot_spi_host_reject(s->ot_id, "cmd fifo full"); + qemu_log_mask(LOG_GUEST_ERROR, "%s: cmd fifo full\n", s->ot_id); REG_UPDATE(s, ERROR_STATUS, CMDBUSY, (uint32_t) true); ot_spi_host_update_error(s); return; @@ -1100,12 +1113,12 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, /* IP not enabled */ if (!(REG_GET(s, CONTROL, SPIEN))) { - trace_ot_spi_host_reject(s->ot_id, "no SPI/EN"); + qemu_log_mask(LOG_GUEST_ERROR, "%s: no SPI/EN\n", s->ot_id); return; } if (!ot_spi_host_is_ready(s)) { - trace_ot_spi_host_reject(s->ot_id, "busy"); + qemu_log_mask(LOG_GUEST_ERROR, "%s: busy\n", s->ot_id); REG_UPDATE(s, ERROR_STATUS, CMDBUSY, 1); ot_spi_host_update_regs(s); break; @@ -1116,28 +1129,38 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, (FIELD_EX32(val32, COMMAND, SPEED) != 0u)) || (FIELD_EX32(val32, COMMAND, SPEED) == 3u)) { /* dual/quad SPI cannot be used w/ full duplex mode */ - trace_ot_spi_host_reject(s->ot_id, "invalid command parameters"); + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid command parameters\n", + s->ot_id); REG_UPDATE(s, ERROR_STATUS, CMDINVAL, 1u); error = true; } if (!(s->regs[R_CSID] < s->num_cs)) { /* CSID exceeds max num_cs */ - trace_ot_spi_host_reject(s->ot_id, "invalid csid"); + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid csid\n", s->ot_id); REG_UPDATE(s, ERROR_STATUS, CSIDINVAL, 1u); error = true; } - uint16_t csid = (uint16_t)s->regs[R_CSID]; + uint8_t csid = (uint8_t)s->regs[R_CSID]; CmdFifoSlot slot = { .opts = s->config_opts[csid], - .command = (uint16_t)val32, /* only b15..b0 are meaningful */ + .command = val32, .csid = csid, - .ongoing = false, + .state = CMD_SCHEDULED, + .cmdid = s->last_command_id++, }; bool activate = cmdfifo_is_empty(s->cmd_fifo) && !s->fsm.rx_stall && !s->fsm.tx_stall && !error; - cmdfifo_push(s->cmd_fifo, slot); + + trace_ot_spi_host_new_command( + s->ot_id, slot.cmdid, + F_COMMAND_DIRECTION[FIELD_EX32(slot.command, COMMAND, DIRECTION)], + F_COMMAND_SPEED[FIELD_EX32(slot.command, COMMAND, SPEED)], + (uint32_t)csid, (bool)FIELD_EX32(slot.command, COMMAND, CSAAT), + FIELD_EX32(slot.command, COMMAND, LEN) + 1u, activate); + + cmdfifo_push(s->cmd_fifo, &slot); ot_spi_host_update_event(s); /* track ready */ if (activate) { ot_spi_host_step_fsm(s, "cmd"); @@ -1152,15 +1175,6 @@ static void ot_spi_host_io_write(void *opaque, hwaddr addr, uint64_t val64, __func__, s->ot_id, addr, REG_NAME(reg)); break; case R_TXDATA: { - /* - * This is a hardware `feature` where the first word written to TXDATA - * after init is omitted entirely - */ - if (s->initbug) { - s->initbug = false; - return; - } - if (txfifo_is_full(s->tx_fifo)) { REG_UPDATE(s, ERROR_STATUS, OVERFLOW, 1u); ot_spi_host_update_regs(s); @@ -1231,7 +1245,8 @@ static Property ot_spi_host_properties[] = { DEFINE_PROP_STRING("ot_id", OtSPIHostState, ot_id), DEFINE_PROP_UINT8("num-cs", OtSPIHostState, num_cs, 1), DEFINE_PROP_UINT32("bus-num", OtSPIHostState, bus_num, 0), - DEFINE_PROP_BOOL("initbug", OtSPIHostState, initbug, false), + DEFINE_PROP_UINT32("completion-delay", OtSPIHostState, completion_delay_ns, + FSM_COMPLETION_DELAY_NS), DEFINE_PROP_END_OF_LIST(), }; diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 8a136bb34cb32..38873fd710882 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -462,19 +462,22 @@ ot_spi_device_update_last_read_addr(uint32_t addr) "0x%08x" # ot_spi_host.c -ot_spi_host_command(const char *id, const char *dir, const char *spd, uint32_t csid, bool active, unsigned len, bool start) "%s: d:%s s:%s cs#:%u csa:%u len:%u (t:%u)" ot_spi_host_cs(const char *id, uint32_t csid, const char *level) "%s: cs#:%u %sselected" ot_spi_host_debug(const char *id, const char *msg) "%s: %s" ot_spi_host_debug1(const char *id, const char *msg, uint32_t val) "%s: %s 0x%x" -ot_spi_host_fsm(const char *id, const char *cause) "%s: step %s" -ot_spi_host_io_read(const char *id, uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "%s: addr:0x%02x (%s), val:0x%x, pc:0x%x" -ot_spi_host_io_read_repeat(const char *id, size_t count) "%s: last read repeated %zu times" -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%x, pc:0x%x" -ot_spi_host_reject(const char *id, const char *msg) "%s: %s" +ot_spi_host_events(const char *id, uint32_t cur, uint32_t raised, uint32_t active, uint32_t mask, uint32_t eff) "%s: cur:0x%02x rsd:0x%02x act:0x%02x msk:0x%02x eff:0x%02x" +ot_spi_host_exec_command(const char *id, uint16_t cmdid, const char *dir, const char *spd, uint32_t csid, bool active, unsigned len, bool start) "%s: {%hu} d:%s s:%s cs#:%u csa:%u len:%u (t:%u)" +ot_spi_host_fsm(const char *id, uint16_t cmdid, const char *cause) "%s: {%hu} step %s" +ot_spi_host_io_read(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_io_read_repeat(const char *id, const char * regname, size_t count) "%s: last %s read repeated %zu times" +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_new_command(const char *id, uint16_t cmdid, const char *dir, const char *spd, uint32_t csid, bool active, unsigned len, bool activate) "%s: {%hu} d:%s s:%s cs#:%u csa:%u len:%u (act:%u)" ot_spi_host_reset(const char *id, const char *msg) "%s: %s" +ot_spi_host_retire_command(const char *id, uint16_t cmdid) "%s: {%hu}" +ot_spi_host_stall(const char *id, const char *msg, uint32_t val) "%s: %s rem %u" ot_spi_host_status(const char *id, const char *msg, uint32_t status, const char *str, unsigned cmd, unsigned rxd, unsigned txd) "%s: %s 0x%08x s:%s cq:%u rq:%u tq:%u" -ot_spi_host_transfer(const char *id, uint32_t tx_data, uint32_t rx_data) "%s: tx_data: 0x%02x rx_data: 0x%02x" -ot_spi_host_update_irq(const char *id, const char *channel, int level) "%s: %s: %d" +ot_spi_host_transfer(const char *id, uint64_t transfer, uint32_t tx_data, uint32_t rx_data) "%s: {%" PRIu64 "} tx_data: 0x%02x rx_data: 0x%02x" +ot_spi_host_update_irq(const char *id, const char *channel, int level) "%s: irq %s: %d" # ot_sram_ctrl.c diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 4041c577dd0a6..33703a788e254 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -27,7 +27,9 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "qapi/qmp/qlist.h" +#include "qom/object.h" #include "exec/address-spaces.h" +#include "hw/block/flash.h" #include "hw/boards.h" #include "hw/intc/sifive_plic.h" #include "hw/jtag/tap_ctrl.h" @@ -155,6 +157,26 @@ enum OtEgResetRequest { OT_EG_RESET_COUNT }; +/* Data flash buses */ +enum OtEgMtdBus { + OT_EG_MTD_SPI0, + OT_EG_MTD_SPI1, + OT_EG_MTD_SPI_COUNT, + OT_EG_MTD_EFLASH = OT_EG_MTD_SPI_COUNT, +}; + +/* "Parallel" flash buses */ +enum OtEgPflashBus { + OT_EG_PFLASH_OTP, +}; + +enum OtEGBoardDevice { + OT_EG_BOARD_DEV_SOC, + OT_EG_BOARD_DEV_FLASH0, + OT_EG_BOARD_DEV_FLASH1, + OT_EG_BOARD_DEV_COUNT, +}; + /* EarlGrey/CW310 Peripheral clock is 6 MHz */ #define OT_EG_PERIPHERAL_CLK_HZ 6000000u @@ -655,6 +677,7 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(0, 19) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("ot_id", "spi0"), IBEX_DEV_UINT_PROP("bus-num", 0) ), }, @@ -669,6 +692,7 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(0, 20) ), .prop = IBEXDEVICEPROPDEFS( + IBEX_DEV_STRING_PROP("ot_id", "spi1"), IBEX_DEV_UINT_PROP("bus-num", 1) ), }, @@ -1108,12 +1132,6 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { /* clang-format on */ }; -enum OtEGBoardDevice { - OT_EG_BOARD_DEV_SOC, - OT_EG_BOARD_DEV_FLASH, - OT_EG_BOARD_DEV_COUNT, -}; - /* ------------------------------------------------------------------------ */ /* Type definitions */ /* ------------------------------------------------------------------------ */ @@ -1133,7 +1151,8 @@ struct OtEGSoCState { struct OtEGBoardState { DeviceState parent_obj; - DeviceState **devices; + /* optional SPI data flash (type of device) */ + char *spiflash[OT_EG_MTD_SPI_COUNT]; }; struct OtEGMachineState { @@ -1168,7 +1187,7 @@ static void ot_eg_soc_dm_configure(DeviceState *dev, const IbexDeviceDef *def, static void ot_eg_soc_flash_ctrl_configure( DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) { - DriveInfo *dinfo = drive_get(IF_MTD, 1, 0); + DriveInfo *dinfo = drive_get(IF_MTD, OT_EG_MTD_EFLASH, 0); (void)def; (void)parent; @@ -1209,7 +1228,7 @@ static void ot_eg_soc_hart_configure(DeviceState *dev, const IbexDeviceDef *def, static void ot_eg_soc_otp_ctrl_configure( DeviceState *dev, const IbexDeviceDef *def, DeviceState *parent) { - DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0); + DriveInfo *dinfo = drive_get(IF_PFLASH, OT_EG_PFLASH_OTP, 0); (void)def; (void)parent; @@ -1387,39 +1406,100 @@ type_init(ot_eg_soc_register_types); /* Board */ /* ------------------------------------------------------------------------ */ +static void ot_eg_board_set_spiflash0(Object *obj, const char *value, + Error **errp) +{ + OtEGBoardState *board = RISCV_OT_EG_BOARD(obj); + (void)errp; + + g_free(board->spiflash[OT_EG_MTD_SPI0]); + board->spiflash[OT_EG_MTD_SPI0] = g_strdup(value); +} + +static void ot_eg_board_set_spiflash1(Object *obj, const char *value, + Error **errp) +{ + OtEGBoardState *board = RISCV_OT_EG_BOARD(obj); + (void)errp; + + g_free(board->spiflash[OT_EG_MTD_SPI1]); + board->spiflash[OT_EG_MTD_SPI1] = g_strdup(value); +} + static void ot_eg_board_realize(DeviceState *dev, Error **errp) { OtEGBoardState *board = RISCV_OT_EG_BOARD(dev); - DeviceState *soc = board->devices[OT_EG_BOARD_DEV_SOC]; + DeviceState *soc = qdev_new(TYPE_RISCV_OT_EG_SOC); + object_property_add_child(OBJECT(board), "soc", OBJECT(soc)); sysbus_realize_and_unref(SYS_BUS_DEVICE(soc), &error_fatal); - DeviceState *spihost = - RISCV_OT_EG_SOC(soc)->devices[OT_EG_SOC_DEV_SPI_HOST0]; - DeviceState *flash = board->devices[OT_EG_BOARD_DEV_FLASH]; - BusState *spibus = qdev_get_child_bus(spihost, "spi0"); - g_assert(spibus); - - DriveInfo *dinfo = drive_get(IF_MTD, 0, 0); - if (dinfo) { - qdev_prop_set_drive_err(DEVICE(flash), "drive", - blk_by_legacy_dinfo(dinfo), &error_fatal); + for (unsigned fix = 0; fix < OT_EG_MTD_SPI_COUNT; fix++) { + const char *flash_type = board->spiflash[OT_EG_MTD_SPI0 + fix]; + /* + * skip this flash slot if no device type has been defined on the QEMU + * command line + */ + if (!flash_type) { + continue; + } + + /* qdev_new aborts if the specified device is not supported */ + DeviceState *flash = qdev_new(flash_type); + + if (!object_dynamic_cast(OBJECT(flash), TYPE_M25P80)) { + error_setg(errp, "%s is not a SPI dataflash device", flash_type); + } + + /* + * retrieve the SPI host controller bus. Although each SPI host only + * has one SPI bus, each bus name in QEMU needs to be unique. The SPI + * host controller uses its bus-num property as a suffix for naming its + * bus + */ + DeviceState *spihost = + RISCV_OT_EG_SOC(soc)->devices[OT_EG_SOC_DEV_SPI_HOST0 + fix]; + char *busname = g_strdup_printf("spi%u", fix); + BusState *spibus = qdev_get_child_bus(spihost, busname); + g_assert(spibus); + + /* + * if a "drive" property for this bus/unit pair is defined on the QEMU + * command line, assigned it to the flash device + */ + DriveInfo *dinfo = drive_get(IF_MTD, (int)fix, 0); + if (dinfo) { + qdev_prop_set_drive_err(DEVICE(flash), "drive", + blk_by_legacy_dinfo(dinfo), &error_fatal); + } + + /* the flash device is a child of the board */ + char *flashname = g_strdup_printf("dataflash%u", fix); + object_property_add_child(OBJECT(board), flashname, OBJECT(flash)); + /* connect it as a peripheral of the SPI host controller bus */ + ssi_realize_and_unref(flash, SSI_BUS(spibus), errp); + + /* + * finally, connect the first CS line of the SPI controller to control + * to select this SPI flash device + */ + qemu_irq cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); + qdev_connect_gpio_out_named(spihost, SSI_GPIO_CS, 0, cs); + + g_free(flashname); + g_free(busname); } - object_property_add_child(OBJECT(board), "dataflash", OBJECT(flash)); - ssi_realize_and_unref(flash, SSI_BUS(spibus), errp); - - qemu_irq cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); - qdev_connect_gpio_out_named(spihost, SSI_GPIO_CS, 0, cs); } static void ot_eg_board_init(Object *obj) { - OtEGBoardState *s = RISCV_OT_EG_BOARD(obj); - - s->devices = g_new0(DeviceState *, OT_EG_BOARD_DEV_COUNT); - s->devices[OT_EG_BOARD_DEV_SOC] = qdev_new(TYPE_RISCV_OT_EG_SOC); - s->devices[OT_EG_BOARD_DEV_FLASH] = qdev_new("is25wp128"); + object_property_add_str(obj, "spiflash0", NULL, &ot_eg_board_set_spiflash0); + object_property_set_description(obj, "spiflash0", + "SPI dataflash on SPI0 bus"); + object_property_add_str(obj, "spiflash1", NULL, &ot_eg_board_set_spiflash1); + object_property_set_description(obj, "spiflash1", + "SPI dataflash on SPI1 bus"); } static void ot_eg_board_class_init(ObjectClass *oc, void *data) diff --git a/include/hw/riscv/ot_earlgrey.h b/include/hw/riscv/ot_earlgrey.h index cb85abb36674b..6b3972d17c8d5 100644 --- a/include/hw/riscv/ot_earlgrey.h +++ b/include/hw/riscv/ot_earlgrey.h @@ -24,13 +24,15 @@ #include "qom/object.h" -#define TYPE_RISCV_OT_EG_MACHINE MACHINE_TYPE_NAME("ot-earlgrey") +#define OT_EARLGREY "ot-earlgrey" + +#define TYPE_RISCV_OT_EG_MACHINE MACHINE_TYPE_NAME(OT_EARLGREY) OBJECT_DECLARE_SIMPLE_TYPE(OtEGMachineState, RISCV_OT_EG_MACHINE) -#define TYPE_RISCV_OT_EG_BOARD "riscv.ot_earlgrey.board" +#define TYPE_RISCV_OT_EG_BOARD OT_EARLGREY "-board" OBJECT_DECLARE_SIMPLE_TYPE(OtEGBoardState, RISCV_OT_EG_BOARD) -#define TYPE_RISCV_OT_EG_SOC "riscv.ot_earlgrey.soc" +#define TYPE_RISCV_OT_EG_SOC OT_EARLGREY "-soc" OBJECT_DECLARE_TYPE(OtEGSoCState, OtEGSoCClass, RISCV_OT_EG_SOC) #endif /* HW_RISCV_OT_EARLGREY_H */