mirror of
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
synced 2026-10-24 00:48:53 +03:00
In order to enable users of the project to reuse the high-level functions provided to gpio-tools via the static tools-common library (for instance: for line lookup by name), expose a sub-set of its functionality in new shared library: libgpiotools, accompanied by a new public header and providing a stable API/ABI. Add a new rst page documenting the public API of the libgpiotools shared library and integrate it into the sphinx build. Closes: https://github.com/brgl/libgpiod/discussions/178 Acked-by: Kent Gibson <warthog618@gmail.com> Link: https://patch.msgid.link/20260513-tools-common-shared-lib-v1-1-58ac28a9f1d5@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
438 lines
9.1 KiB
C
438 lines
9.1 KiB
C
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
|
|
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
|
|
// SPDX-FileCopyrightText: 2026 Qualcomm Technologies, Inc. and/or its subsidiaries
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <gpiod.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "gpiotools.h"
|
|
|
|
#define GT_API __attribute__((visibility("default")))
|
|
|
|
static bool isuint(const char *str)
|
|
{
|
|
for (; *str && isdigit(*str); str++)
|
|
;
|
|
|
|
return *str == '\0';
|
|
}
|
|
|
|
static int parse_uint(const char *option)
|
|
{
|
|
unsigned long o;
|
|
char *end;
|
|
|
|
o = strtoul(option, &end, 10);
|
|
if (*end == '\0' && o <= INT_MAX)
|
|
return o;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int chip_dir_filter(const struct dirent *entry)
|
|
{
|
|
struct stat sb;
|
|
char *path;
|
|
int ret;
|
|
|
|
ret = asprintf(&path, "/dev/%s", entry->d_name);
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
ret = 0;
|
|
if (lstat(path, &sb) == 0 && !S_ISLNK(sb.st_mode) &&
|
|
gpiod_is_gpiochip_device(path))
|
|
ret = 1;
|
|
|
|
free(path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
GT_API char *gpiotools_chip_path_lookup(const char *id)
|
|
{
|
|
char *path;
|
|
int ret;
|
|
|
|
if (isuint(id))
|
|
ret = asprintf(&path, "/dev/gpiochip%s", id);
|
|
else if (strchr(id, '/'))
|
|
ret = asprintf(&path, "%s", id);
|
|
else
|
|
ret = asprintf(&path, "/dev/%s", id);
|
|
|
|
if (ret < 0)
|
|
return NULL;
|
|
|
|
if (!gpiod_is_gpiochip_device(path)) {
|
|
free(path);
|
|
return NULL;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
GT_API int gpiotools_all_chip_paths(char ***paths_ptr)
|
|
{
|
|
int i, j, num_chips, ret;
|
|
struct dirent **entries;
|
|
char **paths;
|
|
|
|
num_chips = scandir("/dev/", &entries, chip_dir_filter, versionsort);
|
|
if (num_chips < 0)
|
|
return -1;
|
|
|
|
paths = calloc(num_chips, sizeof(*paths));
|
|
if (!paths) {
|
|
for (i = 0; i < num_chips; i++)
|
|
free(entries[i]);
|
|
free(entries);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < num_chips; i++) {
|
|
ret = asprintf(&paths[i], "/dev/%s", entries[i]->d_name);
|
|
if (ret < 0) {
|
|
for (j = 0; j < i; j++)
|
|
free(paths[j]);
|
|
free(paths);
|
|
for (j = 0; j < num_chips; j++)
|
|
free(entries[j]);
|
|
free(entries);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
*paths_ptr = paths;
|
|
|
|
for (i = 0; i < num_chips; i++)
|
|
free(entries[i]);
|
|
free(entries);
|
|
|
|
return num_chips;
|
|
}
|
|
|
|
GT_API int gpiotools_chip_paths(const char *id, char ***paths_ptr)
|
|
{
|
|
char **paths, *path;
|
|
|
|
if (!id)
|
|
return gpiotools_all_chip_paths(paths_ptr);
|
|
|
|
path = gpiotools_chip_path_lookup(id);
|
|
if (!path)
|
|
return 0;
|
|
|
|
paths = malloc(sizeof(*paths));
|
|
if (!paths) {
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
paths[0] = path;
|
|
*paths_ptr = paths;
|
|
|
|
return 1;
|
|
}
|
|
|
|
GT_API struct gpiotools_line_resolver *
|
|
gpiotools_resolver_init(int num_lines, char **lines, int num_chips,
|
|
bool strict, bool by_name)
|
|
{
|
|
struct gpiotools_line_resolver *resolver;
|
|
struct gpiotools_resolved_line *line;
|
|
size_t resolver_size;
|
|
int i;
|
|
|
|
resolver_size = sizeof(*resolver) + num_lines * sizeof(*line);
|
|
resolver = malloc(resolver_size);
|
|
if (!resolver)
|
|
return NULL;
|
|
|
|
memset(resolver, 0, resolver_size);
|
|
|
|
resolver->chips = calloc(num_chips,
|
|
sizeof(struct gpiotools_resolved_chip));
|
|
if (!resolver->chips) {
|
|
free(resolver);
|
|
return NULL;
|
|
}
|
|
|
|
resolver->num_lines = num_lines;
|
|
resolver->strict = strict;
|
|
|
|
for (i = 0; i < num_lines; i++) {
|
|
line = &resolver->lines[i];
|
|
line->id = lines[i];
|
|
line->id_as_offset = by_name ? -1 : parse_uint(lines[i]);
|
|
line->chip_num = -1;
|
|
}
|
|
|
|
return resolver;
|
|
}
|
|
|
|
GT_API bool
|
|
gpiotools_resolve_lines_by_offset(struct gpiotools_line_resolver *resolver,
|
|
unsigned int num_lines)
|
|
{
|
|
struct gpiotools_resolved_line *line;
|
|
bool used = false;
|
|
int i;
|
|
|
|
for (i = 0; i < resolver->num_lines; i++) {
|
|
line = &resolver->lines[i];
|
|
|
|
if ((line->id_as_offset != -1) &&
|
|
(line->id_as_offset < (int)num_lines)) {
|
|
line->chip_num = 0;
|
|
line->offset = line->id_as_offset;
|
|
line->resolved = true;
|
|
used = true;
|
|
}
|
|
}
|
|
|
|
return used;
|
|
}
|
|
|
|
GT_API bool gpiotools_resolve_done(struct gpiotools_line_resolver *resolver)
|
|
{
|
|
return (!resolver->strict &&
|
|
resolver->num_found >= resolver->num_lines);
|
|
}
|
|
|
|
/*
|
|
* Try to match a single line_info against all requested lines in the resolver.
|
|
* Returns true if the info was claimed by at least one line, false otherwise.
|
|
* On strict-mode uniqueness violation sets errno to EEXIST and returns false.
|
|
*/
|
|
static bool resolve_line(struct gpiotools_line_resolver *resolver,
|
|
struct gpiod_line_info *info, int chip_num)
|
|
{
|
|
struct gpiotools_resolved_line *line;
|
|
bool resolved = false;
|
|
unsigned int offset;
|
|
const char *name;
|
|
int i;
|
|
|
|
offset = gpiod_line_info_get_offset(info);
|
|
for (i = 0; i < resolver->num_lines; i++) {
|
|
line = &resolver->lines[i];
|
|
|
|
/* already resolved by offset? */
|
|
if (line->resolved && (line->offset == offset) &&
|
|
(line->chip_num == chip_num)) {
|
|
line->info = info;
|
|
resolver->num_found++;
|
|
resolved = true;
|
|
}
|
|
|
|
if (line->resolved && !resolver->strict)
|
|
continue;
|
|
|
|
/* else resolve by name */
|
|
name = gpiod_line_info_get_name(info);
|
|
if (name && strcmp(line->id, name) == 0) {
|
|
if (resolver->strict && line->resolved) {
|
|
line->not_unique = true;
|
|
return false;
|
|
}
|
|
|
|
line->offset = offset;
|
|
line->info = info;
|
|
line->chip_num = resolver->num_chips;
|
|
line->resolved = true;
|
|
resolver->num_found++;
|
|
resolved = true;
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
GT_API struct gpiotools_line_resolver *
|
|
gpiotools_resolve_lines(int num_lines, char **lines, const char *chip_id,
|
|
bool strict, bool by_name)
|
|
{
|
|
struct gpiotools_line_resolver *resolver;
|
|
struct gpiod_chip_info *chip_info;
|
|
struct gpiod_line_info *line_info;
|
|
int num_chips, i, offset;
|
|
struct gpiod_chip *chip;
|
|
bool chip_used;
|
|
char **paths;
|
|
|
|
if (!chip_id)
|
|
by_name = true;
|
|
|
|
num_chips = gpiotools_chip_paths(chip_id, &paths);
|
|
if (num_chips < 0)
|
|
return NULL;
|
|
if (chip_id && (num_chips == 0)) {
|
|
errno = ENODEV;
|
|
return NULL;
|
|
}
|
|
|
|
resolver = gpiotools_resolver_init(num_lines, lines, num_chips,
|
|
strict, by_name);
|
|
if (!resolver)
|
|
goto err_free_paths;
|
|
|
|
for (i = 0; (i < num_chips) && !gpiotools_resolve_done(resolver); i++) {
|
|
chip_used = false;
|
|
chip = gpiod_chip_open(paths[i]);
|
|
if (!chip) {
|
|
if (errno == EACCES && !chip_id) {
|
|
free(paths[i]);
|
|
paths[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
goto err_free_resolver;
|
|
}
|
|
|
|
chip_info = gpiod_chip_get_info(chip);
|
|
if (!chip_info) {
|
|
gpiod_chip_close(chip);
|
|
goto err_free_resolver;
|
|
}
|
|
|
|
num_lines = gpiod_chip_info_get_num_lines(chip_info);
|
|
|
|
if (i == 0 && chip_id && !by_name)
|
|
chip_used = gpiotools_resolve_lines_by_offset(resolver,
|
|
num_lines);
|
|
|
|
for (offset = 0;
|
|
(offset < num_lines) && !gpiotools_resolve_done(resolver);
|
|
offset++) {
|
|
line_info = gpiod_chip_get_line_info(chip, offset);
|
|
if (!line_info) {
|
|
gpiod_chip_info_free(chip_info);
|
|
gpiod_chip_close(chip);
|
|
goto err_free_resolver;
|
|
}
|
|
|
|
if (resolve_line(resolver, line_info, i)) {
|
|
chip_used = true;
|
|
} else {
|
|
gpiod_line_info_free(line_info);
|
|
}
|
|
}
|
|
|
|
gpiod_chip_close(chip);
|
|
|
|
if (chip_used) {
|
|
resolver->chips[resolver->num_chips].info = chip_info;
|
|
resolver->chips[resolver->num_chips].path = paths[i];
|
|
paths[i] = NULL;
|
|
resolver->num_chips++;
|
|
} else {
|
|
gpiod_chip_info_free(chip_info);
|
|
free(paths[i]);
|
|
paths[i] = NULL;
|
|
}
|
|
}
|
|
|
|
free(paths);
|
|
|
|
return resolver;
|
|
|
|
err_free_resolver:
|
|
gpiotools_free_line_resolver(resolver);
|
|
err_free_paths:
|
|
for (i = 0; i < num_chips; i++)
|
|
free(paths[i]);
|
|
free(paths);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GT_API void gpiotools_free_line_resolver(struct gpiotools_line_resolver *resolver)
|
|
{
|
|
int i;
|
|
|
|
if (!resolver)
|
|
return;
|
|
|
|
for (i = 0; i < resolver->num_lines; i++)
|
|
gpiod_line_info_free(resolver->lines[i].info);
|
|
|
|
for (i = 0; i < resolver->num_chips; i++) {
|
|
gpiod_chip_info_free(resolver->chips[i].info);
|
|
free(resolver->chips[i].path);
|
|
}
|
|
|
|
free(resolver->chips);
|
|
free(resolver);
|
|
}
|
|
|
|
GT_API int
|
|
gpiotools_get_line_offsets_and_values(struct gpiotools_line_resolver *resolver,
|
|
int chip_num, unsigned int *offsets,
|
|
enum gpiod_line_value *values)
|
|
{
|
|
struct gpiotools_resolved_line *line;
|
|
int i, num_lines = 0;
|
|
|
|
for (i = 0; i < resolver->num_lines; i++) {
|
|
line = &resolver->lines[i];
|
|
|
|
if (line->chip_num == chip_num) {
|
|
offsets[num_lines] = line->offset;
|
|
if (values)
|
|
values[num_lines] = line->value;
|
|
|
|
num_lines++;
|
|
}
|
|
}
|
|
|
|
return num_lines;
|
|
}
|
|
|
|
GT_API const char *
|
|
gpiotools_get_chip_name(struct gpiotools_line_resolver *resolver,
|
|
int chip_num)
|
|
{
|
|
return gpiod_chip_info_get_name(resolver->chips[chip_num].info);
|
|
}
|
|
|
|
GT_API const char *
|
|
gpiotools_get_line_name(struct gpiotools_line_resolver *resolver,
|
|
int chip_num, unsigned int offset)
|
|
{
|
|
struct gpiotools_resolved_line *line;
|
|
int i;
|
|
|
|
for (i = 0; i < resolver->num_lines; i++) {
|
|
line = &resolver->lines[i];
|
|
|
|
if (line->info && line->offset == offset &&
|
|
line->chip_num == chip_num)
|
|
return gpiod_line_info_get_name(resolver->lines[i].info);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GT_API void gpiotools_set_line_values(struct gpiotools_line_resolver *resolver,
|
|
int chip_num,
|
|
enum gpiod_line_value *values)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0, j = 0; i < resolver->num_lines; i++) {
|
|
if (resolver->lines[i].chip_num == chip_num) {
|
|
resolver->lines[i].value = values[j];
|
|
j++;
|
|
}
|
|
}
|
|
}
|