Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
97f69747 | 2 | /* |
925baf39 | 3 | * gpio-event-mon - monitor GPIO line events from userspace |
97f69747 LW |
4 | * |
5 | * Copyright (C) 2016 Linus Walleij | |
6 | * | |
97f69747 LW |
7 | * Usage: |
8 | * gpio-event-mon -n <device-name> -o <offset> | |
9 | */ | |
10 | ||
11 | #include <unistd.h> | |
12 | #include <stdlib.h> | |
13 | #include <stdbool.h> | |
92e70b83 | 14 | #include <stdint.h> |
97f69747 LW |
15 | #include <stdio.h> |
16 | #include <dirent.h> | |
17 | #include <errno.h> | |
18 | #include <string.h> | |
19 | #include <poll.h> | |
20 | #include <fcntl.h> | |
21 | #include <getopt.h> | |
22 | #include <inttypes.h> | |
23 | #include <sys/ioctl.h> | |
1696784e | 24 | #include <sys/types.h> |
97f69747 | 25 | #include <linux/gpio.h> |
0acda979 | 26 | #include "gpio-utils.h" |
97f69747 LW |
27 | |
28 | int monitor_device(const char *device_name, | |
62757c32 KG |
29 | unsigned int *lines, |
30 | unsigned int num_lines, | |
0acda979 | 31 | struct gpio_v2_line_config *config, |
97f69747 LW |
32 | unsigned int loops) |
33 | { | |
0acda979 | 34 | struct gpio_v2_line_values values; |
97f69747 | 35 | char *chrdev_name; |
0acda979 | 36 | int cfd, lfd; |
97f69747 LW |
37 | int ret; |
38 | int i = 0; | |
39 | ||
40 | ret = asprintf(&chrdev_name, "/dev/%s", device_name); | |
41 | if (ret < 0) | |
42 | return -ENOMEM; | |
43 | ||
0acda979 KG |
44 | cfd = open(chrdev_name, 0); |
45 | if (cfd == -1) { | |
97f69747 LW |
46 | ret = -errno; |
47 | fprintf(stderr, "Failed to open %s\n", chrdev_name); | |
df51f402 | 48 | goto exit_free_name; |
97f69747 LW |
49 | } |
50 | ||
62757c32 | 51 | ret = gpiotools_request_line(device_name, lines, num_lines, config, |
0acda979 KG |
52 | "gpio-event-mon"); |
53 | if (ret < 0) | |
54 | goto exit_device_close; | |
55 | else | |
56 | lfd = ret; | |
97f69747 LW |
57 | |
58 | /* Read initial states */ | |
62757c32 | 59 | values.mask = 0; |
0acda979 | 60 | values.bits = 0; |
62757c32 KG |
61 | for (i = 0; i < num_lines; i++) |
62 | gpiotools_set_bit(&values.mask, i); | |
0acda979 KG |
63 | ret = gpiotools_get_values(lfd, &values); |
64 | if (ret < 0) { | |
65 | fprintf(stderr, | |
66 | "Failed to issue GPIO LINE GET VALUES IOCTL (%d)\n", | |
97f69747 | 67 | ret); |
0acda979 | 68 | goto exit_line_close; |
97f69747 LW |
69 | } |
70 | ||
62757c32 KG |
71 | if (num_lines == 1) { |
72 | fprintf(stdout, "Monitoring line %d on %s\n", lines[0], device_name); | |
73 | fprintf(stdout, "Initial line value: %d\n", | |
74 | gpiotools_test_bit(values.bits, 0)); | |
75 | } else { | |
76 | fprintf(stdout, "Monitoring lines %d", lines[0]); | |
77 | for (i = 1; i < num_lines - 1; i++) | |
78 | fprintf(stdout, ", %d", lines[i]); | |
79 | fprintf(stdout, " and %d on %s\n", lines[i], device_name); | |
80 | fprintf(stdout, "Initial line values: %d", | |
81 | gpiotools_test_bit(values.bits, 0)); | |
82 | for (i = 1; i < num_lines - 1; i++) | |
83 | fprintf(stdout, ", %d", | |
84 | gpiotools_test_bit(values.bits, i)); | |
85 | fprintf(stdout, " and %d\n", | |
86 | gpiotools_test_bit(values.bits, i)); | |
87 | } | |
97f69747 | 88 | |
677d85e1 | 89 | i = 0; |
97f69747 | 90 | while (1) { |
0acda979 | 91 | struct gpio_v2_line_event event; |
97f69747 | 92 | |
0acda979 | 93 | ret = read(lfd, &event, sizeof(event)); |
97f69747 LW |
94 | if (ret == -1) { |
95 | if (errno == -EAGAIN) { | |
96 | fprintf(stderr, "nothing available\n"); | |
97 | continue; | |
98 | } else { | |
99 | ret = -errno; | |
100 | fprintf(stderr, "Failed to read event (%d)\n", | |
101 | ret); | |
102 | break; | |
103 | } | |
104 | } | |
105 | ||
106 | if (ret != sizeof(event)) { | |
107 | fprintf(stderr, "Reading event failed\n"); | |
108 | ret = -EIO; | |
109 | break; | |
110 | } | |
2fe7c2f9 KG |
111 | fprintf(stdout, "GPIO EVENT at %" PRIu64 " on line %d (%d|%d) ", |
112 | (uint64_t)event.timestamp_ns, event.offset, event.line_seqno, | |
0acda979 | 113 | event.seqno); |
97f69747 | 114 | switch (event.id) { |
0acda979 | 115 | case GPIO_V2_LINE_EVENT_RISING_EDGE: |
97f69747 LW |
116 | fprintf(stdout, "rising edge"); |
117 | break; | |
0acda979 | 118 | case GPIO_V2_LINE_EVENT_FALLING_EDGE: |
97f69747 LW |
119 | fprintf(stdout, "falling edge"); |
120 | break; | |
121 | default: | |
122 | fprintf(stdout, "unknown event"); | |
123 | } | |
124 | fprintf(stdout, "\n"); | |
125 | ||
126 | i++; | |
127 | if (i == loops) | |
128 | break; | |
129 | } | |
130 | ||
0acda979 KG |
131 | exit_line_close: |
132 | if (close(lfd) == -1) | |
133 | perror("Failed to close line file"); | |
134 | exit_device_close: | |
135 | if (close(cfd) == -1) | |
97f69747 | 136 | perror("Failed to close GPIO character device file"); |
df51f402 | 137 | exit_free_name: |
97f69747 LW |
138 | free(chrdev_name); |
139 | return ret; | |
140 | } | |
141 | ||
142 | void print_usage(void) | |
143 | { | |
144 | fprintf(stderr, "Usage: gpio-event-mon [options]...\n" | |
145 | "Listen to events on GPIO lines, 0->1 1->0\n" | |
146 | " -n <name> Listen on GPIOs on a named device (must be stated)\n" | |
62757c32 | 147 | " -o <n> Offset of line to monitor (may be repeated)\n" |
97f69747 LW |
148 | " -d Set line as open drain\n" |
149 | " -s Set line as open source\n" | |
150 | " -r Listen for rising edges\n" | |
151 | " -f Listen for falling edges\n" | |
e0822cf9 | 152 | " -w Report the wall-clock time for events\n" |
ed94eb2e | 153 | " -t Report the hardware timestamp for events\n" |
cf048e05 | 154 | " -b <n> Debounce the line with period n microseconds\n" |
97f69747 LW |
155 | " [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n" |
156 | " -? This helptext\n" | |
157 | "\n" | |
158 | "Example:\n" | |
cf048e05 | 159 | "gpio-event-mon -n gpiochip0 -o 4 -r -f -b 10000\n" |
97f69747 LW |
160 | ); |
161 | } | |
162 | ||
0acda979 KG |
163 | #define EDGE_FLAGS \ |
164 | (GPIO_V2_LINE_FLAG_EDGE_RISING | \ | |
165 | GPIO_V2_LINE_FLAG_EDGE_FALLING) | |
166 | ||
97f69747 LW |
167 | int main(int argc, char **argv) |
168 | { | |
169 | const char *device_name = NULL; | |
62757c32 KG |
170 | unsigned int lines[GPIO_V2_LINES_MAX]; |
171 | unsigned int num_lines = 0; | |
97f69747 | 172 | unsigned int loops = 0; |
0acda979 | 173 | struct gpio_v2_line_config config; |
cf048e05 KG |
174 | int c, attr, i; |
175 | unsigned long debounce_period_us = 0; | |
97f69747 | 176 | |
0acda979 KG |
177 | memset(&config, 0, sizeof(config)); |
178 | config.flags = GPIO_V2_LINE_FLAG_INPUT; | |
ed94eb2e | 179 | while ((c = getopt(argc, argv, "c:n:o:b:dsrfwt?")) != -1) { |
97f69747 LW |
180 | switch (c) { |
181 | case 'c': | |
182 | loops = strtoul(optarg, NULL, 10); | |
183 | break; | |
184 | case 'n': | |
185 | device_name = optarg; | |
186 | break; | |
187 | case 'o': | |
62757c32 KG |
188 | if (num_lines >= GPIO_V2_LINES_MAX) { |
189 | print_usage(); | |
190 | return -1; | |
191 | } | |
192 | lines[num_lines] = strtoul(optarg, NULL, 10); | |
193 | num_lines++; | |
97f69747 | 194 | break; |
cf048e05 KG |
195 | case 'b': |
196 | debounce_period_us = strtoul(optarg, NULL, 10); | |
197 | break; | |
97f69747 | 198 | case 'd': |
0acda979 | 199 | config.flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; |
97f69747 LW |
200 | break; |
201 | case 's': | |
0acda979 | 202 | config.flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; |
97f69747 LW |
203 | break; |
204 | case 'r': | |
0acda979 | 205 | config.flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; |
97f69747 LW |
206 | break; |
207 | case 'f': | |
0acda979 | 208 | config.flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; |
97f69747 | 209 | break; |
e0822cf9 KG |
210 | case 'w': |
211 | config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; | |
212 | break; | |
ed94eb2e DP |
213 | case 't': |
214 | config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE; | |
215 | break; | |
97f69747 LW |
216 | case '?': |
217 | print_usage(); | |
218 | return -1; | |
219 | } | |
220 | } | |
221 | ||
cf048e05 KG |
222 | if (debounce_period_us) { |
223 | attr = config.num_attrs; | |
224 | config.num_attrs++; | |
225 | for (i = 0; i < num_lines; i++) | |
226 | gpiotools_set_bit(&config.attrs[attr].mask, i); | |
227 | config.attrs[attr].attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; | |
228 | config.attrs[attr].attr.debounce_period_us = debounce_period_us; | |
229 | } | |
230 | ||
62757c32 | 231 | if (!device_name || num_lines == 0) { |
97f69747 LW |
232 | print_usage(); |
233 | return -1; | |
234 | } | |
0acda979 | 235 | if (!(config.flags & EDGE_FLAGS)) { |
97f69747 LW |
236 | printf("No flags specified, listening on both rising and " |
237 | "falling edges\n"); | |
0acda979 | 238 | config.flags |= EDGE_FLAGS; |
97f69747 | 239 | } |
62757c32 | 240 | return monitor_device(device_name, lines, num_lines, &config, loops); |
97f69747 | 241 | } |