// SPDX-License-Identifier: LGPL-2.1-or-later /* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com> */ /* Low-level, core library code. */ #include <errno.h> #include <fcntl.h> #include <gpiod.h> #include <limits.h> #include <linux/gpio.h> #include <poll.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <sys/types.h> #include <unistd.h> /* * These are symbols first available in linux v5.5. In order to allow to build * libgpiod with kernel headers as early as v4.8 we redefine them here for pre * v5.5 build environments. Certain features will not work on older kernels. */ #ifdef KERNEL_PRE_5_5 #define GPIOLINE_FLAG_BIAS_PULL_UP (1UL << 5) #define GPIOLINE_FLAG_BIAS_PULL_DOWN (1UL << 6) #define GPIOLINE_FLAG_BIAS_DISABLE (1UL << 7) #define GPIOHANDLE_REQUEST_BIAS_PULL_UP (1UL << 5) #define GPIOHANDLE_REQUEST_BIAS_PULL_DOWN (1UL << 6) #define GPIOHANDLE_REQUEST_BIAS_DISABLE (1UL << 7) struct gpiohandle_config { __u32 flags; __u8 default_values[GPIOHANDLES_MAX]; __u32 padding[4]; /* padding for future use */ }; #define GPIOHANDLE_SET_CONFIG_IOCTL _IOWR(0xB4, 0x0a, struct gpiohandle_config) #endif /* KERNEL_PRE_5_5 */ enum { LINE_FREE = 0, LINE_REQUESTED_VALUES, LINE_REQUESTED_EVENTS, }; struct line_fd_handle { int fd; int refcount; }; struct gpiod_line { unsigned int offset; /* The direction of the GPIO line. */ int direction; /* The active-state configuration. */ int active_state; /* The logical value last written to the line. */ int output_value; /* The GPIOLINE_FLAGs returned by GPIO_GET_LINEINFO_IOCTL. */ __u32 info_flags; /* The GPIOD_LINE_REQUEST_FLAGs provided to request the line. */ __u32 req_flags; /* * Indicator of LINE_FREE, LINE_REQUESTED_VALUES or * LINE_REQUESTED_EVENTS. */ int state; struct gpiod_chip *chip; struct line_fd_handle *fd_handle; 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]; }; static bool is_gpiochip_cdev(const char *path) { char *name, *realname, *sysfsp, sysfsdev[16], devstr[16]; struct stat statbuf; bool ret = false; int rv, fd; ssize_t rd; rv = lstat(path, &statbuf); if (rv) goto out; /* * Is it a symbolic link? We have to resolve symbolic link before * checking the rest. */ realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL) : strdup(path); if (realname == NULL) goto out; rv = stat(realname, &statbuf); if (rv) goto out_free_realname; /* Is it a character device? */ if (!S_ISCHR(statbuf.st_mode)) { /* * Passing a file descriptor not associated with a character * device to ioctl() makes it set errno to ENOTTY. Let's do * the same in order to stay compatible with the versions of * libgpiod from before the introduction of this routine. */ errno = ENOTTY; goto out_free_realname; } /* Get the basename. */ name = basename(realname); /* Do we have a corresponding sysfs attribute? */ rv = asprintf(&sysfsp, "/sys/bus/gpio/devices/%s/dev", name); if (rv < 0) goto out_free_realname; if (access(sysfsp, R_OK) != 0) { /* * This is a character device but not the one we're after. * Before the introduction of this function, we'd fail with * ENOTTY on the first GPIO ioctl() call for this file * descriptor. Let's stay compatible here and keep returning * the same error code. */ errno = ENOTTY; goto out_free_sysfsp; } /* * Make sure the major and minor numbers of the character device * correspond to the ones in the dev attribute in sysfs. */ snprintf(devstr, sizeof(devstr), "%u:%u", major(statbuf.st_rdev), minor(statbuf.st_rdev)); fd = open(sysfsp, O_RDONLY); if (fd < 0) goto out_free_sysfsp; memset(sysfsdev, 0, sizeof(sysfsdev)); rd = read(fd, sysfsdev, sizeof(sysfsdev) - 1); close(fd); if (rd < 0) goto out_free_sysfsp; rd--; /* Ignore trailing newline. */ if ((size_t)rd != strlen(devstr) || strncmp(sysfsdev, devstr, rd) != 0) { errno = ENODEV; goto out_free_sysfsp; } ret = true; out_free_sysfsp: free(sysfsp); out_free_realname: free(realname); out: return ret; } struct gpiod_chip *gpiod_chip_open(const char *path) { struct gpiochip_info info; struct gpiod_chip *chip; int rv, fd; fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) return NULL; /* * We were able to open the file but is it really a gpiochip character * device? */ if (!is_gpiochip_cdev(path)) goto err_close_fd; chip = malloc(sizeof(*chip)); if (!chip) goto err_close_fd; memset(chip, 0, sizeof(*chip)); memset(&info, 0, sizeof(info)); rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); if (rv < 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 rv; 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->offset = offset; line->chip = chip; chip->lines[offset] = line; } else { line = chip->lines[offset]; } rv = gpiod_line_update(line); if (rv < 0) return NULL; return line; } static struct line_fd_handle *line_make_fd_handle(int fd) { struct line_fd_handle *handle; handle = malloc(sizeof(*handle)); if (!handle) return NULL; handle->fd = fd; handle->refcount = 0; return handle; } static void line_fd_incref(struct gpiod_line *line) { line->fd_handle->refcount++; } static void line_fd_decref(struct gpiod_line *line) { struct line_fd_handle *handle = line->fd_handle; handle->refcount--; if (handle->refcount == 0) { close(handle->fd); free(handle); line->fd_handle = NULL; } } static void line_set_fd(struct gpiod_line *line, struct line_fd_handle *handle) { line->fd_handle = handle; line_fd_incref(line); } static int line_get_fd(struct gpiod_line *line) { return line->fd_handle->fd; } 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; } int gpiod_line_bias(struct gpiod_line *line) { if (line->info_flags & GPIOLINE_FLAG_BIAS_DISABLE) return GPIOD_LINE_BIAS_DISABLE; if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_UP) return GPIOD_LINE_BIAS_PULL_UP; if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_DOWN) return GPIOD_LINE_BIAS_PULL_DOWN; return GPIOD_LINE_BIAS_AS_IS; } bool gpiod_line_is_used(struct gpiod_line *line) { return line->info_flags & GPIOLINE_FLAG_KERNEL; } bool gpiod_line_is_open_drain(struct gpiod_line *line) { return line->info_flags & GPIOLINE_FLAG_OPEN_DRAIN; } bool gpiod_line_is_open_source(struct gpiod_line *line) { return line->info_flags & GPIOLINE_FLAG_OPEN_SOURCE; } bool gpiod_line_needs_update(struct gpiod_line *line GPIOD_UNUSED) { return false; } 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->info_flags = info.flags; strncpy(line->name, info.name, sizeof(line->name)); strncpy(line->consumer, info.consumer, sizeof(line->consumer)); 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_requested_values(struct gpiod_line_bulk *bulk) { struct gpiod_line *line, **lineptr; gpiod_line_bulk_foreach_line(bulk, line, lineptr) { if (line->state != LINE_REQUESTED_VALUES) { 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 bool line_request_direction_is_valid(int direction) { if ((direction == GPIOD_LINE_REQUEST_DIRECTION_AS_IS) || (direction == GPIOD_LINE_REQUEST_DIRECTION_INPUT) || (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)) return true; errno = EINVAL; return false; } static __u32 line_request_direction_to_gpio_handleflag(int direction) { if (direction == GPIOD_LINE_REQUEST_DIRECTION_INPUT) return GPIOHANDLE_REQUEST_INPUT; if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) return GPIOHANDLE_REQUEST_OUTPUT; return 0; } static __u32 line_request_flag_to_gpio_handleflag(int flags) { int hflags = 0; if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) hflags |= GPIOHANDLE_REQUEST_OPEN_DRAIN; if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE) hflags |= GPIOHANDLE_REQUEST_OPEN_SOURCE; if (flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW) hflags |= GPIOHANDLE_REQUEST_ACTIVE_LOW; if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE) hflags |= GPIOHANDLE_REQUEST_BIAS_DISABLE; if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN) hflags |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP) hflags |= GPIOHANDLE_REQUEST_BIAS_PULL_UP; return hflags; } 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; struct line_fd_handle *line_fd; 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)); req.lines = gpiod_line_bulk_num_lines(bulk); req.flags = line_request_flag_to_gpio_handleflag(config->flags); 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; 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 && default_vals) 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; line_fd = line_make_fd_handle(req.fd); if (!line_fd) return -1; gpiod_line_bulk_foreach_line_off(bulk, line, i) { line->state = LINE_REQUESTED_VALUES; line->req_flags = config->flags; if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) line->output_value = req.default_values[i]; line_set_fd(line, line_fd); rv = gpiod_line_update(line); if (rv) { gpiod_line_release_bulk(bulk); return rv; } } return 0; } static int line_request_event_single(struct gpiod_line *line, const struct gpiod_line_request_config *config) { struct line_fd_handle *line_fd; 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 = line_request_flag_to_gpio_handleflag(config->flags); req.handleflags |= GPIOHANDLE_REQUEST_INPUT; 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_fd = line_make_fd_handle(req.fd); if (!line_fd) return -1; line->state = LINE_REQUESTED_EVENTS; line->req_flags = config->flags; line_set_fd(line, line_fd); rv = gpiod_line_update(line); if (rv) { gpiod_line_release(line); return rv; } 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) { line_fd_decref(line); 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 rv, value; gpiod_line_bulk_init(&bulk); gpiod_line_bulk_add(&bulk, line); rv = gpiod_line_get_value_bulk(&bulk, &value); if (rv < 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 *line; unsigned int i; int rv, fd; if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk)) return -1; line = gpiod_line_bulk_get_line(bulk, 0); if (line->state == LINE_REQUESTED_VALUES) { memset(&data, 0, sizeof(data)); fd = line_get_fd(line); rv = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); if (rv < 0) return -1; for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) values[i] = data.values[i]; } else if (line->state == LINE_REQUESTED_EVENTS) { for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) { line = gpiod_line_bulk_get_line(bulk, i); fd = line_get_fd(line); rv = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); if (rv < 0) return -1; values[i] = data.values[0]; } } else { errno = EINVAL; return -1; } 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 rv, fd; if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk)) return -1; memset(&data, 0, sizeof(data)); if (values) { 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); fd = line_get_fd(line); rv = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); if (rv < 0) return -1; gpiod_line_bulk_foreach_line_off(bulk, line, i) line->output_value = data.values[i]; return 0; } int gpiod_line_set_config(struct gpiod_line *line, int direction, int flags, int value) { struct gpiod_line_bulk bulk; gpiod_line_bulk_init(&bulk); gpiod_line_bulk_add(&bulk, line); return gpiod_line_set_config_bulk(&bulk, direction, flags, &value); } int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk, int direction, int flags, const int *values) { struct gpiohandle_config hcfg; struct gpiod_line *line; unsigned int i; int rv, fd; if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested_values(bulk)) return -1; if (!line_request_direction_is_valid(direction)) return -1; memset(&hcfg, 0, sizeof(hcfg)); hcfg.flags = line_request_flag_to_gpio_handleflag(flags); hcfg.flags |= line_request_direction_to_gpio_handleflag(direction); if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT && values) { for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) hcfg.default_values[i] = (uint8_t)!!values[i]; } line = gpiod_line_bulk_get_line(bulk, 0); fd = line_get_fd(line); rv = ioctl(fd, GPIOHANDLE_SET_CONFIG_IOCTL, &hcfg); if (rv < 0) return -1; gpiod_line_bulk_foreach_line_off(bulk, line, i) { line->req_flags = flags; if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) line->output_value = hcfg.default_values[i]; rv = gpiod_line_update(line); if (rv < 0) return rv; } return 0; } int gpiod_line_set_flags(struct gpiod_line *line, int flags) { struct gpiod_line_bulk bulk; gpiod_line_bulk_init(&bulk); gpiod_line_bulk_add(&bulk, line); return gpiod_line_set_flags_bulk(&bulk, flags); } int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags) { struct gpiod_line *line; int values[GPIOD_LINE_BULK_MAX_LINES]; unsigned int i; int direction; line = gpiod_line_bulk_get_line(bulk, 0); if (line->direction == GPIOD_LINE_DIRECTION_OUTPUT) { gpiod_line_bulk_foreach_line_off(bulk, line, i) values[i] = line->output_value; direction = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT; } else { direction = GPIOD_LINE_REQUEST_DIRECTION_INPUT; } return gpiod_line_set_config_bulk(bulk, direction, flags, values); } int gpiod_line_set_direction_input(struct gpiod_line *line) { return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_INPUT, line->req_flags, 0); } int gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk) { struct gpiod_line *line; line = gpiod_line_bulk_get_line(bulk, 0); return gpiod_line_set_config_bulk(bulk, GPIOD_LINE_REQUEST_DIRECTION_INPUT, line->req_flags, NULL); } int gpiod_line_set_direction_output(struct gpiod_line *line, int value) { return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, line->req_flags, value); } int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk, const int *values) { struct gpiod_line *line; line = gpiod_line_bulk_get_line(bulk, 0); return gpiod_line_set_config_bulk(bulk, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, line->req_flags, values); } 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_get_fd(line); 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) { if (fds[off].revents & POLLNVAL) { errno = EINVAL; return -1; } if (event_bulk) { 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) { int ret; ret = gpiod_line_event_read_multiple(line, event, 1); if (ret < 0) return -1; return 0; } int gpiod_line_event_read_multiple(struct gpiod_line *line, struct gpiod_line_event *events, unsigned int num_events) { int fd; fd = gpiod_line_event_get_fd(line); if (fd < 0) return -1; return gpiod_line_event_read_fd_multiple(fd, events, num_events); } int gpiod_line_event_get_fd(struct gpiod_line *line) { if (line->state != LINE_REQUESTED_EVENTS) { errno = EPERM; return -1; } return line_get_fd(line); } int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event) { int ret; ret = gpiod_line_event_read_fd_multiple(fd, event, 1); if (ret < 0) return -1; return 0; } int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events, unsigned int num_events) { /* * 16 is the maximum number of events the kernel can store in the FIFO * so we can allocate the buffer on the stack. */ struct gpioevent_data evdata[16], *curr; struct gpiod_line_event *event; unsigned int events_read, i; ssize_t rd; memset(evdata, 0, sizeof(evdata)); if (num_events > 16) num_events = 16; rd = read(fd, evdata, num_events * sizeof(*evdata)); if (rd < 0) { return -1; } else if ((unsigned int)rd < sizeof(*evdata)) { errno = EIO; return -1; } events_read = rd / sizeof(*evdata); if (events_read < num_events) num_events = events_read; for (i = 0; i < num_events; i++) { curr = &evdata[i]; event = &events[i]; event->event_type = curr->id == GPIOEVENT_EVENT_RISING_EDGE ? GPIOD_LINE_EVENT_RISING_EDGE : GPIOD_LINE_EVENT_FALLING_EDGE; event->ts.tv_sec = curr->timestamp / 1000000000ULL; event->ts.tv_nsec = curr->timestamp % 1000000000ULL; } return i; }