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(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 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(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 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