1
0
mirror of https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git synced 2026-10-24 00:48:53 +03:00

tools: line name focussed rework

Rework the tool suite to support identifying lines by name and to
support operating on the GPIO lines available to the user at once, rather
than on one particular GPIO chip.

All tools, other than gpiodetect, now provide the name to (chip,offset)
mapping that was previously only performed by gpiofind. As names are not
guaranteed to be unique, a --strict option is provided for all tools to
either abort the operation or report all lines with the matching name, as
appropriate.
By default the tools operate on the first line found with a matching name.

Selection of line by (chip,offset) is still supported with a --chip
option, though it restricts the scope of the operation to an individual
chip.  When the --chip option is specified, the lines are assumed to be
identified by offset where they parse as an integer, else by name.
To cater for the unusual case where a line name parses as an integer,
but is different from the offset, the --by-name option forces the lines
to be identified by name.

The updated tools are intentionally NOT backwardly compatible with the
previous tools. Using old command lines with the updated tools will
almost certainly fail, though migrating old command lines is generally as
simple as adding a '-c' before the chip.

In addition the individual tools are modified as follows:

gpiodetect:

Add the option to select individual chips.

gpioinfo:

Change the focus from chips to lines, so the scope can be
an individual line, a subset of lines, all lines on a particular chip,
or all the lines available to the user.  For line scope a single line
summary is output for each line.  For chip scope the existing format
displaying a summary of the chip and each of its lines is retained.

Line attributes are consolidated into a list format, and are extended
to cover all attributes supported by uAPI v2.

gpioget:

The default output format is becomes line=value, as per the
input for gpioset, and the value is reported as active or inactive,
rather than 0 or 1.
The previous format is available using the --numeric option.

Add an optional hold period between requesting a line and reading the
value to allow the line to settle once the requested configuration has
been applied (e.g. bias).

gpiomon:

Consolidate the edge options into a single option.

Add a debounce period option.

Add options to report event times as UTC or localtime.

Add format specifiers for GPIO chip path, line name, stringified event
type, and event time as a datetime.

Rearrange default output format to place fields with more predicable
widths to the left, and to separate major field groups with tabs.
Lines are identified consistent with the command line.

gpioset:

Add a hold period option that specifies the minimum period the line
value must be held for.  This applies to all set options.

Support line values specified as active/inactive, on/off and
true/false, as well as 1/0.

Add a toggle option that specifies a time sequence over which the
requested lines should be toggled.  If the sequence is 0 terminated then
gpioset exits when the sequence completes, else it repeats the sequence.
This allows for anything from simple blinkers to bit bashing from the
command line. e.g. gpioset -t 500ms LED=on

Add an interactive option to provide a shell-like interface to allow
manual or scripted manipulation of requested lines.  A basic command set
allows lines to be get, set, or toggled, and to insert sleeps between
operations.

Remove the --mode, --sec, and --usec options.
The combination of hold period and interactive mode provide functionality
equivalent to the old --mode options.  By default gpioset now holds the
line indefinitely, rather than exiting immediately.  The old exit
behaviour can be emulated with a "-t 0" option.

Signed-off-by: Kent Gibson <warthog618@gmail.com>
[Bartosz: coding style tweaks, dropped stray newlines and spaces]
Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
This commit is contained in:
Kent Gibson
2022-11-21 18:22:48 +08:00
committed by Bartosz Golaszewski
parent 52de2cddca
commit 8ffb648928
15 changed files with 4422 additions and 1589 deletions

12
TODO
View File

@@ -39,3 +39,15 @@ over unix sockets.
In this case however the goal is to have as few dependencies as possible. This
is because for some small systems dbus is overkill. Since we won't be using any
standardized protocol, it will take much more effort to implement it correctly.
----------
* improve gpioset --interactive tab completion
The existing tab completion uses libedit's readline emulation layer which
has a few limitations, including not being able to correctly handle quoted
line names and being disabled when stdin/stdout are not a tty (which makes
testing with gpio-tools-test.bats using coproc problematic).
One approach that could address both these problems is to bypass the readline
emulation and use the libedit API (histedit.h) directly.

View File

