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.

Unimplemented features:

  • 2-cycle read pipeline is not implemented, as QEMU SPI transactions are modelled with byte granularity.
  • Dummy cycle counts between 1-7 are not supported, for the same reason as above. Any non-zero dummy cycle count will fallback to 8 cycles.

Testing:

This implementation has been tested against spi_passthru_test. See lowRISC/opentitan#28649 for the changes required in OpenTitan. To test manually the steps are:

  • Create a 32MiB backing storage for the flash: dd if=/dev/zero of=<flash> bs=1M count=32
  • Add QEMU to the execution environment of spi_passthru_test, with these additional flags: -global ot-earlgrey-board.spiflash0=w25q256 -drive if=mtd,file=<flash>,format=raw,bus=0

@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

Choose a reason for hiding this comment

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

If not grouped in a structure no big deal but it seems these values are not reset yet.

@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
@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.

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from 28ffc3c to a49e9bf Compare November 3, 2025 14:12
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from a49e9bf to 3a643ee Compare November 5, 2025 12:01
@ziuziakowska ziuziakowska changed the title [WIP] ot_spi_device,ot_spi_host: Refactoring + Passthrough mode implementation ot_spi_device,ot_spi_host: Bugfix + Refactoring + Passthrough mode implementation Nov 5, 2025
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from 3a643ee to 05aa2a7 Compare November 5, 2025 12:14
@ziuziakowska ziuziakowska marked this pull request as ready for review November 5, 2025 12:15
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from 05aa2a7 to 6fc1a21 Compare November 5, 2025 12:16
@ziuziakowska
Copy link
Author

Just missing some checks in OT SPI Host to inhibit its activity when Passthrough on SPI Device is enabled, which should be a very pathological situation. Otherwise ready for review and nits up to now.

ziuziakowska added a commit to ziuziakowska/opentitan that referenced this pull request Nov 5, 2025
ziuziakowska added a commit to ziuziakowska/opentitan that referenced this pull request Nov 5, 2025
Depends on lowRISC/qemu#255

Signed-off-by: Alice Ziuziakowska <[email protected]>
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.

There are two small features missing

  • complete cleanup of passthrough variables from reset_enter
  • add LOG_GUEST_ERROR message when the SPI host is attempting to drive its /CS line or data lines when passthrough_en is enabled. I guess it could be implemented by tracking passthrough_en whenever it attempts to change its first /CS line.

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

Choose a reason for hiding this comment

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

nit pick: I wonder if the name of the trace is not a bit confusing, as unimplemented is usually ... used to report something that is not -yet- implemented, whereas here it is the HW that does not support this feature.

Copy link
Author

Choose a reason for hiding this comment

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

Should this be a LOG_GUEST_ERROR instead maybe? The software should know if there is more than one CS on the hardware it is running on.

Choose a reason for hiding this comment

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

I guess it could be understood this way yes.
In this case, I think it would great to also add a boolean flag so that this error message is only emitted once (as we do for OT_UNIMP) so that the error log is not flooded with the same message for every attempt to transmit a byte or drive the /CS line.

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch 2 times, most recently from a847e12 to 1ef99aa Compare November 6, 2025 14:00
Copy link

@AlexJones0 AlexJones0 left a comment

Choose a reason for hiding this comment

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

Thanks for all the work on this @ziuziakowska, it looks great to me and matches the HW implementation more closely now.

I agree with @rivos-eblot's remaining comments, but I'm happy to approve when those (and my new comments) are addressed.

As one final suggestion, it would also be great if you could rebase on #277 and then update the SPI device documentation to briefly note that passthrough is supported, and any known limitations (e.g. dummy cycles rounded up)?

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from 1ef99aa to 07a9072 Compare November 6, 2025 16:02
static uint8_t ot_spi_host_downstream_transfer(OtSPIHostState *s, uint8_t tx)
{
if (!ot_spi_host_supports_passthrough(s)) {
qemu_log_mask(

Choose a reason for hiding this comment

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

it would be useful to add a boolean value to only emit this error (passthrough triggerred) once per reset session, to avoid flooding the error stream (one per byte...)
The same boolean could be re-used with all siblings errors (/CS, EN)

It could be a bitmask of errors, as there are several ones to track

Copy link
Author

Choose a reason for hiding this comment

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

I agree that on every byte might be a bit too aggressive guest error messaging, but I think once per-reset might be too far in the other direction (for this specific case I think it makes sense, but not in general). I could imagine complicated behaviour in software could cause some of these guest error conditions to be hit but not cause any meaningful issues, and later on in situations where it could be a problem the guest errors would be inhibited.

if (!ot_spi_host_supports_passthrough(s) || !s->passthrough_en) {
rx = (uint8_t)ssi_transfer(s->ssi, tx);
} else {
qemu_log_mask(

Choose a reason for hiding this comment

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

Another boolean for this error (host-triggered) would be nice.

qemu_log_mask(LOG_GUEST_ERROR,
"%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__,
addr, TPM_REG_NAME(reg));
"%s: %s: R/O register 0x%02" HWADDR_PRIx " (%s)\n",

Choose a reason for hiding this comment

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

nit: I got tired on this syntax in other files, so I've started to use something more compact (less lines) and fully valid with OT:

"%s: %s: ..  0x%02x (%s)\n", 
__func__, s->ot_id, (uint32_t)addr, TPM_REG_NAME(reg));

Copy link
Author

Choose a reason for hiding this comment

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

I'm not very sure about this in general, there are some devices where the offset of the register can be greater than 0x100, such as the flash controller, and in the SPI Device TPM implementation. I would rather leave this for a separate PR to do this across all the devices at once in a consistent way.

Choose a reason for hiding this comment

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

I did not mean to use 0x02 in general which was already defined (i.e. there is no change) but to replace HWADDR_PRIx/hwaddr with x/(uint32_t)addr , since OT only uses 32-bit buses.

Choose a reason for hiding this comment

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

Depending on the devices, we have 1, 2, 3 or 4 meaningful nibbles.

@rivos-eblot
Copy link

Note: you will need to update ot_spi_device.md + ot_earlgrey.md and ot_darjeeling.md files. I think the list of supported feature can be removed from the machines, and the TPM vs. regular mode limitation documented in the ot_spi_device.md file with a dedicated section.

@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch 2 times, most recently from 74110d6 to 939c7fe Compare November 7, 2025 11:14
Both EG and DJ use E/I SRAM now

Signed-off-by: Alice Ziuziakowska <[email protected]>
The bit-fields previously used here are just advisory/convention, the
whole 22-bit field is RW and software maintained.

This is also expected behaviour for spi_passthru_test.

Signed-off-by: Alice Ziuziakowska <[email protected]>
The bytes are received over the wire and stored in the buffer
most-significant byte first (big-endian), so the dummy byte is the last
byte not the first.

Signed-off-by: Alice Ziuziakowska <[email protected]>
…ALUE

Name is more verbose but matches the usage better, as in `ot_spi_host`

Signed-off-by: Alice Ziuziakowska <[email protected]>
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 <[email protected]>
... 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 <[email protected]>
This implements a passthrough enable and chip select IRQ and a method to
interact with the downstream SPI bus, for use by upstream SPI Device.

Signed-off-by: Alice Ziuziakowska <[email protected]>
This adds the corresponding out IRQs to OT SPI Device, as well as a
property that contains the downstream OT SPI Host.

Signed-off-by: Alice Ziuziakowska <[email protected]>
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 <[email protected]>
@ziuziakowska ziuziakowska force-pushed the spi_device_passthrough branch from 939c7fe to 4d28dec Compare November 7, 2025 11:44
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.

3 participants