Skip to content

Conversation

@ziuziakowska
Copy link

@ziuziakowska ziuziakowska commented Oct 23, 2025

This PR implements the Passthrough mode for OT SPI Device and any required changes in OT SPI Host. Passthrough mode allows for external SPI transfers to be passed through to a downstream device, and optionally intercepted or modified by OT SPI Device.

In HW, OT SPI Device and OT SPI Host are linked by their SPI bus and a wire that represents whether the Device is in Passthrough mode. If Passthrough mode is enabled, the SPI Host is entirely bypassed and SPI Device has full control over the bus, which it uses to talk to the same downstream flash as SPI Host. See Documentation reference, RTL.
This passthrough mechanism is only implemented by OT SPI Host if it has one CS line: RTL

The structure of the PR is as follows:

  • The initial commits consist of refactoring work in ot_spi_device for the Passthrough implementation.
  • The subsequent commits implement the requirements for Passthrough mode in ot_spi_host - two GPIOs are used to communicate Passthrough enabled and the Chip Select as driven by OT SPI Device, OT SPI Device and OT SPI Host are then linked together.
  • The final commits contain the implementation of Passthrough mode in ot_spi_device.

I have tried to handle most corner-cases I can think of, but some conflicting configuration options might not behave as expected.

The two-stage read pipeline is not yet implemented - we model all transfers as though they are over a single wire, so this would only be used to buffer all read payload bytes by two over the wire for correctness.

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch 2 times, most recently from 2264c77 to f1a68a2 Compare October 29, 2025 14:57
bool loop; /* Keep reading the buffer if end is reached */
bool watermark; /* Read watermark hit, used as flip-flop */
bool new_cmd; /* New command has been pushed in current SPI transaction */
bool cmd_addr_swap_en; /* Passthrough mode - address swap enabled */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe group all the passthrough related value into a structure. (cmd....?)
This would also to clear the whole thing at once in ot_spi_device_passthrough_unmatched_command_params

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from f1a68a2 to 6e8f034 Compare October 30, 2025 11:53
@ziuziakowska ziuziakowska marked this pull request as ready for review October 30, 2025 11:54
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch 2 times, most recently from e8ef6d7 to ad5c39c Compare October 30, 2025 14:20
@ziuziakowska ziuziakowska marked this pull request as draft October 30, 2025 16:51
@ziuziakowska ziuziakowska changed the title [WIP] ot_spi_device: Refactoring + Passthrough mode implementation [WIP] ot_spi_device,ot_spi_host: Refactoring + Passthrough mode implementation Oct 31, 2025
This commit simplifies command slot definitions and the associated state
and control flow.

The HW CFG and the HW STA commands have been combined into just HW
commands, as in flash mode they are handled in hardware by some way and
should therefore share the same data path. For passthrough mode, a
subset of these HW commands can be intercepted (all except for WREN and
WRDI).

As the matched command slot number determines whether the command is a
SW or HW command, the `OtSpiFlashCommand` enum has been removed in
favour of using `is_sw_command` to return the boolean.

Command slot matching has been brought out into its own function,
`match_command_slot`, which returns whether an opcode was matched in the
command info registers. The decoded slot number is only valid if this
returns true.

Also a bug is fixed in `ot_spi_device_exec_command` where hardcoded
opcodes for read commands were used instead of the opcodes in the
associated command slot.

Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
... and removes `OtSpiDeviceAddrMode`.

`ot_spi_device_get_command_address_size` now returns the size of the
address field in the current command, using the value in `cmd_info` and
the current 4B enable state.

Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
See https://opentitan.org/book/hw/ip/spi_host/doc/theory_of_operation.html#pass-through-mode

This commit adds the `passthrough-en` and `passthrough-cs` pins for
controlling Passthrough mode behaviour.

When `passthrough_en` is asserted (i.e, the upstream OT SPI Device is in
Flash Passthrough mode), OT SPI Host is bypassed entirely and OT SPI
Device controls the transfers on the SPI bus.

This passthrough behaviour is only implemented in OT SPI Host if there
is only a single CS pin. See RTL: `spi_host.sv:100`

When in Passthrough mode, OT SPI Device can call into the new method
`downstream_transfer` to perform SPI transfers.

Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
This commit implements the Passthrough mode on SPI Device. Passthrough
mode allows the device to act as a proxy for a downstream flash device,
optionally intercepting commands to send back its own values, filtering
commands sent downstream, or transparently translating address and
payload bytes.

This also "implements" the Disabled mode, which discards all bytes sent
to the device.

Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from ca61a08 to 28ffc3c Compare October 31, 2025 16:09
@ziuziakowska
Copy link
Author

I have discovered that OT SPI Device is not the only device controlling this SPI bus, the bus is actually shared by OT SPI Host 0 in Earlgrey, and OT SPI Host is bypassed entirely when Passthrough mode is enabled. This seems to only be documented here. As such this PR has been reworked a bit.

Copy link

@rivos-eblot rivos-eblot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick review.


qemu_irq *cs_lines; /* CS output lines */
SSIBus *ssi; /* SPI bus */
bool passthrough_en; /* Upstream SPI Device Passthrough enable line */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit pick: move it close to on_reset to avoid creating 7 byte padding.

TxFifo *tx_fifo;
CmdFifo *cmd_fifo;


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra empty line added


uint8_t rx = s->fsm.output_en ? ssi_transfer(s->ssi, tx) : 0xffu;
uint8_t rx =
s->fsm.output_en ? ssi_transfer(s->ssi, tx) : SPI_DEFAULT_TX_VALUE;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to rename the constant w/o "TX" if you want to use is as RX :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected the SPI host output to be disabled when passthrough_en is true (?)

ot_spi_host_device_passthrough_en_input(void *opaque, int irq, int level)
{
OtSPIHostState *s = opaque;
(void)irq;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to g_assert against expected value (0)

(void)irq;

/* Passthrough is not implemented if SPI Host has more than one CS line */
if (s->num_cs != 1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1u
need to add a trace in this case.

OtSPIHostClass *sc = OT_SPI_HOST_CLASS(klass);
sc->downstream_transfer = &ot_spi_host_downstream_transfer;

ResettableClass *rc = RESETTABLE_CLASS(klass);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW passthrough_* should be reset in reset_enter

static uint8_t ot_spi_host_downstream_transfer(OtSPIHostState *s, uint8_t tx)
{
/* Passthrough is not implemented if SPI Host has more than one CS line */
if (s->num_cs != 1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit pick: 1u

g_assert(s->ot_id);

/* CS is active low, Passthrough enable is active high */
ibex_qdev_init_irq_default(OBJECT(s), &s->passthrough_cs, "passthrough-cs",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same note as for the other side, needs to be a public constant, and used to connect lines in the machine

{
if (s->spihost) {
OtSPIHostClass *spihostc =
OBJECT_GET_CLASS(OtSPIHostClass, s->spihost, TYPE_OT_SPI_HOST);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more idiomatic:
OtSPIHostClass *spihostc = OT_SPI_HOST_GET_CLASS(s->sphihost);


static uint8_t ot_spi_device_flash_spi_transfer(OtSPIDeviceState *s, uint8_t rx)
{
if (s->spihost) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a SPI Device can exist w/o a SPI host, I think this should be signal as an GUEST_ERROR when passthough is enable, and may discarded before reaching this point? If not, there should be a trace here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants