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.h
// SPDX-FileCopyrightText: 2018 Frank Hunleth
// SPDX-FileCopyrightText: 2018 Mark Sebald
// SPDX-FileCopyrightText: 2023 Connor Rigby
//
// SPDX-License-Identifier: Apache-2.0
#ifndef GPIO_NIF_H
#define GPIO_NIF_H
#include "erl_nif.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
//#define DEBUG
#ifdef DEBUG
extern FILE *log_location;
#define LOG_PATH "/tmp/circuits_gpio.log"
#define debug(...) do { enif_fprintf(log_location, __VA_ARGS__); enif_fprintf(log_location, "\r\n"); fflush(log_location); } while(0)
#define error(...) do { debug(__VA_ARGS__); } while (0)
#define start_timing() ErlNifTime __start = enif_monotonic_time(ERL_NIF_USEC)
#define elapsed_microseconds() (enif_monotonic_time(ERL_NIF_USEC) - __start)
#else
#define debug(...)
#define error(...) do { enif_fprintf(stderr, __VA_ARGS__); enif_fprintf(stderr, "\n"); } while(0)
#define start_timing()
#define elapsed_microseconds() 0
#endif
#define MAX_GPIOCHIP_PATH_LEN 32
#define MAX_GPIO_LISTENERS 32
// Maximum number of GPIO lines that can be opened together as a group.
// The Linux gpio-cdev v2 API caps a single line request at 64 lines, and the
// group value is carried as a 64-bit integer (one bit per line).
#define GPIO_MAX_LINES 64
enum trigger_mode {
TRIGGER_NONE = 0,
TRIGGER_RISING,
TRIGGER_FALLING,
TRIGGER_BOTH
};
enum pull_mode {
PULL_NOT_SET,
PULL_NONE,
PULL_UP,
PULL_DOWN
};
enum drive_mode {
DRIVE_PUSH_PULL,
DRIVE_OPEN_DRAIN,
DRIVE_OPEN_SOURCE
};
struct gpio_priv {
ErlNifResourceType *gpio_pin_rt;
ErlNifMutex *gpio_pins_lock;
struct gpio_pin *gpio_pins;
uint32_t hal_priv[1];
};
struct gpio_config {
bool is_output;
// trigger is the edge(s) the hardware is configured to detect. For a
// subscription this is forced to TRIGGER_BOTH so the shadow value stays
// accurate; emit_trigger holds the edge(s) the caller actually wants
// notifications for.
enum trigger_mode trigger;
enum trigger_mode emit_trigger;
enum pull_mode pull;
enum drive_mode drive;
bool suppress_glitches;
// Initial output values as an integer. Bit i corresponds to offsets[i].
uint64_t initial_value;
ErlNifPid pid;
};
struct gpio_pin {
char gpiochip[MAX_GPIOCHIP_PATH_LEN];
// Lines in this group. A single GPIO is just num_lines == 1. offsets[i] is
// bit i of the value, with offsets[0] the least significant bit.
int num_lines;
int offsets[GPIO_MAX_LINES];
// cdev: the file descriptor for the whole line request. stub: >= 0 marks
// the group as open.
int fd;
void *hal_priv;
struct gpio_config config;
// Last known value. Used to compute the running aggregate and
// previous_value for change notifications.
uint64_t shadow;
// NIF environment for holding on to terms across calls
ErlNifEnv *env;
// Echoed in legacy set_interrupts notifications ({:circuits_gpio, spec, ...})
ERL_NIF_TERM gpio_spec;
// Echoed in subscribe notifications ({:circuits_gpio, %{ref: ..., ...}}).
// This is the make_ref() (or caller-supplied tag) returned by subscribe/2.
ERL_NIF_TERM notify_id;
// true -> subscribe map format using notify_id
// false -> legacy set_interrupts tuple format using gpio_spec
bool notify_map;
// Linked into gpio_priv.gpio_pins while the resource is alive. This lets
// force_close release handles even when their Erlang terms are unavailable.
struct gpio_pin *next;
bool registered;
};
// Atoms
extern ERL_NIF_TERM atom_ok;
extern ERL_NIF_TERM atom_error;
extern ERL_NIF_TERM atom_name;
extern ERL_NIF_TERM atom_label;
extern ERL_NIF_TERM atom_location;
extern ERL_NIF_TERM atom_controller;
extern ERL_NIF_TERM atom_circuits_gpio;
extern ERL_NIF_TERM atom_consumer;
extern ERL_NIF_TERM atom_ref;
extern ERL_NIF_TERM atom_timestamp;
extern ERL_NIF_TERM atom_value;
extern ERL_NIF_TERM atom_previous_value;
// HAL
/**
* Return information about the HAL.
*
* This should return a map with the name of the HAL and any info that
* would help debug issues with it.
*/
ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info);
/**
* Enumerate all GPIO pins
*
* Returns a list of Circuits.GPIO.identifiers maps
*/
ERL_NIF_TERM hal_enumerate(ErlNifEnv *env, void *hal_priv);
/**
* Return the additional number of bytes of private data to allocate
* for the HAL.
*/
size_t hal_priv_size(void);
/**
* Initialize the HAL
*
* @param hal_priv where to store state
* @return 0 on success
*/
int hal_load(void *hal_priv);
/**
* Release all resources held by the HAL
*
* @param hal_priv private state
*/
void hal_unload(void *hal_priv);
/**
* Open up and initialize a GPIO.
*
* @param pin information about the GPIO
* @param env a NIF environment in case a message is sent
* @return 0 on success, -errno on failure
*/
int hal_open_gpio(struct gpio_pin *pin,
ErlNifEnv *env);
/**
* Free up resources for the specified GPIO
*
* This can be called more than once.
*
* @param pin GPIO pin information
*/
void hal_close_gpio(struct gpio_pin *pin);
/**
* Read the current value of a GPIO group
*
* @param pin which group
* @param value where to store the value (bit i == offsets[i])
* @return 0 on success, -errno on failure
*/
int hal_read_gpio(struct gpio_pin *pin, uint64_t *value);
/**
* Change the value of a GPIO group
*
* @param pin which group
* @param value the value to drive (bit i == offsets[i])
* @param env ErlNifEnv if this causes an event to be sent
* @return 0 on success, -errno on failure
*/
int hal_write_gpio(struct gpio_pin *pin, uint64_t value, ErlNifEnv *env);
/**
* Apply GPIO direction settings
*
* This should set the GPIO to an input or an output. If setting
* as an output, it should check the initial_value. If the
* initial_value is < 0 then the GPIO should retain its value
* if already an output. If set to 0 or 1, the GPIO should be
* initialized to that value.
*
* @param pin which one
* @return 0 on success, -errno on failure
*/
int hal_apply_direction(struct gpio_pin *pin);
/**
* Apply GPIO interrupt settings
*
* @param pin the pin and notification trigger info
* @return 0 on success, -errno on failure
*/
int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env);
/**
* Apply GPIO pull mode settings
*
* @param pin which one
* @return 0 on success, -errno on failure
*/
int hal_apply_pull_mode(struct gpio_pin *pin);
/**
* Apply GPIO drive mode settings
*
* @param pin which one
* @return 0 on success, -errno on failure
*/
int hal_apply_drive_mode(struct gpio_pin *pin);
/**
* Return a map that has runtime information about a GPIO
*
* @param env a NIF environment for making the map
* @param gpiochip which controller
* @param offset the offset on the controller
* @param result where to store the result when successful
* @return 0 on success, -errno on failure
*/
int hal_get_status(void *hal_priv, ErlNifEnv *env, const char *gpiochip, int offset, ERL_NIF_TERM *result);
// nif_utils.c
ERL_NIF_TERM make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value);
ERL_NIF_TERM make_errno_atom(ErlNifEnv *env, int errno_value);
ERL_NIF_TERM make_errno_error(ErlNifEnv *env, int errno_value);
ERL_NIF_TERM make_string_binary(ErlNifEnv *env, const char *str);
int enif_get_boolean(ErlNifEnv *env, ERL_NIF_TERM term, bool *v);
/**
* Send a GPIO interrupt message to a process
*
* @param env the caller's environment: the process bound environment when
* called from a NIF or NULL when called from a custom thread
* @param msg_env a process independent environment for building the message.
* It is cleared before this function returns so that it can be
* reused.
* @param gpio_spec the GPIO spec term (may be from another environment)
* @param pid who to notify
* @param timestamp event timestamp in nanoseconds
* @param value the new GPIO value
* @return true on success (see enif_send)
*/
int send_gpio_message(ErlNifEnv *env,
ErlNifEnv *msg_env,
ERL_NIF_TERM gpio_spec,
ErlNifPid *pid,
int64_t timestamp,
int value);
/**
* Send a GPIO change notification (subscribe/2 map format) to a process
*
* Builds {:circuits_gpio, %{ref: notify_id, timestamp: ts, value: value,
* previous_value: previous_value}}.
*
* @param env the caller's environment: the process bound environment when
* called from a NIF or NULL when called from a custom thread
* @param msg_env a process independent environment for building the message.
* It is cleared before this function returns so that it can be
* reused.
* @param notify_id the ref/tag term to echo (may be from another environment)
* @param pid who to notify
* @param timestamp event timestamp in nanoseconds
* @param value the new group value
* @param previous_value the group value before this change
* @return true on success (see enif_send)
*/
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);
/**
* Decide whether a single-line edge should produce a notification and, if so,
* send it in the right format.
*
* Shared by the stub HAL (which has the gpio_pin) and the cdev poller thread
* (which has copied monitor state). The caller is responsible for tracking the
* shadow value and passing new/previous values.
*
* @param env caller env (NULL from a custom thread)
* @param msg_env reusable message environment
* @param notify_map true => subscribe map format; false => legacy tuple format
* @param notify_term gpio_spec (legacy) or ref/tag (map) to echo
* @param pid who to notify
* @param emit_trigger which edge(s) the caller wants notifications for
* @param timestamp event timestamp in nanoseconds
* @param new_value the new group value
* @param previous_value the group value before this change
* @param changed_bit index of the bit that changed
* @return true on success or when no message was needed; false on send failure
*/
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);
#endif // GPIO_NIF_H