Skip to content

Commit

Permalink
Merge pull request atomvm#1017 from bettio/acquire-i2c-api
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bettio committed Feb 7, 2024
2 parents d46f2f8 + 5c33f13 commit 8a3eca7
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
81 changes: 76 additions & 5 deletions src/platforms/esp32/components/avm_builtins/i2c_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) \
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}

//
Expand Down
50 changes: 50 additions & 0 deletions src/platforms/esp32/components/avm_builtins/include/i2c_driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This file is part of AtomVM.
*
* Copyright 2024 Davide Bettio <davide@uninstall.it>
*
* 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 <driver/i2c.h>

#include <globalcontext.h>
#include <term.h>

#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

0 comments on commit 8a3eca7

Please sign in to comment.