Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug in `supervisor` handling of failing child
- Fixed two bugs related to closing fds in `atomvm:subprocess/4`
- Fixed `erlang:localtime/1` memory leak, use-after-free, and TZ restore bugs on newlib/picolibc
- Fixed ESP32 I2C driver resource leaks, half-closed state, and close-during-transmission errors

## [0.7.0-alpha.1] - 2026-04-06

Expand Down
234 changes: 188 additions & 46 deletions src/platforms/esp32/components/avm_builtins/i2c_resource.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ static term create_error_tuple(Context *ctx, term reason)
return create_pair(ctx, ERROR_ATOM, reason);
}

static bool is_i2c_resource_open(const struct I2CResource *rsrc_obj)
{
return rsrc_obj->i2c_num != I2C_NUM_MAX;
}

static void reset_i2c_transmission_state(struct I2CResource *rsrc_obj)
{
if (rsrc_obj->cmd != NULL) {
i2c_cmd_link_delete(rsrc_obj->cmd);
rsrc_obj->cmd = NULL;
}
rsrc_obj->transmitting_pid = term_invalid_term();
}

static esp_err_t close_i2c_resource(struct I2CResource *rsrc_obj)
{
if (!is_i2c_resource_open(rsrc_obj)) {
return ESP_OK;
}

esp_err_t err = i2c_driver_delete(rsrc_obj->i2c_num);
if (err == ESP_OK) {
rsrc_obj->i2c_num = I2C_NUM_MAX;
reset_i2c_transmission_state(rsrc_obj);
}

return err;
}

static bool is_i2c_resource(GlobalContext *global, term t)
{
bool ret = term_is_tuple(t)
Expand All @@ -110,6 +139,15 @@ static bool to_i2c_resource(term i2c_resource, struct I2CResource **rsrc_obj, Co
return true;
}

static bool to_open_i2c_resource(term i2c_resource, struct I2CResource **rsrc_obj, Context *ctx)
{
if (!to_i2c_resource(i2c_resource, rsrc_obj, ctx)) {
return false;
}

return is_i2c_resource_open(*rsrc_obj);
}

//
// i2c:open_nif/1
//
Expand Down Expand Up @@ -214,10 +252,11 @@ static term nif_i2c_open(Context *ctx, int argc, term argv[])
}
rsrc_obj->transmitting_pid = term_invalid_term();
rsrc_obj->i2c_num = i2c_num;
rsrc_obj->cmd = NULL;
rsrc_obj->send_timeout_ms = send_timeout_ms_val;

if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
i2c_driver_delete(i2c_num);
close_i2c_resource(rsrc_obj);
enif_release_resource(rsrc_obj);
ESP_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
Expand All @@ -232,7 +271,7 @@ static term nif_i2c_open(Context *ctx, int argc, term argv[])
// {'$i2c', Resource :: resource(), Ref :: reference()} :: i2c()
size_t requested_size = TUPLE_SIZE(3) + REF_SIZE;
if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
i2c_driver_delete(i2c_num);
close_i2c_resource(rsrc_obj);
ESP_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}
Expand All @@ -255,20 +294,40 @@ static term nif_i2c_close(Context *ctx, int argc, term argv[])
{
TRACE("nif_close\n");
UNUSED(argc);
GlobalContext *global = ctx->global;

//
// extract the resource
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}