@@ -103,18 +103,20 @@ AM_CONDITIONAL([WITH_TOOLS], [test "x$with_tools" = xtrue])
AC_DEFUN([FUNC_NOT_FOUND_TOOLS],
[ERR_NOT_FOUND([$1()], [tools])])
AC_DEFUN([HEADER_NOT_FOUND_TOOLS],
[ERR_NOT_FOUND([$1 header], [tools])])
AC_ARG_ENABLE([gpioset-interactive],
[AS_HELP_STRING([--enable-gpioset-interactive],
[enable gpioset interactive mode [default=no]])],
[if test "x$enableval" = xyes; then with_gpioset_interactive=true; fi],
[with_gpioset_interactive=false])
AM_CONDITIONAL([WITH_GPIOSET_INTERACTIVE],
[test "x$with_gpioset_interactive" = xtrue])
if test "x$with_tools" = xtrue
then
# These are only needed to build tools
AC_CHECK_FUNC([basename], [], [FUNC_NOT_FOUND_TOOLS([basename])])
AS_IF([test "x$with_tools" = xtrue],
[# These are only needed to build tools
AC_CHECK_FUNC([daemon], [], [FUNC_NOT_FOUND_TOOLS([daemon])])
AC_CHECK_FUNC([signalfd], [], [FUNC_NOT_FOUND_TOOLS([signalfd])])
AC_CHECK_FUNC([setlinebuf], [], [FUNC_NOT_FOUND_TOOLS([setlinebuf])])
AC_CHECK_HEADERS([sys/signalfd.h], [], [HEADER_NOT_FOUND_TOOLS([sys/signalfd.h])])
fi
AS_IF([test "x$with_gpioset_interactive" = xtrue],
[PKG_CHECK_MODULES([LIBEDIT], [libedit >= 3.1])])
])
AC_ARG_ENABLE([tests],
[AS_HELP_STRING([--enable-tests],[enable libgpiod tests [default=no]])],

View File

@@ -3,7 +3,7 @@
if WITH_MANPAGES
dist_man1_MANS = gpiodetect.man gpioinfo.man gpioget.man gpioset.man gpiofind.man gpiomon.man
dist_man1_MANS = gpiodetect.man gpioinfo.man gpioget.man gpioset.man gpiomon.man
%.man: $(top_builddir)/tools/$(*F)
help2man $(top_builddir)/tools/$(*F) --include=$(srcdir)/template --output=$(builddir)/$@ --no-info

1
tools/.gitignore vendored
View File

@@ -6,4 +6,3 @@ gpioinfo
gpioget
gpioset
gpiomon
gpiofind

View File

@@ -9,7 +9,14 @@ libtools_common_la_SOURCES = tools-common.c tools-common.h
LDADD = libtools-common.la $(top_builddir)/lib/libgpiod.la
bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon gpiofind
if WITH_GPIOSET_INTERACTIVE
AM_CFLAGS += -DGPIOSET_INTERACTIVE
LDADD += $(LIBEDIT_LIBS)
endif
bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon
gpiodetect_SOURCES = gpiodetect.c
@@ -21,8 +28,6 @@ gpioset_SOURCES = gpioset.c
gpiomon_SOURCES = gpiomon.c
gpiofind_SOURCES = gpiofind.c
EXTRA_DIST = gpio-tools-test gpio-tools-test.bats
if WITH_TESTS

View File

@@ -37,8 +37,6 @@ check_prog() {
# Check all required non-coreutils tools
check_prog bats
check_prog modprobe
check_prog rmmod
check_prog udevadm
check_prog timeout
# Check if we're running a kernel at the required version or later

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <stdio.h>
@@ -11,31 +10,33 @@
#include "tools-common.h"
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
static const char *const shortopts = "+hv";
static void print_help(void)
{
printf("Usage: %s [OPTIONS]\n", get_progname());
printf("Usage: %s [OPTIONS] [chip]...\n", get_progname());
printf("\n");
printf("List all GPIO chips, print their labels and number of GPIO lines.\n");
printf("List GPIO chips, print their labels and number of GPIO lines.\n");
printf("\n");
printf("Chips may be identified by number, name, or path.\n");
printf("e.g. '0', 'gpiochip0', and '/dev/gpiochip0' all refer to the same chip.\n");
printf("\n");
printf("If no chips are specified then all chips are listed.\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help:\t\tdisplay this message and exit\n");
printf(" -v, --version:\tdisplay the version and exit\n");
printf(" -h, --help\t\tdisplay this help and exit\n");
printf(" -v, --version\t\toutput version information and exit\n");
}
int main(int argc, char **argv)
static int parse_config(int argc, char **argv)
{
int optc, opti, num_chips, i;
struct gpiod_chip *chip;
struct gpiod_chip_info *info;
struct dirent **entries;
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
static const char *const shortopts = "+hv";
int optc, opti;
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
@@ -45,48 +46,79 @@ int main(int argc, char **argv)
switch (optc) {
case 'h':
print_help();
return EXIT_SUCCESS;
exit(EXIT_SUCCESS);
case 'v':
print_version();
return EXIT_SUCCESS;
exit(EXIT_SUCCESS);
case '?':
die("try %s --help", get_progname());
default:
abort();
}
}
return optind;
}
argc -= optind;
argv += optind;
static int print_chip_info(const char *path)
{
struct gpiod_chip_info *info;
struct gpiod_chip *chip;
if (argc > 0)
die("unrecognized argument: %s", argv[0]);
num_chips = scandir("/dev/", &entries, chip_dir_filter, alphasort);
if (num_chips < 0)
die_perror("unable to scan /dev");
for (i = 0; i < num_chips; i++) {
chip = chip_open_by_name(entries[i]->d_name);
if (!chip)
die_perror("unable to open %s", entries[i]->d_name);
info = gpiod_chip_get_info(chip);
if (!info)
die_perror("unable to get info for %s", entries[i]->d_name);
printf("%s [%s] (%zu lines)\n",
gpiod_chip_info_get_name(info),
gpiod_chip_info_get_label(info),
gpiod_chip_info_get_num_lines(info));
gpiod_chip_info_free(info);
gpiod_chip_close(chip);
free(entries[i]);
chip = gpiod_chip_open(path);
if (!chip) {
print_perror("unable to open chip '%s'", path);
return 1;
}
free(entries);
info = gpiod_chip_get_info(chip);
if (!info)
die_perror("unable to read info for '%s'", path);
return EXIT_SUCCESS;
printf("%s [%s] (%zu lines)\n",
gpiod_chip_info_get_name(info),
gpiod_chip_info_get_label(info),
gpiod_chip_info_get_num_lines(info));
gpiod_chip_info_free(info);
gpiod_chip_close(chip);
return 0;
}
int main(int argc, char **argv)
{
int num_chips, i, ret = EXIT_SUCCESS;
char **paths, *path;
i = parse_config(argc, argv);
argc -= i;
argv += i;
if (argc == 0) {
num_chips = all_chip_paths(&paths);
for (i = 0; i < num_chips; i++) {
if (print_chip_info(paths[i]))
ret = EXIT_FAILURE;
free(paths[i]);
}
free(paths);
}
for (i = 0; i < argc; i++) {
if (chip_path_lookup(argv[i], &path)) {
if (print_chip_info(path))
ret = EXIT_FAILURE;
free(path);
} else {
print_error(
"cannot find GPIO chip character device '%s'",
argv[i]);
ret = EXIT_FAILURE;
}
}
return ret;
}

View File

@@ -1,93 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tools-common.h"
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
static const char *const shortopts = "+hv";
static void print_help(void)
{
printf("Usage: %s [OPTIONS] <name>\n", get_progname());
printf("\n");
printf("Find a GPIO line by name. The output of this command can be used as input for gpioget/set.\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help:\t\tdisplay this message and exit\n");
printf(" -v, --version:\tdisplay the version and exit\n");
}
int main(int argc, char **argv)
{
int i, num_chips, optc, opti, offset;
struct gpiod_chip *chip;
struct gpiod_chip_info *info;
struct dirent **entries;
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
if (optc < 0)
break;
switch (optc) {
case 'h':
print_help();
return EXIT_SUCCESS;
case 'v':
print_version();
return EXIT_SUCCESS;
case '?':
die("try %s --help", get_progname());
default:
abort();
}
}
argc -= optind;
argv += optind;
if (argc != 1)
die("exactly one GPIO line name must be specified");
num_chips = scandir("/dev/", &entries, chip_dir_filter, alphasort);
if (num_chips < 0)
die_perror("unable to scan /dev");
for (i = 0; i < num_chips; i++) {
chip = chip_open_by_name(entries[i]->d_name);
if (!chip) {
if (errno == EACCES)
continue;
die_perror("unable to open %s", entries[i]->d_name);
}
offset = gpiod_chip_get_line_offset_from_name(chip, argv[0]);
if (offset >= 0) {
info = gpiod_chip_get_info(chip);
if (!info)
die_perror("unable to get info for %s", entries[i]->d_name);
printf("%s %u\n",
gpiod_chip_info_get_name(info), offset);
gpiod_chip_info_free(info);
gpiod_chip_close(chip);
return EXIT_SUCCESS;
}
}
return EXIT_FAILURE;
}

View File

@@ -1,57 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
#include <getopt.h>
#include <gpiod.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "tools-common.h"
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ "active-low", no_argument, NULL, 'l' },
{ "dir-as-is", no_argument, NULL, 'n' },
{ "bias", required_argument, NULL, 'B' },
{ GETOPT_NULL_LONGOPT },
struct config {
bool active_low;
bool by_name;
bool numeric;
bool strict;
bool unquoted;
int bias;
int direction;
unsigned int hold_period_us;
const char *chip_id;
const char *consumer;
};
static const char *const shortopts = "+hvlnB:";
static void print_help(void)
{
printf("Usage: %s [OPTIONS] <chip name/number> <offset 1> <offset 2> ...\n",
get_progname());
printf("Usage: %s [OPTIONS] <line>...\n", get_progname());
printf("\n");
printf("Read line value(s) from a GPIO chip\n");
printf("Read values of GPIO lines.\n");
printf("\n");
printf("Lines are specified by name, or optionally by offset if the chip option\n");
printf("is provided.\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help:\t\tdisplay this message and exit\n");
printf(" -v, --version:\tdisplay the version and exit\n");
printf(" -l, --active-low:\tset the line active state to low\n");
printf(" -n, --dir-as-is:\tdon't force-reconfigure line direction\n");
printf(" -B, --bias=[as-is|disable|pull-down|pull-up] (defaults to 'as-is'):\n");
printf(" set the line bias\n");
printf("\n");
printf(" -a, --as-is\t\tleave the line direction unchanged, not forced to input\n");
print_bias_help();
printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
printf(" -C, --consumer <name>\tconsumer name applied to requested lines (default is 'gpioget')\n");
printf(" -h, --help\t\tdisplay this help and exit\n");
printf(" -l, --active-low\ttreat the line as active low\n");
printf(" -p, --hold-period <period>\n");
printf("\t\t\twait between requesting the lines and reading the values\n");
printf(" --numeric\t\tdisplay line values as '0' (inactive) or '1' (active)\n");
printf(" -s, --strict\t\tabort if requested line names are not unique\n");
printf(" --unquoted\tdon't quote line names\n");
printf(" -v, --version\t\toutput version information and exit\n");
print_chip_help();
print_period_help();
}
int main(int argc, char **argv)
static int parse_config(int argc, char **argv, struct config *cfg)
{
int direction = GPIOD_LINE_DIRECTION_INPUT;
int optc, opti, bias = 0, ret, *values;
struct gpiod_line_settings *settings;
struct gpiod_request_config *req_cfg;
struct gpiod_line_request *request;
struct gpiod_line_config *line_cfg;
struct gpiod_chip *chip;
bool active_low = false;
unsigned int *offsets;
size_t i, num_lines;
char *device, *end;
static const struct option longopts[] = {
{ "active-low", no_argument, NULL, 'l' },
{ "as-is", no_argument, NULL, 'a' },
{ "bias", required_argument, NULL, 'b' },
{ "by-name", no_argument, NULL, 'B' },
{ "chip", required_argument, NULL, 'c' },
{ "consumer", required_argument, NULL, 'C' },
{ "help", no_argument, NULL, 'h' },
{ "hold-period", required_argument, NULL, 'p' },
{ "numeric", no_argument, NULL, 'N' },
{ "strict", no_argument, NULL, 's' },
{ "unquoted", no_argument, NULL, 'Q' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
static const char *const shortopts = "+ab:c:C:hlp:sv";
int opti, optc;
memset(cfg, 0, sizeof(*cfg));
cfg->direction = GPIOD_LINE_DIRECTION_INPUT;
cfg->consumer = "gpioget";
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
@@ -59,105 +83,157 @@ int main(int argc, char **argv)
break;
switch (optc) {
case 'h':
print_help();
return EXIT_SUCCESS;
case 'v':
print_version();
return EXIT_SUCCESS;
case 'l':
active_low = true;
case 'a':
cfg->direction = GPIOD_LINE_DIRECTION_AS_IS;
break;
case 'n':
direction = GPIOD_LINE_DIRECTION_AS_IS;
case 'b':
cfg->bias = parse_bias_or_die(optarg);
break;
case 'B':
bias = parse_bias(optarg);
cfg->by_name = true;
break;
case 'c':
cfg->chip_id = optarg;
break;
case 'C':
cfg->consumer = optarg;
break;
case 'l':
cfg->active_low = true;
break;
case 'N':
cfg->numeric = true;
break;
case 'p':
cfg->hold_period_us = parse_period_or_die(optarg);
break;
case 'Q':
cfg->unquoted = true;
break;
case 's':
cfg->strict = true;
break;
case 'h':
print_help();
exit(EXIT_SUCCESS);
case 'v':
print_version();
exit(EXIT_SUCCESS);
case '?':
die("try %s --help", get_progname());
case 0:
break;
default:
abort();
}
}
argc -= optind;
argv += optind;
return optind;
}
int main(int argc, char **argv)
{
struct gpiod_line_settings *settings;
struct gpiod_request_config *req_cfg;
struct gpiod_line_request *request;
struct gpiod_line_config *line_cfg;
int i, num_lines, ret, *values;
struct line_resolver *resolver;
struct resolved_line *line;
struct gpiod_chip *chip;
unsigned int *offsets;
struct config cfg;
const char *fmt;
i = parse_config(argc, argv, &cfg);
argc -= i;
argv += i;
if (argc < 1)
die("gpiochip must be specified");
die("at least one GPIO line must be specified");
if (argc < 2)
die("at least one GPIO line offset must be specified");
resolver = resolve_lines(argc, argv, cfg.chip_id, cfg.strict,
cfg.by_name);
validate_resolution(resolver, cfg.chip_id);
device = argv[0];
num_lines = argc - 1;
offsets = calloc(num_lines, sizeof(*offsets));
values = calloc(num_lines, sizeof(*values));
if (!offsets || ! values)
offsets = calloc(resolver->num_lines, sizeof(*offsets));
values = calloc(resolver->num_lines, sizeof(*values));
if (!offsets || !values)
die("out of memory");
for (i = 0; i < num_lines; i++) {
offsets[i] = strtoul(argv[i + 1], &end, 10);
if (*end != '\0' || offsets[i] > INT_MAX)
die("invalid GPIO offset: %s", argv[i + 1]);
}
if (has_duplicate_offsets(num_lines, offsets))
die("offsets must be unique");
chip = chip_open_lookup(device);
if (!chip)
die_perror("unable to open %s", device);
settings = gpiod_line_settings_new();
if (!settings)
die_perror("unable to allocate line settings");
gpiod_line_settings_set_direction(settings, direction);
gpiod_line_settings_set_direction(settings, cfg.direction);
if (bias)
gpiod_line_settings_set_bias(settings, bias);
if (cfg.bias)
gpiod_line_settings_set_bias(settings, cfg.bias);
if (active_low)
gpiod_line_settings_set_active_low(settings, active_low);
if (cfg.active_low)
gpiod_line_settings_set_active_low(settings, true);
req_cfg = gpiod_request_config_new();
if (!req_cfg)
die_perror("unable to allocate the request config structure");
gpiod_request_config_set_consumer(req_cfg, "gpioget");
line_cfg = gpiod_line_config_new();
if (!line_cfg)
die_perror("unable to allocate the line config structure");
ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
num_lines, settings);
if (ret)
die_perror("unable to add line settings");
gpiod_request_config_set_consumer(req_cfg, cfg.consumer);
request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
if (!request)
die_perror("unable to request lines");
for (i = 0; i < resolver->num_chips; i++) {
chip = gpiod_chip_open(resolver->chips[i].path);
if (!chip)
die_perror("unable to open chip '%s'",
resolver->chips[i].path);
ret = gpiod_line_request_get_values(request, values);
if (ret)
die_perror("unable to read GPIO line values");
num_lines = get_line_offsets_and_values(resolver, i, offsets,
NULL);
for (i = 0; i < num_lines; i++) {
printf("%d", values[i]);
if (i != num_lines - 1)
gpiod_line_config_reset(line_cfg);
ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
num_lines, settings);
if (ret)
die_perror("unable to add line settings");
request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
if (!request)
die_perror("unable to request lines");
if (cfg.hold_period_us)
usleep(cfg.hold_period_us);
ret = gpiod_line_request_get_values(request, values);
if (ret)
die_perror("unable to read GPIO line values");
set_line_values(resolver, i, values);
gpiod_line_request_release(request);
gpiod_chip_close(chip);
}
fmt = cfg.unquoted ? "%s=%s" : "\"%s\"=%s";
for (i = 0; i < resolver->num_lines; i++) {
line = &resolver->lines[i];
if (cfg.numeric)
printf("%d", line->value);
else
printf(fmt, line->id,
line->value ? "active" : "inactive");
if (i != resolver->num_lines - 1)
printf(" ");
}
printf("\n");
gpiod_line_request_release(request);
free_line_resolver(resolver);
gpiod_request_config_free(req_cfg);
gpiod_line_config_free(line_cfg);
gpiod_line_settings_free(settings);
gpiod_chip_close(chip);
free(offsets);
free(values);

View File

@@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <stdarg.h>
@@ -12,191 +11,52 @@
#include "tools-common.h"
typedef bool (*is_set_func)(struct gpiod_line_info *);
struct flag {
const char *name;
is_set_func is_set;
struct config {
bool by_name;
bool strict;
bool unquoted_strings;
const char *chip_id;
};
static bool line_bias_is_pullup(struct gpiod_line_info *info)
{
return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_UP;
}
static bool line_bias_is_pulldown(struct gpiod_line_info *info)
{
return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_DOWN;
}
static bool line_bias_is_disabled(struct gpiod_line_info *info)
{
return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_DISABLED;
}
static bool line_drive_is_open_drain(struct gpiod_line_info *info)
{
return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
}
static bool line_drive_is_open_source(struct gpiod_line_info *info)
{
return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
}
static const struct flag flags[] = {
{
.name = "used",
.is_set = gpiod_line_info_is_used,
},
{
.name = "open-drain",
.is_set = line_drive_is_open_drain,
},
{
.name = "open-source",
.is_set = line_drive_is_open_source,
},
{
.name = "pull-up",
.is_set = line_bias_is_pullup,
},
{
.name = "pull-down",
.is_set = line_bias_is_pulldown,
},
{
.name = "bias-disabled",
.is_set = line_bias_is_disabled,
},
};
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
static const char *const shortopts = "+hv";
static void print_help(void)
{
printf("Usage: %s [OPTIONS] <gpiochip1> ...\n", get_progname());
printf("Usage: %s [OPTIONS] [line]...\n", get_progname());
printf("\n");
printf("Print information about all lines of the specified GPIO chip(s) (or all gpiochips if none are specified).\n");
printf("Print information about GPIO lines.\n");
printf("\n");
printf("Lines are specified by name, or optionally by offset if the chip option\n");
printf("is provided.\n");
printf("\n");
printf("If no lines are specified then all lines are displayed.\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help:\t\tdisplay this message and exit\n");
printf(" -v, --version:\tdisplay the version and exit\n");
printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
printf(" -h, --help\t\tdisplay this help and exit\n");
printf(" -s, --strict\t\tcheck all lines - don't assume line names are unique\n");
printf(" --unquoted\tdon't quote line or consumer names\n");
printf(" -v, --version\t\toutput version information and exit\n");
print_chip_help();
}
static PRINTF(3, 4) void prinfo(bool *of,
unsigned int prlen, const char *fmt, ...)
static int parse_config(int argc, char **argv, struct config *cfg)
{
char *buf, *buffmt = NULL;
size_t len;
va_list va;
int rv;
static const struct option longopts[] = {
{ "by-name", no_argument, NULL, 'B' },
{ "chip", required_argument, NULL, 'c' },
{ "help", no_argument, NULL, 'h' },
{ "strict", no_argument, NULL, 's' },
{ "unquoted", no_argument, NULL, 'Q' },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
va_start(va, fmt);
rv = vasprintf(&buf, fmt, va);
va_end(va);
if (rv < 0)
die("vasprintf: %s\n", strerror(errno));
len = strlen(buf) - 1;
static const char *const shortopts = "+c:hsv";
if (len >= prlen || *of) {
*of = true;
printf("%s", buf);
} else {
rv = asprintf(&buffmt, "%%%us", prlen);
if (rv < 0)
die("asprintf: %s\n", strerror(errno));
int opti, optc;
printf(buffmt, buf);
}
free(buf);
if (fmt)
free(buffmt);
}
static void list_lines(struct gpiod_chip *chip)
{
bool flag_printed, of, active_low;
struct gpiod_chip_info *chip_info;
struct gpiod_line_info *info;
const char *name, *consumer;
size_t i, offset, num_lines;
int direction;
chip_info = gpiod_chip_get_info(chip);
if (!chip_info)
die_perror("unable to retrieve the chip info from chip");
num_lines = gpiod_chip_info_get_num_lines(chip_info);
printf("%s - %zu lines:\n",
gpiod_chip_info_get_name(chip_info), num_lines);
for (offset = 0; offset < num_lines; offset++) {
info = gpiod_chip_get_line_info(chip, offset);
if (!info)
die_perror("unable to retrieve the line info from chip");
name = gpiod_line_info_get_name(info);
consumer = gpiod_line_info_get_consumer(info);
direction = gpiod_line_info_get_direction(info);
active_low = gpiod_line_info_is_active_low(info);
of = false;
printf("\tline ");
prinfo(&of, 3, "%zu", offset);
printf(": ");
name ? prinfo(&of, 12, "\"%s\"", name)
: prinfo(&of, 12, "unnamed");
printf(" ");
if (!gpiod_line_info_is_used(info))
prinfo(&of, 12, "unused");
else
consumer ? prinfo(&of, 12, "\"%s\"", consumer)
: prinfo(&of, 12, "kernel");
printf(" ");
prinfo(&of, 8, "%s ", direction == GPIOD_LINE_DIRECTION_INPUT
? "input" : "output");
prinfo(&of, 13, "%s ",
active_low ? "active-low" : "active-high");
flag_printed = false;
for (i = 0; i < ARRAY_SIZE(flags); i++) {
if (flags[i].is_set(info)) {
if (flag_printed)
printf(" ");
else
printf("[");
printf("%s", flags[i].name);
flag_printed = true;
}
}
if (flag_printed)
printf("]");
printf("\n");
gpiod_line_info_free(info);
}
gpiod_chip_info_free(chip_info);
}
int main(int argc, char **argv)
{
int num_chips, i, optc, opti;
struct gpiod_chip *chip;
struct dirent **entries;
memset(cfg, 0, sizeof(*cfg));
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
@@ -204,51 +64,207 @@ int main(int argc, char **argv)
break;
switch (optc) {
case 'B':
cfg->by_name = true;
break;
case 'c':
cfg->chip_id = optarg;
break;
case 's':
cfg->strict = true;
break;
case 'h':
print_help();
return EXIT_SUCCESS;
exit(EXIT_SUCCESS);
case 'Q':
cfg->unquoted_strings = true;
break;
case 'v':
print_version();
return EXIT_SUCCESS;
exit(EXIT_SUCCESS);
case '?':
die("try %s --help", get_progname());
case 0:
break;
default:
abort();
}
}
argc -= optind;
argv += optind;
return optind;
}
if (argc == 0) {
num_chips = scandir("/dev/", &entries,
chip_dir_filter, alphasort);
if (num_chips < 0)
die_perror("unable to scan /dev");
/*
* Minimal version similar to tools-common that indicates if a line should be
* printed rather than storing details into the resolver.
* Does not die on non-unique lines.
*/
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;
for (i = 0; i < num_chips; i++) {
chip = chip_open_by_name(entries[i]->d_name);
if (!chip)
die_perror("unable to open %s",
entries[i]->d_name);
offset = gpiod_line_info_get_offset(info);
list_lines(chip);
for (i = 0; i < resolver->num_lines; i++) {
line = &resolver->lines[i];
gpiod_chip_close(chip);
free(entries[i]);
/* already resolved by offset? */
if (line->resolved &&
(line->offset == offset) &&
(line->chip_num == chip_num)) {
resolved = true;
}
free(entries);
} else {
for (i = 0; i < argc; i++) {
chip = chip_open_lookup(argv[i]);
if (!chip)
die_perror("looking up chip %s", argv[i]);
list_lines(chip);
if (line->resolved && !resolver->strict)
continue;
gpiod_chip_close(chip);
/* else resolve by name */
name = gpiod_line_info_get_name(info);
if (name && (strcmp(line->id, name) == 0)) {
line->resolved = true;
line->offset = offset;
line->chip_num = chip_num;
resolved = true;
}
}
return EXIT_SUCCESS;
return resolved;
}
static void print_line_info(struct gpiod_line_info *info, bool unquoted_strings)
{
char quoted_name[17];
const char *name;
int len;
name = gpiod_line_info_get_name(info);
if (!name) {
name = "unnamed";
unquoted_strings = true;
}
if (unquoted_strings) {
printf("%-16s\t", name);
} else {
len = strlen(name);
if (len <= 14) {
quoted_name[0] = '"';
memcpy(&quoted_name[1], name, len);
quoted_name[len + 1] = '"';
quoted_name[len + 2] = '\0';
printf("%-16s\t", quoted_name);
} else {
printf("\"%s\"\t", name);
}
}
print_line_attributes(info, unquoted_strings);
}
/*
* based on resolve_lines, but prints lines immediately rather than collecting
* details in the resolver.
*/
static void list_lines(struct line_resolver *resolver, struct gpiod_chip *chip,
int chip_num, struct config *cfg)
{
struct gpiod_chip_info *chip_info;
struct gpiod_line_info *info;
int offset, num_lines;
chip_info = gpiod_chip_get_info(chip);
if (!chip_info)
die_perror("unable to read info from chip %s",
gpiod_chip_get_path(chip));
num_lines = gpiod_chip_info_get_num_lines(chip_info);
if ((chip_num == 0) && (cfg->chip_id && !cfg->by_name))
resolve_lines_by_offset(resolver, num_lines);
for (offset = 0;
((offset < num_lines) &&
!(resolver->num_lines && resolve_done(resolver)));
offset++) {
info = gpiod_chip_get_line_info(chip, offset);
if (!info)
die_perror("unable to read info for line %d from %s",
offset, gpiod_chip_info_get_name(chip_info));
if (resolver->num_lines &&
!resolve_line(resolver, info, chip_num))
continue;
if (resolver->num_lines) {
printf("%s %u", gpiod_chip_info_get_name(chip_info),
offset);
} else {
if (offset == 0)
printf("%s - %u lines:\n",
gpiod_chip_info_get_name(chip_info),
num_lines);
printf("\tline %3u:", offset);
}
fputc('\t', stdout);
print_line_info(info, cfg->unquoted_strings);
fputc('\n', stdout);
gpiod_line_info_free(info);
resolver->num_found++;
}
gpiod_chip_info_free(chip_info);
}
int main(int argc, char **argv)
{
struct line_resolver *resolver = NULL;
int num_chips, i, ret = EXIT_SUCCESS;
struct gpiod_chip *chip;
struct config cfg;
char **paths;
i = parse_config(argc, argv, &cfg);
argc -= i;
argv += i;
if (!cfg.chip_id)
cfg.by_name = true;
num_chips = chip_paths(cfg.chip_id, &paths);
if (cfg.chip_id && (num_chips == 0))
die("cannot find GPIO chip character device '%s'", cfg.chip_id);
resolver = resolver_init(argc, argv, num_chips, cfg.strict,
cfg.by_name);
for (i = 0; i < num_chips; i++) {
chip = gpiod_chip_open(paths[i]);
if (chip) {
list_lines(resolver, chip, i, &cfg);
gpiod_chip_close(chip);
} else {
print_perror("unable to open chip '%s'", paths[i]);
if (cfg.chip_id)
return EXIT_FAILURE;
ret = EXIT_FAILURE;
}
free(paths[i]);
}
free(paths);
validate_resolution(resolver, cfg.chip_id);
if (argc && resolver->num_found != argc)
ret = EXIT_FAILURE;
free(resolver);
return ret;
}

View File

@@ -1,79 +1,253 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "tools-common.h"
#define EVENT_BUF_SIZE 32
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
{ "active-low", no_argument, NULL, 'l' },
{ "bias", required_argument, NULL, 'B' },
{ "num-events", required_argument, NULL, 'n' },
{ "silent", no_argument, NULL, 's' },
{ "rising-edge", no_argument, NULL, 'r' },
{ "falling-edge", no_argument, NULL, 'f' },
{ "line-buffered", no_argument, NULL, 'b' },
{ "format", required_argument, NULL, 'F' },
{ GETOPT_NULL_LONGOPT },
struct config {
bool active_low;
bool banner;
bool by_name;
bool quiet;
bool strict;
bool unquoted;
int bias;
int edges;
int events_wanted;
unsigned int debounce_period_us;
const char *chip_id;
const char *consumer;
const char *fmt;
int event_clock;
int timestamp_fmt;
};
static const char *const shortopts = "+hvlB:n:srfbF:";
static void print_help(void)
{
printf("Usage: %s [OPTIONS] <chip name/number> <offset 1> <offset 2> ...\n",
get_progname());
printf("Usage: %s [OPTIONS] <line>...\n", get_progname());
printf("\n");
printf("Wait for events on GPIO lines and print them to standard output\n");
printf("Wait for events on GPIO lines and print them to standard output.\n");
printf("\n");
printf("Lines are specified by name, or optionally by offset if the chip option\n");
printf("is provided.\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help:\t\tdisplay this message and exit\n");
printf(" -v, --version:\tdisplay the version and exit\n");
printf(" -l, --active-low:\tset the line active state to low\n");
printf(" -B, --bias=[as-is|disable|pull-down|pull-up] (defaults to 'as-is'):\n");
printf(" set the line bias\n");
printf(" -n, --num-events=NUM:\texit after processing NUM events\n");
printf(" -s, --silent:\t\tdon't print event info\n");
printf(" -r, --rising-edge:\tonly process rising edge events\n");
printf(" -f, --falling-edge:\tonly process falling edge events\n");
printf(" -b, --line-buffered:\tset standard output as line buffered\n");
printf(" -F, --format=FMT\tspecify custom output format\n");
printf("\n");
printf(" --banner\t\tdisplay a banner on successful startup\n");
print_bias_help();
printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
printf(" -C, --consumer <name>\tconsumer name applied to requested lines (default is 'gpiomon')\n");
printf(" -e, --edges <edges>\tspecify the edges to monitor\n");
printf("\t\t\tPossible values: 'falling', 'rising', 'both'.\n");
printf("\t\t\t(default is 'both')\n");
printf(" -E, --event-clock <clock>\n");
printf("\t\t\tspecify the source clock for event timestamps\n");
printf("\t\t\tPossible values: 'monotonic', 'realtime', 'hte'.\n");
printf("\t\t\t(default is 'monotonic')\n");
printf("\t\t\tBy default 'realtime' is formatted as UTC, others as raw u64.\n");
printf(" -h, --help\t\tdisplay this help and exit\n");
printf(" -F, --format <fmt>\tspecify a custom output format\n");
printf(" -l, --active-low\ttreat the line as active low, flipping the sense of\n");
printf("\t\t\trising and falling edges\n");
printf(" --localtime\tformat event timestamps as local time\n");
printf(" -n, --num-events <num>\n");
printf("\t\t\texit after processing num events\n");
printf(" -p, --debounce-period <period>\n");
printf("\t\t\tdebounce the line(s) with the specified period\n");
printf(" -q, --quiet\t\tdon't generate any output\n");
printf(" -s, --strict\t\tabort if requested line names are not unique\n");
printf(" --unquoted\tdon't quote line or consumer names\n");
printf(" --utc\t\tformat event timestamps as UTC (default for 'realtime')\n");
printf(" -v, --version\t\toutput version information and exit\n");
print_chip_help();
print_period_help();
printf("\n");
printf("Format specifiers:\n");
printf(" %%o: GPIO line offset\n");
printf(" %%e: event type (0 - falling edge, 1 rising edge)\n");
printf(" %%s: seconds part of the event timestamp\n");
printf(" %%n: nanoseconds part of the event timestamp\n");
printf(" %%o GPIO line offset\n");
printf(" %%l GPIO line name\n");
printf(" %%c GPIO chip name\n");
printf(" %%e numeric edge event type ('1' - rising or '2' - falling)\n");
printf(" %%E edge event type ('rising' or 'falling')\n");
printf(" %%S event timestamp as seconds\n");
printf(" %%U event timestamp as UTC\n");
printf(" %%L event timestamp as local time\n");
}
struct mon_ctx {
unsigned int offset;
bool silent;
char *fmt;
};
static void event_print_custom(unsigned int offset, uint64_t timeout,
int event_type, struct mon_ctx *ctx)
static int parse_edges_or_die(const char *option)
{
char *prev, *curr, fmt;
if (strcmp(option, "rising") == 0)
return GPIOD_LINE_EDGE_RISING;
if (strcmp(option, "falling") == 0)
return GPIOD_LINE_EDGE_FALLING;
if (strcmp(option, "both") != 0)
die("invalid edges: %s", option);
for (prev = curr = ctx->fmt;;) {
return GPIOD_LINE_EDGE_BOTH;
}
static int parse_event_clock_or_die(const char *option)
{
if (strcmp(option, "realtime") == 0)
return GPIOD_LINE_EVENT_CLOCK_REALTIME;
if (strcmp(option, "hte") != 0)
return GPIOD_LINE_EVENT_CLOCK_HTE;
if (strcmp(option, "monotonic") != 0)
die("invalid event clock: %s", option);
return GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
}
static int parse_config(int argc, char **argv, struct config *cfg)
{
static const char *const shortopts = "+b:c:C:e:E:hF:ln:p:qshv";
const struct option longopts[] = {
{ "active-low", no_argument, NULL, 'l' },
{ "banner", no_argument, NULL, '-'},
{ "bias", required_argument, NULL, 'b' },
{ "by-name", no_argument, NULL, 'B'},
{ "chip", required_argument, NULL, 'c' },
{ "consumer", required_argument, NULL, 'C' },
{ "debounce-period", required_argument, NULL, 'p' },
{ "edges", required_argument, NULL, 'e' },
{ "event-clock", required_argument, NULL, 'E' },
{ "format", required_argument, NULL, 'F' },
{ "help", no_argument, NULL, 'h' },
{ "localtime", no_argument, &cfg->timestamp_fmt, 2 },
{ "num-events", required_argument, NULL, 'n' },
{ "quiet", no_argument, NULL, 'q' },
{ "silent", no_argument, NULL, 'q' },
{ "strict", no_argument, NULL, 's' },
{ "unquoted", no_argument, NULL, 'Q' },
{ "utc", no_argument, &cfg->timestamp_fmt, 1 },
{ "version", no_argument, NULL, 'v' },
{ GETOPT_NULL_LONGOPT },
};
int opti, optc;
memset(cfg, 0, sizeof(*cfg));
cfg->edges = GPIOD_LINE_EDGE_BOTH;
cfg->consumer = "gpiomon";
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
if (optc < 0)
break;
switch (optc) {
case '-':
cfg->banner = true;
break;
case 'b':
cfg->bias = parse_bias_or_die(optarg);
break;
case 'B':
cfg->by_name = true;
break;
case 'c':
cfg->chip_id = optarg;
break;
case 'C':
cfg->consumer = optarg;
break;
case 'e':
cfg->edges = parse_edges_or_die(optarg);
break;
case 'E':
cfg->event_clock = parse_event_clock_or_die(optarg);
break;
case 'F':
cfg->fmt = optarg;
break;
case 'l':
cfg->active_low = true;
break;
case 'n':
cfg->events_wanted = parse_uint_or_die(optarg);
break;
case 'p':
cfg->debounce_period_us = parse_period_or_die(optarg);
break;
case 'q':
cfg->quiet = true;
break;
case 'Q':
cfg->unquoted = true;
break;
case 's':
cfg->strict = true;
break;
case 'h':
print_help();
exit(EXIT_SUCCESS);
case 'v':
print_version();
exit(EXIT_SUCCESS);
case '?':
die("try %s --help", get_progname());
case 0:
break;
default:
abort();
}
}
/* setup default clock/format combinations, where not overridden */
if (cfg->event_clock == 0) {
if (cfg->timestamp_fmt)
cfg->event_clock = GPIOD_LINE_EVENT_CLOCK_REALTIME;
else
cfg->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
} else if ((cfg->event_clock == GPIOD_LINE_EVENT_CLOCK_REALTIME) &&
(cfg->timestamp_fmt == 0)) {
cfg->timestamp_fmt = 1;
}
return optind;
}
static void print_banner(int num_lines, char **lines)
{
int i;
if (num_lines > 1) {
printf("Monitoring lines ");
for (i = 0; i < num_lines - 1; i++)
printf("'%s', ", lines[i]);
printf("and '%s'...\n", lines[i]);
} else {
printf("Monitoring line '%s'...\n", lines[0]);
}
}
static void event_print_formatted(struct gpiod_edge_event *event,
struct line_resolver *resolver, int chip_num,
struct config *cfg)
{
const char *lname, *prev, *curr;
unsigned int offset;
uint64_t evtime;
int evtype;
char fmt;
offset = gpiod_edge_event_get_line_offset(event);
evtime = gpiod_edge_event_get_timestamp_ns(event);
evtype = gpiod_edge_event_get_event_type(event);
for (prev = curr = cfg->fmt;;) {
curr = strchr(curr, '%');
if (!curr) {
fputs(prev, stdout);
@@ -86,20 +260,35 @@ static void event_print_custom(unsigned int offset, uint64_t timeout,
fmt = *(curr + 1);
switch (fmt) {
case 'c':
fputs(get_chip_name(resolver, chip_num), stdout);
break;
case 'e':
printf("%d", evtype);
break;
case 'E':
if (evtype == GPIOD_EDGE_EVENT_RISING_EDGE)
fputs("rising", stdout);
else
fputs("falling", stdout);
break;
case 'l':
lname = get_line_name(resolver, chip_num, offset);
if (!lname)
lname = "unnamed";
fputs(lname, stdout);
break;
case 'L':
print_event_time(evtime, 2);
break;
case 'o':
printf("%u", offset);
break;
case 'e':
if (event_type == GPIOD_EDGE_EVENT_RISING_EDGE)
fputc('1', stdout);
else
fputc('0', stdout);
case 'S':
print_event_time(evtime, 0);
break;
case 's':
printf("%"PRIu64, timeout / 1000000000);
break;
case 'n':
printf("%"PRIu64, timeout % 1000000000);
case 'U':
print_event_time(evtime, 1);
break;
case '%':
fputc('%', stdout);
@@ -120,214 +309,179 @@ end:
fputc('\n', stdout);
}
static void event_print_human_readable(unsigned int offset,
uint64_t timeout, int event_type)
static void event_print_human_readable(struct gpiod_edge_event *event,
struct line_resolver *resolver,
int chip_num, struct config *cfg)
{
char *evname;
unsigned int offset;
uint64_t evtime;
if (event_type == GPIOD_EDGE_EVENT_RISING_EDGE)
evname = " RISING EDGE";
offset = gpiod_edge_event_get_line_offset(event);
evtime = gpiod_edge_event_get_timestamp_ns(event);
print_event_time(evtime, cfg->timestamp_fmt);
if (gpiod_edge_event_get_event_type(event) ==
GPIOD_EDGE_EVENT_RISING_EDGE)
fputs("\trising\t", stdout);
else
evname = "FALLING EDGE";
fputs("\tfalling\t", stdout);
printf("event: %s offset: %u timestamp: [%8"PRIu64".%09"PRIu64"]\n",
evname, offset, timeout / 1000000000, timeout % 1000000000);
print_line_id(resolver, chip_num, offset, cfg->chip_id, cfg->unquoted);
fputc('\n', stdout);
}
static void handle_event(unsigned int line_offset, unsigned int event_type,
uint64_t timestamp, struct mon_ctx *ctx)
static void event_print(struct gpiod_edge_event *event,
struct line_resolver *resolver, int chip_num,
struct config *cfg)
{
if (!ctx->silent) {
if (ctx->fmt)
event_print_custom(line_offset, timestamp,
event_type, ctx);
else
event_print_human_readable(line_offset,
timestamp, event_type);
}
}
if (cfg->quiet)
return;
static void handle_signal(int signum UNUSED)
{
exit(EXIT_SUCCESS);
if (cfg->fmt)
event_print_formatted(event, resolver, chip_num, cfg);
else
event_print_human_readable(event, resolver, chip_num, cfg);
}
int main(int argc, char **argv)
{
bool watch_rising = false, watch_falling = false, active_low = false;
size_t num_lines = 0, events_wanted = 0, events_done = 0;
struct gpiod_edge_event_buffer *event_buffer;
int optc, opti, ret, i, edge, bias = 0;
uint64_t timeout = 10 * 1000000000LLU;
struct gpiod_line_settings *settings;
struct gpiod_request_config *req_cfg;
struct gpiod_line_request *request;
struct gpiod_line_request **requests;
struct gpiod_line_config *line_cfg;
unsigned int offsets[64], offset;
int num_lines, events_done = 0;
struct gpiod_edge_event *event;
struct line_resolver *resolver;
struct gpiod_chip *chip;
struct mon_ctx ctx;
char *end;
struct pollfd *pollfds;
unsigned int *offsets;
struct config cfg;
int ret, i, j;
/*
* FIXME: use signalfd once the API has been converted to using a single file
* descriptor as provided by uAPI v2.
*/
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
memset(&ctx, 0, sizeof(ctx));
for (;;) {
optc = getopt_long(argc, argv, shortopts, longopts, &opti);
if (optc < 0)
break;
switch (optc) {
case 'h':
print_help();
return EXIT_SUCCESS;
case 'v':
print_version();
return EXIT_SUCCESS;
case 'l':
active_low = true;
break;
case 'B':
bias = parse_bias(optarg);
break;
case 'n':
events_wanted = strtoul(optarg, &end, 10);
if (*end != '\0')
die("invalid number: %s", optarg);
break;
case 's':
ctx.silent = true;
break;
case 'r':
watch_rising = true;
break;
case 'f':
watch_falling = true;
break;
case 'b':
setlinebuf(stdout);
break;
case 'F':
ctx.fmt = optarg;
break;
case '?':
die("try %s --help", get_progname());
default:
abort();
}
}
argc -= optind;
argv += optind;
if (watch_rising && !watch_falling)
edge = GPIOD_LINE_EDGE_RISING;
else if (watch_falling && !watch_rising)
edge = GPIOD_LINE_EDGE_FALLING;
else
edge = GPIOD_LINE_EDGE_BOTH;
i = parse_config(argc, argv, &cfg);
argc -= i;
argv += i;
if (argc < 1)
die("gpiochip must be specified");
die("at least one GPIO line must be specified");
if (argc < 2)
die("at least one GPIO line offset must be specified");
if (argc > 65)
die("too many offsets given");
for (i = 1; i < argc; i++) {
offset = strtoul(argv[i], &end, 10);
if (*end != '\0' || offset > INT_MAX)
die("invalid GPIO offset: %s", argv[i]);
offsets[i - 1] = offset;
num_lines++;
}
if (has_duplicate_offsets(num_lines, offsets))
die("offsets must be unique");
chip = chip_open_lookup(argv[0]);
if (!chip)
die_perror("unable to open %s", argv[0]);
if (argc > 64)
die("too many lines given");
settings = gpiod_line_settings_new();
if (!settings)
die_perror("unable to allocate line settings");
if (bias)
gpiod_line_settings_set_bias(settings, bias);
if (active_low)
gpiod_line_settings_set_active_low(settings, active_low);
gpiod_line_settings_set_edge_detection(settings, edge);
if (cfg.bias)
gpiod_line_settings_set_bias(settings, cfg.bias);
req_cfg = gpiod_request_config_new();
if (!req_cfg)
die_perror("unable to allocate the request config structure");
if (cfg.active_low)
gpiod_line_settings_set_active_low(settings, true);
gpiod_request_config_set_consumer(req_cfg, "gpiomon");
if (cfg.debounce_period_us)
gpiod_line_settings_set_debounce_period_us(settings,
cfg.debounce_period_us);
gpiod_line_settings_set_event_clock(settings, cfg.event_clock);
gpiod_line_settings_set_edge_detection(settings, cfg.edges);
line_cfg = gpiod_line_config_new();
if (!line_cfg)
die_perror("unable to allocate the line config structure");
ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
num_lines, settings);
if (ret)
die_perror("unable to add line settings");
req_cfg = gpiod_request_config_new();
if (!req_cfg)
die_perror("unable to allocate the request config structure");
request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
if (!request)
die_perror("unable to request lines");
gpiod_request_config_set_consumer(req_cfg, cfg.consumer);
event_buffer = gpiod_edge_event_buffer_new(EVENT_BUF_SIZE);
if (!event_buffer)
die_perror("unable to allocate the line event buffer");
resolver = resolve_lines(argc, argv, cfg.chip_id, cfg.strict,
cfg.by_name);
validate_resolution(resolver, cfg.chip_id);
requests = calloc(resolver->num_chips, sizeof(*requests));
pollfds = calloc(resolver->num_chips, sizeof(*pollfds));
offsets = calloc(resolver->num_lines, sizeof(*offsets));
if (!requests || !pollfds || !offsets)
die("out of memory");
for (i = 0; i < resolver->num_chips; i++) {
num_lines = get_line_offsets_and_values(resolver, i, offsets,
NULL);
gpiod_line_config_reset(line_cfg);
ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
num_lines, settings);
if (ret)
die_perror("unable to add line settings");
chip = gpiod_chip_open(resolver->chips[i].path);
if (!chip)
die_perror("unable to open chip '%s'",
resolver->chips[i].path);
requests[i] = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
if (!requests[i])
die_perror("unable to request lines on chip %s",
resolver->chips[i].path);
pollfds[i].fd = gpiod_line_request_get_fd(requests[i]);
pollfds[i].events = POLLIN;
gpiod_chip_close(chip);
}
gpiod_request_config_free(req_cfg);
gpiod_line_config_free(line_cfg);
gpiod_line_settings_free(settings);
if (cfg.banner)
print_banner(argc, argv);
for (;;) {
ret = gpiod_line_request_wait_edge_event(request, timeout);
if (ret < 0)
die_perror("error waiting for events");
if (ret == 0)
continue;
fflush(stdout);
ret = gpiod_line_request_read_edge_event(request, event_buffer,
EVENT_BUF_SIZE);
if (ret < 0)
die_perror("error reading line events");
if (poll(pollfds, resolver->num_chips, -1) < 0)
die_perror("error polling for events");
for (i = 0; i < ret; i++) {
event = gpiod_edge_event_buffer_get_event(event_buffer,
i);
if (!event)
die_perror("unable to retrieve the event from the buffer");
for (i = 0; i < resolver->num_chips; i++) {
if (pollfds[i].revents == 0)
continue;
handle_event(gpiod_edge_event_get_line_offset(event),
gpiod_edge_event_get_event_type(event),
gpiod_edge_event_get_timestamp_ns(event),
&ctx);
ret = gpiod_line_request_read_edge_event(requests[i],
event_buffer, EVENT_BUF_SIZE);
if (ret < 0)
die_perror("error reading line events");
events_done++;
for (j = 0; j < ret; j++) {
event = gpiod_edge_event_buffer_get_event(
event_buffer, j);
if (!event)
die_perror("unable to retrieve event from buffer");
if (events_wanted && events_done >= events_wanted)
goto done;
event_print(event, resolver, i, &cfg);
events_done++;
if (cfg.events_wanted &&
events_done >= cfg.events_wanted)
goto done;
}
}
}
done:
for (i = 0; i < resolver->num_chips; i++)
gpiod_line_request_release(requests[i]);
free(requests);
free_line_resolver(resolver);
gpiod_edge_event_buffer_free(event_buffer);
gpiod_line_request_release(request);
gpiod_request_config_free(req_cfg);
gpiod_line_config_free(line_cfg);
gpiod_line_settings_free(settings);
gpiod_chip_close(chip);
free(offsets);
return EXIT_SUCCESS;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
/* Common code for GPIO tools. */
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <gpiod.h>
#include <inttypes.h>
#include <libgen.h>
#include <signal.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <time.h>
#include "tools-common.h"
@@ -21,6 +25,28 @@ const char *get_progname(void)
return program_invocation_name;
}
void print_error(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
fprintf(stderr, "%s: ", program_invocation_name);
vfprintf(stderr, fmt, va);
fprintf(stderr, "\n");
va_end(va);
}
void print_perror(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
fprintf(stderr, "%s: ", program_invocation_name);
vfprintf(stderr, fmt, va);
fprintf(stderr, ": %s\n", strerror(errno));
va_end(va);
}
void die(const char *fmt, ...)
{
va_list va;
@@ -57,93 +83,311 @@ void print_version(void)
printf("There is NO WARRANTY, to the extent permitted by law.\n");
}
int parse_bias(const char *option)
int parse_bias_or_die(const char *option)
{
if (strcmp(option, "pull-down") == 0)
return GPIOD_LINE_BIAS_PULL_DOWN;
if (strcmp(option, "pull-up") == 0)
return GPIOD_LINE_BIAS_PULL_UP;
if (strcmp(option, "disable") == 0)
return GPIOD_LINE_BIAS_DISABLED;
if (strcmp(option, "as-is") != 0)
if (strcmp(option, "disabled") != 0)
die("invalid bias: %s", option);
return 0;
return GPIOD_LINE_BIAS_DISABLED;
}
int parse_period(const char *option)
{
unsigned long p, m = 0;
char *end;
p = strtoul(option, &end, 10);
switch (*end) {
case 'u':
m = 1;
end++;
break;
case 'm':
m = 1000;
end++;
break;
case 's':
m = 1000000;
break;
case '\0':
break;
default:
return -1;
}
if (m) {
if (*end != 's')
return -1;
end++;
} else {
m = 1000;
}
p *= m;
if (*end != '\0' || p > INT_MAX)
return -1;
return p;
}
unsigned int parse_period_or_die(const char *option)
{
int period = parse_period(option);
if (period < 0)
die("invalid period: %s", option);
return period;
}
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;
}
unsigned int parse_uint_or_die(const char *option)
{
int i = parse_uint(option);
if (i < 0)
die("invalid number: '%s'", option);
return i;
}
void print_bias_help(void)
{
printf("Biases:\n");
printf(" as-is:\tleave bias unchanged\n");
printf(" disable:\tdisable bias\n");
printf(" pull-up:\tenable pull-up\n");
printf(" pull-down:\tenable pull-down\n");
printf(" -b, --bias <bias>\tspecify the line bias\n");
printf("\t\t\tPossible values: 'pull-down', 'pull-up', 'disabled'.\n");
printf("\t\t\t(default is to leave bias unchanged)\n");
}
int make_signalfd(void)
void print_chip_help(void)
{
sigset_t sigmask;
int sigfd, rv;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGTERM);
sigaddset(&sigmask, SIGINT);
rv = sigprocmask(SIG_BLOCK, &sigmask, NULL);
if (rv < 0)
die("error masking signals: %s", strerror(errno));
sigfd = signalfd(-1, &sigmask, 0);
if (sigfd < 0)
die("error creating signalfd: %s", strerror(errno));
return sigfd;
printf("\nChips:\n");
printf(" A GPIO chip may be identified by number, name, or path.\n");
printf(" e.g. '0', 'gpiochip0', and '/dev/gpiochip0' all refer to the same chip.\n");
}
int chip_dir_filter(const struct dirent *entry)
void print_period_help(void)
{
bool is_chip;
printf("\nPeriods:\n");
printf(" Periods are taken as milliseconds unless units are specified. e.g. 10us.\n");
printf(" Supported units are 's', 'ms', and 'us'.\n");
}
#define TIME_BUFFER_SIZE 20
/*
* format:
* 0: raw seconds
* 1: utc time
* 2: local time
*/
void print_event_time(uint64_t evtime, int format)
{
char tbuf[TIME_BUFFER_SIZE];
time_t evtsec;
struct tm t;
char *tz;
if (format) {
evtsec = evtime / 1000000000;
if (format == 2) {
localtime_r(&evtsec, &t);
tz = "";
} else {
gmtime_r(&evtsec, &t);
tz = "Z";
}
strftime(tbuf, TIME_BUFFER_SIZE, "%FT%T", &t);
printf("%s.%09"PRIu64"%s", tbuf, evtime % 1000000000, tz);
} else {
printf("%"PRIu64".%09"PRIu64,
evtime / 1000000000, evtime % 1000000000);
}
}
static void print_bias(struct gpiod_line_info *info)
{
const char *name;
switch (gpiod_line_info_get_bias(info)) {
case GPIOD_LINE_BIAS_PULL_UP:
name = "pull-up";
break;
case GPIOD_LINE_BIAS_PULL_DOWN:
name = "pull-down";
break;
case GPIOD_LINE_BIAS_DISABLED:
name = "disabled";
break;
default:
return;
}
printf(" bias=%s", name);
}
static void print_drive(struct gpiod_line_info *info)
{
const char *name;
switch (gpiod_line_info_get_drive(info)) {
case GPIOD_LINE_DRIVE_OPEN_DRAIN:
name = "open-drain";
break;
case GPIOD_LINE_DRIVE_OPEN_SOURCE:
name = "open-source";
break;
default:
return;
}
printf(" drive=%s", name);
}
static void print_edge_detection(struct gpiod_line_info *info)
{
const char *name;
switch (gpiod_line_info_get_edge_detection(info)) {
case GPIOD_LINE_EDGE_BOTH:
name = "both";
break;
case GPIOD_LINE_EDGE_RISING:
name = "rising";
break;
case GPIOD_LINE_EDGE_FALLING:
name = "falling";
break;
default:
return;
}
printf(" edges=%s", name);
}
static void print_event_clock(struct gpiod_line_info *info)
{
const char *name;
switch (gpiod_line_info_get_event_clock(info)) {
case GPIOD_LINE_EVENT_CLOCK_REALTIME:
name = "realtime";
break;
case GPIOD_LINE_EVENT_CLOCK_HTE:
name = "hte";
break;
default:
return;
}
printf(" event-clock=%s", name);
}
static void print_debounce(struct gpiod_line_info *info)
{
const char *units = "us";
unsigned long debounce;
debounce = gpiod_line_info_get_debounce_period_us(info);
if (!debounce)
return;
if (debounce % 1000000 == 0) {
debounce /= 1000000;
units = "s";
} else if (debounce % 1000 == 0) {
debounce /= 1000;
units = "ms";
}
printf(" debounce-period=%lu%s", debounce, units);
}
static void print_consumer(struct gpiod_line_info *info, bool unquoted)
{
const char *consumer;
const char *fmt;
if (!gpiod_line_info_is_used(info))
return;
consumer = gpiod_line_info_get_consumer(info);
if (!consumer)
consumer = "kernel";
fmt = unquoted ? " consumer=%s" : " consumer=\"%s\"";
printf(fmt, consumer);
}
void print_line_attributes(struct gpiod_line_info *info, bool unquoted_strings)
{
int direction;
direction = gpiod_line_info_get_direction(info);
printf("%s", direction == GPIOD_LINE_DIRECTION_INPUT ?
"input" : "output");
if (gpiod_line_info_is_active_low(info))
printf(" active-low");
print_drive(info);
print_bias(info);
print_edge_detection(info);
print_event_clock(info);
print_debounce(info);
print_consumer(info, unquoted_strings);
}
void print_line_id(struct line_resolver *resolver, int chip_num,
unsigned int offset, const char *chip_id, bool unquoted)
{
const char *lname, *fmt;
lname = get_line_name(resolver, chip_num, offset);
if (!lname) {
printf("%s %u", get_chip_name(resolver, chip_num), offset);
return;
}
if (chip_id)
printf("%s %u ", get_chip_name(resolver, chip_num), offset);
fmt = unquoted ? "%s" : "\"%s\"";
printf(fmt, lname);
}
static int chip_dir_filter(const struct dirent *entry)
{
struct stat sb;
int ret = 0;
char *path;
int ret;
ret = asprintf(&path, "/dev/%s", entry->d_name);
if (ret < 0)
if (asprintf(&path, "/dev/%s", entry->d_name) < 0)
return 0;
is_chip = gpiod_is_gpiochip_device(path);
free(path);
return !!is_chip;
}
if ((lstat(path, &sb) == 0) &&
(!S_ISLNK(sb.st_mode)) &&
gpiod_is_gpiochip_device(path))
ret = 1;
struct gpiod_chip *chip_open_by_name(const char *name)
{
struct gpiod_chip *chip;
char *path;
int ret;
ret = asprintf(&path, "/dev/%s", name);
if (ret < 0)
return NULL;
chip = gpiod_chip_open(path);
free(path);
return chip;
}
static struct gpiod_chip *chip_open_by_number(unsigned int num)
{
struct gpiod_chip *chip;
char *path;
int ret;
ret = asprintf(&path, "/dev/gpiochip%u", num);
if (!ret)
return NULL;
chip = gpiod_chip_open(path);
free(path);
return chip;
return ret;
}
static bool isuint(const char *str)
@@ -154,31 +398,370 @@ static bool isuint(const char *str)
return *str == '\0';
}
struct gpiod_chip *chip_open_lookup(const char *device)
bool chip_path_lookup(const char *id, char **path_ptr)
{
struct gpiod_chip *chip;
char *path;
if (isuint(device)) {
chip = chip_open_by_number(strtoul(device, NULL, 10));
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 {
if (strncmp(device, "/dev/", 5))
chip = chip_open_by_name(device);
else
chip = gpiod_chip_open(device);
/* by device name */
if (asprintf(&path, "/dev/%s", id) < 0)
return false;
}
return chip;
if (!gpiod_is_gpiochip_device(path)) {
free(path);
return false;
}
*path_ptr = path;
return true;
}
bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets)
int chip_paths(const char *id, char ***paths_ptr)
{
size_t i, j;
char **paths;
char *path;
for (i = 0; i < num_offsets; i++) {
for (j = i + 1; j < num_offsets; j++)
if (offsets[i] == offsets[j])
return true;
if (id == NULL)
return all_chip_paths(paths_ptr);
if (!chip_path_lookup(id, &path))
return 0;
paths = malloc(sizeof(*paths));
if (paths == NULL)
die("out of memory");
paths[0] = path;
*paths_ptr = paths;
return 1;
}
int all_chip_paths(char ***paths_ptr)
{
int i, j, num_chips, ret = 0;
struct dirent **entries;
char **paths;
num_chips = scandir("/dev/", &entries, chip_dir_filter, alphasort);
if (num_chips < 0)
die_perror("unable to scan /dev");
paths = calloc(num_chips, sizeof(*paths));
if (paths == NULL)
die("out of memory");
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;
}
}
return false;
*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
*
* This only applies to the first chip, as otherwise the lines must be
* identified by name.
*/
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;
}
bool resolve_done(struct line_resolver *resolver)
{
return (!resolver->strict &&
resolver->num_found >= resolver->num_lines);
}
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("out of memory");
memset(resolver, 0, resolver_size);
resolver->chips = calloc(num_chips, sizeof(struct resolved_chip));
if (resolver->chips == NULL)
die("out of memory");
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;
}
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);
}
gpiod_chip_close(chip);
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]);
}
}
free(paths);
return resolver;
}
void validate_resolution(struct line_resolver *resolver, const char *chip_id)
{
struct resolved_line *line, *prev;
bool valid = true;
int i, j;
for (i = 0 ; i < resolver->num_lines; i++) {
line = &resolver->lines[i];
if (line->resolved) {
for (j = 0; j < i; j++) {
prev = &resolver->lines[j];
if (prev->resolved &&
(prev->chip_num == line->chip_num) &&
(prev->offset == line->offset)) {
print_error("lines '%s' and '%s' are the same line",
prev->id, line->id);
valid = false;
break;
}
}
continue;
}
valid = false;
if (chip_id && line->id_as_offset != -1)
print_error("offset %s is out of range on chip '%s'",
line->id, chip_id);
else
print_error("cannot find line '%s'", line->id);
}
if (!valid)
exit(EXIT_FAILURE);
}
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);
}
int get_line_offsets_and_values(struct line_resolver *resolver,
int chip_num, unsigned int *offsets,
int *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;
}
const char *get_chip_name(struct line_resolver *resolver, int chip_num)
{
return gpiod_chip_info_get_name(resolver->chips[chip_num].info);
}
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;
}
void set_line_values(struct line_resolver *resolver, int chip_num, int *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++;
}
}
}

