blob: a39ae17c6a2a7bcdabfc69bc3865dc5b12db2e7a [file] [edit]
/* Copyright 2025 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <dirent.h>
#include <fcntl.h>
#if !defined(HAVE_MACOS) && !defined (__FreeBSD__) && !defined(__OpenBSD__)
#include <linux/fs.h>
#include <linux/gpio.h>
#endif
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "gpio_uapi.h"
#if defined(GPIO_V2_GET_LINE_IOCTL)
static int gpio_uapi_v2_read_value(int chip_fd, int idx, bool active_low)
{
struct gpio_v2_line_request request;
struct gpio_v2_line_values line_value;
int trynum;
int ret;
memset(&request, 0, sizeof(request));
memset(&line_value, 0, sizeof(line_value));
/* Request a single line with an input mode and corresponding active state.
Set consumer to allow for easy identification of access. */
request.offsets[0] = idx;
request.num_lines = 1,
request.config.flags =
GPIO_V2_LINE_FLAG_INPUT | (active_low ? GPIO_V2_LINE_FLAG_ACTIVE_LOW : 0);
strcpy(request.consumer, "vboot");
/* Set first bit corresponding to the first an only index/offset from the request. */
line_value.mask = 0x1;
/*
* If two callers try to read the same GPIO at the same time then
* one of the two will get back EBUSY. There's no great way to
* solve this, so we'll just retry a bunch with a small sleep in
* between.
*/
for (trynum = 0; true; trynum++) {
ret = ioctl(chip_fd, GPIO_V2_GET_LINE_IOCTL, &request);
/*
* Not part of the loop condition so usleep doesn't clobber
* errno (implicitly used by perror).
*/
if (ret >= 0 || errno != EBUSY || trynum >= 50)
break;
usleep(trynum * 1000);
}
if (ret < 0) {
perror("GPIO_V2_GET_LINE_IOCTL");
return -1;
}
if (request.fd < 0) {
fprintf(stderr, "bad LINE fd %d\n", request.fd);
return -1;
}
ret = ioctl(request.fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &line_value);
if (ret < 0) {
perror("GPIO_V2_LINE_GET_VALUES_IOCTL");
close(request.fd);
return -1;
}
close(request.fd);
return line_value.bits & 0x1;
}
/*
* Checks if the pin name at the @idx index is equal to the @name.
* Returns -1 on failure, 0 on match, 1 on mismatch.
*/
static int gpio_uapi_v2_name_match(int chip_fd, int idx, const char *name)
{
struct gpio_v2_line_info info;
memset(&info, 0, sizeof(info));
info.offset = idx;
if (ioctl(chip_fd, GPIO_V2_GET_LINEINFO_IOCTL, &info) < 0) {
perror("GPIO_V2_GET_LINEINFO_IOCTL");
return -1;
}
return strncmp(info.name, name, sizeof(info.name)) != 0;
}
#elif defined(GPIO_GET_LINEHANDLE_IOCTL)
static int gpio_uapi_v1_read_value(int chip_fd, int idx, bool active_low)
{
struct gpiohandle_request request = {
.lineoffsets = {idx},
.flags = GPIOHANDLE_REQUEST_INPUT |
(active_low ? GPIOHANDLE_REQUEST_ACTIVE_LOW : 0),
.lines = 1,
};
struct gpiohandle_data data;
int trynum;
int ret;
/*
* If two callers try to read the same GPIO at the same time then
* one of the two will get back EBUSY. There's no great way to
* solve this, so we'll just retry a bunch with a small sleep in
* between.
*/
for (trynum = 0; true; trynum++) {
ret = ioctl(chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &request);
/*
* Not part of the loop condition so usleep doesn't clobber
* errno (implicitly used by perror).
*/
if (ret >= 0 || errno != EBUSY || trynum >= 50)
break;
usleep(trynum * 1000);
}
if (ret < 0) {
perror("GPIO_GET_LINEHANDLE_IOCTL");
return -1;
}
if (request.fd < 0) {
fprintf(stderr, "bad LINEHANDLE fd %d\n", request.fd);
return -1;
}
ret = ioctl(request.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (ret < 0) {
perror("GPIOHANDLE_GET_LINE_VALUES_IOCTL");
close(request.fd);
return -1;
}
close(request.fd);
return data.values[0];
}
/*
* Checks if the pin name at the @idx index is equal to the @name.
* Returns -1 on failure, 0 on match, 1 on mismatch.
*/
static int gpio_uapi_v1_name_match(int chip_fd, int idx, const char *name)
{
struct gpioline_info info;
memset(&info, 0, sizeof(info));
info.line_offset = idx;
if (ioctl(chip_fd, GPIO_GET_LINEINFO_IOCTL, &info) < 0) {
perror("GPIO_GET_LINEINFO_IOCTL");
return -1;
}
return strncmp(info.name, name, sizeof(info.name)) != 0;
}
#endif
static int gpio_uapi_read_value_by_idx(int fd, int idx, bool active_low)
{
#if defined(GPIO_V2_GET_LINE_IOCTL)
return gpio_uapi_v2_read_value(fd, idx, active_low);
#elif defined(GPIO_GET_LINEHANDLE_IOCTL)
return gpio_uapi_v1_read_value(fd, idx, active_low);
#else
return -1;
#endif
}
static int gpio_uapi_read_value_by_name(int chip_fd, const char *name, bool active_low)
{
struct gpiochip_info info;
int ret;
if (ioctl(chip_fd, GPIO_GET_CHIPINFO_IOCTL, &info) < 0) {
perror("GPIO_GET_CHIPINFO_IOCTL");
return -1;
}
for (int i = 0; i < info.lines; i++) {
#if defined(GPIO_V2_GET_LINE_IOCTL)
ret = gpio_uapi_v2_name_match(chip_fd, i, name);
#elif defined(GPIO_GET_LINEHANDLE_IOCTL)
ret = gpio_uapi_v1_name_match(chip_fd, i, name);
#else
return -1;
#endif
if (ret < 0)
return -1;
/* No match */
if (ret)
continue;
return gpio_uapi_read_value_by_idx(chip_fd, i, active_low);
}
return -1;
}
/* Return nonzero for entries with a 'gpiochip'-prefixed name. */
static int gpiochip_scan_filter(const struct dirent *d)
{
const char prefix[] = "gpiochip";
return !strncmp(prefix, d->d_name, strlen(prefix));
}
int gpio_read_value_by_name(const char *name, bool active_low)
{
struct dirent **list;
int i, entries_num, ret;
ret = scandir("/dev", &list, gpiochip_scan_filter, alphasort);
if (ret < 0) {
perror("scandir");
return -1;
}
entries_num = ret;
/* No /dev/gpiochip* -- API not supported. */
if (!entries_num)
return -1;
for (i = 0; i < entries_num; i++) {
char buf[5 + NAME_MAX + 1];
int fd;
snprintf(buf, sizeof(buf), "/dev/%s", list[i]->d_name);
ret = open(buf, O_RDWR);
if (ret < 0) {
perror("open");
break;
}
fd = ret;
ret = gpio_uapi_read_value_by_name(fd, name, active_low);
close(fd);
if (ret >= 0)
break;
}
for (i = 0; i < entries_num; i++)
free(list[i]);
free(list);
return ret;
}
int gpio_read_value_by_idx(int controller_num, int idx, bool active_low)
{
int fd, ret;
char path[5 + NAME_MAX + 1];
snprintf(path, sizeof(path), "/dev/gpiochip%d", controller_num);
fd = open(path, O_RDWR);
if (fd < 0) {
fprintf(stderr, "Unable to open %s\n", path);
perror("open");
return -1;
}
ret = gpio_uapi_read_value_by_idx(fd, idx, active_low);
close(fd);
return ret;
}