Skip to content

Commit 8a3eca7

Browse files
committed
Merge pull request #1017 from bettio/acquire-i2c-api
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. Note: I2C API (the legacy one) from v4.4 until v5.1 is thread safe, new API is not, but we are not using it. 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
2 parents d46f2f8 + 5c33f13 commit 8a3eca7

File tree

3 files changed

+127
-5
lines changed

3 files changed

+127
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- ESP32: Added support to SPI peripherals other than hspi and vspi
1313
- Added `gpio:set_int/4`, with the 4th parameter being the pid() or registered name of the process to receive interrupt messages
1414
- Added support for `lists:split/2`
15+
- Added ESP32 API for allowing coexistence of native and Erlang I2C drivers
1516

1617
### Changed
1718

src/platforms/esp32/components/avm_builtins/i2c_driver.c

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
#include "esp32_sys.h"
5050
#include "sys.h"
5151

52+
#include "include/i2c_driver.h"
53+
5254
#define TAG "i2c_driver"
5355

5456
static void i2c_driver_init(GlobalContext *global);
@@ -89,6 +91,9 @@ struct I2CData
8991
i2c_cmd_handle_t cmd;
9092
term transmitting_pid;
9193
i2c_port_t i2c_num;
94+
95+
// no need to make it atomic, we use it only when the process table is locked
96+
int ref_count;
9297
};
9398

9499
#define I2C_VALIDATE_NOT_INVALID(moniker) \
@@ -106,6 +111,7 @@ void i2c_driver_init(GlobalContext *global)
106111
Context *i2c_driver_create_port(GlobalContext *global, term opts)
107112
{
108113
struct I2CData *i2c_data = calloc(1, sizeof(struct I2CData));
114+
i2c_data->ref_count = 1;
109115
i2c_data->transmitting_pid = term_invalid_term();
110116

111117
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)
165171
return NULL;
166172
}
167173

168-
static void i2c_driver_close(Context *ctx)
174+
static NativeHandlerResult i2c_driver_maybe_close(Context *ctx)
169175
{
170176
struct I2CData *i2c_data = ctx->platform_data;
177+
if (--i2c_data->ref_count != 0) {
178+
return NativeContinue;
179+
}
180+
181+
ctx->platform_data = NULL;
171182

172183
esp_err_t err = i2c_driver_delete(i2c_data->i2c_num);
173184
if (UNLIKELY(err != ESP_OK)) {
174185
ESP_LOGW(TAG, "Failed to delete I2C driver. err=%i", err);
175186
}
176-
free(ctx->platform_data);
177-
ctx->platform_data = NULL;
187+
188+
free(i2c_data);
189+
190+
return NativeTerminate;
178191
}
179192

180193
static term i2cdriver_begin_transmission(Context *ctx, term pid, term req)
@@ -564,6 +577,7 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
564577
int local_process_id = term_to_local_process_id(gen_message.pid);
565578

566579
term ret;
580+
NativeHandlerResult handler_result = NativeContinue;
567581

568582
enum i2c_cmd cmd = interop_atom_term_select_int(cmd_table, cmd_term, ctx->global);
569583
switch (cmd) {
@@ -591,7 +605,11 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
591605
}
592606
break;
593607
case I2CCloseCmd:
594-
i2c_driver_close(ctx);
608+
// ugly hack: we lock before closing so _release and _acquire can assume
609+
// ctx->platform is not changed.
610+
globalcontext_get_process_lock(ctx->global, ctx->process_id);
611+
handler_result = i2c_driver_maybe_close(ctx);
612+
globalcontext_get_process_unlock(ctx->global, ctx);
595613
ret = OK_ATOM;
596614
break;
597615

@@ -610,7 +628,60 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
610628
globalcontext_send_message(ctx->global, local_process_id, ret_msg);
611629
mailbox_remove_message(&ctx->mailbox, &ctx->heap);
612630

613-
return cmd == I2CCloseCmd ? NativeTerminate : NativeContinue;
631+
return handler_result;
632+
}
633+
634+
I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global)
635+
{
636+
if (UNLIKELY(!term_is_pid(i2c_port))) {
637+
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
638+
return I2CAcquireInvalidPeripheral;
639+
}
640+
641+
int local_process_id = term_to_local_process_id(i2c_port);
642+
Context *ctx = globalcontext_get_process_lock(global, local_process_id);
643+
644+
if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox)
645+
|| (ctx->platform_data == NULL)) {
646+
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
647+
globalcontext_get_process_unlock(global, ctx);
648+
return I2CAcquireInvalidPeripheral;
649+
}
650+
651+
struct I2CData *i2c_data = ctx->platform_data;
652+
i2c_data->ref_count++;
653+
654+
*i2c_num = i2c_data->i2c_num;
655+
656+
globalcontext_get_process_unlock(global, ctx);
657+
658+
return I2CAcquireOk;
659+
}
660+
661+
void i2c_driver_release(term i2c_port, GlobalContext *global)
662+
{
663+
if (UNLIKELY(!term_is_pid(i2c_port))) {
664+
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
665+
return;
666+
}
667+
668+
int local_process_id = term_to_local_process_id(i2c_port);
669+
Context *ctx = globalcontext_get_process_lock(global, local_process_id);
670+
671+
if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox)
672+
|| (ctx->platform_data == NULL)) {
673+
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
674+
globalcontext_get_process_unlock(global, ctx);
675+
return;
676+
}
677+
678+
struct I2CData *i2c_data = ctx->platform_data;
679+
i2c_data->ref_count--;
680+
NativeHandlerResult close_result = i2c_driver_maybe_close(ctx);
681+
if (close_result == NativeTerminate) {
682+
mailbox_send_term_signal(ctx, KillSignal, NORMAL_ATOM);
683+
}
684+
globalcontext_get_process_unlock(global, ctx);
614685
}
615686

616687
//
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* This file is part of AtomVM.
3+
*
4+
* Copyright 2024 Davide Bettio <[email protected]>
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
*/
20+
21+
#ifndef _I2C_DRIVER_H_
22+
#define _I2C_DRIVER_H_
23+
24+
#include <driver/i2c.h>
25+
26+
#include <globalcontext.h>
27+
#include <term.h>
28+
29+
#define ATOMVM_ESP32_I2C_OLD_API 1
30+
31+
enum I2CAcquireOpts
32+
{
33+
I2CAcquireNoOpts
34+
};
35+
36+
enum I2CAcquireResult
37+
{
38+
I2CAcquireOk,
39+
I2CAcquireInvalidPeripheral
40+
};
41+
42+
typedef enum I2CAcquireResult I2CAcquireResult;
43+
44+
// These functions are meant for integrating native drivers with the I2C port driver
45+
// defined as following only when ATOMVM_ESP32_I2C_OLD_API is set
46+
// it will be changed in future.
47+
I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global);
48+
void i2c_driver_release(term i2c_port, GlobalContext *global);
49+
50+
#endif

0 commit comments

Comments
 (0)