blob: 8bf2c7009d3d791b342a91860bfaab24321017ee [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdio.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' },
{ "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 },
};
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("\n");
printf("Wait for events on GPIO lines and print them to standard output\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");
print_bias_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");
}
struct mon_ctx {
unsigned int offset;
bool silent;
char *fmt;
};
static void event_print_custom(unsigned int offset,
const struct timespec *ts,
int event_type,
struct mon_ctx *ctx)
{
char *prev, *curr, fmt;
for (prev = curr = ctx->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 'o':
printf("%u", offset);
break;
case 'e':
if (event_type == GPIOD_LINE_EVENT_RISING_EDGE)
fputc('1', stdout);
else
fputc('0', stdout);
break;
case 's':
printf("%ld", ts->tv_sec);
break;
case 'n':
printf("%ld", ts->tv_nsec);
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(unsigned int offset,
const struct timespec *ts,
int event_type)
{
char *evname;
if (event_type == GPIOD_LINE_EVENT_RISING_EDGE)
evname = " RISING EDGE";
else
evname = "FALLING EDGE";
printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
evname, offset, ts->tv_sec, ts->tv_nsec);
}
static void handle_event(unsigned int line_offset, unsigned int event_type,
struct timespec *timestamp, struct mon_ctx *ctx)
{
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);
}
}
static void handle_signal(int signum UNUSED)
{
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv)
{
unsigned int offsets[64], num_lines = 0, offset,
events_wanted = 0, events_done = 0, x;
bool watch_rising = false, watch_falling = false;
int flags = 0;
struct timespec timeout = { 10, 0 };
int optc, opti, rv, i, y, event_type;
struct mon_ctx ctx;
struct gpiod_chip *chip;
struct gpiod_line_bulk *lines, *evlines;
char *end;
struct gpiod_line_request_config config;
struct gpiod_line *line;
struct gpiod_line_event events[16];
/*
* 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':
flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
break;
case 'B':
flags |= bias_flags(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)
event_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
else if (watch_falling && !watch_rising)
event_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
else
event_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
if (argc < 1)
die("gpiochip 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++;
}
chip = chip_open_lookup(argv[0]);
if (!chip)
die_perror("unable to open %s", argv[0]);
lines = gpiod_chip_get_lines(chip, offsets, num_lines);
if (!lines)
die_perror("unable to retrieve GPIO lines from chip");
memset(&config, 0, sizeof(config));
config.consumer = "gpiomon";
config.request_type = event_type;
config.flags = flags;
rv = gpiod_line_request_bulk(lines, &config, NULL);
if (rv)
die_perror("unable to request GPIO lines for events");
evlines = gpiod_line_bulk_new(num_lines);
if (!evlines)
die("out of memory");
for (;;) {
gpiod_line_bulk_reset(evlines);
rv = gpiod_line_event_wait_bulk(lines, &timeout, evlines);
if (rv < 0)
die_perror("error waiting for events");
if (rv == 0)
continue;
num_lines = gpiod_line_bulk_num_lines(evlines);
for (x = 0; x < num_lines; x++) {
line = gpiod_line_bulk_get_line(evlines, x);
rv = gpiod_line_event_read_multiple(line, events,
ARRAY_SIZE(events));
if (rv < 0)
die_perror("error reading line events");
for (y = 0; y < rv; y++) {
handle_event(gpiod_line_offset(line),
events[y].event_type,
&events[y].ts, &ctx);
events_done++;
if (events_wanted &&
events_done >= events_wanted)
goto done;
}
}
}
done:
gpiod_line_release_bulk(lines);
gpiod_line_bulk_free(lines);
gpiod_line_bulk_free(evlines);
gpiod_chip_close(chip);
return EXIT_SUCCESS;
}