Current section

Files

Jump to
circuits_gpio c_src gpio_nif.c
Raw

c_src/gpio_nif.c

// SPDX-FileCopyrightText: 2018 Frank Hunleth
// SPDX-FileCopyrightText: 2018 Mark Sebald
// SPDX-FileCopyrightText: 2018 Matt Ludwigs
// SPDX-FileCopyrightText: 2023 Connor Rigby
//
// SPDX-License-Identifier: Apache-2.0
#include "gpio_nif.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
ERL_NIF_TERM atom_ok;
ERL_NIF_TERM atom_error;
ERL_NIF_TERM atom_name;
ERL_NIF_TERM atom_label;
ERL_NIF_TERM atom_location;
ERL_NIF_TERM atom_controller;
ERL_NIF_TERM atom_circuits_gpio;
ERL_NIF_TERM atom_consumer;
ERL_NIF_TERM atom_ref;
ERL_NIF_TERM atom_timestamp;
ERL_NIF_TERM atom_value;
ERL_NIF_TERM atom_previous_value;
#ifdef DEBUG
FILE *log_location = NULL;
#endif
static void release_gpio_pin(struct gpio_priv *priv, struct gpio_pin *pin)
{
hal_close_gpio(pin);
if (pin->env) {
enif_free_env(pin->env);
pin->env = NULL;
}
}
static void register_gpio_pin(struct gpio_priv *priv, struct gpio_pin *pin)
{
enif_mutex_lock(priv->gpio_pins_lock);
pin->next = priv->gpio_pins;
priv->gpio_pins = pin;
pin->registered = true;
enif_mutex_unlock(priv->gpio_pins_lock);
}
static void unregister_gpio_pin(struct gpio_priv *priv, struct gpio_pin *pin)
{
enif_mutex_lock(priv->gpio_pins_lock);
if (pin->registered) {
struct gpio_pin **current = &priv->gpio_pins;
while (*current && *current != pin)
current = &(*current)->next;
if (*current == pin)
*current = pin->next;
pin->registered = false;
pin->next = NULL;
}
enif_mutex_unlock(priv->gpio_pins_lock);
}
// Reset the pin's environment to hold exactly the terms it needs. Without this,
// repeated subscribe calls would accumulate copies in pin->env and grow it
// without bound.
static void set_pin_terms(struct gpio_pin *pin,
ERL_NIF_TERM gpio_spec,
bool notify_map,
ERL_NIF_TERM notify_id)
{
enif_clear_env(pin->env);
pin->gpio_spec = enif_make_copy(pin->env, gpio_spec);
pin->notify_id = notify_map ? enif_make_copy(pin->env, notify_id) : 0;
}
static void gpio_pin_dtor(ErlNifEnv *env, void *obj)
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin = (struct gpio_pin*) obj;
debug("gpio_pin_dtor called on pin={%s,%d+%d}", pin->gpiochip, pin->offsets[0], pin->num_lines);
unregister_gpio_pin(priv, pin);
release_gpio_pin(priv, pin);
}
static void gpio_pin_stop(ErlNifEnv *env, void *obj, int fd, int is_direct_call)
{
(void) env;
(void) obj;
(void) fd;
(void) is_direct_call;
//struct gpio_priv *priv = enif_priv_data(env);
#ifdef DEBUG
struct gpio_pin *pin = (struct gpio_pin*) obj;
debug("gpio_pin_stop called %s, pin={%s,%d}", (is_direct_call ? "DIRECT" : "LATER"), pin->gpiochip, pin->offsets[0]);
#endif
}
static void gpio_pin_down(ErlNifEnv *env, void *obj, ErlNifPid *pid, ErlNifMonitor *monitor)
{
(void) env;
(void) obj;
(void) pid;
(void) monitor;
#ifdef DEBUG
struct gpio_pin *pin = (struct gpio_pin*) obj;
debug("gpio_pin_down called on pin={%s,%d}", pin->gpiochip, pin->offsets[0]);
#endif
}
#if (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 16)
// OTP-24 and later
static ErlNifResourceTypeInit gpio_pin_init = {gpio_pin_dtor, gpio_pin_stop, gpio_pin_down, 3, NULL};
#else
// Old way
static ErlNifResourceTypeInit gpio_pin_init = {gpio_pin_dtor, gpio_pin_stop, gpio_pin_down};
#endif
int send_gpio_message(ErlNifEnv *env,
ErlNifEnv *msg_env,
ERL_NIF_TERM gpio_spec,
ErlNifPid *pid,
int64_t timestamp,
int value)
{
// gpio_spec lives in the pin's environment, so it has to be copied to
// msg_env before it can be used in a term created there.
ERL_NIF_TERM msg = enif_make_tuple4(msg_env,
atom_circuits_gpio,
enif_make_copy(msg_env, gpio_spec),
enif_make_int64(msg_env, timestamp),
enif_make_int(msg_env, value));
int rc = enif_send(env, pid, msg_env, msg);
// Clear msg_env so that it can be reused. Not clearing it would leak the
// message terms until the environment is freed.
enif_clear_env(msg_env);
return rc;
}
int send_gpio_change(ErlNifEnv *env,
ErlNifEnv *msg_env,
ERL_NIF_TERM notify_id,
ErlNifPid *pid,
int64_t timestamp,
uint64_t value,
uint64_t previous_value)
{
// notify_id lives in the pin's environment, so it has to be copied to
// msg_env before it can be used in a term created there.
ERL_NIF_TERM map = enif_make_new_map(msg_env);
enif_make_map_put(msg_env, map, atom_ref, enif_make_copy(msg_env, notify_id), &map);
enif_make_map_put(msg_env, map, atom_timestamp, enif_make_int64(msg_env, timestamp), &map);
enif_make_map_put(msg_env, map, atom_value, enif_make_uint64(msg_env, value), &map);
enif_make_map_put(msg_env, map, atom_previous_value, enif_make_uint64(msg_env, previous_value), &map);
ERL_NIF_TERM msg = enif_make_tuple2(msg_env, atom_circuits_gpio, map);
int rc = enif_send(env, pid, msg_env, msg);
enif_clear_env(msg_env);
return rc;
}
bool emit_gpio_change(ErlNifEnv *env,
ErlNifEnv *msg_env,
bool notify_map,
ERL_NIF_TERM notify_term,
ErlNifPid *pid,
enum trigger_mode emit_trigger,
int64_t timestamp,
uint64_t new_value,
uint64_t previous_value,
int changed_bit)
{
int new_bit = (int) ((new_value >> changed_bit) & 1);
bool rising = new_bit != 0;
bool want;
switch (emit_trigger) {
case TRIGGER_BOTH:
want = true;
break;
case TRIGGER_RISING:
want = rising;
break;
case TRIGGER_FALLING:
want = !rising;
break;
case TRIGGER_NONE:
default:
want = false;
break;
}
if (!want)
return true;
if (notify_map)
return send_gpio_change(env, msg_env, notify_term, pid, timestamp, new_value, previous_value);
else
return send_gpio_message(env, msg_env, notify_term, pid, timestamp, new_bit);
}
static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM info)
{
(void) info;
#ifdef DEBUG
#ifdef LOG_PATH
log_location = fopen(LOG_PATH, "w");
#else
log_location = stderr;
#endif
#endif
atom_ok = enif_make_atom(env, "ok");
atom_error = enif_make_atom(env, "error");
atom_name = enif_make_atom(env, "name");
atom_label = enif_make_atom(env, "label");
atom_location = enif_make_atom(env, "location");
atom_controller = enif_make_atom(env, "controller");
atom_circuits_gpio = enif_make_atom(env, "circuits_gpio");
atom_consumer = enif_make_atom(env, "consumer");
atom_ref = enif_make_atom(env, "ref");
atom_timestamp = enif_make_atom(env, "timestamp");
atom_value = enif_make_atom(env, "value");
atom_previous_value = enif_make_atom(env, "previous_value");
size_t extra_size = hal_priv_size();
struct gpio_priv *priv = enif_alloc(sizeof(struct gpio_priv) + extra_size);
if (!priv) {
error("Can't allocate gpio_priv");
return 1;
}
priv->gpio_pin_rt = enif_open_resource_type_x(env, "gpio_pin", &gpio_pin_init, ERL_NIF_RT_CREATE, NULL);
priv->gpio_pins_lock = enif_mutex_create("gpio_pins");
priv->gpio_pins = NULL;
if (!priv->gpio_pins_lock) {
error("Can't create GPIO pin lock");
enif_free(priv);
return 1;
}
if (hal_load(&priv->hal_priv) < 0) {
error("Can't initialize HAL");
enif_mutex_destroy(priv->gpio_pins_lock);
enif_free(priv);
return 1;
}
*priv_data = (void *) priv;
return 0;
}
static void unload(ErlNifEnv *env, void *priv_data)
{
(void) env;
struct gpio_priv *priv = priv_data;
debug("unload");
hal_unload(&priv->hal_priv);
enif_mutex_destroy(priv->gpio_pins_lock);
enif_free(priv);
}
static ERL_NIF_TERM read_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 1 || !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
uint64_t value;
int rc = hal_read_gpio(pin, &value);
if (rc < 0)
return enif_raise_exception(env, make_errno_atom(env, rc));
return enif_make_uint64(env, value);
}
static ERL_NIF_TERM write_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
ErlNifUInt64 value;
if (argc != 2 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin) ||
!enif_get_uint64(env, argv[1], &value))
return enif_make_badarg(env);
if (!pin->config.is_output)
return enif_raise_exception(env, enif_make_atom(env, "pin_not_output"));
int rc = hal_write_gpio(pin, value, env);
if (rc < 0)
return enif_raise_exception(env, make_errno_atom(env, rc));
return atom_ok;
}
static int get_trigger(ErlNifEnv *env, ERL_NIF_TERM term, enum trigger_mode *mode)
{
char buffer[16];
if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1))
return false;
if (strcmp("none", buffer) == 0) *mode = TRIGGER_NONE;
else if (strcmp("rising", buffer) == 0) *mode = TRIGGER_RISING;
else if (strcmp("falling", buffer) == 0) *mode = TRIGGER_FALLING;
else if (strcmp("both", buffer) == 0) *mode = TRIGGER_BOTH;
else return false;
return true;
}
static int get_direction(ErlNifEnv *env, ERL_NIF_TERM term, bool *is_output)
{
char buffer[8];
if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1))
return false;
if (strcmp("input", buffer) == 0) *is_output = false;
else if (strcmp("output", buffer) == 0) *is_output = true;
else return false;
return true;
}
static int get_resolved_location(ErlNifEnv *env, ERL_NIF_TERM term, char *gpiochip_path, int *offset)
{
int arity;
const ERL_NIF_TERM *tuple;
ErlNifBinary gpiochip_binary;
if (!enif_get_tuple(env, term, &arity, &tuple) ||
arity != 2 ||
!enif_inspect_binary(env, tuple[0], &gpiochip_binary) ||
gpiochip_binary.size + 1 > MAX_GPIOCHIP_PATH_LEN ||
!enif_get_int(env, tuple[1], offset))
return false;
memcpy(gpiochip_path, gpiochip_binary.data, gpiochip_binary.size);
gpiochip_path[gpiochip_binary.size] = '\0';
return true;
}
// Parse a resolved group location: {gpiochip_binary, [offset, ...]}. All lines
// in a group live on the same controller.
static int get_resolved_group(ErlNifEnv *env, ERL_NIF_TERM term, char *gpiochip_path, int *offsets, int *num_lines)
{
int arity;
const ERL_NIF_TERM *tuple;
ErlNifBinary gpiochip_binary;
if (!enif_get_tuple(env, term, &arity, &tuple) ||
arity != 2 ||
!enif_inspect_binary(env, tuple[0], &gpiochip_binary) ||
gpiochip_binary.size + 1 > MAX_GPIOCHIP_PATH_LEN)
return false;
memcpy(gpiochip_path, gpiochip_binary.data, gpiochip_binary.size);
gpiochip_path[gpiochip_binary.size] = '\0';
unsigned int len;
if (!enif_get_list_length(env, tuple[1], &len) || len == 0 || len > GPIO_MAX_LINES)
return false;
ERL_NIF_TERM list = tuple[1];
ERL_NIF_TERM head, tail;
int i = 0;
while (enif_get_list_cell(env, list, &head, &tail)) {
if (!enif_get_int(env, head, &offsets[i]))
return false;
i++;
list = tail;
}
*num_lines = (int) len;
return true;
}
static int get_value(ErlNifEnv *env, ERL_NIF_TERM term, uint64_t *value)
{
ErlNifUInt64 v;
if (enif_get_uint64(env, term, &v)) {
*value = v;
} else {
// Interpret anything else as 0 for backwards compatibility
// with Circuit.GPIO v1's ":not_set". 0 is cdev's default.
*value = 0;
}
return true;
}
static int get_pull_mode(ErlNifEnv *env, ERL_NIF_TERM term, enum pull_mode *pull)
{
char buffer[16];
if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1))
return false;
if (strcmp("not_set", buffer) == 0) *pull = PULL_NOT_SET;
else if (strcmp("none", buffer) == 0) *pull = PULL_NONE;
else if (strcmp("pullup", buffer) == 0) *pull = PULL_UP;
else if (strcmp("pulldown", buffer) == 0) *pull = PULL_DOWN;
else return false;
return true;
}
static int get_drive_mode(ErlNifEnv *env, ERL_NIF_TERM term, enum drive_mode *drive)
{
char buffer[16];
if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1))
return false;
if (strcmp("push_pull", buffer) == 0) *drive = DRIVE_PUSH_PULL;
else if (strcmp("open_drain", buffer) == 0) *drive = DRIVE_OPEN_DRAIN;
else if (strcmp("open_source", buffer) == 0) *drive = DRIVE_OPEN_SOURCE;
else return false;
return true;
}
static ERL_NIF_TERM set_interrupts(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 4 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
// Groups have no single-line tuple representation; they must use subscribe/3.
if (pin->num_lines != 1)
return enif_make_tuple2(env, atom_error, enif_make_atom(env, "group_handle"));
struct gpio_config old_config = pin->config;
if (!get_trigger(env, argv[1], &pin->config.trigger) ||
!enif_get_boolean(env, argv[2], &pin->config.suppress_glitches) ||
!enif_get_local_pid(env, argv[3], &pin->config.pid)) {
pin->config = old_config;
return enif_make_badarg(env);
}
// Legacy notifications emit on exactly the hardware-detected edge and use
// the {:circuits_gpio, spec, ts, value} tuple format.
pin->config.emit_trigger = pin->config.trigger;
pin->notify_map = false;
int rc = hal_apply_interrupts(pin, env);
if (rc < 0) {
pin->config = old_config;
return make_errno_error(env, rc);
}
return atom_ok;
}
static ERL_NIF_TERM subscribe(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
// subscribe(resource, notify_id, trigger, pid)
if (argc != 4 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
struct gpio_config old_config = pin->config;
bool old_notify_map = pin->notify_map;
ERL_NIF_TERM old_gpio_spec = enif_make_copy(env, pin->gpio_spec);
ERL_NIF_TERM old_notify_id = old_notify_map ? enif_make_copy(env, pin->notify_id) : 0;
enum trigger_mode emit_trigger;
ErlNifPid pid;
if (!get_trigger(env, argv[2], &emit_trigger) ||
!enif_get_local_pid(env, argv[3], &pid)) {
return enif_make_badarg(env);
}
// Seed the shadow with the current value so the first notification's
// previous_value is well defined.
uint64_t seed;
if (hal_read_gpio(pin, &seed) >= 0)
pin->shadow = seed;
// The hardware tracks both edges so the shadow stays accurate even when the
// caller only wants one direction; emit_trigger filters what's sent.
pin->config.trigger = (emit_trigger == TRIGGER_NONE) ? TRIGGER_NONE : TRIGGER_BOTH;
pin->config.emit_trigger = emit_trigger;
pin->config.pid = pid;
pin->notify_map = true;
set_pin_terms(pin, old_gpio_spec, true, argv[1]);
int rc = hal_apply_interrupts(pin, env);
if (rc < 0) {
pin->config = old_config;
pin->notify_map = old_notify_map;
set_pin_terms(pin, old_gpio_spec, old_notify_map, old_notify_id);
return make_errno_error(env, rc);
}
return atom_ok;
}
static ERL_NIF_TERM unsubscribe(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 1 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
struct gpio_config old_config = pin->config;
pin->config.trigger = TRIGGER_NONE;
pin->config.emit_trigger = TRIGGER_NONE;
int rc = hal_apply_interrupts(pin, env);
if (rc < 0) {
pin->config = old_config;
return make_errno_error(env, rc);
}
pin->notify_map = false;
return atom_ok;
}
static ERL_NIF_TERM set_direction(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 2 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
struct gpio_config old_config = pin->config;
if (!get_direction(env, argv[1], &pin->config.is_output))
return enif_make_badarg(env);
int rc = hal_apply_direction(pin);
if (rc < 0) {
pin->config = old_config;
return make_errno_error(env, rc);
}
return atom_ok;
}
static ERL_NIF_TERM set_pull_mode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 2 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
struct gpio_config old_config = pin->config;
if (!get_pull_mode(env, argv[1], &pin->config.pull))
return enif_make_badarg(env);
int rc = hal_apply_pull_mode(pin);
if (rc < 0) {
pin->config = old_config;
return make_errno_error(env, rc);
}
return atom_ok;
}
static ERL_NIF_TERM set_drive_mode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 2 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
struct gpio_config old_config = pin->config;
if (!get_drive_mode(env, argv[1], &pin->config.drive))
return enif_make_badarg(env);
int rc = hal_apply_drive_mode(pin);
if (rc < 0) {
pin->config = old_config;
return make_errno_error(env, rc);
}
return atom_ok;
}
static ERL_NIF_TERM get_status(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
char gpiochip_path[MAX_GPIOCHIP_PATH_LEN];
int offset;
if (argc != 1 || !get_resolved_location(env, argv[0], gpiochip_path, &offset))
return enif_make_badarg(env);
ERL_NIF_TERM result;
int rc = hal_get_status(priv->hal_priv, env, gpiochip_path, offset, &result);
if (rc >= 0)
return make_ok_tuple(env, result);
else
return make_errno_error(env, rc);
}
static ERL_NIF_TERM open_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
bool is_output;
int offsets[GPIO_MAX_LINES];
int num_lines;
uint64_t initial_value;
enum pull_mode pull;
enum drive_mode drive;
char gpiochip_path[MAX_GPIOCHIP_PATH_LEN];
if (argc != 6 ||
!get_resolved_group(env, argv[1], gpiochip_path, offsets, &num_lines) ||
!get_direction(env, argv[2], &is_output) ||
!get_value(env, argv[3], &initial_value) ||
!get_pull_mode(env, argv[4], &pull) ||
!get_drive_mode(env, argv[5], &drive))
return enif_make_badarg(env);
debug("open {%s, %d lines}", gpiochip_path, num_lines);
struct gpio_pin *pin = enif_alloc_resource(priv->gpio_pin_rt, sizeof(struct gpio_pin));
pin->fd = -1;
memcpy(pin->gpiochip, gpiochip_path, MAX_GPIOCHIP_PATH_LEN);
pin->num_lines = num_lines;
memcpy(pin->offsets, offsets, sizeof(int) * num_lines);
pin->shadow = 0;
pin->env = enif_alloc_env();
pin->gpio_spec = enif_make_copy(pin->env, argv[0]);
pin->notify_id = 0;
pin->notify_map = false;
pin->next = NULL;
pin->registered = false;
pin->hal_priv = priv->hal_priv;
pin->config.is_output = is_output;
pin->config.trigger = TRIGGER_NONE;
pin->config.emit_trigger = TRIGGER_NONE;
pin->config.pull = pull;
pin->config.drive = drive;
pin->config.suppress_glitches = false;
pin->config.initial_value = initial_value;
int rc = hal_open_gpio(pin, env);
if (rc < 0) {
enif_release_resource(pin);
return make_errno_error(env, rc);
}
register_gpio_pin(priv, pin);
// Transfer ownership of the resource to Erlang so that it can be garbage collected.
ERL_NIF_TERM pin_resource = enif_make_resource(env, pin);
enif_release_resource(pin);
return make_ok_tuple(env, pin_resource);
}
static ERL_NIF_TERM close_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
struct gpio_pin *pin;
if (argc != 1 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin))
return enif_make_badarg(env);
release_gpio_pin(priv, pin);
return atom_ok;
}
static bool pin_references_gpio(struct gpio_pin *pin, const char *gpiochip_path, int offset)
{
if (strcmp(pin->gpiochip, gpiochip_path) != 0)
return false;
for (int i = 0; i < pin->num_lines; i++) {
if (pin->offsets[i] == offset)
return true;
}
return false;
}
static ERL_NIF_TERM force_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct gpio_priv *priv = enif_priv_data(env);
char gpiochip_path[MAX_GPIOCHIP_PATH_LEN];
int offset;
if (argc != 1 || !get_resolved_location(env, argv[0], gpiochip_path, &offset))
return enif_make_badarg(env);
enif_mutex_lock(priv->gpio_pins_lock);
for (struct gpio_pin *pin = priv->gpio_pins; pin; pin = pin->next) {
if (pin_references_gpio(pin, gpiochip_path, offset)) {
// Close the GPIO, but don't free up everything until the pin
// has been properly closed.
hal_close_gpio(pin);
}
}
enif_mutex_unlock(priv->gpio_pins_lock);
return atom_ok;
}
static ERL_NIF_TERM backend_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
(void) argc;
(void) argv;
struct gpio_priv *priv = enif_priv_data(env);
ERL_NIF_TERM info = enif_make_new_map(env);
return hal_info(env, priv->hal_priv, info);
}
static ERL_NIF_TERM gpio_enumerate(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
(void) argc;
(void) argv;
struct gpio_priv *priv = enif_priv_data(env);
return hal_enumerate(env, priv->hal_priv);
}
static ErlNifFunc nif_funcs[] = {
{"open", 6, open_gpio, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"close", 1, close_gpio, 0},
{"force_close", 1, force_close, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"read", 1, read_gpio, 0},
{"write", 2, write_gpio, 0},
{"set_interrupts", 4, set_interrupts, 0},
{"subscribe", 4, subscribe, 0},
{"unsubscribe", 1, unsubscribe, 0},
{"set_direction", 2, set_direction, 0},
{"set_pull_mode", 2, set_pull_mode, 0},
{"set_drive_mode", 2, set_drive_mode, 0},
{"status", 1, get_status, 0},
{"backend_info", 0, backend_info, 0},
{"enumerate", 0, gpio_enumerate, 0}
};
ERL_NIF_INIT(Elixir.Circuits.GPIO.Nif, nif_funcs, load, NULL, NULL, unload)