Packages
circuits_gpio
2.1.3
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/hal_cdev_gpio_interrupts.c
// SPDX-FileCopyrightText: 2018 Frank Hunleth
// SPDX-FileCopyrightText: 2019 Matt Ludwigs
// SPDX-FileCopyrightText: 2023 Connor Rigby
//
// SPDX-License-Identifier: Apache-2.0
#include "gpio_nif.h"
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "linux/gpio.h"
#include "hal_cdev_gpio.h"
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 1
#endif
struct gpio_monitor_info {
enum trigger_mode trigger;
int fd;
int offset;
ErlNifPid pid;
ERL_NIF_TERM gpio_spec;
};
static void init_listeners(struct gpio_monitor_info *infos)
{
memset(infos, 0, MAX_GPIO_LISTENERS * sizeof(struct gpio_monitor_info));
}
static void compact_listeners(struct gpio_monitor_info *infos)
{
int write_pos = 0;
int read_pos;
for (read_pos = 0; read_pos < MAX_GPIO_LISTENERS; read_pos++) {
if (infos[read_pos].trigger != TRIGGER_NONE) {
if (write_pos != read_pos) {
memcpy(&infos[write_pos], &infos[read_pos], sizeof(struct gpio_monitor_info));
}
write_pos++;
}
}
int remaining = MAX_GPIO_LISTENERS - write_pos;
memset(&infos[write_pos], 0, remaining * sizeof(struct gpio_monitor_info));
}
static int handle_gpio_update(ErlNifEnv *env,
struct gpio_monitor_info *info,
uint64_t timestamp,
int event_id)
{
debug("handle_gpio_update %d", info->offset);
int value = event_id == GPIO_V2_LINE_EVENT_RISING_EDGE ? 1 : 0;
// Convert true/false return to the typical 0/negative returns of this file
if (send_gpio_message(env, info->gpio_spec, &info->pid, timestamp, value))
return 0;
else
return -1;
}
static int process_gpio_events(ErlNifEnv *env,
struct gpio_monitor_info *info)
{
struct gpio_v2_line_event events[16];
ssize_t amount_read = read(info->fd, events, sizeof(events));
if (amount_read < 0) {
error("Unexpected return from reading gpio events: %d, errno=%d", amount_read, errno);
return -1;
}
int num_events = amount_read / sizeof(struct gpio_v2_line_event);
for (int i = 0; i < num_events; i++) {
if (handle_gpio_update(env,
info,
events[i].timestamp_ns,
events[i].id) < 0) {
error("send for gpio %d failed, so not listening to it any more", info->offset);
return -1;
}
}
return 0;
}
static void add_listener(ErlNifEnv *env, struct gpio_monitor_info *infos, const struct gpio_monitor_info *to_add)
{
for (int i = 0; i < MAX_GPIO_LISTENERS; i++) {
if (infos[i].trigger == TRIGGER_NONE || infos[i].fd == to_add->fd) {
memcpy(&infos[i], to_add, sizeof(struct gpio_monitor_info));
return;
}
}
error("Too many gpio listeners. Max is %d", MAX_GPIO_LISTENERS);
}
static void remove_listener(struct gpio_monitor_info *infos, int fd)
{
debug("remove_listener fd=%d", fd);
bool cleanup = false;
for (int i = 0; i < MAX_GPIO_LISTENERS; i++) {
if (infos[i].fd == fd) {
infos[i].trigger = TRIGGER_NONE;
cleanup = true;
}
}
if (cleanup)
compact_listeners(infos);
}
void *gpio_poller_thread(void *arg)
{
struct gpio_monitor_info monitor_info[MAX_GPIO_LISTENERS];
struct pollfd fdset[MAX_GPIO_LISTENERS + 1];
int *pipefd = arg;
debug("gpio_poller_thread started");
ErlNifEnv *env = enif_alloc_env();
init_listeners(monitor_info);
for (;;) {
struct pollfd *fds = &fdset[0];
nfds_t count = 0;
struct gpio_monitor_info *info = &monitor_info[0];
while (info->trigger != TRIGGER_NONE) {
debug("adding fd %d to poll list", info->fd);
fds->fd = info->fd;
fds->events = POLLIN;
fds->revents = 0;
fds++;
info++;
count++;
}
fds->fd = *pipefd;
fds->events = POLLIN;
fds->revents = 0;
count++;
debug("poll waiting on %d handles", count);
int rc = poll(fdset, count, -1);
if (rc < 0) {
// Retry if EINTR
if (errno == EINTR)
continue;
error("poll failed. errno=%d", errno);
break;
}
debug("poll returned rc=%d", rc);
short revents = fdset[count - 1].revents;
if (revents & (POLLERR | POLLNVAL)) {
// Socket closed so quit thread. This happens on NIF unload.
break;
}
bool cleanup = false;
for (nfds_t i = 0; i < count - 1; i++) {
short gpio_revents = fdset[i].revents;
if (gpio_revents & POLLIN) {
if (process_gpio_events(env, &monitor_info[i]) < 0) {
error("error processing gpio events for %d", monitor_info[i].offset);
monitor_info[i].trigger = TRIGGER_NONE;
cleanup = true;
}
} else if (gpio_revents & (POLLERR | POLLHUP | POLLNVAL)) {
error("error listening on gpio %d", monitor_info[i].offset);
monitor_info[i].trigger = TRIGGER_NONE;
cleanup = true;
}
}
if (revents & (POLLIN | POLLHUP)) {
struct gpio_monitor_info message;
ssize_t amount_read = read(*pipefd, &message, sizeof(message));
if (amount_read != sizeof(message)) {
error("Unexpected return from read: %d, errno=%d", amount_read, errno);
break;
}
if (message.trigger != TRIGGER_NONE)
add_listener(env, monitor_info, &message);
else
remove_listener(monitor_info, message.fd);
}
// Compact the listener list if any failed
if (cleanup)
compact_listeners(monitor_info);
}
enif_free_env(env);
debug("gpio_poller_thread ended");
return NULL;
}
int update_polling_thread(struct gpio_pin *pin)
{
struct hal_cdev_gpio_priv *priv = (struct hal_cdev_gpio_priv *) pin->hal_priv;
struct gpio_monitor_info message;
message.trigger = pin->config.trigger;
message.fd = pin->fd;
message.offset = pin->offset;
message.gpio_spec = pin->gpio_spec;
message.pid = pin->config.pid;
if (write(priv->pipe_fds[1], &message, sizeof(message)) != sizeof(message)) {
error("Error writing polling thread!");
return -1;
}
return 0;
}