mirror of
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
synced 2026-10-24 00:48:53 +03:00
We currently store time as microseconds in 32-bit integers and allow seconds as the longest time unit when parsing command-line arguments limiting the time period possible to specify when passing arguments such as --hold-period to 35 minutes. Let's use 64-bit integers to vastly increase that. Use nanosleep() instead of usleep() to extend the possible sleep time range. Reported-by: Gunnar Thörnqvist <gunnar@igl.se> Link: https://lore.kernel.org/r/20240423100452.32958-4-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
514 lines
13 KiB
C
514 lines
13 KiB
C
// 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 <inttypes.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tools-common.h"
|
|
|
|
#define EVENT_BUF_SIZE 32
|
|
|
|
struct config {
|
|
bool active_low;
|
|
bool banner;
|
|
bool by_name;
|
|
bool quiet;
|
|
bool strict;
|
|
bool unquoted;
|
|
enum gpiod_line_bias bias;
|
|
enum gpiod_line_edge edges;
|
|
int events_wanted;
|
|
unsigned long long debounce_period_us;
|
|
const char *chip_id;
|
|
const char *consumer;
|
|
const char *fmt;
|
|
enum gpiod_line_clock event_clock;
|
|
int timestamp_fmt;
|
|
long long idle_timeout;
|
|
};
|
|
|
|
static void print_help(void)
|
|
{
|
|
printf("Usage: %s [OPTIONS] <line>...\n", get_prog_name());
|
|
printf("\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(" --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(" --idle-timeout <period>\n");
|
|
printf("\t\t\texit gracefully if no events occur for the period specified\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(" %%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");
|
|
}
|
|
|
|
static int parse_edges_or_die(const char *option)
|
|
{
|
|
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);
|
|
|
|
return GPIOD_LINE_EDGE_BOTH;
|
|
}
|
|
|
|
static int parse_event_clock_or_die(const char *option)
|
|
{
|
|
if (strcmp(option, "realtime") == 0)
|
|
return GPIOD_LINE_CLOCK_REALTIME;
|
|
if (strcmp(option, "hte") == 0)
|
|
return GPIOD_LINE_CLOCK_HTE;
|
|
if (strcmp(option, "monotonic") != 0)
|
|
die("invalid event clock: %s", option);
|
|
|
|
return GPIOD_LINE_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' },
|
|
{ "idle-timeout", required_argument, NULL, 'i' },
|
|
{ "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";
|
|
cfg->idle_timeout = -1;
|
|
|
|
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 'i':
|
|
cfg->idle_timeout = parse_period_or_die(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_prog_name());
|
|
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_CLOCK_REALTIME;
|
|
else
|
|
cfg->event_clock = GPIOD_LINE_CLOCK_MONOTONIC;
|
|
} else if ((cfg->event_clock == GPIOD_LINE_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);
|
|
break;
|
|
}
|
|
|
|
if (prev != curr)
|
|
fwrite(prev, curr - prev, 1, stdout);
|
|
|
|
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 'S':
|
|
print_event_time(evtime, 0);
|
|
break;
|
|
case 'U':
|
|
print_event_time(evtime, 1);
|
|
break;
|
|
case '%':
|
|
fputc('%', stdout);
|
|
break;
|
|
case '\0':
|
|
fputc('%', stdout);
|
|
goto end;
|
|
default:
|
|
fwrite(curr, 2, 1, stdout);
|
|
break;
|
|
}
|
|
|
|
curr += 2;
|
|
prev = curr;
|
|
}
|
|
|
|
end:
|
|
fputc('\n', stdout);
|
|
}
|
|
|
|
static void event_print_human_readable(struct gpiod_edge_event *event,
|
|
struct line_resolver *resolver,
|
|
int chip_num, struct config *cfg)
|
|
{
|
|
unsigned int offset;
|
|
uint64_t evtime;
|
|
|
|
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
|
|
fputs("\tfalling\t", stdout);
|
|
|
|
print_line_id(resolver, chip_num, offset, cfg->chip_id, cfg->unquoted);
|
|
fputc('\n', stdout);
|
|
}
|
|
|
|
static void event_print(struct gpiod_edge_event *event,
|
|
struct line_resolver *resolver, int chip_num,
|
|
struct config *cfg)
|
|
{
|
|
if (cfg->quiet)
|
|
return;
|
|
|
|
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)
|
|
{
|
|
struct gpiod_edge_event_buffer *event_buffer;
|
|
struct gpiod_line_settings *settings;
|
|
struct gpiod_request_config *req_cfg;
|
|
struct gpiod_line_request **requests;
|
|
struct gpiod_line_config *line_cfg;
|
|
int num_lines, events_done = 0;
|
|
struct gpiod_edge_event *event;
|
|
struct line_resolver *resolver;
|
|
struct timespec idle_timeout;
|
|
struct gpiod_chip *chip;
|
|
struct pollfd *pollfds;
|
|
unsigned int *offsets;
|
|
struct config cfg;
|
|
int ret, i, j;
|
|
|
|
set_prog_name(argv[0]);
|
|
i = parse_config(argc, argv, &cfg);
|
|
argc -= i;
|
|
argv += i;
|
|
|
|
if (argc < 1)
|
|
die("at least one GPIO line must be specified");
|
|
|
|
if (argc > 64)
|
|
die("too many lines given");
|
|
|
|
settings = gpiod_line_settings_new();
|
|
if (!settings)
|
|
die_perror("unable to allocate line settings");
|
|
|
|
if (cfg.bias)
|
|
gpiod_line_settings_set_bias(settings, cfg.bias);
|
|
|
|
if (cfg.active_low)
|
|
gpiod_line_settings_set_active_low(settings, true);
|
|
|
|
if (cfg.debounce_period_us) {
|
|
if (cfg.debounce_period_us > UINT_MAX)
|
|
die("maximum debounce period is %uus, got %lluus",
|
|
UINT_MAX, cfg.debounce_period_us);
|
|
|
|
gpiod_line_settings_set_debounce_period_us(
|
|
settings, (unsigned long)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");
|
|
|
|
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, 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);
|
|
|
|
if (cfg.idle_timeout > 0) {
|
|
idle_timeout.tv_sec = cfg.idle_timeout / 1000000;
|
|
idle_timeout.tv_nsec =
|
|
(cfg.idle_timeout % 1000000) * 1000;
|
|
}
|
|
|
|
for (;;) {
|
|
fflush(stdout);
|
|
|
|
ret = ppoll(pollfds, resolver->num_chips,
|
|
cfg.idle_timeout > 0 ? &idle_timeout : NULL, NULL);
|
|
if (ret < 0)
|
|
die_perror("error polling for events");
|
|
|
|
if (ret == 0)
|
|
goto done;
|
|
|
|
for (i = 0; i < resolver->num_chips; i++) {
|
|
if (pollfds[i].revents == 0)
|
|
continue;
|
|
|
|
ret = gpiod_line_request_read_edge_events(requests[i],
|
|
event_buffer, EVENT_BUF_SIZE);
|
|
if (ret < 0)
|
|
die_perror("error reading line events");
|
|
|
|
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");
|
|
|
|
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);
|
|
free(offsets);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|