Skip to content

i2c: Answer an unanswered slave read - #470

Open
EvanChuchen731 wants to merge 1 commit into
OpenPRoT:mainfrom
EvanChuchen731:i2c-slave-read-filler
Open

EvanChuchen731 wants to merge 1 commit into
OpenPRoT:mainfrom
EvanChuchen731:i2c-slave-read-filler

Conversation

@EvanChuchen731

Copy link
Copy Markdown

Why

A master read of the AST1060 slave address stalls the bus when the
application has staged no reply. The controller parks the transaction at
WAIT_TX_DMA and holds SCL low until a byte reaches the transmit path,
and the old handler reported the read as DataSent while staging
nothing. On a bus with the hardware SMBus timeout disabled there is no
controller-side backstop.

MCTP over SMBus is write-only in both directions, so no MCTP peer
triggers this. A host that reads us does: an OpenBMC service that probes
by reading, such as fru-device, or a host expecting a mailbox at our
address.

What changes

answer_slave_read stages one filler byte, 0xFF, arms transmit,
re-arms receive and writes the command register. It runs on the three
WAIT_TX_DMA branches: a read's first byte, a later byte of the same
read, and a read on a repeated START after a write.

The 0xFF value is what a master reads back from a released SDA line.

The answer belongs in the handler rather than in the application because
the i2cs28 enable bits are one-shot and do not survive a transaction.
Any intervening transaction leaves the next read unanswered, and an
application would have to re-arm across an IPC round trip it does not
control. A reply slave_write staged is unaffected: the hardware serves
it from that call's own arming, so WAIT_TX_DMA never occurs.

Scope

This fix keeps the bus from stalling. It is not a read interface: a
reader gets no length and no framing, so a probe reads 0xFF for as many
bytes as it asks for. Whether OpenPRoT wants a real I2C read interface, a
mailbox for a boot stage that runs before MCTP is up, is a separate
question this does not answer.

services/i2c/README.md records the filler behavior.

Testing

  • bazel build --config=k_ast1060_evb //target/ast10x0/...: 405 targets,
    completed successfully
  • bazel test --config=k_ast1060_evb of
    //target/ast10x0/tests/peripherals/i2c/i2c_irq:no_panics_test and
    :slave_no_panics_test: both pass. These are panic-detector checks on
    the binaries and execute no I2C logic
  • ./pw format --check over the two changed files: no changes needed
  • The clippy aspect over //target/ast10x0/peripherals/... and
    //target/ast10x0/backend/i2c/... reports 25 findings, the same 25 as
    on main before this change, none of them in a changed file

Verified on hardware

An AST1060 running the MCTP control image, driven by a BMC master in DMA
mode at slave address 0x40:

  • i2ctransfer -y <bus> r1@0x40 returns ff
  • i2ctransfer -y <bus> r4@0x40 returns ff ff ff ff
  • i2ctransfer -y <bus> w1@0x40 0x00 r1@0x40 returns ff

So all three branches answer and the read completes instead of holding
SCL. An MCTP Get Endpoint ID request following those reads is received,
decoded and answered, which confirms the receive path survives the
skipped re-arm on the read-after-write branch.

A register capture at the interrupt entry read 0x00070240 right after
the handler wrote it, with i2cs24 at 0 on that same interrupt, so no
packet completed in between. The next packet's interrupt entry read
0x00070000, with both TX_BUFF_EN and RX_DMA_EN gone. No code wrote
i2cs28 between those two reads, so the hardware cleared them. The
arming therefore does not survive a transaction, which is why an
application would have to re-stage after every one.

Known gap

A read the service never asked for still logs slave_receive failed: the
STOP that ends it wakes the MCTP server, whose latch holds no received
bytes. That log line predates this change.

The slave interrupt handler reported a master read as data already sent
and staged nothing, so the controller parked the transaction at
WAIT_TX_DMA and held SCL low, where it stays until a byte reaches the
transmit path. On a bus with the hardware SMBus timeout disabled,
nothing on this side ends such a transaction. The handler now stages one
filler byte, 0xFF, the value a master reads back from a released SDA
line, and re-arms receive in the same command write.

Answering in the handler rather than in the application is what closes
the window. The i2cs28 enable bits are one-shot and do not survive a
transaction, so any intervening transaction leaves the next read
unanswered and an application would have to re-arm across an IPC round
trip it does not control. A reply slave_write staged is unaffected: the
hardware serves it from that call's own arming, so WAIT_TX_DMA never
occurs and the filler path never runs.

The read-after-write branch skips the receive re-arm, which would reset
the length registers the pending drain reads. In buffer mode the
transmit FIFO and the receive pool are one 32-byte region, so the filler
write replaces byte 0 alone; a whole-DWORD write would zero the received
bytes behind it. In DMA mode the two paths are separate, and byte mode
has no filler path.

Signed-off-by: Evan Chuchen <evan_chuchen@jabil.com>
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.

1 participant