//
// reject close if another process has an active transmission
//
if (UNLIKELY(!term_is_invalid_term(rsrc_obj->transmitting_pid))) {
ESP_LOGE(TAG, "nif_close: A process is in the process of transmitting.");

// {error, {einprogress, Pid :: pid()}}
if (UNLIKELY(memory_ensure_free(ctx, 2 * TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
ESP_LOGW(TAG, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__);
return OUT_OF_MEMORY_ATOM;
}
term reason = create_pair(
ctx,
globalcontext_make_atom(global, EINPROGRESS_ATOMSTR),
rsrc_obj->transmitting_pid
);
return create_error_tuple(ctx, reason);
}

esp_err_t err;

err = i2c_driver_delete(rsrc_obj->i2c_num);
err = close_i2c_resource(rsrc_obj);
CHECK_ERROR(ctx, err, "nif_close; Failed to delete driver");

return OK_ATOM;
Expand All @@ -289,7 +348,7 @@ static term nif_i2c_write_bytes(Context *ctx, int argc, term argv[])
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -351,27 +410,49 @@ static term nif_i2c_write_bytes(Context *ctx, int argc, term argv[])

esp_err_t err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
{
err = i2c_master_start(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue start bit");

err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue I2C bus address");
err = i2c_master_start(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_write_bytes; enqueue start bit: err: %i.", err);
goto cleanup_write;
}

if (!term_is_invalid_term(register_)) {
err = i2c_master_write_byte(cmd, register_address, ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue register address");
err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_write_bytes; enqueue I2C bus address: err: %i.", err);
goto cleanup_write;
}

if (!term_is_invalid_term(register_)) {
err = i2c_master_write_byte(cmd, register_address, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_write_bytes; enqueue register address: err: %i.", err);
goto cleanup_write;
}
}

err = i2c_master_write(cmd, buf, data_len, ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue write data");
err = i2c_master_write(cmd, buf, data_len, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_write_bytes; enqueue write data: err: %i.", err);
goto cleanup_write;
}

err = i2c_master_stop(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue stop bit");
err = i2c_master_stop(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_write_bytes; enqueue stop bit: err: %i.", err);
goto cleanup_write;
}

err = i2c_master_cmd_begin(rsrc_obj->i2c_num, cmd, MS_TO_TICKS(rsrc_obj->send_timeout_ms));

cleanup_write:
i2c_cmd_link_delete(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; run the command");
if (UNLIKELY(err != ESP_OK)) {
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
return create_error_tuple(ctx, esp_err_to_term(ctx->global, err));
}

//
// return result
Expand All @@ -395,7 +476,7 @@ static term nif_i2c_read_bytes(Context *ctx, int argc, term argv[])
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -456,28 +537,59 @@ static term nif_i2c_read_bytes(Context *ctx, int argc, term argv[])

esp_err_t err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
{
err = i2c_master_start(cmd);
CHECK_ERROR(ctx, err, "nif_read_bytes; enqueue start bit");

if (!term_is_invalid_term(register_)) {
err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
err = i2c_master_write_byte(cmd, register_address, ACK_ENABLE);
err = i2c_master_start(cmd);
err = i2c_master_start(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue start bit: err: %i.", err);
goto cleanup_read;
}

if (!term_is_invalid_term(register_)) {
err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue write address: err: %i.", err);
goto cleanup_read;
}
err = i2c_master_write_byte(cmd, register_address, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue register address: err: %i.", err);
goto cleanup_read;
}
err = i2c_master_start(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue repeated start: err: %i.", err);
goto cleanup_read;
}
}

err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_read_bytes; enqueue I2C bus address");
err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue I2C bus address: err: %i.", err);
goto cleanup_read;
}

err = i2c_master_read(cmd, buf, read_count, I2C_MASTER_LAST_NACK);
CHECK_ERROR(ctx, err, "nif_read_bytes; enqueue write data");
err = i2c_master_read(cmd, buf, read_count, I2C_MASTER_LAST_NACK);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue read data: err: %i.", err);
goto cleanup_read;
}

err = i2c_master_stop(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue stop bit");
err = i2c_master_stop(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_read_bytes; enqueue stop bit: err: %i.", err);
goto cleanup_read;
}

err = i2c_master_cmd_begin(rsrc_obj->i2c_num, cmd, MS_TO_TICKS(rsrc_obj->send_timeout_ms));

cleanup_read:
i2c_cmd_link_delete(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; run the command");
if (UNLIKELY(err != ESP_OK)) {
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
return create_error_tuple(ctx, esp_err_to_term(ctx->global, err));
}

//
// return result
Expand All @@ -502,7 +614,7 @@ static term nif_i2c_begin_transmission(Context *ctx, int argc, term argv[])
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -539,17 +651,30 @@ static term nif_i2c_begin_transmission(Context *ctx, int argc, term argv[])

esp_err_t err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
{
err = i2c_master_start(cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue start bit");

err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue I2C bus address");
err = i2c_master_start(cmd);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_begin_transmission; enqueue start bit: err: %i.", err);
goto cleanup_begin;
}

err = i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_ENABLE);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_begin_transmission; enqueue I2C bus address: err: %i.", err);
goto cleanup_begin;
}

rsrc_obj->transmitting_pid = term_from_local_process_id(ctx->process_id);
rsrc_obj->cmd = cmd;

return OK_ATOM;

cleanup_begin:
i2c_cmd_link_delete(cmd);
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
return create_error_tuple(ctx, esp_err_to_term(ctx->global, err));
}

//
Expand All @@ -567,7 +692,7 @@ static term nif_i2c_enqueue_write_bytes(Context *ctx, int argc, term argv[])
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -599,7 +724,14 @@ static term nif_i2c_enqueue_write_bytes(Context *ctx, int argc, term argv[])
esp_err_t err;
for (size_t i = 0; i < len; ++i) {
err = i2c_master_write_byte(rsrc_obj->cmd, buf[i], ACK_ENABLE);
CHECK_ERROR(ctx, err, "nif_enqueue_write_bytes; enqueue i2c_master_write_byte");
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_enqueue_write_bytes; enqueue i2c_master_write_byte: err: %i.", err);
reset_i2c_transmission_state(rsrc_obj);
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
return create_error_tuple(ctx, esp_err_to_term(ctx->global, err));
}
}

return OK_ATOM;
Expand All @@ -620,7 +752,7 @@ static term nif_i2c_end_transmission(Context *ctx, int argc, term argv[])
//
term i2c_resource = argv[0];
struct I2CResource *rsrc_obj;
if (UNLIKELY(!to_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
if (UNLIKELY(!to_open_i2c_resource(i2c_resource, &rsrc_obj, ctx))) {
ESP_LOGE(TAG, "Failed to convert i2c_resource");
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -651,13 +783,23 @@ static term nif_i2c_end_transmission(Context *ctx, int argc, term argv[])

esp_err_t err;
err = i2c_master_stop(rsrc_obj->cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; enqueue stop bit");
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGE(TAG, "nif_end_transmission; enqueue stop bit: err: %i.", err);
goto cleanup_end;
}

err = i2c_master_cmd_begin(rsrc_obj->i2c_num, rsrc_obj->cmd, MS_TO_TICKS(rsrc_obj->send_timeout_ms));
i2c_cmd_link_delete(rsrc_obj->cmd);
CHECK_ERROR(ctx, err, "nif_write_bytes; run the command");

cleanup_end:
i2c_cmd_link_delete(rsrc_obj->cmd);
rsrc_obj->cmd = NULL;
rsrc_obj->transmitting_pid = term_invalid_term();
if (UNLIKELY(err != ESP_OK)) {
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
return create_error_tuple(ctx, esp_err_to_term(ctx->global, err));
}

return OK_ATOM;
}
Expand All @@ -671,7 +813,7 @@ static void i2c_resource_dtor(ErlNifEnv *caller_env, void *obj)
UNUSED(caller_env);
struct I2CResource *rsrc_obj = (struct I2CResource *) obj;

esp_err_t err = i2c_driver_delete(rsrc_obj->i2c_num);
esp_err_t err = close_i2c_resource(rsrc_obj);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGW(TAG, "Failed to delete driver in resource d'tor. err=%i", err);
}
Expand Down
Loading
Loading