aboutsummaryrefslogtreecommitdiff
path: root/tools/gpio/gpio-event-mon.c
blob: 5dee2b98ab60439fed115d0aae0e558935adc571 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// SPDX-License-Identifier: GPL-2.0-only
/*
 * gpio-event-mon - monitor GPIO line events from userspace
 *
 * Copyright (C) 2016 Linus Walleij
 *
 * Usage:
 *	gpio-event-mon -n <device-name> -o <offset>
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/gpio.h>
#include "gpio-utils.h"

int monitor_device(const char *device_name,
		   unsigned int *lines,
		   unsigned int num_lines,
		   struct gpio_v2_line_config *config,
		   unsigned int loops)
{
	struct gpio_v2_line_values values;
	char *chrdev_name;
	int cfd, lfd;
	int ret;
	int i = 0;

	ret = asprintf(&chrdev_name, "/dev/%s", device_name);
	if (ret < 0)
		return -ENOMEM;

	cfd = open(chrdev_name, 0);
	if (cfd == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to open %s\n", chrdev_name);
		goto exit_free_name;
	}

	ret = gpiotools_request_line(device_name, lines, num_lines, config,
				     "gpio-event-mon");
	if (ret < 0)
		goto exit_device_close;
	else
		lfd = ret;

	/* Read initial states */
	values.mask = 0;
	values.bits = 0;
	for (i = 0; i < num_lines; i++)
		gpiotools_set_bit(&values.mask, i);
	ret = gpiotools_get_values(lfd, &values);
	if (ret < 0) {
		fprintf(stderr,
			"Failed to issue GPIO LINE GET VALUES IOCTL (%d)\n",
			ret);
		goto exit_line_close;
	}

	if (num_lines == 1) {
		fprintf(stdout, "Monitoring line %d on %s\n", lines[0], device_name);
		fprintf(stdout, "Initial line value: %d\n",
			gpiotools_test_bit(values.bits, 0));
	} else {
		fprintf(stdout, "Monitoring lines %d", lines[0]);
		for (i = 1; i < num_lines - 1; i++)
			fprintf(stdout, ", %d", lines[i]);
		fprintf(stdout, " and %d on %s\n", lines[i], device_name);
		fprintf(stdout, "Initial line values: %d",
			gpiotools_test_bit(values.bits, 0));
		for (i = 1; i < num_lines - 1; i++)
			fprintf(stdout, ", %d",
				gpiotools_test_bit(values.bits, i));
		fprintf(stdout, " and %d\n",
			gpiotools_test_bit(values.bits, i));
	}

	i = 0;
	while (1) {
		struct gpio_v2_line_event event;

		ret = read(lfd, &event, sizeof(event));
		if (ret == -1) {
			if (errno == -EAGAIN) {
				fprintf(stderr, "nothing available\n");
				continue;
			} else {
				ret = -errno;
				fprintf(stderr, "Failed to read event (%d)\n",
					ret);
				break;
			}
		}

		if (ret != sizeof(event)) {
			fprintf(stderr, "Reading event failed\n");
			ret = -EIO;
			break;
		}
		fprintf(stdout, "GPIO EVENT at %" PRIu64 " on line %d (%d|%d) ",
			(uint64_t)event.timestamp_ns, event.offset, event.line_seqno,
			event.seqno);
		switch (event.id) {
		case GPIO_V2_LINE_EVENT_RISING_EDGE:
			fprintf(stdout, "rising edge");
			break;
		case GPIO_V2_LINE_EVENT_FALLING_EDGE:
			fprintf(stdout, "falling edge");
			break;
		default:
			fprintf(stdout, "unknown event");
		}
		fprintf(stdout, "\n");

		i++;
		if (i == loops)
			break;
	}

exit_line_close:
	if (close(lfd) == -1)
		perror("Failed to close line file");
exit_device_close:
	if (close(cfd) == -1)
		perror("Failed to close GPIO character device file");
exit_free_name:
	free(chrdev_name);
	return ret;
}

void print_usage(void)
{
	fprintf(stderr, "Usage: gpio-event-mon [options]...\n"
		"Listen to events on GPIO lines, 0->1 1->0\n"
		"  -n <name>  Listen on GPIOs on a named device (must be stated)\n"
		"  -o <n>     Offset of line to monitor (may be repeated)\n"
		"  -d         Set line as open drain\n"
		"  -s         Set line as open source\n"
		"  -r         Listen for rising edges\n"
		"  -f         Listen for falling edges\n"
		"  -w         Report the wall-clock time for events\n"
		"  -t         Report the hardware timestamp for events\n"
		"  -b <n>     Debounce the line with period n microseconds\n"
		" [-c <n>]    Do <n> loops (optional, infinite loop if not stated)\n"
		"  -?         This helptext\n"
		"\n"
		"Example:\n"
		"gpio-event-mon -n gpiochip0 -o 4 -r -f -b 10000\n"
	);
}

#define EDGE_FLAGS \
	(GPIO_V2_LINE_FLAG_EDGE_RISING | \
	 GPIO_V2_LINE_FLAG_EDGE_FALLING)

int main(int argc, char **argv)
{
	const char *device_name = NULL;
	unsigned int lines[GPIO_V2_LINES_MAX];
	unsigned int num_lines = 0;
	unsigned int loops = 0;
	struct gpio_v2_line_config config;
	int c, attr, i;
	unsigned long debounce_period_us = 0;

	memset(&config, 0, sizeof(config));
	config.flags = GPIO_V2_LINE_FLAG_INPUT;
	while ((c = getopt(argc, argv, "c:n:o:b:dsrfwt?")) != -1) {
		switch (c) {
		case 'c':
			loops = strtoul(optarg, NULL, 10);
			break;
		case 'n':
			device_name = optarg;
			break;
		case 'o':
			if (num_lines >= GPIO_V2_LINES_MAX) {
				print_usage();
				return -1;
			}
			lines[num_lines] = strtoul(optarg, NULL, 10);
			num_lines++;
			break;
		case 'b':
			debounce_period_us = strtoul(optarg, NULL, 10);
			break;
		case 'd':
			config.flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
			break;
		case 's':
			config.flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
			break;
		case 'r':
			config.flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
			break;
		case 'f':
			config.flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
			break;
		case 'w':
			config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
			break;
		case 't':
			config.flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
			break;
		case '?':
			print_usage();
			return -1;
		}
	}

	if (debounce_period_us) {
		attr = config.num_attrs;
		config.num_attrs++;
		for (i = 0; i < num_lines; i++)
			gpiotools_set_bit(&config.attrs[attr].mask, i);
		config.attrs[attr].attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
		config.attrs[attr].attr.debounce_period_us = debounce_period_us;
	}

	if (!device_name || num_lines == 0) {
		print_usage();
		return -1;
	}
	if (!(config.flags & EDGE_FLAGS)) {
		printf("No flags specified, listening on both rising and "
		       "falling edges\n");
		config.flags |= EDGE_FLAGS;
	}
	return monitor_device(device_name, lines, num_lines, &config, loops);
}