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
sigio_handler(int sig __unused)59 sigio_handler(int sig __unused){
60 sigio = 1;
61 }
62
63 static void
usage(void)64 usage(void)
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
verbose(const char * fmt,...)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*
poll_event_to_str(short event)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
print_poll_events(short event)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
calc_utc_offset(void)155 calc_utc_offset(void)
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
print_timestamp(const char * str,sbintime_t timestamp)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
print_event_detail(const struct gpio_event_detail * det)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
print_event_summary(const struct gpio_event_summary * sum)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
print_gpio_event(const void * buf)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
run_read(bool loop,int handle,const char * file,u_int delayus)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
run_poll(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_select(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_kqueue(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_aio_read(bool loop,int handle,const char * file,u_int delayus)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
run_sigio(bool loop,int handle,const char * file)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
main(int argc,char * argv[])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