Linux GPIO subsystem development
 help / color / mirror / Atom feed
* [PATCH 0/2] tools: provide a shared library with high-level functions used by gpio-tools
@ 2026-05-13 11:47 Bartosz Golaszewski
  2026-05-13 11:47 ` [PATCH 1/2] tools: provide the libgpiotools shared library Bartosz Golaszewski
  2026-05-13 11:47 ` [PATCH 2/2] tools: reuse libgpiotools Bartosz Golaszewski
  0 siblings, 2 replies; 3+ messages in thread
From: Bartosz Golaszewski @ 2026-05-13 11:47 UTC (permalink / raw)
  To: Linus Walleij, Kent Gibson, Vincent Fazio, Alexander Dahl
  Cc: brgl, linux-gpio, Bartosz Golaszewski

The API of libgpiod v2 is a bit more limited than that of v1. Notably:
the whole category of high-level helpers as well as functions allowing
to look up lines and chips by names/labels have been removed and replaced
by low-level interfaces which require users to implement the look-up
logic locally.

It's been requested several times that equivalents of these functions
should be provided in some form. I don't want to expose more
functionality in libgpiod core API than what the linux kernel uAPI
provides. However, the gpio-tools already contain a shared library -
tools-common - which implements the bulk of the desired functions in a
new form.

This series splits out parts of tools-common into a new shared library:
libgpiotools, and reuses it in tools-common while keeping the existing
interface facing the gpio-tools in order to not have to adapt them to
the new API.

Kent: I allowed myself to change the license of the factored out code to
LGPL-v2.1-or-later to enable linking against libgpiotools in line with
libgpiod and other shared libraries from this project. Your Ack would be
appreciated.

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Bartosz Golaszewski (2):
      tools: provide the libgpiotools shared library
      tools: reuse libgpiotools

 docs/Doxyfile           |   1 +
 docs/gpio_tools.rst     |  16 ++
 docs/gpio_tools_lib.rst |  12 ++
 meson.build             |   3 +
 tools/gpiotools.c       | 437 ++++++++++++++++++++++++++++++++++++++++++++++++
 tools/gpiotools.h       | 236 ++++++++++++++++++++++++++
 tools/meson.build       |  28 +++-
 tools/tools-common.c    | 316 ++++------------------------------
 tools/tools-common.h    |  63 +------
 9 files changed, 772 insertions(+), 340 deletions(-)
---
base-commit: 5eed939e0d544afa311a4dd6dac90833d38d7d29
change-id: 20260311-tools-common-shared-lib-97b58de050e2

Best regards,
-- 
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] tools: provide the libgpiotools shared library
  2026-05-13 11:47 [PATCH 0/2] tools: provide a shared library with high-level functions used by gpio-tools Bartosz Golaszewski
@ 2026-05-13 11:47 ` Bartosz Golaszewski
  2026-05-13 11:47 ` [PATCH 2/2] tools: reuse libgpiotools Bartosz Golaszewski
  1 sibling, 0 replies; 3+ messages in thread
From: Bartosz Golaszewski @ 2026-05-13 11:47 UTC (permalink / raw)
  To: Linus Walleij, Kent Gibson, Vincent Fazio, Alexander Dahl
  Cc: brgl, linux-gpio, Bartosz Golaszewski

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
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
 docs/Doxyfile           |   1 +
 docs/gpio_tools.rst     |  16 ++
 docs/gpio_tools_lib.rst |  12 ++
 meson.build             |   3 +
 tools/gpiotools.c       | 437 ++++++++++++++++++++++++++++++++++++++++++++++++
 tools/gpiotools.h       | 236 ++++++++++++++++++++++++++
 tools/meson.build       |  20 +++
 7 files changed, 725 insertions(+)

diff --git a/docs/Doxyfile b/docs/Doxyfile
index 8c5b5dff54721fe4ccc9f3049e6c2c3f484cb911..3ee98ab21c0412a6d62d50686981c82cb3c8f464 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -4,6 +4,7 @@
 PROJECT_NAME		= libgpiod
 OUTPUT_DIRECTORY	= doxygen-output
 INPUT			= ../include/gpiod.h \
