Explicit I2C bus recovery

nx_i2c_bus_clear_t is an optional caller-owned GPIO recovery mechanism. It neither scans devices nor replays failed transactions, and does not automatically replace the SoC’s nx_i2c_port_recover. Existing controller recovery is separate: it proves controller stop and removes hardware references; this mechanism needs explicit board authorization before switching both pins and driving the electrical bus.

The public interface is io/include/nexus/io/i2c_bus_clear.h, and the algorithm is io/src/i2c_bus_clear.c. Production remains C11, with no heap, default worker, dynamic registry or hidden OS lock. The caller retains the session, read-only operations table, adapter context and clock, and follows the single-executor contract; these objects must remain alive until the lease is successfully returned.

Board authorization and resource handoff

The caller explicitly supplies five methods, acquire/drive/sample/wait_until/release, and an nx_clock_t. Successful acquire means the controller is disabled, DMA and IRQ publishers are drained, other bus masters and pin users are quiescent, SCL/SDA are exclusively owned, and both pins are safely released in open-drain mode. Failure must retain no references or leases and leave no unreported hardware effects.

For drive(scl_low, sda_low), true permits only driving low and false permits only high-impedance release. Public nx_gpio_port_write does not prove open-drain mode, voltage, pull-ups or pin multiplexing and cannot directly replace this authorization contract. sample must read actual input levels. The adapter must review PCB revision, both pin permissions, pull-ups, voltage and ownership handoff first; chip capabilities do not establish wiring. This delivery does not automatically bind F407/F470 physical pins or operate unreviewed boards.

When scl_low=true, the adapter must establish SCL LOW before changing SDA. The pins may belong to different GPIO ports, so atomic dual-pin register writes cannot be assumed; particularly when retrying STOP from retained failure state, SDA’s previous output state may be unknown.

release restores saved peripheral/pin ownership. Failure must retain the lease and saved state; the caller cannot re-enable the controller or reuse the context. All methods are bounded; acquire/release/drive/sample do not wait for another owner. Only explicit wait_until waits against absolute time. The wait adapter must prove a finite return bound and report failure even if the clock stops; it cannot wait forever inside a callback on a frozen timer. The helper cannot preempt or repair a callback violating this contract. It rereads the clock after callback return and reports deadline failure if late. The caller must not recursively call this session from a callback.

Execution and stop

The caller explicitly selects nonzero low_us/high_us/stop_setup_us/stretch_poll_us from actual device and electrical-mode requirements. There are no default ‘safe’ values. run takes a finite primary deadline and independent cleanup deadline in the same monotonic clock domain; cleanup must not precede primary. NX_DEADLINE_NEVER, invalid timing and missing methods are rejected before hardware access.

After acquiring the lease, the mechanism observes actual SCL/SDA. If SCL is held low, it waits for clock stretching with a bounded poll interval. Every successful wait_until is followed by a clock reread to prove progress to the target. A frozen, backward or exhausted clock reports IO and exits. High hold time starts only after actual HIGH is observed following SCL release. If SDA is already released, no recovery pulses are generated; otherwise at most nine complete low/high cycles occur, stopping immediately once SDA releases.

At the end of the low phase, sampling also confirms actual SCL LOW; at the end of a released high cycle, it confirms SCL remains HIGH. STOP first verifies both lines LOW, and after setup again verifies SCL HIGH and SDA LOW before releasing SDA. Host models cannot prove unsampled transients between observations; real waveform acceptance remains required.

Every execution that acquired a lease attempts STOP within the cleanup budget: drive SCL low, drive SDA low while SCL is low, hold low, release SCL and wait for actual HIGH, hold STOP setup time, release SDA, then hold high and observe both lines HIGH. STOP preparation edges do not count toward the nine recovery pulses, and an incomplete high cycle is not counted as completed. Failed STOP is never reported as bus free.

STOP and recovery state are not transaction completion. Even successful bus recovery leaves the original failed data operation failed; starting a new transaction is external product policy.

Separate errors and retained state

Outputs include primary/cleanup/pulses/stop_observed/lease_held. run returns the primary failure if present, otherwise cleanup; cleanup never overwrites the primary error. Without an acquired lease, release is not called. After acquisition, failed STOP or a failed release method retains the session and full lease and stops new transactions and recovery pulses.

finish retries only unproven STOP or ownership return; it neither reacquires nor generates recovery pulses. If STOP is proven but release failed, the next call retries release only; successful steps are not repeated. finish returns the latest cleanup, preserving the original primary and pulse count in outputs. A new run or reinitialization is rejected while a lease remains; reinitialization cannot conceal residual state.

If the release method actually succeeds but returns after the deadline, cleanup reports TIMEOUT while lease_held=false truthfully records returned resources. A deadline error cannot invent a lease. Other retained failures prohibit releasing, discarding or moving session/context; the product must isolate the faulty device and follow explicit recovery/reset policy. The mechanism neither resets automatically nor claims safe outputs.

Verification boundary

GoogleTest/GoogleMock execute the real production algorithm with external pin/clock/lease mocks to verify the nine-cycle bound, SCL stretching, frozen clocks, primary/cleanup separation, retained STOP failures, cleanup-only retries and instance isolation. This proves the software contract, not real open-drain operation or waveforms.

Physical qualification requires actual station binding and raw level capture, measuring SCL low/high, SDA STOP setup, stretching and pin/peripheral ownership after failure. Pull-ups, rise time, multi-master exclusion, stuck-low devices and SDA release timing must be tested using real equipment; boards and devices determine numeric budgets. All relevant physical status remains not_executed.