Packages
circuits_gpio
0.4.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
src/hal_sysfs.c
/*
* Copyright 2018 Frank Hunleth, Mark Sebald
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* GPIO NIF implementation.
*/
#include "gpio_nif.h"
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "hal_sysfs.h"
size_t hal_priv_size()
{
return sizeof(struct sysfs_priv);
}
/* This is a workaround for a first-time initialization issue where the file doesn't appear
* quickly after export.
*/
static int retry_open(const char *pathname, int flags, int retries)
{
do {
int fd = open(pathname, flags);
if (fd >= 0)
return fd;
error("Error opening %s", pathname);
usleep(1000);
retries--;
} while (retries > 0);
return -1;
}
static ssize_t sysfs_write_file(const char *pathname, const char *value, int retries)
{
int fd = retry_open(pathname, O_WRONLY, retries);
if (fd < 0) {
error("Error opening %s", pathname);
return -1;
}
size_t count = strlen(value);
ssize_t written = write(fd, value, count);
close(fd);
if (written < 0 || (size_t) written != count) {
error("Error writing '%s' to %s", value, pathname);
return -1;
}
return written;
}
static ssize_t sysfs_read_file(const char *pathname, char *value, size_t len, int retries)
{
int fd = retry_open(pathname, O_RDONLY, retries);
if (fd < 0) {
error("Error opening %s", pathname);
return -1;
}
ssize_t amount_read = read(fd, value, len);
close(fd);
if (amount_read <= 0) {
error("Error writing '%s' to %s", value, pathname);
return -1;
}
return amount_read;
}
static int export_pin(int pin_number)
{
char pinstr[16];
sprintf(pinstr, "%d", pin_number);
if (sysfs_write_file("/sys/class/gpio/export", pinstr, 0) <= 0)
return - 1;
return 0;
}
static const char *edge_mode_string(enum trigger_mode mode)
{
switch (mode) {
default:
case TRIGGER_NONE:
return "none";
case TRIGGER_FALLING:
return "falling";
case TRIGGER_RISING:
return "rising";
case TRIGGER_BOTH:
return "both";
}
}
ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info)
{
enif_make_map_put(env, info, enif_make_atom(env, "name"), enif_make_atom(env, "sysfs"), &info);
#ifdef TARGET_RPI
return rpi_info(env, hal_priv, info);
#else
(void) hal_priv;
return info;
#endif
}
int hal_load(void *hal_priv)
{
struct sysfs_priv *priv = hal_priv;
memset(priv, 0, sizeof(struct sysfs_priv));
if (pipe(priv->pipe_fds) < 0) {
error("pipe failed");
return 1;
}
if (enif_thread_create("gpio_poller", &priv->poller_tid, gpio_poller_thread, &priv->pipe_fds[0], NULL) != 0) {
error("enif_thread_create failed");
return 1;
}
return 0;
}
void hal_unload(void *hal_priv)
{
struct sysfs_priv *priv = hal_priv;
// Close everything related to the listening thread so that it exits
close(priv->pipe_fds[0]);
close(priv->pipe_fds[1]);
// If the listener thread hasn't exited already, it should do so soon.
enif_thread_join(priv->poller_tid, NULL);
#ifdef TARGET_RPI
rpi_unload(priv);
#endif
// TODO free everything else!
}
int hal_open_gpio(struct gpio_pin *pin,
char *error_str,
ErlNifEnv *env)
{
*error_str = '\0';
char value_path[64];
sprintf(value_path, "/sys/class/gpio/gpio%d/value", pin->pin_number);
pin->fd = open(value_path, O_RDWR);
if (pin->fd < 0) {
if (export_pin(pin->pin_number) < 0) {
strcpy(error_str, "export_failed");
return -1;
}
pin->fd = open(value_path, O_RDWR);
if (pin->fd < 0) {
strcpy(error_str, "access_denied");
return -1;
}
}
if (hal_apply_direction(pin) < 0) {
strcpy(error_str, "error_setting_direction");
goto error;
}
if (hal_apply_pull_mode(pin) < 0) {
strcpy(error_str, "error_setting_pull_mode");
goto error;
}
if (hal_apply_interrupts(pin, env) < 0) {
strcpy(error_str, "error_setting_interrupts");
goto error;
}
return 0;
error:
close(pin->fd);
pin->fd = -1;
return -1;
}
void hal_close_gpio(struct gpio_pin *pin)
{
if (pin->fd >= 0) {
// Turn off interrupts if they're on.
if (pin->config.trigger != TRIGGER_NONE) {
pin->config.trigger = TRIGGER_NONE;
update_polling_thread(pin);
}
close(pin->fd);
pin->fd = -1;
}
}
int sysfs_read_gpio(int fd)
{
char buf;
ssize_t amount_read = pread(fd, &buf, sizeof(buf), 0);
if (amount_read == sizeof(buf))
return buf == '1';
else
return -1;
}
int hal_read_gpio(struct gpio_pin *pin)
{
return sysfs_read_gpio(pin->fd);
}
int hal_write_gpio(struct gpio_pin *pin, int value, ErlNifEnv *env)
{
(void) env;
char buff = value ? '1' : '0';
return (int) pwrite(pin->fd, &buff, sizeof(buff), 0);
}
int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env)
{
(void) env;
char edge_path[64];
sprintf(edge_path, "/sys/class/gpio/gpio%d/edge", pin->pin_number);
/* Allow 1000 * 1ms = 1 second max for retries. This is a workaround
* for a first-time initialization issue where the file doesn't appear
* quickly after export */
if (sysfs_write_file(edge_path, edge_mode_string(pin->config.trigger), 1000) < 0)
return -1;
// Tell polling thread to wait for notifications
if (update_polling_thread(pin) < 0)
return -1;
return 0;
}
int hal_apply_direction(struct gpio_pin *pin)
{
char direction_path[64];
sprintf(direction_path, "/sys/class/gpio/gpio%d/direction", pin->pin_number);
/* Allow 1000 * 1ms = 1 second max for retries. See hal_apply_interrupts too. */
char current_dir[16];
if (sysfs_read_file(direction_path, current_dir, sizeof(current_dir), 1000) < 0)
return -1;
/* Linux only reports "in" and "out". (current_dir is NOT null terminated here) */
int current_is_output = (current_dir[0] == 'o');
if (pin->config.is_output == 0) {
// Input
if (!current_is_output)
return 0;
else
return sysfs_write_file(direction_path, "in", 0);
} else {
// Output
if (pin->config.initial_value < 0) {
// Output, don't set
if (current_is_output)
return 0;
else
return sysfs_write_file(direction_path, "out", 0);
} else if (pin->config.initial_value == 0) {
// Set as output and initialize low
return sysfs_write_file(direction_path, "low", 0);
} else {
// Set as output and initialize high
return sysfs_write_file(direction_path, "high", 0);
}
}
}
int hal_apply_pull_mode(struct gpio_pin *pin)
{
if (pin->config.pull == PULL_NOT_SET)
return 0;
// Setting the pull mode is platform-specific, so delegate.
int rc = -1;
#ifdef TARGET_RPI
rc = rpi_apply_pull_mode(pin);
#endif
return rc;
}