// SPDX-License-Identifier: LGPL-2.1-or-later /* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com> */ /* Implementation of the high-level API. */ #include <errno.h> #include <gpiod.h> #include <poll.h> #include <stdio.h> #include <string.h> static int ctxless_flags_to_line_request_flags(bool active_low, int flags) { int req_flags = 0; if (active_low) req_flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW; if (flags & GPIOD_CTXLESS_FLAG_OPEN_DRAIN) req_flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN; if (flags & GPIOD_CTXLESS_FLAG_OPEN_SOURCE) req_flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE; if (flags & GPIOD_CTXLESS_FLAG_BIAS_DISABLE) req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE; if (flags & GPIOD_CTXLESS_FLAG_BIAS_PULL_UP) req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP; if (flags & GPIOD_CTXLESS_FLAG_BIAS_PULL_DOWN) req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN; return req_flags; } int gpiod_ctxless_get_value(const char *device, unsigned int offset, bool active_low, const char *consumer) { int value, rv; rv = gpiod_ctxless_get_value_multiple(device, &offset, &value, 1, active_low, consumer); if (rv < 0) return rv; return value; } int gpiod_ctxless_get_value_ext(const char *device, unsigned int offset, bool active_low, const char *consumer, int flags) { int value, rv; rv = gpiod_ctxless_get_value_multiple_ext(device, &offset, &value, 1, active_low, consumer, flags); if (rv < 0) return rv; return value; } int gpiod_ctxless_get_value_multiple(const char *device, const unsigned int *offsets, int *values, unsigned int num_lines, bool active_low, const char *consumer) { int rv; rv = gpiod_ctxless_get_value_multiple_ext(device, offsets, values, num_lines, active_low, consumer, 0); return rv; } int gpiod_ctxless_get_value_multiple_ext(const char *device, const unsigned int *offsets, int *values, unsigned int num_lines, bool active_low, const char *consumer, int flags) { struct gpiod_line_bulk bulk; struct gpiod_chip *chip; struct gpiod_line *line; unsigned int i; int rv, req_flags; if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) { errno = EINVAL; return -1; } chip = gpiod_chip_open_lookup(device); if (!chip) return -1; gpiod_line_bulk_init(&bulk); for (i = 0; i < num_lines; i++) { line = gpiod_chip_get_line(chip, offsets[i]); if (!line) { gpiod_chip_close(chip); return -1; } gpiod_line_bulk_add(&bulk, line); } req_flags = ctxless_flags_to_line_request_flags(active_low, flags); rv = gpiod_line_request_bulk_input_flags(&bulk, consumer, req_flags); if (rv < 0) { gpiod_chip_close(chip); return -1; } memset(values, 0, sizeof(*values) * num_lines); rv = gpiod_line_get_value_bulk(&bulk, values); gpiod_chip_close(chip); return rv; } int gpiod_ctxless_set_value(const char *device, unsigned int offset, int value, bool active_low, const char *consumer, gpiod_ctxless_set_value_cb cb, void *data) { return gpiod_ctxless_set_value_multiple(device, &offset, &value, 1, active_low, consumer, cb, data); } int gpiod_ctxless_set_value_ext(const char *device, unsigned int offset, int value, bool active_low, const char *consumer, gpiod_ctxless_set_value_cb cb, void *data, int flags) { return gpiod_ctxless_set_value_multiple_ext(device, &offset, &value, 1, active_low, consumer, cb, data, flags); } int gpiod_ctxless_set_value_multiple(const char *device, const unsigned int *offsets, const int *values, unsigned int num_lines, bool active_low, const char *consumer, gpiod_ctxless_set_value_cb cb, void *data) { return gpiod_ctxless_set_value_multiple_ext(device, offsets, values, num_lines, active_low, consumer, cb, data, 0); } int gpiod_ctxless_set_value_multiple_ext( const char *device, const unsigned int *offsets, const int *values, unsigned int num_lines, bool active_low, const char *consumer, gpiod_ctxless_set_value_cb cb, void *data, int flags) { struct gpiod_line_bulk bulk; struct gpiod_chip *chip; struct gpiod_line *line; unsigned int i; int rv, req_flags; if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) { errno = EINVAL; return -1; } chip = gpiod_chip_open_lookup(device); if (!chip) return -1; gpiod_line_bulk_init(&bulk); for (i = 0; i < num_lines; i++) { line = gpiod_chip_get_line(chip, offsets[i]); if (!line) { gpiod_chip_close(chip); return -1; } gpiod_line_bulk_add(&bulk, line); } req_flags = ctxless_flags_to_line_request_flags(active_low, flags); rv = gpiod_line_request_bulk_output_flags(&bulk, consumer, req_flags, values); if (rv < 0) { gpiod_chip_close(chip); return -1; } if (cb) cb(data); gpiod_chip_close(chip); return 0; } static int basic_event_poll(unsigned int num_lines, struct gpiod_ctxless_event_poll_fd *fds, const struct timespec *timeout, void *data GPIOD_UNUSED) { struct pollfd poll_fds[GPIOD_LINE_BULK_MAX_LINES]; unsigned int i; int rv, ret; if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) return GPIOD_CTXLESS_EVENT_POLL_RET_ERR; memset(poll_fds, 0, sizeof(poll_fds)); for (i = 0; i < num_lines; i++) { poll_fds[i].fd = fds[i].fd; poll_fds[i].events = POLLIN | POLLPRI; } rv = ppoll(poll_fds, num_lines, timeout, NULL); if (rv < 0) { if (errno == EINTR) return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT; else return GPIOD_CTXLESS_EVENT_POLL_RET_ERR; } else if (rv == 0) { return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT; } ret = rv; for (i = 0; i < num_lines; i++) { if (poll_fds[i].revents) { fds[i].event = true; if (!--rv) break; } } return ret; } int gpiod_ctxless_event_loop(const char *device, unsigned int offset, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data) { return gpiod_ctxless_event_monitor(device, GPIOD_CTXLESS_EVENT_BOTH_EDGES, offset, active_low, consumer, timeout, poll_cb, event_cb, data); } int gpiod_ctxless_event_loop_multiple(const char *device, const unsigned int *offsets, unsigned int num_lines, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data) { return gpiod_ctxless_event_monitor_multiple( device, GPIOD_CTXLESS_EVENT_BOTH_EDGES, offsets, num_lines, active_low, consumer, timeout, poll_cb, event_cb, data); } int gpiod_ctxless_event_monitor(const char *device, int event_type, unsigned int offset, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data) { return gpiod_ctxless_event_monitor_multiple(device, event_type, &offset, 1, active_low, consumer, timeout, poll_cb, event_cb, data); } int gpiod_ctxless_event_monitor_ext(const char *device, int event_type, unsigned int offset, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data, int flags) { return gpiod_ctxless_event_monitor_multiple_ext( device, event_type, &offset, 1, active_low, consumer, timeout, poll_cb, event_cb, data, flags); } int gpiod_ctxless_event_monitor_multiple( const char *device, int event_type, const unsigned int *offsets, unsigned int num_lines, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data) { return gpiod_ctxless_event_monitor_multiple_ext( device, event_type, offsets, num_lines, active_low, consumer, timeout, poll_cb, event_cb, data, 0); } int gpiod_ctxless_event_monitor_multiple_ext( const char *device, int event_type, const unsigned int *offsets, unsigned int num_lines, bool active_low, const char *consumer, const struct timespec *timeout, gpiod_ctxless_event_poll_cb poll_cb, gpiod_ctxless_event_handle_cb event_cb, void *data, int flags) { struct gpiod_ctxless_event_poll_fd fds[GPIOD_LINE_BULK_MAX_LINES]; struct gpiod_line_request_config conf; struct gpiod_line_event event; struct gpiod_line_bulk bulk; int rv, ret, evtype, cnt; struct gpiod_chip *chip; struct gpiod_line *line; unsigned int i; if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) { errno = EINVAL; return -1; } if (!poll_cb) poll_cb = basic_event_poll; chip = gpiod_chip_open_lookup(device); if (!chip) return -1; gpiod_line_bulk_init(&bulk); for (i = 0; i < num_lines; i++) { line = gpiod_chip_get_line(chip, offsets[i]); if (!line) { gpiod_chip_close(chip); return -1; } gpiod_line_bulk_add(&bulk, line); } conf.flags = ctxless_flags_to_line_request_flags(active_low, flags); conf.consumer = consumer; if (event_type == GPIOD_CTXLESS_EVENT_RISING_EDGE) { conf.request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE; } else if (event_type == GPIOD_CTXLESS_EVENT_FALLING_EDGE) { conf.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE; } else if (event_type == GPIOD_CTXLESS_EVENT_BOTH_EDGES) { conf.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES; } else { errno = -EINVAL; ret = -1; goto out; } rv = gpiod_line_request_bulk(&bulk, &conf, NULL); if (rv) { ret = -1; goto out; } memset(fds, 0, sizeof(fds)); for (i = 0; i < num_lines; i++) { line = gpiod_line_bulk_get_line(&bulk, i); fds[i].fd = gpiod_line_event_get_fd(line); } for (;;) { for (i = 0; i < num_lines; i++) fds[i].event = false; cnt = poll_cb(num_lines, fds, timeout, data); if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_ERR) { ret = -1; goto out; } else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT) { rv = event_cb(GPIOD_CTXLESS_EVENT_CB_TIMEOUT, 0, &event.ts, data); if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) { ret = -1; goto out; } else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) { ret = 0; goto out; } } else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_STOP) { ret = 0; goto out; } for (i = 0; i < num_lines; i++) { if (!fds[i].event) continue; line = gpiod_line_bulk_get_line(&bulk, i); rv = gpiod_line_event_read(line, &event); if (rv < 0) { ret = rv; goto out; } if (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE) evtype = GPIOD_CTXLESS_EVENT_CB_RISING_EDGE; else evtype = GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE; rv = event_cb(evtype, gpiod_line_offset(line), &event.ts, data); if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) { ret = -1; goto out; } else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) { ret = 0; goto out; } if (!--cnt) break; } } out: gpiod_chip_close(chip); return ret; } int gpiod_ctxless_find_line(const char *name, char *chipname, size_t chipname_size, unsigned int *offset) { struct gpiod_chip *chip; struct gpiod_line *line; line = gpiod_line_find(name); if (!line) { if (errno == ENOENT) return 0; else return -1; } chip = gpiod_line_get_chip(line); snprintf(chipname, chipname_size, "%s", gpiod_chip_name(chip)); *offset = gpiod_line_offset(line); gpiod_chip_close(chip); return 1; }