Path: blob/main/tools/test/gpioevents/gpioevents.c
39507 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2018 Christian Kramer4* Copyright (c) 2020 Ian Lepore <[email protected]>5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND16* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE17* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE18* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE19* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL20* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS21* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)22* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT23* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF25* SUCH DAMAGE.26*27* make LDFLAGS+=-lgpio gpioevents28*/2930#include <stdarg.h>31#include <stdio.h>32#include <stdlib.h>33#include <limits.h>34#include <fcntl.h>35#include <unistd.h>36#include <signal.h>37#include <aio.h>38#include <string.h>39#include <stdbool.h>40#include <errno.h>41#include <err.h>4243#include <sys/endian.h>44#include <sys/event.h>45#include <sys/poll.h>46#include <sys/select.h>47#include <sys/time.h>4849#include <libgpio.h>5051static bool be_verbose = false;52static int report_format = GPIO_EVENT_REPORT_DETAIL;53static struct timespec utc_offset;5455static volatile sig_atomic_t sigio = 0;5657static void58sigio_handler(int sig __unused){59sigio = 1;60}6162static void63usage(void)64{65fprintf(stderr, "usage: %s [-f ctldev] [-m method] [-s] [-n] [-S] [-u]"66"[-t timeout] [-d delay-usec] pin intr-config pin-mode [pin intr-config pin-mode ...]\n\n",67getprogname());68fprintf(stderr, " -d delay before each call to read/poll/select/etc\n");69fprintf(stderr, " -n Non-blocking IO\n");70fprintf(stderr, " -s Single-shot (else loop continuously)\n");71fprintf(stderr, " -S Report summary data (else report each event)\n");72fprintf(stderr, " -u Show timestamps as UTC (else monotonic time)\n");73fprintf(stderr, "\n");74fprintf(stderr, "Possible options for method:\n\n");75fprintf(stderr, " r\tread (default)\n");76fprintf(stderr, " p\tpoll\n");77fprintf(stderr, " s\tselect\n");78fprintf(stderr, " k\tkqueue\n");79fprintf(stderr, " a\taio_read (needs sysctl vfs.aio.enable_unsafe=1)\n");80fprintf(stderr, " i\tsignal-driven I/O\n\n");81fprintf(stderr, "Possible options for intr-config:\n\n");82fprintf(stderr, " no\t no interrupt\n");83fprintf(stderr, " er\t edge rising\n");84fprintf(stderr, " ef\t edge falling\n");85fprintf(stderr, " eb\t edge both\n\n");86fprintf(stderr, "Possible options for pin-mode:\n\n");87fprintf(stderr, " ft\t floating\n");88fprintf(stderr, " pd\t pull-down\n");89fprintf(stderr, " pu\t pull-up\n");90}9192static void93verbose(const char *fmt, ...)94{95va_list args;9697if (!be_verbose)98return;99100va_start(args, fmt);101vprintf(fmt, args);102va_end(args);103}104105static const char*106poll_event_to_str(short event)107{108switch (event) {109case POLLIN:110return "POLLIN";111case POLLPRI:112return "POLLPRI:";113case POLLOUT:114return "POLLOUT:";115case POLLRDNORM:116return "POLLRDNORM";117case POLLRDBAND:118return "POLLRDBAND";119case POLLWRBAND:120return "POLLWRBAND";121case POLLINIGNEOF:122return "POLLINIGNEOF";123case POLLERR:124return "POLLERR";125case POLLHUP:126return "POLLHUP";127case POLLNVAL:128return "POLLNVAL";129default:130return "unknown event";131}132}133134static void135print_poll_events(short event)136{137short curr_event = 0;138bool first = true;139140for (size_t i = 0; i <= sizeof(short) * CHAR_BIT - 1; i++) {141curr_event = 1 << i;142if ((event & curr_event) == 0)143continue;144if (!first) {145printf(" | ");146} else {147first = false;148}149printf("%s", poll_event_to_str(curr_event));150}151}152153static void154calc_utc_offset(void)155{156struct timespec monotime, utctime;157158clock_gettime(CLOCK_MONOTONIC, &monotime);159clock_gettime(CLOCK_REALTIME, &utctime);160timespecsub(&utctime, &monotime, &utc_offset);161}162163static void164print_timestamp(const char *str, sbintime_t timestamp)165{166struct timespec ts;167char timebuf[32];168169ts = sbttots(timestamp);170171if (!timespecisset(&utc_offset)) {172printf("%s %jd.%09ld ", str, (intmax_t)ts.tv_sec, ts.tv_nsec);173} else {174timespecadd(&utc_offset, &ts, &ts);175strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S",176gmtime(&ts.tv_sec));177printf("%s %s.%09ld ", str, timebuf, ts.tv_nsec);178}179}180181static void182print_event_detail(const struct gpio_event_detail *det)183{184print_timestamp("time", det->gp_time);185printf("pin %hu state %u\n", det->gp_pin, det->gp_pinstate);186}187188static void189print_event_summary(const struct gpio_event_summary *sum)190{191print_timestamp("first_time", sum->gp_first_time);192print_timestamp("last_time", sum->gp_last_time);193printf("pin %hu count %hu first state %u last state %u\n",194sum->gp_pin, sum->gp_count,195sum->gp_first_state, sum->gp_last_state);196}197198static void199print_gpio_event(const void *buf)200{201if (report_format == GPIO_EVENT_REPORT_DETAIL)202print_event_detail((const struct gpio_event_detail *)buf);203else204print_event_summary((const struct gpio_event_summary *)buf);205}206207static void208run_read(bool loop, int handle, const char *file, u_int delayus)209{210const size_t numrecs = 64;211union {212const struct gpio_event_summary sum[numrecs];213const struct gpio_event_detail det[numrecs];214uint8_t data[1];215} buffer;216ssize_t reccount, recsize, res;217218if (report_format == GPIO_EVENT_REPORT_DETAIL)219recsize = sizeof(struct gpio_event_detail);220else221recsize = sizeof(struct gpio_event_summary);222223do {224if (delayus != 0) {225verbose("sleep %f seconds before read()\n",226delayus / 1000000.0);227usleep(delayus);228}229verbose("read into %zd byte buffer\n", sizeof(buffer));230res = read(handle, buffer.data, sizeof(buffer));231if (res < 0)232err(EXIT_FAILURE, "Cannot read from %s", file);233234if ((res % recsize) != 0) {235fprintf(stderr, "%s: read() %zd bytes from %s; "236"expected a multiple of %zu\n",237getprogname(), res, file, recsize);238} else {239reccount = res / recsize;240verbose("read returned %zd bytes; %zd events\n", res,241reccount);242for (ssize_t i = 0; i < reccount; ++i) {243if (report_format == GPIO_EVENT_REPORT_DETAIL)244print_event_detail(&buffer.det[i]);245else246print_event_summary(&buffer.sum[i]);247}248}249} while (loop);250}251252static void253run_poll(bool loop, int handle, const char *file, int timeout, u_int delayus)254{255struct pollfd fds;256int res;257258fds.fd = handle;259fds.events = POLLIN | POLLRDNORM;260fds.revents = 0;261262do {263if (delayus != 0) {264verbose("sleep %f seconds before poll()\n",265delayus / 1000000.0);266usleep(delayus);267}268res = poll(&fds, 1, timeout);269if (res < 0) {270err(EXIT_FAILURE, "Cannot poll() %s", file);271} else if (res == 0) {272printf("%s: poll() timed out on %s\n", getprogname(),273file);274} else {275printf("%s: poll() returned %i (revents: ",276getprogname(), res);277print_poll_events(fds.revents);278printf(") on %s\n", file);279if (fds.revents & (POLLHUP | POLLERR)) {280err(EXIT_FAILURE, "Recieved POLLHUP or POLLERR "281"on %s", file);282}283run_read(false, handle, file, 0);284}285} while (loop);286}287288static void289run_select(bool loop, int handle, const char *file, int timeout, u_int delayus)290{291fd_set readfds;292struct timeval tv;293struct timeval *tv_ptr;294int res;295296FD_ZERO(&readfds);297FD_SET(handle, &readfds);298if (timeout != INFTIM) {299tv.tv_sec = timeout / 1000;300tv.tv_usec = (timeout % 1000) * 1000;301tv_ptr = &tv;302} else {303tv_ptr = NULL;304}305306do {307if (delayus != 0) {308verbose("sleep %f seconds before select()\n",309delayus / 1000000.0);310usleep(delayus);311}312res = select(FD_SETSIZE, &readfds, NULL, NULL, tv_ptr);313if (res < 0) {314err(EXIT_FAILURE, "Cannot select() %s", file);315} else if (res == 0) {316printf("%s: select() timed out on %s\n", getprogname(),317file);318} else {319printf("%s: select() returned %i on %s\n",320getprogname(), res, file);321run_read(false, handle, file, 0);322}323} while (loop);324}325326static void327run_kqueue(bool loop, int handle, const char *file, int timeout, u_int delayus)328{329struct kevent event[1];330struct kevent tevent[1];331int kq = -1;332int nev = -1;333struct timespec tv;334struct timespec *tv_ptr;335336if (timeout != INFTIM) {337tv.tv_sec = timeout / 1000;338tv.tv_nsec = (timeout % 1000) * 10000000;339tv_ptr = &tv;340} else {341tv_ptr = NULL;342}343344kq = kqueue();345if (kq == -1)346err(EXIT_FAILURE, "kqueue() %s", file);347348EV_SET(&event[0], handle, EVFILT_READ, EV_ADD, 0, 0, NULL);349nev = kevent(kq, event, 1, NULL, 0, NULL);350if (nev == -1)351err(EXIT_FAILURE, "kevent() %s", file);352353do {354if (delayus != 0) {355verbose("sleep %f seconds before kevent()\n",356delayus / 1000000.0);357usleep(delayus);358}359nev = kevent(kq, NULL, 0, tevent, 1, tv_ptr);360if (nev == -1) {361err(EXIT_FAILURE, "kevent() %s", file);362} else if (nev == 0) {363printf("%s: kevent() timed out on %s\n", getprogname(),364file);365} else {366printf("%s: kevent() returned %i events (flags: %d) on "367"%s\n", getprogname(), nev, tevent[0].flags, file);368if (tevent[0].flags & EV_EOF) {369err(EXIT_FAILURE, "Recieved EV_EOF on %s",370file);371}372run_read(false, handle, file, 0);373}374} while (loop);375}376377static void378run_aio_read(bool loop, int handle, const char *file, u_int delayus)379{380uint8_t buffer[1024];381size_t recsize;382ssize_t res;383struct aiocb iocb;384385/*386* Note that async IO to character devices is no longer allowed by387* default (since freebsd 11). This code is still here (for now)388* because you can use sysctl vfs.aio.enable_unsafe=1 to bypass the389* prohibition and run this code.390*/391392if (report_format == GPIO_EVENT_REPORT_DETAIL)393recsize = sizeof(struct gpio_event_detail);394else395recsize = sizeof(struct gpio_event_summary);396397bzero(&iocb, sizeof(iocb));398399iocb.aio_fildes = handle;400iocb.aio_nbytes = sizeof(buffer);401iocb.aio_offset = 0;402iocb.aio_buf = buffer;403404do {405if (delayus != 0) {406verbose("sleep %f seconds before aio_read()\n",407delayus / 1000000.0);408usleep(delayus);409}410res = aio_read(&iocb);411if (res < 0)412err(EXIT_FAILURE, "Cannot aio_read from %s", file);413do {414res = aio_error(&iocb);415} while (res == EINPROGRESS);416if (res < 0)417err(EXIT_FAILURE, "aio_error on %s", file);418res = aio_return(&iocb);419if (res < 0)420err(EXIT_FAILURE, "aio_return on %s", file);421if ((res % recsize) != 0) {422fprintf(stderr, "%s: aio_read() %zd bytes from %s; "423"expected a multiple of %zu\n",424getprogname(), res, file, recsize);425} else {426for (ssize_t i = 0; i < res; i += recsize)427print_gpio_event(&buffer[i]);428}429} while (loop);430}431432433static void434run_sigio(bool loop, int handle, const char *file)435{436int res;437struct sigaction sigact;438int flags;439int pid;440441bzero(&sigact, sizeof(sigact));442sigact.sa_handler = sigio_handler;443if (sigaction(SIGIO, &sigact, NULL) < 0)444err(EXIT_FAILURE, "cannot set SIGIO handler on %s", file);445flags = fcntl(handle, F_GETFL);446flags |= O_ASYNC;447res = fcntl(handle, F_SETFL, flags);448if (res < 0)449err(EXIT_FAILURE, "fcntl(F_SETFL) on %s", file);450pid = getpid();451res = fcntl(handle, F_SETOWN, pid);452if (res < 0)453err(EXIT_FAILURE, "fnctl(F_SETOWN) on %s", file);454455do {456if (sigio == 1) {457sigio = 0;458printf("%s: received SIGIO on %s\n", getprogname(),459file);460run_read(false, handle, file, 0);461}462pause();463} while (loop);464}465466int467main(int argc, char *argv[])468{469int ch;470const char *file = "/dev/gpioc0";471char method = 'r';472bool loop = true;473bool nonblock = false;474u_int delayus = 0;475int flags;476int timeout = INFTIM;477int handle;478int res;479gpio_config_t pin_config;480481while ((ch = getopt(argc, argv, "d:f:m:sSnt:uv")) != -1) {482switch (ch) {483case 'd':484delayus = strtol(optarg, NULL, 10);485if (errno != 0) {486warn("Invalid delay value");487usage();488return EXIT_FAILURE;489}490break;491case 'f':492file = optarg;493break;494case 'm':495method = optarg[0];496break;497case 's':498loop = false;499break;500case 'S':501report_format = GPIO_EVENT_REPORT_SUMMARY;502break;503case 'n':504nonblock= true;505break;506case 't':507errno = 0;508timeout = strtol(optarg, NULL, 10);509if (errno != 0) {510warn("Invalid timeout value");511usage();512return EXIT_FAILURE;513}514break;515case 'u':516calc_utc_offset();517break;518case 'v':519be_verbose = true;520break;521default:522usage();523return EXIT_FAILURE;524}525}526argv += optind;527argc -= optind;528529if (argc == 0) {530fprintf(stderr, "%s: No pin number specified.\n",531getprogname());532usage();533return EXIT_FAILURE;534}535536if (argc == 1) {537fprintf(stderr, "%s: No trigger type specified.\n",538getprogname());539usage();540return EXIT_FAILURE;541}542543if (argc == 1) {544fprintf(stderr, "%s: No trigger type specified.\n",545getprogname());546usage();547return EXIT_FAILURE;548}549550if (argc % 3 != 0) {551fprintf(stderr, "%s: Invalid number of (pin intr-conf mode) triplets.\n",552getprogname());553usage();554return EXIT_FAILURE;555}556557handle = gpio_open_device(file);558if (handle == GPIO_INVALID_HANDLE)559err(EXIT_FAILURE, "Cannot open %s", file);560561if (report_format == GPIO_EVENT_REPORT_SUMMARY) {562struct gpio_event_config cfg =563{GPIO_EVENT_REPORT_SUMMARY, 0};564565res = ioctl(handle, GPIOCONFIGEVENTS, &cfg);566if (res < 0)567err(EXIT_FAILURE, "GPIOCONFIGEVENTS failed on %s", file);568}569570if (nonblock == true) {571flags = fcntl(handle, F_GETFL);572flags |= O_NONBLOCK;573res = fcntl(handle, F_SETFL, flags);574if (res < 0)575err(EXIT_FAILURE, "cannot set O_NONBLOCK on %s", file);576}577578for (int i = 0; i <= argc - 3; i += 3) {579580errno = 0;581pin_config.g_pin = strtol(argv[i], NULL, 10);582if (errno != 0) {583warn("Invalid pin number");584usage();585return EXIT_FAILURE;586}587588if (strnlen(argv[i + 1], 2) < 2) {589fprintf(stderr, "%s: Invalid trigger type (argument "590"too short).\n", getprogname());591usage();592return EXIT_FAILURE;593}594595switch((argv[i + 1][0] << 8) + argv[i + 1][1]) {596case ('n' << 8) + 'o':597pin_config.g_flags = GPIO_INTR_NONE;598break;599case ('e' << 8) + 'r':600pin_config.g_flags = GPIO_INTR_EDGE_RISING;601break;602case ('e' << 8) + 'f':603pin_config.g_flags = GPIO_INTR_EDGE_FALLING;604break;605case ('e' << 8) + 'b':606pin_config.g_flags = GPIO_INTR_EDGE_BOTH;607break;608default:609fprintf(stderr, "%s: Invalid trigger type.\n",610getprogname());611usage();612return EXIT_FAILURE;613}614615if (strnlen(argv[i + 2], 2) < 2) {616fprintf(stderr, "%s: Invalid pin mode (argument "617"too short).\n", getprogname());618usage();619return EXIT_FAILURE;620}621622switch((argv[i + 2][0] << 8) + argv[i + 2][1]) {623case ('f' << 8) + 't':624/* no changes to pin_config */625break;626case ('p' << 8) + 'd':627pin_config.g_flags |= GPIO_PIN_PULLDOWN;628break;629case ('p' << 8) + 'u':630pin_config.g_flags |= GPIO_PIN_PULLUP;631break;632default:633fprintf(stderr, "%s: Invalid pin mode.\n",634getprogname());635usage();636return EXIT_FAILURE;637}638639pin_config.g_flags |= GPIO_PIN_INPUT;640641res = gpio_pin_set_flags(handle, &pin_config);642if (res < 0)643err(EXIT_FAILURE, "configuration of pin %d on %s "644"failed (flags=%d)", pin_config.g_pin, file,645pin_config.g_flags);646}647648switch (method) {649case 'r':650run_read(loop, handle, file, delayus);651break;652case 'p':653run_poll(loop, handle, file, timeout, delayus);654break;655case 's':656run_select(loop, handle, file, timeout, delayus);657break;658case 'k':659run_kqueue(loop, handle, file, timeout, delayus);660break;661case 'a':662run_aio_read(loop, handle, file, delayus);663break;664case 'i':665run_sigio(loop, handle, file);666break;667default:668fprintf(stderr, "%s: Unknown method.\n", getprogname());669usage();670return EXIT_FAILURE;671}672673return EXIT_SUCCESS;674}675676677