Conversation
9c1b110 to
53ff0bb
Compare
|
per https://ampcode.com/threads/T-019cfaf6-743c-757d-815f-f4a647d40079 usual caveats PR Review: Add I2C support to RP2 platformCommit: OverviewAdds I2C support for the Raspberry Pi Pico (RP2) platform with:
Binary/GC handling is correct — no use-after-GC bugs found. Resource rooting via Must-Fix Issues1. Use-after-deinit crash (i2cdriver.c)
Fix: Check if (UNLIKELY(IS_NULL_PTR(rsrc_obj->i2c_inst))) {
RAISE_ERROR(BADARG_ATOM);
}Make 2. Duplicate peripheral ownership (i2cdriver.c:88–131)
Fix: Track per-peripheral ownership: static bool i2c_in_use[NUM_I2C_INSTANCES] = {false, false};Return 3.
|
| Argument | Current | Should be |
|---|---|---|
addr |
cast to uint8_t |
0 ≤ addr ≤ 127 (7-bit I2C) |
baudrate |
cast to uint |
> 0 |
timeout_us |
cast to uint |
≥ 0, fits in uint |
count |
checked ≥ 0 |
also cap to a sane max |
Negative values wrap silently via unsigned cast.
5. Erlang server crash = caller hangs forever (i2c.erl)
call/2 sends a message and waits with no monitor and no timeout:
call(Pid, Request) ->
Ref = make_ref(),
Pid ! {self(), Ref, Request},
receive
{Ref, Reply} -> Reply
end.If the server process dies (e.g., bad write_byte value crashes <<Byte:8>> construction), callers block forever.
Fix: Monitor the server:
call(Pid, Request) ->
MRef = monitor(process, Pid),
Ref = make_ref(),
Pid ! {self(), Ref, Request},
receive
{Ref, Reply} ->
demonitor(MRef, [flush]),
Reply;
{'DOWN', MRef, process, Pid, Reason} ->
{error, Reason}
end.Also add input validation in handle_request/4 and a catch-all clause returning {error, badarg}.
Should-Fix Issues
6. Direct ops allowed during buffered transaction (i2c.erl:428–450)
This is currently permitted but surprising:
begin_transmission(I2C, Addr),
write_byte(I2C, 16#00),
read_bytes(I2C, Addr, 4), %% executes immediately — intended?
end_transmission(I2C).Fix: When TxState =/= undefined, reject read_bytes/write_bytes with {error, transaction_in_progress}. Only allow write_byte, write_bytes (tx variant), end_transmission, and close.
7. open/1 missing parameter validation (i2c.erl:116–127)
- Missing
{scl, _}or{sda, _}→proplists:get_valuereturnsundefined→ crash ingpio:set_function/2 - No check that SCL ≠ SDA
- No check that pins are compatible with the selected peripheral
- If pin muxing succeeds but
init/2fails, pins are left reconfigured with no rollback
Fix: Validate before side effects:
open(Params) ->
SCL = proplists:get_value(scl, Params),
SDA = proplists:get_value(sda, Params),
(is_integer(SCL) andalso is_integer(SDA) andalso SCL =/= SDA)
orelse error(badarg),
...8. Blocking NIFs risk stalling the VM
The high-level API wraps blocking NIFs. With send_timeout_ms = infinity (or large timeouts), a wedged I2C bus can stall the entire scheduler.
Recommendation: Default to finite timeouts in the high-level API and document that infinity may block the VM.
What Looks Good
- GC / binary handling —
memory_ensure_free_with_rootscorrectly roots the resource term; read paths pre-allocate withMEMORY_NO_GC; write paths useterm_binary_dataimmediately without intervening allocations - Resource destructor —
i2c_resource_dtorchecks for NULL before callingi2c_deinit gpio:set_function/2— clean implementation with proper pin range and atom table validation- API design — dual low-level/high-level API is well structured and consistent with the existing ESP32
i2c_halbehavior pattern - Examples — clear, practical, well-documented
- Documentation — thorough edoc with type specs throughout
i2c.erl
Suggested Test Cases
Before merge, these scenarios should be covered:
-
deinit(Resource)called twice →ok(idempotent) - Operation after
deinit(Resource)→badargor{error, closed} -
init(0, ...)called twice →{error, busy} -
write_blocking(Resource, Addr, Data, not_a_boolean)→badarg - Invalid address:
-1,128,300→badarg - Invalid timeout:
-1→badarg -
write_byte(Pid, 300)→ error, not server crash -
read_bytes(Pid, Addr, -1)→ error, not server crash - Call to dead server pid →
{error, Reason}, not hang -
read_bytesduring activebegin_transmission→{error, transaction_in_progress} -
open([])(missing scl/sda) → clean error
…afety, call monitoring, open validation Address valid findings from Opus 4.6 review of PR atomvm#2112: - Track per-peripheral ownership with i2c_in_use[] to prevent duplicate init() on the same hardware controller (returns {error, busy}) - Replace permissive term_to_nostop with strict term_to_bool that rejects non-boolean atoms with badarg instead of silently treating them as false - Make deinit/1 idempotent: double-deinit returns ok instead of raising - Add process monitor in call/2 so callers get {error, {server_died, Reason}} instead of blocking forever if the server process dies - Validate SCL/SDA parameters in open/1: must be integers and distinct - Add catch-all clause in handle_request to return {error, badarg} for unknown messages instead of crashing the server https://claude.ai/code/session_01E5K3QqgCxL8hazDrrTiTWs
Provide low-level API (nifs) and high-level API following `i2c_hal` behavior Also create cross-platform i2c examples: - i2c_scanner - i2c_lis3dh Signed-off-by: Paul Guyot <pguyot@kallisys.net>
Continuation of #2111
These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).
SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later