1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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