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;
#ifdef DEBUG
FILE *log_location = NULL;
#endif
static void release_gpio_pin(struct gpio_priv *priv, struct gpio_pin *pin)
{
if (pin->env) {
enif_free_env(pin->env);
pin->env = 0;
}
if (pin->fd >= 0) {
hal_close_gpio(pin);
pin->fd = -1;
}
}
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}", pin->gpiochip, pin->offset);
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->offset);
#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->offset);
#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,
ERL_NIF_TERM gpio_spec,
ErlNifPid *pid,
int64_t timestamp,
int value)
{
ERL_NIF_TERM msg = enif_make_tuple4(env,
atom_circuits_gpio,
gpio_spec,
enif_make_int64(env, timestamp),
enif_make_int(env, value));
return enif_send(env, pid, NULL, msg);
}
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");
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);
if (hal_load(&priv->hal_priv) < 0) {
error("Can't initialize HAL");
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);
}
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);
int value = hal_read_gpio(pin);
if (value < 0)
return enif_raise_exception(env, enif_make_atom(env, strerror(errno)));
return enif_make_int(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;
int value;
if (argc != 2 ||
!enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin) ||
!enif_get_int(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_input"));
// Make sure value is 0 or 1
value = !!value;
if (hal_write_gpio(pin, value, env) < 0)
return enif_raise_exception(env, enif_make_atom(env, strerror(errno)));
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;
}
static int get_value(ErlNifEnv *env, ERL_NIF_TERM term, int *value)
{
int v;
if (enif_get_int(env, term, &v)) {
// Force v to be 0 or 1
*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 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);
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);
}
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 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 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 offset;
int initial_value;
enum pull_mode pull;
char gpiochip_path[MAX_GPIOCHIP_PATH_LEN];
if (argc != 5 ||
!get_resolved_location(env, argv[1], gpiochip_path, &offset) ||
!get_direction(env, argv[2], &is_output) ||
!get_value(env, argv[3], &initial_value) ||
!get_pull_mode(env, argv[4], &pull))
return enif_make_badarg(env);
debug("open {%s, %d}", gpiochip_path, offset);
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->offset = offset;
pin->env = enif_alloc_env();
pin->gpio_spec = enif_make_copy(pin->env, argv[0]);
pin->hal_priv = priv->hal_priv;
pin->config.is_output = is_output;
pin->config.trigger = TRIGGER_NONE;
pin->config.pull = pull;
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);
}
// 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 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", 5, open_gpio, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"close", 1, close_gpio, 0},
{"read", 1, read_gpio, 0},
{"write", 2, write_gpio, 0},
{"set_interrupts", 4, set_interrupts, 0},
{"set_direction", 2, set_direction, 0},
{"set_pull_mode", 2, set_pull_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)