Packages
circuits_gpio
2.3.0
2.3.0
2.2.0
2.1.3
2.1.2
retired
2.1.1
retired
2.1.0
retired
2.0.2
retired
2.0.1
retired
2.0.0
retired
2.0.0-pre.6
retired
2.0.0-pre.5
retired
2.0.0-pre.4
retired
2.0.0-pre.3
retired
2.0.0-pre.2
retired
2.0.0-pre.1
retired
2.0.0-pre.0
retired
1.2.2
1.2.1
1.2.0
1.1.0
1.0.1
1.0.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.0
0.1.0
Use GPIOs in Elixir
Current section
Files
Jump to
Current section
Files
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)