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