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