+			  ../tools/gpiotools.h \
 			  ../bindings/cxx/gpiod.hpp \
 			  ../bindings/cxx/gpiodcxx/
 GENERATE_XML		= YES
diff --git a/docs/gpio_tools.rst b/docs/gpio_tools.rst
index e4bf5847315ce93859bd4179df306a43a0445fec..2876970e4028451b05f7b20a551fc97d0a1685c2 100644
--- a/docs/gpio_tools.rst
+++ b/docs/gpio_tools.rst
@@ -33,6 +33,22 @@ There are currently six command-line tools available:
   changes to watch for, how many events to process before exiting, or if the
   events should be reported to the console
 
+Shared library
+--------------
+
+A number of functions used to implement **gpio-tools** has been made available
+in a shared library separate from the low-level, core libgpiod API in the form
+of libgpiotools.
+
+.. note::
+   The libgpiotools library does not get nearly as much attention and care as
+   the core libgpiod C library so your mileage may vary.
+
+.. toctree::
+   :maxdepth: 1
+
+   libgpiotools API documentation<gpio_tools_lib>
+
 .. toctree::
    :maxdepth: 1
    :caption: Manual entries
diff --git a/docs/gpio_tools_lib.rst b/docs/gpio_tools_lib.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2931f950d4b60c4e445e6d8c9d29dad1a4975214
--- /dev/null
+++ b/docs/gpio_tools_lib.rst
@@ -0,0 +1,12 @@
+..
+   SPDX-License-Identifier: CC-BY-SA-4.0
+   SPDX-FileCopyrightText: 2026 Qualcomm Technologies, Inc. and/or its subsidiaries
+
+..
+   This file is part of libgpiod.
+
+libgpiotools API
+================
+
+.. doxygengroup:: gpiotools
+   :members:
diff --git a/meson.build b/meson.build
index 4ec1e575820ff64245b52823312b57e8e09127d9..0bf970eb155178394410c4fbd680eb7544834637 100644
--- a/meson.build
+++ b/meson.build
@@ -33,6 +33,9 @@ libgpiosim_version      = '1.0.1'
 # ... and another one for GLib bindings:
 libgpiod_glib_soversion = 1
 libgpiod_glib_version   = '1.0.0'
