1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2018 Christian Kramer
5 * Copyright (c) 2020 Ian Lepore <[email protected]>
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
sigio_handler(int sig __unused)61 sigio_handler(int sig __unused){
62 sigio = 1;
63 }
64
65 static void
usage()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
verbose(const char * fmt,...)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*
poll_event_to_str(short event)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
print_poll_events(short event)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
calc_utc_offset()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
print_timestamp(const char * str,sbintime_t timestamp)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
print_event_detail(const struct gpio_event_detail * det)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
print_event_summary(const struct gpio_event_summary * sum)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
print_gpio_event(const void * buf)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
run_read(bool loop,int handle,const char * file,u_int delayus)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
run_poll(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_select(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_kqueue(bool loop,int handle,const char * file,int timeout,u_int delayus)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
run_aio_read(bool loop,int handle,const char * file,u_int delayus)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
run_sigio(bool loop,int handle,const char * file)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
main(int argc,char * argv[])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