From 5c33f1311e4d5903f69404f31ff37e89d19b5584 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sun, 28 Jan 2024 18:11:32 +0100 Subject: [PATCH] ESP32: allow coexistence of native I2C drivers and Erlang ones Using `i2c_driver_acquire` and `i2c_driver_release` it is possible to implement native I2C drivers that coexist with erlang ones, without corruptions or race conditions. This API has been designed on top of deprecated I2C API, so a new one might be needed. Signed-off-by: Davide Bettio --- CHANGELOG.md | 1 + .../components/avm_builtins/i2c_driver.c | 81 +++++++++++++++++-- .../avm_builtins/include/i2c_driver.h | 50 ++++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/platforms/esp32/components/avm_builtins/include/i2c_driver.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 800284bd8..42f931126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ESP32: Added support to SPI peripherals other than hspi and vspi - Added `gpio:set_int/4`, with the 4th parameter being the pid() or registered name of the process to receive interrupt messages - Added support for `lists:split/2` +- Added ESP32 API for allowing coexistence of native and Erlang I2C drivers ### Changed diff --git a/src/platforms/esp32/components/avm_builtins/i2c_driver.c b/src/platforms/esp32/components/avm_builtins/i2c_driver.c index da6ebff98..ec9337792 100644 --- a/src/platforms/esp32/components/avm_builtins/i2c_driver.c +++ b/src/platforms/esp32/components/avm_builtins/i2c_driver.c @@ -49,6 +49,8 @@ #include "esp32_sys.h" #include "sys.h" +#include "include/i2c_driver.h" + #define TAG "i2c_driver" static void i2c_driver_init(GlobalContext *global); @@ -89,6 +91,9 @@ struct I2CData i2c_cmd_handle_t cmd; term transmitting_pid; i2c_port_t i2c_num; + + // no need to make it atomic, we use it only when the process table is locked + int ref_count; }; #define I2C_VALIDATE_NOT_INVALID(moniker) \ @@ -106,6 +111,7 @@ void i2c_driver_init(GlobalContext *global) Context *i2c_driver_create_port(GlobalContext *global, term opts) { struct I2CData *i2c_data = calloc(1, sizeof(struct I2CData)); + i2c_data->ref_count = 1; i2c_data->transmitting_pid = term_invalid_term(); term scl_io_num_term = interop_kv_get_value(opts, ATOM_STR("\x3", "scl"), global); @@ -165,16 +171,23 @@ Context *i2c_driver_create_port(GlobalContext *global, term opts) return NULL; } -static void i2c_driver_close(Context *ctx) +static NativeHandlerResult i2c_driver_maybe_close(Context *ctx) { struct I2CData *i2c_data = ctx->platform_data; + if (--i2c_data->ref_count != 0) { + return NativeContinue; + } + + ctx->platform_data = NULL; esp_err_t err = i2c_driver_delete(i2c_data->i2c_num); if (UNLIKELY(err != ESP_OK)) { ESP_LOGW(TAG, "Failed to delete I2C driver. err=%i", err); } - free(ctx->platform_data); - ctx->platform_data = NULL; + + free(i2c_data); + + return NativeTerminate; } static term i2cdriver_begin_transmission(Context *ctx, term pid, term req) @@ -564,6 +577,7 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx) int local_process_id = term_to_local_process_id(gen_message.pid); term ret; + NativeHandlerResult handler_result = NativeContinue; enum i2c_cmd cmd = interop_atom_term_select_int(cmd_table, cmd_term, ctx->global); switch (cmd) { @@ -591,7 +605,11 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx) } break; case I2CCloseCmd: - i2c_driver_close(ctx); + // ugly hack: we lock before closing so _release and _acquire can assume + // ctx->platform is not changed. + globalcontext_get_process_lock(ctx->global, ctx->process_id); + handler_result = i2c_driver_maybe_close(ctx); + globalcontext_get_process_unlock(ctx->global, ctx); ret = OK_ATOM; break; @@ -610,7 +628,60 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx) globalcontext_send_message(ctx->global, local_process_id, ret_msg); mailbox_remove_message(&ctx->mailbox, &ctx->heap); - return cmd == I2CCloseCmd ? NativeTerminate : NativeContinue; + return handler_result; +} + +I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global) +{ + if (UNLIKELY(!term_is_pid(i2c_port))) { + ESP_LOGW(TAG, "Given term is not a I2C port driver."); + return I2CAcquireInvalidPeripheral; + } + + int local_process_id = term_to_local_process_id(i2c_port); + Context *ctx = globalcontext_get_process_lock(global, local_process_id); + + if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox) + || (ctx->platform_data == NULL)) { + ESP_LOGW(TAG, "Given term is not a I2C port driver."); + globalcontext_get_process_unlock(global, ctx); + return I2CAcquireInvalidPeripheral; + } + + struct I2CData *i2c_data = ctx->platform_data; + i2c_data->ref_count++; + + *i2c_num = i2c_data->i2c_num; + + globalcontext_get_process_unlock(global, ctx); + + return I2CAcquireOk; +} + +void i2c_driver_release(term i2c_port, GlobalContext *global) +{ + if (UNLIKELY(!term_is_pid(i2c_port))) { + ESP_LOGW(TAG, "Given term is not a I2C port driver."); + return; + } + + int local_process_id = term_to_local_process_id(i2c_port); + Context *ctx = globalcontext_get_process_lock(global, local_process_id); + + if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox) + || (ctx->platform_data == NULL)) { + ESP_LOGW(TAG, "Given term is not a I2C port driver."); + globalcontext_get_process_unlock(global, ctx); + return; + } + + struct I2CData *i2c_data = ctx->platform_data; + i2c_data->ref_count--; + NativeHandlerResult close_result = i2c_driver_maybe_close(ctx); + if (close_result == NativeTerminate) { + mailbox_send_term_signal(ctx, KillSignal, NORMAL_ATOM); + } + globalcontext_get_process_unlock(global, ctx); } // diff --git a/src/platforms/esp32/components/avm_builtins/include/i2c_driver.h b/src/platforms/esp32/components/avm_builtins/include/i2c_driver.h new file mode 100644 index 000000000..bb200e68f --- /dev/null +++ b/src/platforms/esp32/components/avm_builtins/include/i2c_driver.h @@ -0,0 +1,50 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2024 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _I2C_DRIVER_H_ +#define _I2C_DRIVER_H_ + +#include + +#include +#include + +#define ATOMVM_ESP32_I2C_OLD_API 1 + +enum I2CAcquireOpts +{ + I2CAcquireNoOpts +}; + +enum I2CAcquireResult +{ + I2CAcquireOk, + I2CAcquireInvalidPeripheral +}; + +typedef enum I2CAcquireResult I2CAcquireResult; + +// These functions are meant for integrating native drivers with the I2C port driver +// defined as following only when ATOMVM_ESP32_I2C_OLD_API is set +// it will be changed in future. +I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global); +void i2c_driver_release(term i2c_port, GlobalContext *global); + +#endif