+# ... and another for the libgpiotools shared library:
+libgpiotools_soversion  = 1
+libgpiotools_version    = '1.0.0'
 
 add_project_arguments(
   '-D_GNU_SOURCE',
diff --git a/tools/gpiotools.c b/tools/gpiotools.c
new file mode 100644
index 0000000000000000000000000000000000000000..dda36da888b94098231e977380aa38af2aa5069c
--- /dev/null
+++ b/tools/gpiotools.c
@@ -0,0 +1,437 @@
+// 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++;
+		}
+	}
+}
diff --git a/tools/gpiotools.h b/tools/gpiotools.h
new file mode 100644
index 0000000000000000000000000000000000000000..f1c35c436fe5acf837e4b0487cea16925112c2e1
--- /dev/null
+++ b/tools/gpiotools.h
@@ -0,0 +1,236 @@
+/* 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 */
+
+/**
+ * @file gpiotools.h
+ */
+
+#ifndef __GPIOTOOLS_H__
+#define __GPIOTOOLS_H__
+
+#include <gpiod.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * @defgroup gpiotools GPIO tools helpers
+ * @{
+ *
+ * Reusable chip and line resolution helpers used by the suite of GPIO
+ * command-line tools. These functions allow users to build their own programs
+ * on top of libgpiod using the same high-level abstractions as the gpio-tools.
+ *
+ * Unlike libgpiod core C API, public structures exposed by libgpiotools are
+ * not opaque by design. The goal is to allow easy access to members without
+ * providing a large number of very similar accessors. We leave some padding
+ * for potential future extensions.
+ *
+ * Error handling: functions typically return negative integer values or NULL
+ * pointers on failure and set errno to indicate the cause unless otherwise
+ * noted.
+ */
+
+/**
+ * @brief Descriptor for a single resolved GPIO line.
+ */
+struct gpiotools_resolved_line {
+	/** Identifier string from the command line. */
+	const char *id;
+	/**
+	 * ID parsed as an integer offset, or -1 if the line must be resolved
+	 * by name.
+	 */
+	int id_as_offset;
+	/** True once the line has been located on a chip. */
+	bool resolved;
+	/** True if strict mode found the name on more than one chip. */
+	bool not_unique;
+	/** Line info object; only valid once resolved. */
+	struct gpiod_line_info *info;
+	/** Index of the owning chip in the resolver's chips array. */
+	int chip_num;
+	/** Offset of the line on its chip. */
+	unsigned int offset;
+	/** Line value used by gpioget/gpioset. */
+	int value;
+	/** @cond INTERNAL */
+	/* Reserved for future extensions. */
+	uint32_t padding[5];
+	/** @endcond */
+};
+
+/**
+ * @brief Descriptor for a single GPIO chip referenced by a resolver.
+ */
+struct gpiotools_resolved_chip {
+	/** Chip info object. */
+	struct gpiod_chip_info *info;
+	/** Path to the chip device file. */
+	char *path;
+	/** @cond INTERNAL */
+	/* Reserved for future extensions. */
+	uint32_t padding[5];
+	/** @endcond */
+};
+
+/**
+ * @brief Resolver mapping requested line names or offsets to GPIO lines.
+ */
+struct gpiotools_line_resolver {
+	/** Number of chips the lines span; also the number of entries in chips. */
+	int num_chips;
+	/** Number of entries in lines. */
+	int num_lines;
+	/** Number of lines that have been found so far. */
+	int num_found;
+	/** If true, perform an exhaustive search to verify line name uniqueness. */
+	bool strict;
+	/** Array of chip descriptors for the chips that own the requested lines. */
+	struct gpiotools_resolved_chip *chips;
+	/** @cond INTERNAL */
+	/* Reserved for future extensions. */
+	uint32_t padding[10];
+	/** @endcond */
+	/** Flexible array of line descriptors for the requested lines. */
+	struct gpiotools_resolved_line lines[];
+};
+
+/**
+ * @brief Look up the path to a GPIO chip device.
+ * @param id Chip identifier: a number ("0"), a name ("gpiochip0"), or a path
+ *           ("/dev/gpiochip0").
+ * @return Pointer to a newly allocated string containing the filesystem path
+ *         to the GPIO chip or NULL on failure. The user is responsible for
+ *         calling free() on the pointer returned on success.
+ */
+char *gpiotools_chip_path_lookup(const char *id);
+
+/**
+ * @brief Get the paths of GPIO chip devices matching an identifier.
+ * @param id Chip identifier, or NULL to return all chips.
+ * @param paths_ptr On success, set to a newly allocated array of path strings.
+ *                  The caller must free() each element and the array itself.
+ * @return Number of chips found (0 if none), or negative errno on error.
+ */
+int gpiotools_chip_paths(const char *id, char ***paths_ptr);
+
+/**
+ * @brief Get the paths of all GPIO chip devices on the system.
+ * @param paths_ptr On success, set to a newly allocated array of path strings.
+ *                  The caller must free() each element and the array itself.
+ * @return Number of chips found (0 if none), or negative errno on error.
+ */
+int gpiotools_all_chip_paths(char ***paths_ptr);
+
+/**
+ * @brief Allocate and initialise a line resolver.
+ * @param num_lines Number of lines to resolve.
+ * @param lines Array of line identifiers (names or offset strings).
+ * @param num_chips Number of chips to allocate space for.
+ * @param strict If true, perform an exhaustive search to verify that line
+ *               names are unique.
+ * @param by_name If true, treat all identifiers as names; if false, try to
+ *                parse them as numeric offsets first.
+ * @return Pointer to the new resolver, or NULL on failure.
+ */
+struct gpiotools_line_resolver *
+gpiotools_resolver_init(int num_lines, char **lines, int num_chips,
+			bool strict, bool by_name);
+
+/**
+ * @brief Resolve lines by numeric offset.
+ * @param resolver Resolver to update.
+ * @param num_lines Number of lines on the chip.
+ *
+ * Marks lines whose identifier was successfully parsed as a numeric offset as
+ * resolved. Only applies to the first chip (chip_num == 0).
+ *
+ * @return True if any line was resolved, false otherwise.
+ */
+bool gpiotools_resolve_lines_by_offset(struct gpiotools_line_resolver *resolver,
+				       unsigned int num_lines);
+
+/**
+ * @brief Check whether line resolution is complete.
+ * @param resolver Resolver to check.
+ *
+ * In non-strict mode, resolution is considered done when all requested lines
+ * have been found. In strict mode this always returns false so that the
+ * caller performs an exhaustive search.
+ *
+ * @return True if resolution is complete, false otherwise.
+ */
+bool gpiotools_resolve_done(struct gpiotools_line_resolver *resolver);
+
+/**
+ * @brief Resolve line names or offsets to GPIO lines on the system.
+ * @param num_lines Number of lines to resolve.
+ * @param lines Array of line identifiers (names or offset strings).
+ * @param chip_id Chip identifier to restrict the search, or NULL to search
+ *                all chips.
+ * @param strict If true, verify that line names are unique across all chips.
+ * @param by_name If true, treat all identifiers as names.
+ * @return Pointer to the populated resolver, or NULL on error with errno set.
+ */
+struct gpiotools_line_resolver *
+gpiotools_resolve_lines(int num_lines, char **lines, const char *chip_id,
+			bool strict, bool by_name);
+
+/**
+ * @brief Free a line resolver and all memory it owns.
+ * @param resolver Resolver to free.  May be NULL.
+ */
+void gpiotools_free_line_resolver(struct gpiotools_line_resolver *resolver);
+
+/**
+ * @brief Extract offsets and optionally values for lines on a specific chip.
+ * @param resolver Resolver containing the resolved lines.
+ * @param chip_num Index of the chip to query.
+ * @param offsets Pre-allocated array to receive the line offsets.
+ * @param values Pre-allocated array to receive the line values, or NULL.
+ * @return Number of lines belonging to the specified chip.
+ * @note offsets and values must be large enough to hold the number of lines
+ *       stored in the resolver
+ */
+int
+gpiotools_get_line_offsets_and_values(struct gpiotools_line_resolver *resolver,
+				      int chip_num, unsigned int *offsets,
+				      enum gpiod_line_value *values);
+
+/**
+ * @brief Get the name of a chip referenced by the resolver.
+ * @param resolver Resolver containing the chip.
+ * @param chip_num Index of the chip.
+ * @return Pointer to the chip name string owned by the resolver.
+ */
+const char *gpiotools_get_chip_name(struct gpiotools_line_resolver *resolver,
+				    int chip_num);
+
+/**
+ * @brief Get the name of a resolved line.
+ * @param resolver Resolver containing the line.
+ * @param chip_num Index of the chip the line belongs to.
+ * @param offset Offset of the line on the chip.
+ * @return Pointer to the line name string owned by the resolver, or NULL if
+ *         the line has no name.
+ */
+const char *gpiotools_get_line_name(struct gpiotools_line_resolver *resolver,
+				    int chip_num, unsigned int offset);
+
+/**
+ * @brief Update line values stored in the resolver for a specific chip.
+ * @param resolver Resolver to update.
+ * @param chip_num Index of the chip whose lines should be updated.
+ * @param values Array of values to store, one per line on the chip in the
+ *               order they appear in the resolver.
+ */
+void gpiotools_set_line_values(struct gpiotools_line_resolver *resolver,
+			       int chip_num, enum gpiod_line_value *values);
+
+/**
+ * @}
+ */
+
+#endif /* __GPIOTOOLS_H__ */
diff --git a/tools/meson.build b/tools/meson.build
index b26718ba69679d03d210562768f71ea02bc3ed01..d755167cfbd7c2a7400d28cf5e5d5f9eece699d7 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -1,6 +1,26 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-FileCopyrightText: 2026 Qualcomm Technologies, Inc. and/or its subsidiaries
 
+libgpiotools_lib = library('gpiotools',
+  sources: ['gpiotools.c'],
+  include_directories: libgpiod_inc,
+  c_args: ['-fvisibility=hidden'],
+  dependencies: [libgpiod_dep],
+  version: libgpiotools_version,
+  soversion: libgpiotools_soversion,
+  install: true,
+)
+
+install_headers('gpiotools.h')
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(libgpiotools_lib,
+  name: 'libgpiotools',
+  description: 'Collection of high-level functions for building programs using libgpiod',
+  url: project_url,
+  version: meson.project_version(),
+)
+
 tools_common_lib = static_library('tools-common',
   sources: ['tools-common.c'],
   include_directories: libgpiod_inc,

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] tools: reuse libgpiotools
  2026-05-13 11:47 [PATCH 0/2] tools: provide a shared library with high-level functions used by gpio-tools Bartosz Golaszewski
  2026-05-13 11:47 ` [PATCH 1/2] tools: provide the libgpiotools shared library Bartosz Golaszewski
@ 2026-05-13 11:47 ` Bartosz Golaszewski
  1 sibling, 0 replies; 3+ messages in thread
From: Bartosz Golaszewski @ 2026-05-13 11:47 UTC (permalink / raw)
  To: Linus Walleij, Kent Gibson, Vincent Fazio, Alexander Dahl
  Cc: brgl, linux-gpio, Bartosz Golaszewski

Reuse as much of the functionality provided by the new libgpiotools
shared library in tools-common while retaining the functions used to
validate command-line arguments, print help text or bail-out on errors.
We keep the existing interfaces to gpio-tools and use them to wrap the
API exposed by libgpiotools as we want to keep bailing out on errors
without requiring tools to do a lot of repeated error checking.

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
 tools/meson.build    |   8 +-
 tools/tools-common.c | 316 ++++++---------------------------------------------
 tools/tools-common.h |  63 +---------
 3 files changed, 47 insertions(+), 340 deletions(-)

diff --git a/tools/meson.build b/tools/meson.build
index d755167cfbd7c2a7400d28cf5e5d5f9eece699d7..5ecf7dc5d221d52243478f9a126e6512d2408981 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -21,9 +21,15 @@ pkgconfig.generate(libgpiotools_lib,
   version: meson.project_version(),
 )
 
+libgpiotools_dep = declare_dependency(
+  link_with: libgpiotools_lib,
+  include_directories: include_directories('.'),
+)
+
 tools_common_lib = static_library('tools-common',
   sources: ['tools-common.c'],
   include_directories: libgpiod_inc,
+  dependencies: [libgpiotools_dep],
   pic: true,
 )
 
@@ -33,7 +39,7 @@ tools_common_dep = declare_dependency(
 )
 
 tools_c_args = []
-tools_deps = [libgpiod_dep, tools_common_dep]
+tools_deps = [libgpiod_dep, libgpiotools_dep, tools_common_dep]
 
 if opt_gpioset_interactive.allowed() and libedit_dep.found()
   tools_c_args += ['-DGPIOSET_INTERACTIVE']
diff --git a/tools/tools-common.c b/tools/tools-common.c
index 5aa51197c521a373b421054459c8ef38f3a35a1e..56ed90f639790ec54a3d6e9eb6ad594104474d7e 100644
--- a/tools/tools-common.c
+++ b/tools/tools-common.c
@@ -4,8 +4,6 @@
 
 /* Common code for GPIO tools. */
 
-#include <ctype.h>
-#include <dirent.h>
 #include <errno.h>
 #include <gpiod.h>
 #include <inttypes.h>
@@ -14,7 +12,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/stat.h>
 #include <time.h>
 
 #include "tools-common.h"
@@ -431,154 +428,39 @@ void print_line_vals(struct line_resolver *resolver, bool is_unquoted,
 	printf("\n");
 }
 
-static int chip_dir_filter(const struct dirent *entry)
-{
-	struct stat sb;
-	int ret = 0;
-	char *path;
-
-	if (asprintf(&path, "/dev/%s", entry->d_name) < 0)
-		return 0;
-
-	if ((lstat(path, &sb) == 0) && (!S_ISLNK(sb.st_mode)) &&
-	    gpiod_is_gpiochip_device(path))
-		ret = 1;
-
-	free(path);
-
-	return ret;
-}
-
-static bool isuint(const char *str)
-{
-	for (; *str && isdigit(*str); str++)
-		;
-
-	return *str == '\0';
-}
-
 bool chip_path_lookup(const char *id, char **path_ptr)
 {
 	char *path;
 
-	if (isuint(id)) {
-		/* by number */
-		if (asprintf(&path, "/dev/gpiochip%s", id) < 0)
-			return false;
-	} else if (strchr(id, '/')) {
-		/* by path */
-		if (asprintf(&path, "%s", id) < 0)
-			return false;
-	} else {
-		/* by device name */
-		if (asprintf(&path, "/dev/%s", id) < 0)
-			return false;
-	}
-
-	if (!gpiod_is_gpiochip_device(path)) {
-		free(path);
-		return false;
-	}
-
-	*path_ptr = path;
+	path = gpiotools_chip_path_lookup(id);
+	if (path)
+		*path_ptr = path;
 
-	return true;
+	return !!path;
 }
 
 int chip_paths(const char *id, char ***paths_ptr)
 {
-	char **paths;
-	char *path;
+	int ret;
 
-	if (id == NULL)
-		return all_chip_paths(paths_ptr);
-
-	if (!chip_path_lookup(id, &path))
-		return 0;
-
-	paths = malloc(sizeof(*paths));
-	if (paths == NULL)
+	ret = gpiotools_chip_paths(id, paths_ptr);
+	if (ret < 0)
 		die_oom();
 
-	paths[0] = path;
-	*paths_ptr = paths;
-
-	return 1;
+	return ret;
 }
 
 int all_chip_paths(char ***paths_ptr)
 {
-	int i, j, num_chips, ret = 0;
-	struct dirent **entries;
-	char **paths;
+	int ret;
 
-	num_chips = scandir("/dev/", &entries, chip_dir_filter, versionsort);
-	if (num_chips < 0)
+	ret = gpiotools_all_chip_paths(paths_ptr);
+	if (ret < 0)
 		die_perror("unable to scan /dev");
 
-	paths = calloc(num_chips, sizeof(*paths));
-	if (paths == NULL)
-		die_oom();
-
-	for (i = 0; i < num_chips; i++) {
-		if (asprintf(&paths[i], "/dev/%s", entries[i]->d_name) < 0) {
-			for (j = 0; j < i; j++)
-				free(paths[j]);
-
-			free(paths);
-			return 0;
-		}
-	}
-
-	*paths_ptr = paths;
-	ret = num_chips;
-
-	for (i = 0; i < num_chips; i++)
-		free(entries[i]);
-
-	free(entries);
 	return ret;
 }
 
-static bool resolve_line(struct line_resolver *resolver,
-			 struct gpiod_line_info *info, int chip_num)
-{
-	struct 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)
-				die("line '%s' is not unique", line->id);
-			line->offset = offset;
-			line->info = info;
-			line->chip_num = resolver->num_chips;
-			line->resolved = true;
-			resolver->num_found++;
-			resolved = true;
-		}
-	}
-
-	return resolved;
-}
-
 /*
  * check for lines that can be identified by offset
  *
@@ -588,57 +470,23 @@ static bool resolve_line(struct line_resolver *resolver,
 bool resolve_lines_by_offset(struct line_resolver *resolver,
 			     unsigned int num_lines)
 {
-	struct 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;
+	return gpiotools_resolve_lines_by_offset(resolver, num_lines);
 }
 
 bool resolve_done(struct line_resolver *resolver)
 {
-	return (!resolver->strict &&
-		resolver->num_found >= resolver->num_lines);
+	return gpiotools_resolve_done(resolver);
 }
 
 struct line_resolver *resolver_init(int num_lines, char **lines, int num_chips,
 				    bool strict, bool by_name)
 {
 	struct line_resolver *resolver;
-	struct resolved_line *line;
-	size_t resolver_size;
-	int i;
-
-	resolver_size = sizeof(*resolver) + num_lines * sizeof(*line);
-	resolver = malloc(resolver_size);
-	if (resolver == NULL)
-		die_oom();
 
-	memset(resolver, 0, resolver_size);
-
-	resolver->chips = calloc(num_chips, sizeof(struct resolved_chip));
-	if (resolver->chips == NULL)
+	resolver = gpiotools_resolver_init(num_lines, lines, num_chips,
+					   strict, by_name);
+	if (!resolver)
 		die_oom();
-	memset(resolver->chips, 0, num_chips * sizeof(struct resolved_chip));
-
-	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;
 }
@@ -647,72 +495,23 @@ struct line_resolver *resolve_lines(int num_lines, char **lines,
 				    const char *chip_id, bool strict,
 				    bool by_name)
 {
-	struct gpiod_chip_info *chip_info;
-	struct gpiod_line_info *line_info;
 	struct line_resolver *resolver;
-	int num_chips, i, offset;
-	struct gpiod_chip *chip;
-	bool chip_used;
-	char **paths;
-
-	if (chip_id == NULL)
-		by_name = true;
-
-	num_chips = chip_paths(chip_id, &paths);
-	if (chip_id && (num_chips == 0))
-		die("cannot find GPIO chip character device '%s'", chip_id);
-
-	resolver = resolver_init(num_lines, lines, num_chips, strict, by_name);
-
-	for (i = 0; (i < num_chips) && !resolve_done(resolver); i++) {
-		chip_used = false;
-		chip = gpiod_chip_open(paths[i]);
-		if (!chip) {
-			if ((errno == EACCES) && (chip_id == NULL)) {
-				free(paths[i]);
-				continue;
-			}
-
-			die_perror("unable to open chip '%s'", paths[i]);
-		}
-
-		chip_info = gpiod_chip_get_info(chip);
-		if (!chip_info)
-			die_perror("unable to get info for '%s'", paths[i]);
-
-		num_lines = gpiod_chip_info_get_num_lines(chip_info);
-
-		if (i == 0 && chip_id && !by_name)
-			chip_used = resolve_lines_by_offset(resolver, num_lines);
-
-		for (offset = 0;
-		     (offset < num_lines) && !resolve_done(resolver);
-		     offset++) {
-			line_info = gpiod_chip_get_line_info(chip, offset);
-			if (!line_info)
-				die_perror("unable to read the info for line %d from %s",
-					   offset,
-					   gpiod_chip_info_get_name(chip_info));
-
-			if (resolve_line(resolver, line_info, i))
-				chip_used = true;
-			else
-				gpiod_line_info_free(line_info);
-
-		}
+	int i;
 
-		gpiod_chip_close(chip);
+	resolver = gpiotools_resolve_lines(num_lines, lines, chip_id,
+					   strict, by_name);
+	if (!resolver) {
+		if (errno == ENODEV)
+			die("cannot find GPIO chip character device '%s'",
+			    chip_id);
+		else
+			die_perror("error resolving lines");
+	}
 
-		if (chip_used) {
-			resolver->chips[resolver->num_chips].info = chip_info;
-			resolver->chips[resolver->num_chips].path = paths[i];
-			resolver->num_chips++;
-		} else {
-			gpiod_chip_info_free(chip_info);
-			free(paths[i]);
-		}
+	for (i = 0; i < resolver->num_lines; i++) {
+		if (resolver->lines[i].not_unique)
+			die("line '%s' is not unique", resolver->lines[i].id);
 	}
-	free(paths);
 
 	return resolver;
 }
@@ -752,75 +551,30 @@ void validate_resolution(struct line_resolver *resolver, const char *chip_id)
 
 void free_line_resolver(struct 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);
+	gpiotools_free_line_resolver(resolver);
 }
 
 int get_line_offsets_and_values(struct line_resolver *resolver, int chip_num,
 				unsigned int *offsets,
 				enum gpiod_line_value *values)
 {
-	struct 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;
+	return gpiotools_get_line_offsets_and_values(resolver, chip_num,
+						     offsets, values);
 }
 
 const char *get_chip_name(struct line_resolver *resolver, int chip_num)
 {
-	return gpiod_chip_info_get_name(resolver->chips[chip_num].info);
+	return gpiotools_get_chip_name(resolver, chip_num);
 }
 
 const char *get_line_name(struct line_resolver *resolver, int chip_num,
 			  unsigned int offset)
 {
-	struct 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 0;
+	return gpiotools_get_line_name(resolver, chip_num, offset);
 }
 
 void set_line_values(struct 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++;
-		}
-	}
+	gpiotools_set_line_values(resolver, chip_num, values);
 }
diff --git a/tools/tools-common.h b/tools/tools-common.h
index 165c250f8d664687d06232426941020e0c8a4ae7..85ee765255bbba991cd2e1fbe6b727a9e998d47e 100644
--- a/tools/tools-common.h
+++ b/tools/tools-common.h
@@ -7,6 +7,8 @@
 
 #include <gpiod.h>
 
+#include "gpiotools.h"
+
 /*
  * Various helpers for the GPIO tools.
  *
@@ -19,64 +21,9 @@
 
 #define GETOPT_NULL_LONGOPT	NULL, 0, NULL, 0
 
-struct resolved_line {
-	/* from the command line */
-	const char *id;
-
-	/*
-	 * id parsed as int, if that is an option, or -1 if line must be
-	 * resolved by name
-	 */
-	int id_as_offset;
-
-	/* line has been located on a chip */
-	bool resolved;
-
-	/* remaining fields only valid once resolved... */
-
-	/* info for the line */
-	struct gpiod_line_info *info;
-
-	/* num of relevant chip in line_resolver */
-	int chip_num;
-
-	/* offset of line on chip */
-	unsigned int offset;
-
-	/* line value for gpioget/set */
-	int value;
-};
-
-struct resolved_chip {
-	/* info of the relevant chips */
-	struct gpiod_chip_info *info;
-
-	/* path to the chip */
-	char *path;
-};
-
-/* a resolver from requested line names/offsets to lines on the system */
-struct line_resolver {
-	/*
-	 * number of chips the lines span, and number of entries in chips
-	 */
-	int num_chips;
-
-	/* number of lines in lines */
-	int num_lines;
-
-	/* number of lines found */
-	int num_found;
-
-	/* perform exhaustive search to check line names are unique */
-	bool strict;
-
-	/* details of the relevant chips */
-	struct resolved_chip *chips;
-
-	/* descriptors for the requested lines */
-	struct resolved_line lines[];
-};
+#define resolved_line		gpiotools_resolved_line
+#define resolved_chip		gpiotools_resolved_chip
+#define line_resolver		gpiotools_line_resolver
 
 void set_prog_name(const char *name);
 const char *get_prog_name(void);

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-05-13 11:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-13 11:47 [PATCH 0/2] tools: provide a shared library with high-level functions used by gpio-tools Bartosz Golaszewski
2026-05-13 11:47 ` [PATCH 1/2] tools: provide the libgpiotools shared library Bartosz Golaszewski
2026-05-13 11:47 ` [PATCH 2/2] tools: reuse libgpiotools Bartosz Golaszewski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox