Skip to content

Commit

Permalink
ESP32: allow coexistence of native I2C drivers and Erlang ones
Browse files Browse the repository at this point in the history
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 <davide@uninstall.it>
  • Loading branch information
bettio committed Feb 6, 2024
1 parent d46f2f8 commit 5c33f13
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 5c33f13

Please sign in to comment.