162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * gpio-event-mon - monitor GPIO line events from userspace 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 Linus Walleij 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Usage: 862306a36Sopenharmony_ci * gpio-event-mon -n <device-name> -o <offset> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <unistd.h> 1262306a36Sopenharmony_ci#include <stdlib.h> 1362306a36Sopenharmony_ci#include <stdbool.h> 1462306a36Sopenharmony_ci#include <stdint.h> 1562306a36Sopenharmony_ci#include <stdio.h> 1662306a36Sopenharmony_ci#include <dirent.h> 1762306a36Sopenharmony_ci#include <errno.h> 1862306a36Sopenharmony_ci#include <string.h> 1962306a36Sopenharmony_ci#include <poll.h> 2062306a36Sopenharmony_ci#include <fcntl.h> 2162306a36Sopenharmony_ci#include <getopt.h> 2262306a36Sopenharmony_ci#include <inttypes.h> 2362306a36Sopenharmony_ci#include <sys/ioctl.h> 2462306a36Sopenharmony_ci#include <sys/types.h> 2562306a36Sopenharmony_ci#include <linux/gpio.h> 2662306a36Sopenharmony_ci#include "gpio-utils.h" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ciint monitor_device(const char *device_name, 2962306a36Sopenharmony_ci unsigned int *lines, 3062306a36Sopenharmony_ci unsigned int num_lines, 3162306a36Sopenharmony_ci struct gpio_v2_line_config *config, 3262306a36Sopenharmony_ci unsigned int loops) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct gpio_v2_line_values values; 3562306a36Sopenharmony_ci char *chrdev_name; 3662306a36Sopenharmony_ci int cfd, lfd; 3762306a36Sopenharmony_ci int ret; 3862306a36Sopenharmony_ci int i = 0; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci ret = asprintf(&chrdev_name, "/dev/%s", device_name); 4162306a36Sopenharmony_ci if (ret < 0) 4262306a36Sopenharmony_ci return -ENOMEM; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci cfd = open(chrdev_name, 0); 4562306a36Sopenharmony_ci if (cfd == -1) { 4662306a36Sopenharmony_ci ret = -errno; 4762306a36Sopenharmony_ci fprintf(stderr, "Failed to open %s\n", chrdev_name); 4862306a36Sopenharmony_ci goto exit_free_name; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci ret = gpiotools_request_line(device_name, lines, num_lines, config, 5262306a36Sopenharmony_ci "gpio-event-mon"); 5362306a36Sopenharmony_ci if (ret < 0) 5462306a36Sopenharmony_ci goto exit_device_close; 5562306a36Sopenharmony_ci else 5662306a36Sopenharmony_ci lfd = ret; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* Read initial states */ 5962306a36Sopenharmony_ci values.mask = 0; 6062306a36Sopenharmony_ci values.bits = 0; 6162306a36Sopenharmony_ci for (i = 0; i < num_lines; i++) 6262306a36Sopenharmony_ci gpiotools_set_bit(&values.mask, i); 6362306a36Sopenharmony_ci ret = gpiotools_get_values(lfd, &values); 6462306a36Sopenharmony_ci if (ret < 0) { 6562306a36Sopenharmony_ci fprintf(stderr, 6662306a36Sopenharmony_ci "Failed to issue GPIO LINE GET VALUES IOCTL (%d)\n", 6762306a36Sopenharmony_ci ret); 6862306a36Sopenharmony_ci goto exit_line_close; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (num_lines == 1) { 7262306a36Sopenharmony_ci fprintf(stdout, "Monitoring line %d on %s\n", lines[0], device_name); 7362306a36Sopenharmony_ci fprintf(stdout, "Initial line value: %d\n", 7462306a36Sopenharmony_ci gpiotools_test_bit(values.bits, 0)); 7562306a36Sopenharmony_ci } else { 7662306a36Sopenharmony_ci fprintf(stdout, "Monitoring lines %d", lines[0]); 7762306a36Sopenharmony_ci for (i = 1; i < num_lines - 1; i++) 7862306a36Sopenharmony_ci fprintf(stdout, ", %d", lines[i]); 7962306a36Sopenharmony_ci fprintf(stdout, " and %d on %s\n", lines[i], device_name); 8062306a36Sopenharmony_ci fprintf(stdout, "Initial line values: %d", 8162306a36Sopenharmony_ci gpiotools_test_bit(values.bits, 0)); 8262306a36Sopenharmony_ci for (i = 1; i < num_lines - 1; i++) 8362306a36Sopenharmony_ci fprintf(stdout, ", %d", 8462306a36Sopenharmony_ci gpiotools_test_bit(values.bits, i)); 8562306a36Sopenharmony_ci fprintf(stdout, " and %d\n", 8662306a36Sopenharmony_ci gpiotools_test_bit(values.bits, i)); 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci i = 0; 9062306a36Sopenharmony_ci while (1) { 9162306a36Sopenharmony_ci struct gpio_v2_line_event event; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci ret = read(lfd, &event, sizeof(event)); 9462306a36Sopenharmony_ci if (ret == -1) { 9562306a36Sopenharmony_ci if (errno == -EAGAIN) { 9662306a36Sopenharmony_ci fprintf(stderr, "nothing available\n"); 9762306a36Sopenharmony_ci continue; 9862306a36Sopenharmony_ci } else { 9962306a36Sopenharmony_ci ret = -errno; 10062306a36Sopenharmony_ci fprintf(stderr, "Failed to read event (%d)\n", 10162306a36Sopenharmony_ci ret); 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (ret != sizeof(event)) { 10762306a36Sopenharmony_ci fprintf(stderr, "Reading event failed\n"); 10862306a36Sopenharmony_ci ret = -EIO; 10962306a36Sopenharmony_ci break; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci fprintf(stdout, "GPIO EVENT at %" PRIu64 " on line %d (%d|%d) ", 11262306a36Sopenharmony_ci (uint64_t)event.timestamp_ns, event.offset, event.line_seqno, 11362306a36Sopenharmony_ci event.seqno); 11462306a36Sopenharmony_ci switch (event.id) { 11562306a36Sopenharmony_ci case GPIO_V2_LINE_EVENT_RISING_EDGE: 11662306a36Sopenharmony_ci fprintf(stdout, "rising edge"); 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci case GPIO_V2_LINE_EVENT_FALLING_EDGE: 11962306a36Sopenharmony_ci fprintf(stdout, "falling edge"); 12062306a36Sopenharmony_ci break; 12162306a36Sopenharmony_ci default: 12262306a36Sopenharmony_ci fprintf(stdout, "unknown event"); 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci fprintf(stdout, "\n"); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci i++; 12762306a36Sopenharmony_ci if (i == loops) 12862306a36Sopenharmony_ci break; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ciexit_line_close: 13262306a36Sopenharmony_ci if (close(lfd) == -1) 13362306a36Sopenharmony_ci perror("Failed to close line file"); 13462306a36Sopenharmony_ciexit_device_close: 13562306a36Sopenharmony_ci if (close(cfd) == -1) 13662306a36Sopenharmony_ci perror("Failed to close GPIO character device file"); 13762306a36Sopenharmony_ciexit_free_name: 13862306a36Sopenharmony_ci free(chrdev_name); 13962306a36Sopenharmony_ci return ret; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_civoid print_usage(void) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci fprintf(stderr, "Usage: gpio-event-mon [options]...\n" 14562306a36Sopenharmony_ci "Listen to events on GPIO lines, 0->1 1->0\n" 14662306a36Sopenharmony_ci " -n <name> Listen on GPIOs on a named device (must be stated)\n" 14762306a36Sopenharmony_ci " -o <n> Offset of line to monitor (may be repeated)\n" 14862306a36Sopenharmony_ci " -d Set line as open drain\n" 14962306a36Sopenharmony_ci " -s Set line as open source\n" 15062306a36Sopenharmony_ci " -r Listen for rising edges\n" 15162306a36Sopenharmony_ci " -f Listen for falling edges\n" 15262306a36Sopenharmony_ci " -w Report the wall-clock time for events\n" 15362306a36Sopenharmony_ci " -t Report the hardware timestamp for events\n" 15462306a36Sopenharmony_ci " -b <n> Debounce the line with period n microseconds\n" 15562306a36Sopenharmony_ci " [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n" 15662306a36Sopenharmony_ci " -? This helptext\n" 15762306a36Sopenharmony_ci "\n" 15862306a36Sopenharmony_ci "Example:\n" 15962306a36Sopenharmony_ci "gpio-event-mon -n gpiochip0 -o 4 -r -f -b 10000\n" 16062306a36Sopenharmony_ci ); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci#define EDGE_FLAGS \ 16462306a36Sopenharmony_ci (GPIO_V2_LINE_FLAG_EDGE_RISING | \ 16562306a36Sopenharmony_ci GPIO_V2_LINE_FLAG_EDGE_FALLING) 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ciint main(int argc, char **argv) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci const char *device_name = NULL; 17062306a36Sopenharmony_ci unsigned int lines[GPIO_V2_LINES_MAX]; 17162306a36Sopenharmony_ci unsigned int num_lines = 0; 17262306a36Sopenharmony_ci unsigned int loops = 0; 17362306a36Sopenharmony_ci struct gpio_v2_line_config config; 17462306a36Sopenharmony_ci int c, attr, i; 17562306a36Sopenharmony_ci unsigned long debounce_period_us = 0; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci memset(&config, 0, sizeof(config)); 17862306a36Sopenharmony_ci config.flags = GPIO_V2_LINE_FLAG_INPUT; 17962306a36Sopenharmony_ci while ((c = getopt(argc, argv, "c:n:o:b:dsrfwt?")) != -1) { 18062306a36Sopenharmony_ci switch (c) { 18162306a36Sopenharmony_ci case 'c': 18262306a36Sopenharmony_ci loops = strtoul(optarg, NULL, 10); 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci case 'n': 18562306a36Sopenharmony_ci device_name = optarg; 18662306a36Sopenharmony_ci break; 18762306a36Sopenharmony_ci case 'o': 18862306a36Sopenharmony_ci if (num_lines >= GPIO_V2_LINES_MAX) { 18962306a36Sopenharmony_ci print_usage(); 19062306a36Sopenharmony_ci return -1; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci lines[num_lines] = strtoul(optarg, NULL, 10); 19362306a36Sopenharmony_ci num_lines++; 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci case 'b': 19662306a36Sopenharmony_ci debounce_period_us = strtoul(optarg, NULL, 10); 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci case 'd': 19962306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci case 's': 20262306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; 20362306a36Sopenharmony_ci break; 20462306a36Sopenharmony_ci case 'r': 20562306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; 20662306a36Sopenharmony_ci break; 20762306a36Sopenharmony_ci case 'f': 20862306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; 20962306a36Sopenharmony_ci break; 21062306a36Sopenharmony_ci case 'w': 21162306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; 21262306a36Sopenharmony_ci break; 21362306a36Sopenharmony_ci case 't': 21462306a36Sopenharmony_ci config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE; 21562306a36Sopenharmony_ci break; 21662306a36Sopenharmony_ci case '?': 21762306a36Sopenharmony_ci print_usage(); 21862306a36Sopenharmony_ci return -1; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (debounce_period_us) { 22362306a36Sopenharmony_ci attr = config.num_attrs; 22462306a36Sopenharmony_ci config.num_attrs++; 22562306a36Sopenharmony_ci for (i = 0; i < num_lines; i++) 22662306a36Sopenharmony_ci gpiotools_set_bit(&config.attrs[attr].mask, i); 22762306a36Sopenharmony_ci config.attrs[attr].attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; 22862306a36Sopenharmony_ci config.attrs[attr].attr.debounce_period_us = debounce_period_us; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (!device_name || num_lines == 0) { 23262306a36Sopenharmony_ci print_usage(); 23362306a36Sopenharmony_ci return -1; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci if (!(config.flags & EDGE_FLAGS)) { 23662306a36Sopenharmony_ci printf("No flags specified, listening on both rising and " 23762306a36Sopenharmony_ci "falling edges\n"); 23862306a36Sopenharmony_ci config.flags |= EDGE_FLAGS; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci return monitor_device(device_name, lines, num_lines, &config, loops); 24162306a36Sopenharmony_ci} 242