Skip to content

fix(i2c): refresh target RX FIFO count between interrupt causes - #135

Merged
floitsch merged 2 commits into
patch-head-5.4.2from
floitsch/i2c-target-refresh-rx-count
Sep 20, 2026
Merged

floitsch merged 2 commits into
patch-head-5.4.2from
floitsch/i2c-target-refresh-rx-count

Conversation

@floitsch

@floitsch floitsch commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

When RX-watermark and transaction-complete interrupts are pending together, the I2C target v2 ISR drains the RX FIFO in the watermark branch and then reuses its entry-time byte count in the completion branch. A 20-byte write can therefore be reported as 40 bytes, with a corrupt extra 20-byte suffix.

Refresh the hardware RX count before COMPLETE handling and before passing it to STRETCH handling, since an earlier branch may already have drained the FIFO. This keeps later consumers from reading bytes that are no longer present.

Validation

A deterministic two-ESP32 hardware test disables only the target's I2C interrupt, writes 20 bytes followed by STOP, and then re-enables that interrupt. The 20 bytes exceed the 16-byte watermark but fit inside the 32-byte FIFO, forcing watermark and completion to be pending together without overflowing it.

  • Unpatched: all three repetitions reported 40 received bytes, with the first mismatch at byte 20.
  • Patched: all three repetitions reported exactly 20 correct bytes.
  • Both runs reported zero raw and driver overflow flags.
  • The patch applies cleanly to patch-head-5.4.2; git diff --check passes. The production change is the same two-line fix tested on hardware.

Test source, unpatched log, patched log, and investigation are included with toitlang/toit#3241. The forced hardware case covers watermark plus COMPLETE on classic ESP32; the conditional STRETCH path receives the same defensive refresh.

Assertion-based regression in this PR

components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_slave_v2.c now includes I2C slave refreshes RX FIFO count between interrupt causes. The classic-ESP32 two-device Unity case is discovered by the existing slave_v2 pytest configuration.

For three distinct payloads, it disables only the target I2C interrupt until the controller has written 20 bytes and issued STOP, verifies co-latched watermark/completion interrupts and FIFO occupancy, then re-enables the interrupt and asserts exact receive length, exact contents, and no hardware/driver overflow. Peripheral resources are released before checking the captured results. Both controller operations and callback waits have timeouts; the multi-device harness also enforces the case timeout.

The ESP32 slave_v2 test application compiled and linked successfully. The app README documents wiring, an isolated build configuration, and the pytest command.

Fresh hardware A/B of this Unity case on two classic ESP32s (2026-09-20, test commit 5a7e6182a615416095a09e7c1b44094c34b059fa):

Driver Controller Target
Two RX-count refreshes removed PASS FAIL: Expected 20 Was 40
Fix present PASS PASS

Both roles complete three transfers before asserting their captured results. The fixed target passes all three sets of interrupt, FIFO, overflow, length, and payload assertions. The unpatched target fails the first length assertion after its interrupt/FIFO/overflow preconditions pass.

The case was selected by name and its two stages run through the Unity serial menu, relaying the standard Unity synchronization signals. The existing rig required build-only overrides from GPIO18/19 to GPIO17/16 and releasing GPIO13 as an input with pull-up: GPIO13 is connected through a resistor to the rig's SCL. These fixture overrides were identical in both builds and are absent from the committed generic test. Initial runs without releasing that probe timed out and are not counted as regression evidence. The controller remains synchronous.

Application SHA-256:

  • Unpatched: 238ede84b48ba92401c98d5cef5f4c7eca2a88e55b903c5093eaaf50b15680ad
  • Fixed: b346785ccaeb6ab21dc8d1ff6c393c7c4fe2381e1a09146012f829e1352cce4b

Relationship to other I2C fixes

This is independent of #134, which drains the controller TX FIFO before a repeated START. That change touches i2c_master.c; this change fixes target RX handling in i2c_slave_v2.c, so the two can be merged independently.

This patch fixes the demonstrated corrupt suffix. It does not claim to fix the separate intermittent early NACK/long interrupt-service gaps observed with a classic ESP32 Jaguar target at 400 kHz; an IRAM-safe A/B did not eliminate those gaps.

@floitsch
floitsch merged commit bf81d4c into patch-head-5.4.2 Sep 20, 2026
1 of 2 checks passed
@floitsch
floitsch deleted the floitsch/i2c-target-refresh-rx-count branch September 20, 2026 14:30
floitsch added a commit to toitlang/toit that referenced this pull request Sep 20, 2026
Update `third_party/esp-idf` from
`1d89388f11383182b35c06fe44278847235b3a53` to
`bf81d4c8cdaeaee5a13feca0c8aea60988f12807`, incorporating two merged I2C
fixes:

- [toitware/esp-idf#134](toitware/esp-idf#134):
drain the controller TX FIFO before a repeated START so the next address
cannot overwrite pending write data.
- [toitware/esp-idf#135](toitware/esp-idf#135):
refresh the target RX FIFO count between interrupt causes, preventing a
20-byte write from being reported as 40 bytes with a corrupt suffix.
Includes a deterministic two-device Unity regression.
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