| // 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_unref(chip); |
| |
| return EXIT_SUCCESS; |
| } |