blob: fa276f68531d14c94c1ec29e905ec25730e5935b [file] [log] [blame]
/*
* This file is part of libgpiod.
*
* Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*/
/* Low-level, core library code. */
#include <gpiod.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
enum {
LINE_FREE = 0,
LINE_REQUESTED_VALUES,
LINE_REQUESTED_EVENTS,
};
struct gpiod_line {
unsigned int offset;
int direction;
int active_state;
bool used;
bool open_source;
bool open_drain;
int state;
bool up_to_date;
struct gpiod_chip *chip;
int fd;
char name[32];
char consumer[32];
};
struct gpiod_chip {
struct gpiod_line **lines;
unsigned int num_lines;
int fd;
char name[32];
char label[32];
};
struct gpiod_chip *gpiod_chip_open(const char *path)
{
struct gpiochip_info info;
struct gpiod_chip *chip;
int status, fd;
fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0)
return NULL;
chip = malloc(sizeof(*chip));
if (!chip)
goto err_close_fd;
memset(chip, 0, sizeof(*chip));
memset(&info, 0, sizeof(info));
status = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if (status < 0)
goto err_free_chip;
chip->fd = fd;
chip->num_lines = info.lines;
/*
* GPIO device must have a name - don't bother checking this field. In
* the worst case (would have to be a weird kernel bug) it'll be empty.
*/
strncpy(chip->name, info.name, sizeof(chip->name));
/*
* The kernel sets the label of a GPIO device to "unknown" if it
* hasn't been defined in DT, board file etc. On the off-chance that
* we got an empty string, do the same.
*/
if (info.label[0] == '\0')
strncpy(chip->label, "unknown", sizeof(chip->label));
else
strncpy(chip->label, info.label, sizeof(chip->label));
return chip;
err_free_chip:
free(chip);
err_close_fd:
close(fd);
return NULL;
}
void gpiod_chip_close(struct gpiod_chip *chip)
{
struct gpiod_line *line;
unsigned int i;
if (chip->lines) {
for (i = 0; i < chip->num_lines; i++) {
line = chip->lines[i];
if (line) {
gpiod_line_release(line);
free(line);
}
}
free(chip->lines);
}
close(chip->fd);
free(chip);
}
const char *gpiod_chip_name(struct gpiod_chip *chip)
{
return chip->name;
}
const char *gpiod_chip_label(struct gpiod_chip *chip)
{
return chip->label;
}
unsigned int gpiod_chip_num_lines(struct gpiod_chip *chip)
{
return chip->num_lines;
}
struct gpiod_line *
gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset)
{
struct gpiod_line *line;
int status;
if (offset >= chip->num_lines) {
errno = EINVAL;
return NULL;
}
if (!chip->lines) {
chip->lines = calloc(chip->num_lines,
sizeof(struct gpiod_line *));
if (!chip->lines)
return NULL;
}
if (!chip->lines[offset]) {
line = malloc(sizeof(*line));
if (!line)
return NULL;
memset(line, 0, sizeof(*line));
line->fd = -1;
line->offset = offset;
line->chip = chip;
chip->lines[offset] = line;
} else {
line = chip->lines[offset];
}
status = gpiod_line_update(line);
if (status < 0)
return NULL;
return line;
}
static void line_maybe_update(struct gpiod_line *line)
{
int status;
status = gpiod_line_update(line);
if (status < 0)
line->up_to_date = false;
}
struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
{
return line->chip;
}
unsigned int gpiod_line_offset(struct gpiod_line *line)
{
return line->offset;
}
const char *gpiod_line_name(struct gpiod_line *line)
{
return line->name[0] == '\0' ? NULL : line->name;
}
const char *gpiod_line_consumer(struct gpiod_line *line)
{
return line->consumer[0] == '\0' ? NULL : line->consumer;
}
int gpiod_line_direction(struct gpiod_line *line)
{
return line->direction;
}
int gpiod_line_active_state(struct gpiod_line *line)
{
return line->active_state;
}
bool gpiod_line_is_used(struct gpiod_line *line)
{
return line->used;
}
bool gpiod_line_is_open_drain(struct gpiod_line *line)
{
return line->open_drain;
}
bool gpiod_line_is_open_source(struct gpiod_line *line)
{
return line->open_source;
}
bool gpiod_line_needs_update(struct gpiod_line *line)
{
return !line->up_to_date;
}
int gpiod_line_update(struct gpiod_line *line)
{
struct gpioline_info info;
int rv;
memset(&info, 0, sizeof(info));
info.line_offset = line->offset;
rv = ioctl(line->chip->fd, GPIO_GET_LINEINFO_IOCTL, &info);
if (rv < 0)
return -1;
line->direction = info.flags & GPIOLINE_FLAG_IS_OUT
? GPIOD_LINE_DIRECTION_OUTPUT
: GPIOD_LINE_DIRECTION_INPUT;
line->active_state = info.flags & GPIOLINE_FLAG_ACTIVE_LOW
? GPIOD_LINE_ACTIVE_STATE_LOW
: GPIOD_LINE_ACTIVE_STATE_HIGH;
line->used = info.flags & GPIOLINE_FLAG_KERNEL;
line->open_drain = info.flags & GPIOLINE_FLAG_OPEN_DRAIN;
line->open_source = info.flags & GPIOLINE_FLAG_OPEN_SOURCE;
strncpy(line->name, info.name, sizeof(line->name));
strncpy(line->consumer, info.consumer, sizeof(line->consumer));
line->up_to_date = true;
return 0;
}
static bool line_bulk_same_chip(struct gpiod_line_bulk *bulk)
{
struct gpiod_line *first_line, *line;
struct gpiod_chip *first_chip, *chip;
unsigned int i;
if (bulk->num_lines == 1)
return true;
first_line = gpiod_line_bulk_get_line(bulk, 0);
first_chip = gpiod_line_get_chip(first_line);
for (i = 1; i < bulk->num_lines; i++) {
line = bulk->lines[i];
chip = gpiod_line_get_chip(line);
if (first_chip != chip) {
errno = EINVAL;
return false;
}
}
return true;
}
static bool line_bulk_all_requested(struct gpiod_line_bulk *bulk)
{
struct gpiod_line *line, **lineptr;
gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
if (!gpiod_line_is_requested(line)) {
errno = EPERM;
return false;
}
}
return true;
}
static bool line_bulk_all_free(struct gpiod_line_bulk *bulk)
{
struct gpiod_line *line, **lineptr;
gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
if (!gpiod_line_is_free(line)) {
errno = EBUSY;
return false;
}
}
return true;
}
static int line_request_values(struct gpiod_line_bulk *bulk,
const struct gpiod_line_request_config *config,
const int *default_vals)
{
struct gpiod_line *line, **lineptr;
struct gpiohandle_request req;
unsigned int i;
int rv, fd;
if ((config->request_type != GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) &&
(config->flags & (GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE))) {
errno = EINVAL;
return -1;
}
if ((config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) &&
(config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)) {
errno = EINVAL;
return -1;
}
memset(&req, 0, sizeof(req));
if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
req.flags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
req.flags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
if (config->flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
req.flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_INPUT)
req.flags |= GPIOHANDLE_REQUEST_INPUT;
else if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
req.flags |= GPIOHANDLE_REQUEST_OUTPUT;
req.lines = gpiod_line_bulk_num_lines(bulk);
gpiod_line_bulk_foreach_line_off(bulk, line, i) {
req.lineoffsets[i] = gpiod_line_offset(line);
if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
req.default_values[i] = !!default_vals[i];
}
if (config->consumer)
strncpy(req.consumer_label, config->consumer,
sizeof(req.consumer_label) - 1);
line = gpiod_line_bulk_get_line(bulk, 0);
fd = line->chip->fd;
rv = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if (rv < 0)
return -1;
gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
line->state = LINE_REQUESTED_VALUES;
line->fd = req.fd;
line_maybe_update(line);
}
return 0;
}
static int line_request_event_single(struct gpiod_line *line,
const struct gpiod_line_request_config *config)
{
struct gpioevent_request req;
int rv;
memset(&req, 0, sizeof(req));
if (config->consumer)
strncpy(req.consumer_label, config->consumer,
sizeof(req.consumer_label) - 1);
req.lineoffset = gpiod_line_offset(line);
req.handleflags |= GPIOHANDLE_REQUEST_INPUT;
if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
req.handleflags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
req.handleflags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
if (config->flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
req.handleflags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
if (config->request_type == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE)
req.eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
else if (config->request_type == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE)
req.eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
else if (config->request_type == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES)
req.eventflags |= GPIOEVENT_REQUEST_BOTH_EDGES;
rv = ioctl(line->chip->fd, GPIO_GET_LINEEVENT_IOCTL, &req);
if (rv < 0)
return -1;
line->state = LINE_REQUESTED_EVENTS;
line->fd = req.fd;
line_maybe_update(line);
return 0;
}
static int line_request_events(struct gpiod_line_bulk *bulk,
const struct gpiod_line_request_config *config)
{
struct gpiod_line *line;
unsigned int off;
int rv, rev;
gpiod_line_bulk_foreach_line_off(bulk, line, off) {
rv = line_request_event_single(line, config);
if (rv) {
for (rev = off - 1; rev >= 0; rev--) {
line = gpiod_line_bulk_get_line(bulk, rev);
gpiod_line_release(line);
}
return -1;
}
}
return 0;
}
int gpiod_line_request(struct gpiod_line *line,
const struct gpiod_line_request_config *config,
int default_val)
{
struct gpiod_line_bulk bulk;
gpiod_line_bulk_init(&bulk);
gpiod_line_bulk_add(&bulk, line);
return gpiod_line_request_bulk(&bulk, config, &default_val);
}
static bool line_request_is_direction(int request)
{
return request == GPIOD_LINE_REQUEST_DIRECTION_AS_IS ||
request == GPIOD_LINE_REQUEST_DIRECTION_INPUT ||
request == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
}
static bool line_request_is_events(int request)
{
return request == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE ||
request == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE ||
request == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
}
int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
const struct gpiod_line_request_config *config,
const int *default_vals)
{
if (!line_bulk_same_chip(bulk) || !line_bulk_all_free(bulk))
return -1;
if (line_request_is_direction(config->request_type))
return line_request_values(bulk, config, default_vals);
else if (line_request_is_events(config->request_type))
return line_request_events(bulk, config);
errno = EINVAL;
return -1;
}
void gpiod_line_release(struct gpiod_line *line)
{
struct gpiod_line_bulk bulk;
gpiod_line_bulk_init(&bulk);
gpiod_line_bulk_add(&bulk, line);
gpiod_line_release_bulk(&bulk);
}
void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk)
{
struct gpiod_line *line, **lineptr;
gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
if (line->state != LINE_FREE) {
close(line->fd);
line->state = LINE_FREE;
}
}
}
bool gpiod_line_is_requested(struct gpiod_line *line)
{
return (line->state == LINE_REQUESTED_VALUES ||
line->state == LINE_REQUESTED_EVENTS);
}
bool gpiod_line_is_free(struct gpiod_line *line)
{
return line->state == LINE_FREE;
}
int gpiod_line_get_value(struct gpiod_line *line)
{
struct gpiod_line_bulk bulk;
int status, value;
gpiod_line_bulk_init(&bulk);
gpiod_line_bulk_add(&bulk, line);
status = gpiod_line_get_value_bulk(&bulk, &value);
if (status < 0)
return -1;
return value;
}
int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk, int *values)
{
struct gpiohandle_data data;
struct gpiod_line *first;
unsigned int i;
int status, fd;
if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
return -1;
first = gpiod_line_bulk_get_line(bulk, 0);
memset(&data, 0, sizeof(data));
fd = first->fd;
status = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (status < 0)
return -1;
for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
values[i] = data.values[i];
return 0;
}
int gpiod_line_set_value(struct gpiod_line *line, int value)
{
struct gpiod_line_bulk bulk;
gpiod_line_bulk_init(&bulk);
gpiod_line_bulk_add(&bulk, line);
return gpiod_line_set_value_bulk(&bulk, &value);
}
int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk, const int *values)
{
struct gpiohandle_data data;
struct gpiod_line *line;
unsigned int i;
int status;
if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
return -1;
memset(&data, 0, sizeof(data));
for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
data.values[i] = (uint8_t)!!values[i];
line = gpiod_line_bulk_get_line(bulk, 0);
status = ioctl(line->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
if (status < 0)
return -1;
return 0;
}
int gpiod_line_event_wait(struct gpiod_line *line,
const struct timespec *timeout)
{
struct gpiod_line_bulk bulk;
gpiod_line_bulk_init(&bulk);
gpiod_line_bulk_add(&bulk, line);
return gpiod_line_event_wait_bulk(&bulk, timeout, NULL);
}
int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
const struct timespec *timeout,
struct gpiod_line_bulk *event_bulk)
{
struct pollfd fds[GPIOD_LINE_BULK_MAX_LINES];
unsigned int off, num_lines;
struct gpiod_line *line;
int rv;
if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
return -1;
memset(fds, 0, sizeof(fds));
num_lines = gpiod_line_bulk_num_lines(bulk);
gpiod_line_bulk_foreach_line_off(bulk, line, off) {
fds[off].fd = line->fd;
fds[off].events = POLLIN | POLLPRI;
}
rv = ppoll(fds, num_lines, timeout, NULL);
if (rv < 0)
return -1;
else if (rv == 0)
return 0;
if (event_bulk) {
gpiod_line_bulk_init(event_bulk);
for (off = 0; off < num_lines; off++) {
if (fds[off].revents) {
line = gpiod_line_bulk_get_line(bulk, off);
gpiod_line_bulk_add(event_bulk, line);
if (!--rv)
break;
}
}
}
return 1;
}
int gpiod_line_event_read(struct gpiod_line *line,
struct gpiod_line_event *event)
{
if (line->state != LINE_REQUESTED_EVENTS) {
errno = EPERM;
return -1;
}
return gpiod_line_event_read_fd(line->fd, event);
}
int gpiod_line_event_get_fd(struct gpiod_line *line)
{
if (line->state != LINE_REQUESTED_EVENTS) {
errno = EPERM;
return -1;
}
return line->fd;
}
int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event)
{
struct gpioevent_data evdata;
ssize_t rd;
memset(&evdata, 0, sizeof(evdata));
rd = read(fd, &evdata, sizeof(evdata));
if (rd < 0) {
return -1;
} else if (rd != sizeof(evdata)) {
errno = EIO;
return -1;
}
event->event_type = evdata.id == GPIOEVENT_EVENT_RISING_EDGE
? GPIOD_LINE_EVENT_RISING_EDGE
: GPIOD_LINE_EVENT_FALLING_EDGE;
event->ts.tv_sec = evdata.timestamp / 1000000000ULL;
event->ts.tv_nsec = evdata.timestamp % 1000000000ULL;
return 0;
}