View File

@@ -1,10 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
/* SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com> */
#ifndef __GPIOD_TOOLS_COMMON_H__
#define __GPIOD_TOOLS_COMMON_H__
#include <dirent.h>
#include <gpiod.h>
/*
@@ -21,16 +21,100 @@
#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 */
uint 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[];
};
const char *get_progname(void);
void print_error(const char *fmt, ...) PRINTF(1, 2);
void print_perror(const char *fmt, ...) PRINTF(1, 2);
void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
void print_version(void);
int parse_bias(const char *option);
int parse_bias_or_die(const char *option);
int parse_period(const char *option);
unsigned int parse_period_or_die(const char *option);
int parse_uint(const char *option);
unsigned int parse_uint_or_die(const char *option);
void print_bias_help(void);
int make_signalfd(void);
int chip_dir_filter(const struct dirent *entry);
struct gpiod_chip *chip_open_by_name(const char *name);
struct gpiod_chip *chip_open_lookup(const char *device);
bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets);
void print_chip_help(void);
void print_period_help(void);
void print_event_time(uint64_t evtime, int format);
void print_line_attributes(struct gpiod_line_info *info, bool unquoted_strings);
void print_line_id(struct line_resolver *resolver, int chip_num,
unsigned int offset, const char *chip_id, bool unquoted);
bool chip_path_lookup(const char *id, char **path_ptr);
int chip_paths(const char *id, char ***paths_ptr);
int all_chip_paths(char ***paths_ptr);
struct line_resolver *resolve_lines(int num_lines, char **lines,
const char *chip_id, bool strict, bool by_name);
struct line_resolver *resolver_init(int num_lines, char **lines, int num_chips,
bool strict, bool by_name);
bool resolve_lines_by_offset(struct line_resolver *resolver,
unsigned int num_lines);
bool resolve_done(struct line_resolver *resolver);
void validate_resolution(struct line_resolver *resolver, const char *chip_id);
void free_line_resolver(struct line_resolver *resolver);
int get_line_offsets_and_values(struct line_resolver *resolver,
int chip_num, unsigned int *offsets, int *values);
const char *get_chip_name(struct line_resolver *resolver, int chip_num);
const char *get_line_name(struct line_resolver *resolver, int chip_num,
unsigned int offset);
void set_line_values(struct line_resolver *resolver, int chip_num, int *values);
#endif /* __GPIOD_TOOLS_COMMON_H__ */