xref: /freebsd/tools/test/gpioevents/gpioevents.c (revision 369d70ec9282cccf131ab0400b05dad13ba8116d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2018 Christian Kramer
5  * Copyright (c) 2020 Ian Lepore <ian@FreeBSD.org>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  *
30  * make LDFLAGS+=-lgpio gpioevents
31  */
32 
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <limits.h>
37 #include <fcntl.h>
38 #include <unistd.h>
39 #include <signal.h>
40 #include <aio.h>
41 #include <string.h>
42 #include <stdbool.h>
43 #include <errno.h>
44 #include <err.h>
45 
46 #include <sys/endian.h>
47 #include <sys/event.h>
48 #include <sys/poll.h>
49 #include <sys/select.h>
50 #include <sys/time.h>
51 
52 #include <libgpio.h>
53 
54 static bool be_verbose = false;
55 static int report_format = GPIO_EVENT_REPORT_DETAIL;
56 static struct timespec utc_offset;
57 
58 static volatile sig_atomic_t sigio = 0;
59 
60 static void
61 sigio_handler(int sig __unused){
62 	sigio = 1;
63 }
64 
65 static void
66 usage()
67 {
68 	fprintf(stderr, "usage: %s [-f ctldev] [-m method] [-s] [-n] [-S] [-u]"
69 	    "[-t timeout] [-d delay-usec] pin intr-config [pin intr-config ...]\n\n",
70 	    getprogname());
71 	fprintf(stderr, "  -d  delay before each call to read/poll/select/etc\n");
72 	fprintf(stderr, "  -n  Non-blocking IO\n");
73 	fprintf(stderr, "  -s  Single-shot (else loop continuously)\n");
74 	fprintf(stderr, "  -S  Report summary data (else report each event)\n");
75 	fprintf(stderr, "  -u  Show timestamps as UTC (else monotonic time)\n");
76 	fprintf(stderr, "\n");
77 	fprintf(stderr, "Possible options for method:\n\n");
78 	fprintf(stderr, "  r\tread (default)\n");
79 	fprintf(stderr, "  p\tpoll\n");
80 	fprintf(stderr, "  s\tselect\n");
81 	fprintf(stderr, "  k\tkqueue\n");
82 	fprintf(stderr, "  a\taio_read (needs sysctl vfs.aio.enable_unsafe=1)\n");
83 	fprintf(stderr, "  i\tsignal-driven I/O\n\n");
84 	fprintf(stderr, "Possible options for intr-config:\n\n");
85 	fprintf(stderr, "  no\t no interrupt\n");
86 	fprintf(stderr, "  er\t edge rising\n");
87 	fprintf(stderr, "  ef\t edge falling\n");
88 	fprintf(stderr, "  eb\t edge both\n");
89 }
90 
91 static void
92 verbose(const char *fmt, ...)
93 {
94 	va_list args;
95 
96 	if (!be_verbose)
97 		return;
98 
99 	va_start(args, fmt);
100 	vprintf(fmt, args);
101 	va_end(args);
102 }
103 
104 static const char*
105 poll_event_to_str(short event)
106 {
107 	switch (event) {
108 	case POLLIN:
109 		return "POLLIN";
110 	case POLLPRI:
111 		return "POLLPRI:";
112 	case POLLOUT:
113 		return "POLLOUT:";
114 	case POLLRDNORM:
115 		return "POLLRDNORM";
116 	case POLLRDBAND:
117 		return "POLLRDBAND";
118 	case POLLWRBAND:
119 		return "POLLWRBAND";
120 	case POLLINIGNEOF:
121 		return "POLLINIGNEOF";
122 	case POLLERR:
123 		return "POLLERR";
124 	case POLLHUP:
125 		return "POLLHUP";
126 	case POLLNVAL:
127 		return "POLLNVAL";
128 	default:
129 		return "unknown event";
130 	}
131 }
132 
133 static void
134 print_poll_events(short event)
135 {
136 	short curr_event = 0;
137 	bool first = true;
138 
139 	for (size_t i = 0; i <= sizeof(short) * CHAR_BIT - 1; i++) {
140 		curr_event = 1 << i;
141 		if ((event & curr_event) == 0)
142 			continue;
143 		if (!first) {
144 			printf(" | ");
145 		} else {
146 			first = false;
147 		}
148 		printf("%s", poll_event_to_str(curr_event));
149 	}
150 }
151 
152 static void
153 calc_utc_offset()
154 {
155 	struct timespec monotime, utctime;
156 
157 	clock_gettime(CLOCK_MONOTONIC, &monotime);
158 	clock_gettime(CLOCK_REALTIME, &utctime);
159 	timespecsub(&utctime, &monotime, &utc_offset);
160 }
161 
162 static void
163 print_timestamp(const char *str, sbintime_t timestamp)
164 {
165 	struct timespec ts;
166 	char timebuf[32];
167 
168 	ts = sbttots(timestamp);
169 
170 	if (!timespecisset(&utc_offset)) {
171 		printf("%s %jd.%09ld ", str, (intmax_t)ts.tv_sec, ts.tv_nsec);
172 	} else {
173                 timespecadd(&utc_offset, &ts, &ts);
174 		strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S",
175 		    gmtime(&ts.tv_sec));
176 		printf("%s %s.%09ld ", str, timebuf, ts.tv_nsec);
177 	}
178 }
179 
180 static void
181 print_event_detail(const struct gpio_event_detail *det)
182 {
183 	print_timestamp("time", det->gp_time);
184 	printf("pin %hu state %u\n", det->gp_pin, det->gp_pinstate);
185 }
186 
187 static void
188 print_event_summary(const struct gpio_event_summary *sum)
189 {
190 	print_timestamp("first_time", sum->gp_first_time);
191 	print_timestamp("last_time", sum->gp_last_time);
192 	printf("pin %hu count %hu first state %u last state %u\n",
193 	    sum->gp_pin, sum->gp_count,
194 	    sum->gp_first_state, sum->gp_last_state);
195 }
196 
197 static void
198 print_gpio_event(const void *buf)
199 {
200 	if (report_format == GPIO_EVENT_REPORT_DETAIL)
201 		print_event_detail((const struct gpio_event_detail *)buf);
202 	else
203 		print_event_summary((const struct gpio_event_summary *)buf);
204 }
205 
206 static void
207 run_read(bool loop, int handle, const char *file, u_int delayus)
208 {
209 	const size_t numrecs = 64;
210 	union {
211 		const struct gpio_event_summary sum[numrecs];
212 		const struct gpio_event_detail  det[numrecs];
213 		uint8_t                         data[1];
214 	} buffer;
215 	ssize_t reccount, recsize, res;
216 
217 	if (report_format == GPIO_EVENT_REPORT_DETAIL)
218 		recsize = sizeof(struct gpio_event_detail);
219 	else
220 		recsize = sizeof(struct gpio_event_summary);
221 
222 	do {
223 		if (delayus != 0) {
224 			verbose("sleep %f seconds before read()\n",
225 			    delayus / 1000000.0);
226 			usleep(delayus);
227 		}
228 		verbose("read into %zd byte buffer\n", sizeof(buffer));
229 		res = read(handle, buffer.data, sizeof(buffer));
230 		if (res < 0)
231 			err(EXIT_FAILURE, "Cannot read from %s", file);
232 
233 		if ((res % recsize) != 0) {
234 			fprintf(stderr, "%s: read() %zd bytes from %s; "
235 			    "expected a multiple of %zu\n",
236 			    getprogname(), res, file, recsize);
237 		} else {
238 			reccount = res / recsize;
239 			verbose("read returned %zd bytes; %zd events\n", res,
240 			    reccount);
241 			for (ssize_t i = 0; i < reccount; ++i) {
242 				if (report_format == GPIO_EVENT_REPORT_DETAIL)
243 					print_event_detail(&buffer.det[i]);
244 				else
245 					print_event_summary(&buffer.sum[i]);
246 			}
247 		}
248 	} while (loop);
249 }
250 
251 static void
252 run_poll(bool loop, int handle, const char *file, int timeout, u_int delayus)
253 {
254 	struct pollfd fds;
255 	int res;
256 
257         fds.fd = handle;
258         fds.events = POLLIN | POLLRDNORM;
259         fds.revents = 0;
260 
261 	do {
262 		if (delayus != 0) {
263 			verbose("sleep %f seconds before poll()\n",
264 			    delayus / 1000000.0);
265 			usleep(delayus);
266 		}
267 		res = poll(&fds, 1, timeout);
268 		if (res < 0) {
269 			err(EXIT_FAILURE, "Cannot poll() %s", file);
270 		} else if (res == 0) {
271 			printf("%s: poll() timed out on %s\n", getprogname(),
272 			    file);
273 		} else {
274 			printf("%s: poll() returned %i (revents: ",
275 			    getprogname(), res);
276 			print_poll_events(fds.revents);
277 			printf(") on %s\n", file);
278 			if (fds.revents & (POLLHUP | POLLERR)) {
279 				err(EXIT_FAILURE, "Recieved POLLHUP or POLLERR "
280 				    "on %s", file);
281 			}
282 			run_read(false, handle, file, 0);
283 		}
284 	} while (loop);
285 }
286 
287 static void
288 run_select(bool loop, int handle, const char *file, int timeout, u_int delayus)
289 {
290 	fd_set readfds;
291 	struct timeval tv;
292 	struct timeval *tv_ptr;
293 	int res;
294 
295 	FD_ZERO(&readfds);
296 	FD_SET(handle, &readfds);
297 	if (timeout != INFTIM) {
298 		tv.tv_sec = timeout / 1000;
299 		tv.tv_usec = (timeout % 1000) * 1000;
300 		tv_ptr = &tv;
301 	} else {
302 		tv_ptr = NULL;
303 	}
304 
305 	do {
306 		if (delayus != 0) {
307 			verbose("sleep %f seconds before select()\n",
308 			    delayus / 1000000.0);
309 			usleep(delayus);
310 		}
311 		res = select(FD_SETSIZE, &readfds, NULL, NULL, tv_ptr);
312 		if (res < 0) {
313 			err(EXIT_FAILURE, "Cannot select() %s", file);
314 		} else if (res == 0) {
315 			printf("%s: select() timed out on %s\n", getprogname(),
316 			    file);
317 		} else {
318 			printf("%s: select() returned %i on %s\n",
319 			    getprogname(), res, file);
320 			run_read(false, handle, file, 0);
321 		}
322 	} while (loop);
323 }
324 
325 static void
326 run_kqueue(bool loop, int handle, const char *file, int timeout, u_int delayus)
327 {
328 	struct kevent event[1];
329 	struct kevent tevent[1];
330 	int kq = -1;
331 	int nev = -1;
332 	struct timespec tv;
333 	struct timespec *tv_ptr;
334 
335 	if (timeout != INFTIM) {
336 		tv.tv_sec = timeout / 1000;
337 		tv.tv_nsec = (timeout % 1000) * 10000000;
338 		tv_ptr = &tv;
339 	} else {
340 		tv_ptr = NULL;
341 	}
342 
343 	kq = kqueue();
344 	if (kq == -1)
345 		err(EXIT_FAILURE, "kqueue() %s", file);
346 
347 	EV_SET(&event[0], handle, EVFILT_READ, EV_ADD, 0, 0, NULL);
348 	nev = kevent(kq, event, 1, NULL, 0, NULL);
349 	if (nev == -1)
350 		err(EXIT_FAILURE, "kevent() %s", file);
351 
352 	do {
353 		if (delayus != 0) {
354 			verbose("sleep %f seconds before kevent()\n",
355 			    delayus / 1000000.0);
356 			usleep(delayus);
357 		}
358 		nev = kevent(kq, NULL, 0, tevent, 1, tv_ptr);
359 		if (nev == -1) {
360 			err(EXIT_FAILURE, "kevent() %s", file);
361 		} else if (nev == 0) {
362 			printf("%s: kevent() timed out on %s\n", getprogname(),
363 			    file);
364 		} else {
365 			printf("%s: kevent() returned %i events (flags: %d) on "
366 			    "%s\n", getprogname(), nev, tevent[0].flags, file);
367 			if (tevent[0].flags & EV_EOF) {
368 				err(EXIT_FAILURE, "Recieved EV_EOF on %s",
369 				    file);
370 			}
371 			run_read(false, handle, file, 0);
372 		}
373 	} while (loop);
374 }
375 
376 static void
377 run_aio_read(bool loop, int handle, const char *file, u_int delayus)
378 {
379 	uint8_t buffer[1024];
380 	size_t recsize;
381 	ssize_t res;
382 	struct aiocb iocb;
383 
384 	/*
385 	 * Note that async IO to character devices is no longer allowed by
386 	 * default (since freebsd 11).  This code is still here (for now)
387 	 * because you can use sysctl vfs.aio.enable_unsafe=1 to bypass the
388 	 * prohibition and run this code.
389 	 */
390 
391 	if (report_format == GPIO_EVENT_REPORT_DETAIL)
392 		recsize = sizeof(struct gpio_event_detail);
393 	else
394 		recsize = sizeof(struct gpio_event_summary);
395 
396 	bzero(&iocb, sizeof(iocb));
397 
398 	iocb.aio_fildes = handle;
399 	iocb.aio_nbytes = sizeof(buffer);
400 	iocb.aio_offset = 0;
401 	iocb.aio_buf = buffer;
402 
403 	do {
404 		if (delayus != 0) {
405 			verbose("sleep %f seconds before aio_read()\n",
406 			    delayus / 1000000.0);
407 			usleep(delayus);
408 		}
409 		res = aio_read(&iocb);
410 		if (res < 0)
411 			err(EXIT_FAILURE, "Cannot aio_read from %s", file);
412 		do {
413 			res = aio_error(&iocb);
414 		} while (res == EINPROGRESS);
415 		if (res < 0)
416 			err(EXIT_FAILURE, "aio_error on %s", file);
417 		res = aio_return(&iocb);
418 		if (res < 0)
419 			err(EXIT_FAILURE, "aio_return on %s", file);
420 		if ((res % recsize) != 0) {
421 			fprintf(stderr, "%s: aio_read() %zd bytes from %s; "
422 			    "expected a multiple of %zu\n",
423 			    getprogname(), res, file, recsize);
424 		} else {
425 			for (ssize_t i = 0; i < res; i += recsize)
426 				print_gpio_event(&buffer[i]);
427 		}
428 	} while (loop);
429 }
430 
431 
432 static void
433 run_sigio(bool loop, int handle, const char *file)
434 {
435 	int res;
436 	struct sigaction sigact;
437 	int flags;
438 	int pid;
439 
440 	bzero(&sigact, sizeof(sigact));
441 	sigact.sa_handler = sigio_handler;
442 	if (sigaction(SIGIO, &sigact, NULL) < 0)
443 		err(EXIT_FAILURE, "cannot set SIGIO handler on %s", file);
444 	flags = fcntl(handle, F_GETFL);
445 	flags |= O_ASYNC;
446 	res = fcntl(handle, F_SETFL, flags);
447 	if (res < 0)
448 		err(EXIT_FAILURE, "fcntl(F_SETFL) on %s", file);
449 	pid = getpid();
450 	res = fcntl(handle, F_SETOWN, pid);
451 	if (res < 0)
452 		err(EXIT_FAILURE, "fnctl(F_SETOWN) on %s", file);
453 
454 	do {
455 		if (sigio == 1) {
456 			sigio = 0;
457 			printf("%s: recieved SIGIO on %s\n", getprogname(),
458 			    file);
459 			run_read(false, handle, file, 0);
460 		}
461 		pause();
462 	} while (loop);
463 }
464 
465 int
466 main(int argc, char *argv[])
467 {
468 	int ch;
469 	const char *file = "/dev/gpioc0";
470 	char method = 'r';
471 	bool loop = true;
472 	bool nonblock = false;
473 	u_int delayus = 0;
474 	int flags;
475 	int timeout = INFTIM;
476 	int handle;
477 	int res;
478 	gpio_config_t pin_config;
479 
480 	while ((ch = getopt(argc, argv, "d:f:m:sSnt:uv")) != -1) {
481 		switch (ch) {
482 		case 'd':
483 			delayus = strtol(optarg, NULL, 10);
484 			if (errno != 0) {
485 				warn("Invalid delay value");
486 				usage();
487 				return EXIT_FAILURE;
488 			}
489 			break;
490 		case 'f':
491 			file = optarg;
492 			break;
493 		case 'm':
494 			method = optarg[0];
495 			break;
496 		case 's':
497 			loop = false;
498 			break;
499 		case 'S':
500 			report_format = GPIO_EVENT_REPORT_SUMMARY;
501 			break;
502 		case 'n':
503 			nonblock= true;
504 			break;
505 		case 't':
506 			errno = 0;
507 			timeout = strtol(optarg, NULL, 10);
508 			if (errno != 0) {
509 				warn("Invalid timeout value");
510 				usage();
511 				return EXIT_FAILURE;
512 			}
513 			break;
514 		case 'u':
515 			calc_utc_offset();
516 			break;
517 		case 'v':
518 			be_verbose = true;
519 			break;
520 		default:
521 			usage();
522 			return EXIT_FAILURE;
523 		}
524 	}
525 	argv += optind;
526 	argc -= optind;
527 
528 	if (argc == 0) {
529 		fprintf(stderr, "%s: No pin number specified.\n",
530 		    getprogname());
531 		usage();
532 		return EXIT_FAILURE;
533 	}
534 
535 	if (argc == 1) {
536 		fprintf(stderr, "%s: No trigger type specified.\n",
537 		    getprogname());
538 		usage();
539 		return EXIT_FAILURE;
540 	}
541 
542 	if (argc % 2 == 1) {
543 		fprintf(stderr, "%s: Invalid number of pin intr-conf pairs.\n",
544 		    getprogname());
545 		usage();
546 		return EXIT_FAILURE;
547 	}
548 
549 	handle = gpio_open_device(file);
550 	if (handle == GPIO_INVALID_HANDLE)
551 		err(EXIT_FAILURE, "Cannot open %s", file);
552 
553 	if (report_format == GPIO_EVENT_REPORT_SUMMARY) {
554 		struct gpio_event_config cfg =
555 		    {GPIO_EVENT_REPORT_SUMMARY, 0};
556 
557 		res = ioctl(handle, GPIOCONFIGEVENTS, &cfg);
558 		if (res < 0)
559 			err(EXIT_FAILURE, "GPIOCONFIGEVENTS failed on %s", file);
560 	}
561 
562 	if (nonblock == true) {
563 		flags = fcntl(handle, F_GETFL);
564 		flags |= O_NONBLOCK;
565 		res = fcntl(handle, F_SETFL, flags);
566 		if (res < 0)
567 			err(EXIT_FAILURE, "cannot set O_NONBLOCK on %s", file);
568 	}
569 
570 	for (int i = 0; i <= argc - 2; i += 2) {
571 
572 		errno = 0;
573 		pin_config.g_pin = strtol(argv[i], NULL, 10);
574 		if (errno != 0) {
575 			warn("Invalid pin number");
576 			usage();
577 			return EXIT_FAILURE;
578 		}
579 
580 		if (strnlen(argv[i + 1], 2) < 2) {
581 			fprintf(stderr, "%s: Invalid trigger type (argument "
582 			    "too short).\n", getprogname());
583 			usage();
584 			return EXIT_FAILURE;
585 		}
586 
587 		switch((argv[i + 1][0] << 8) + argv[i + 1][1]) {
588 		case ('n' << 8) + 'o':
589 			pin_config.g_flags = GPIO_INTR_NONE;
590 			break;
591 		case ('e' << 8) + 'r':
592 			pin_config.g_flags = GPIO_INTR_EDGE_RISING;
593 			break;
594 		case ('e' << 8) + 'f':
595 			pin_config.g_flags = GPIO_INTR_EDGE_FALLING;
596 			break;
597 		case ('e' << 8) + 'b':
598 			pin_config.g_flags = GPIO_INTR_EDGE_BOTH;
599 			break;
600 		default:
601 			fprintf(stderr, "%s: Invalid trigger type.\n",
602 			    getprogname());
603 			usage();
604 			return EXIT_FAILURE;
605 		}
606 
607 		pin_config.g_flags |= GPIO_PIN_INPUT | GPIO_PIN_PULLUP;
608 
609 		res = gpio_pin_set_flags(handle, &pin_config);
610 		if (res < 0)
611 			err(EXIT_FAILURE, "configuration of pin %d on %s "
612 			    "failed (flags=%d)", pin_config.g_pin, file,
613 			    pin_config.g_flags);
614 	}
615 
616 	switch (method) {
617 	case 'r':
618 		run_read(loop, handle, file, delayus);
619 		break;
620 	case 'p':
621 		run_poll(loop, handle, file, timeout, delayus);
622 		break;
623 	case 's':
624 		run_select(loop, handle, file, timeout, delayus);
625 		break;
626 	case 'k':
627 		run_kqueue(loop, handle, file, timeout, delayus);
628 		break;
629 	case 'a':
630 		run_aio_read(loop, handle, file, delayus);
631 		break;
632 	case 'i':
633 		run_sigio(loop, handle, file);
634 		break;
635 	default:
636 		fprintf(stderr, "%s: Unknown method.\n", getprogname());
637 		usage();
638 		return EXIT_FAILURE;
639 	}
640 
641 	return EXIT_SUCCESS;
642 }
643