diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/test/README | 1 | ||||
-rw-r--r-- | tools/test/gpioevents/Makefile | 12 | ||||
-rw-r--r-- | tools/test/gpioevents/gpioevents.c | 642 |
3 files changed, 655 insertions, 0 deletions
diff --git a/tools/test/README b/tools/test/README index 1e4aef85253eb..2bafa48a9764f 100644 --- a/tools/test/README +++ b/tools/test/README @@ -10,6 +10,7 @@ Please make a subdir per program, and add a brief description to this file. auxinfo Return information on page sizes, CPUs, and OS release date. devrandom Programs to test /dev/*random. +gpioevents Test delivery of gpio pin-change events to userland. hwpmc Automatically trigger every event in hwpmc(4). iconv Character set conversion tests. malloc A program to test and benchmark malloc(). diff --git a/tools/test/gpioevents/Makefile b/tools/test/gpioevents/Makefile new file mode 100644 index 0000000000000..89edfefa87ead --- /dev/null +++ b/tools/test/gpioevents/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG= gpioevents +SRCS= gpioevents.c + +LIBADD= gpio + +MK_MAN= no + +BINDIR= /usr/bin + +.include <bsd.prog.mk> diff --git a/tools/test/gpioevents/gpioevents.c b/tools/test/gpioevents/gpioevents.c new file mode 100644 index 0000000000000..20d18257f4a84 --- /dev/null +++ b/tools/test/gpioevents/gpioevents.c @@ -0,0 +1,642 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Christian Kramer + * Copyright (c) 2020 Ian Lepore <ian@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * make LDFLAGS+=-lgpio gpioevents + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <aio.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> +#include <err.h> + +#include <sys/endian.h> +#include <sys/event.h> +#include <sys/poll.h> +#include <sys/select.h> +#include <sys/time.h> + +#include <libgpio.h> + +static bool be_verbose = false; +static int report_format = GPIO_EVENT_REPORT_DETAIL; +static struct timespec utc_offset; + +static volatile sig_atomic_t sigio = 0; + +static void +sigio_handler(int sig __unused){ + sigio = 1; +} + +static void +usage() +{ + fprintf(stderr, "usage: %s [-f ctldev] [-m method] [-s] [-n] [-S] [-u]" + "[-t timeout] [-d delay-usec] pin intr-config [pin intr-config ...]\n\n", + getprogname()); + fprintf(stderr, " -d delay before each call to read/poll/select/etc\n"); + fprintf(stderr, " -n Non-blocking IO\n"); + fprintf(stderr, " -s Single-shot (else loop continuously)\n"); + fprintf(stderr, " -S Report summary data (else report each event)\n"); + fprintf(stderr, " -u Show timestamps as UTC (else monotonic time)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Possible options for method:\n\n"); + fprintf(stderr, " r\tread (default)\n"); + fprintf(stderr, " p\tpoll\n"); + fprintf(stderr, " s\tselect\n"); + fprintf(stderr, " k\tkqueue\n"); + fprintf(stderr, " a\taio_read (needs sysctl vfs.aio.enable_unsafe=1)\n"); + fprintf(stderr, " i\tsignal-driven I/O\n\n"); + fprintf(stderr, "Possible options for intr-config:\n\n"); + fprintf(stderr, " no\t no interrupt\n"); + fprintf(stderr, " er\t edge rising\n"); + fprintf(stderr, " ef\t edge falling\n"); + fprintf(stderr, " eb\t edge both\n"); +} + +static void +verbose(const char *fmt, ...) +{ + va_list args; + + if (!be_verbose) + return; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +static const char* +poll_event_to_str(short event) +{ + switch (event) { + case POLLIN: + return "POLLIN"; + case POLLPRI: + return "POLLPRI:"; + case POLLOUT: + return "POLLOUT:"; + case POLLRDNORM: + return "POLLRDNORM"; + case POLLRDBAND: + return "POLLRDBAND"; + case POLLWRBAND: + return "POLLWRBAND"; + case POLLINIGNEOF: + return "POLLINIGNEOF"; + case POLLERR: + return "POLLERR"; + case POLLHUP: + return "POLLHUP"; + case POLLNVAL: + return "POLLNVAL"; + default: + return "unknown event"; + } +} + +static void +print_poll_events(short event) +{ + short curr_event = 0; + bool first = true; + + for (size_t i = 0; i <= sizeof(short) * CHAR_BIT - 1; i++) { + curr_event = 1 << i; + if ((event & curr_event) == 0) + continue; + if (!first) { + printf(" | "); + } else { + first = false; + } + printf("%s", poll_event_to_str(curr_event)); + } +} + +static void +calc_utc_offset() +{ + struct timespec monotime, utctime; + + clock_gettime(CLOCK_MONOTONIC, &monotime); + clock_gettime(CLOCK_REALTIME, &utctime); + timespecsub(&utctime, &monotime, &utc_offset); +} + +static void +print_timestamp(const char *str, sbintime_t timestamp) +{ + struct timespec ts; + char timebuf[32]; + + ts = sbttots(timestamp); + + if (!timespecisset(&utc_offset)) { + printf("%s %jd.%09ld ", str, (intmax_t)ts.tv_sec, ts.tv_nsec); + } else { + timespecadd(&utc_offset, &ts, &ts); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", + gmtime(&ts.tv_sec)); + printf("%s %s.%09ld ", str, timebuf, ts.tv_nsec); + } +} + +static void +print_event_detail(const struct gpio_event_detail *det) +{ + print_timestamp("time", det->gp_time); + printf("pin %hu state %u\n", det->gp_pin, det->gp_pinstate); +} + +static void +print_event_summary(const struct gpio_event_summary *sum) +{ + print_timestamp("first_time", sum->gp_first_time); + print_timestamp("last_time", sum->gp_last_time); + printf("pin %hu count %hu first state %u last state %u\n", + sum->gp_pin, sum->gp_count, + sum->gp_first_state, sum->gp_last_state); +} + +static void +print_gpio_event(const void *buf) +{ + if (report_format == GPIO_EVENT_REPORT_DETAIL) + print_event_detail((const struct gpio_event_detail *)buf); + else + print_event_summary((const struct gpio_event_summary *)buf); +} + +static void +run_read(bool loop, int handle, const char *file, u_int delayus) +{ + const size_t numrecs = 64; + union { + const struct gpio_event_summary sum[numrecs]; + const struct gpio_event_detail det[numrecs]; + uint8_t data[1]; + } buffer; + ssize_t reccount, recsize, res; + + if (report_format == GPIO_EVENT_REPORT_DETAIL) + recsize = sizeof(struct gpio_event_detail); + else + recsize = sizeof(struct gpio_event_summary); + + do { + if (delayus != 0) { + verbose("sleep %f seconds before read()\n", + delayus / 1000000.0); + usleep(delayus); + } + verbose("read into %zd byte buffer\n", sizeof(buffer)); + res = read(handle, buffer.data, sizeof(buffer)); + if (res < 0) + err(EXIT_FAILURE, "Cannot read from %s", file); + + if ((res % recsize) != 0) { + fprintf(stderr, "%s: read() %zd bytes from %s; " + "expected a multiple of %zu\n", + getprogname(), res, file, recsize); + } else { + reccount = res / recsize; + verbose("read returned %zd bytes; %zd events\n", res, + reccount); + for (ssize_t i = 0; i < reccount; ++i) { + if (report_format == GPIO_EVENT_REPORT_DETAIL) + print_event_detail(&buffer.det[i]); + else + print_event_summary(&buffer.sum[i]); + } + } + } while (loop); +} + +static void +run_poll(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + struct pollfd fds; + int res; + + fds.fd = handle; + fds.events = POLLIN | POLLRDNORM; + fds.revents = 0; + + do { + if (delayus != 0) { + verbose("sleep %f seconds before poll()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = poll(&fds, 1, timeout); + if (res < 0) { + err(EXIT_FAILURE, "Cannot poll() %s", file); + } else if (res == 0) { + printf("%s: poll() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: poll() returned %i (revents: ", + getprogname(), res); + print_poll_events(fds.revents); + printf(") on %s\n", file); + if (fds.revents & (POLLHUP | POLLERR)) { + err(EXIT_FAILURE, "Recieved POLLHUP or POLLERR " + "on %s", file); + } + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_select(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + fd_set readfds; + struct timeval tv; + struct timeval *tv_ptr; + int res; + + FD_ZERO(&readfds); + FD_SET(handle, &readfds); + if (timeout != INFTIM) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + tv_ptr = &tv; + } else { + tv_ptr = NULL; + } + + do { + if (delayus != 0) { + verbose("sleep %f seconds before select()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = select(FD_SETSIZE, &readfds, NULL, NULL, tv_ptr); + if (res < 0) { + err(EXIT_FAILURE, "Cannot select() %s", file); + } else if (res == 0) { + printf("%s: select() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: select() returned %i on %s\n", + getprogname(), res, file); + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_kqueue(bool loop, int handle, const char *file, int timeout, u_int delayus) +{ + struct kevent event[1]; + struct kevent tevent[1]; + int kq = -1; + int nev = -1; + struct timespec tv; + struct timespec *tv_ptr; + + if (timeout != INFTIM) { + tv.tv_sec = timeout / 1000; + tv.tv_nsec = (timeout % 1000) * 10000000; + tv_ptr = &tv; + } else { + tv_ptr = NULL; + } + + kq = kqueue(); + if (kq == -1) + err(EXIT_FAILURE, "kqueue() %s", file); + + EV_SET(&event[0], handle, EVFILT_READ, EV_ADD, 0, 0, NULL); + nev = kevent(kq, event, 1, NULL, 0, NULL); + if (nev == -1) + err(EXIT_FAILURE, "kevent() %s", file); + + do { + if (delayus != 0) { + verbose("sleep %f seconds before kevent()\n", + delayus / 1000000.0); + usleep(delayus); + } + nev = kevent(kq, NULL, 0, tevent, 1, tv_ptr); + if (nev == -1) { + err(EXIT_FAILURE, "kevent() %s", file); + } else if (nev == 0) { + printf("%s: kevent() timed out on %s\n", getprogname(), + file); + } else { + printf("%s: kevent() returned %i events (flags: %d) on " + "%s\n", getprogname(), nev, tevent[0].flags, file); + if (tevent[0].flags & EV_EOF) { + err(EXIT_FAILURE, "Recieved EV_EOF on %s", + file); + } + run_read(false, handle, file, 0); + } + } while (loop); +} + +static void +run_aio_read(bool loop, int handle, const char *file, u_int delayus) +{ + uint8_t buffer[1024]; + size_t recsize; + ssize_t res; + struct aiocb iocb; + + /* + * Note that async IO to character devices is no longer allowed by + * default (since freebsd 11). This code is still here (for now) + * because you can use sysctl vfs.aio.enable_unsafe=1 to bypass the + * prohibition and run this code. + */ + + if (report_format == GPIO_EVENT_REPORT_DETAIL) + recsize = sizeof(struct gpio_event_detail); + else + recsize = sizeof(struct gpio_event_summary); + + bzero(&iocb, sizeof(iocb)); + + iocb.aio_fildes = handle; + iocb.aio_nbytes = sizeof(buffer); + iocb.aio_offset = 0; + iocb.aio_buf = buffer; + + do { + if (delayus != 0) { + verbose("sleep %f seconds before aio_read()\n", + delayus / 1000000.0); + usleep(delayus); + } + res = aio_read(&iocb); + if (res < 0) + err(EXIT_FAILURE, "Cannot aio_read from %s", file); + do { + res = aio_error(&iocb); + } while (res == EINPROGRESS); + if (res < 0) + err(EXIT_FAILURE, "aio_error on %s", file); + res = aio_return(&iocb); + if (res < 0) + err(EXIT_FAILURE, "aio_return on %s", file); + if ((res % recsize) != 0) { + fprintf(stderr, "%s: aio_read() %zd bytes from %s; " + "expected a multiple of %zu\n", + getprogname(), res, file, recsize); + } else { + for (ssize_t i = 0; i < res; i += recsize) + print_gpio_event(&buffer[i]); + } + } while (loop); +} + + +static void +run_sigio(bool loop, int handle, const char *file) +{ + int res; + struct sigaction sigact; + int flags; + int pid; + + bzero(&sigact, sizeof(sigact)); + sigact.sa_handler = sigio_handler; + if (sigaction(SIGIO, &sigact, NULL) < 0) + err(EXIT_FAILURE, "cannot set SIGIO handler on %s", file); + flags = fcntl(handle, F_GETFL); + flags |= O_ASYNC; + res = fcntl(handle, F_SETFL, flags); + if (res < 0) + err(EXIT_FAILURE, "fcntl(F_SETFL) on %s", file); + pid = getpid(); + res = fcntl(handle, F_SETOWN, pid); + if (res < 0) + err(EXIT_FAILURE, "fnctl(F_SETOWN) on %s", file); + + do { + if (sigio == 1) { + sigio = 0; + printf("%s: recieved SIGIO on %s\n", getprogname(), + file); + run_read(false, handle, file, 0); + } + pause(); + } while (loop); +} + +int +main(int argc, char *argv[]) +{ + int ch; + const char *file = "/dev/gpioc0"; + char method = 'r'; + bool loop = true; + bool nonblock = false; + u_int delayus = 0; + int flags; + int timeout = INFTIM; + int handle; + int res; + gpio_config_t pin_config; + + while ((ch = getopt(argc, argv, "d:f:m:sSnt:uv")) != -1) { + switch (ch) { + case 'd': + delayus = strtol(optarg, NULL, 10); + if (errno != 0) { + warn("Invalid delay value"); + usage(); + return EXIT_FAILURE; + } + break; + case 'f': + file = optarg; + break; + case 'm': + method = optarg[0]; + break; + case 's': + loop = false; + break; + case 'S': + report_format = GPIO_EVENT_REPORT_SUMMARY; + break; + case 'n': + nonblock= true; + break; + case 't': + errno = 0; + timeout = strtol(optarg, NULL, 10); + if (errno != 0) { + warn("Invalid timeout value"); + usage(); + return EXIT_FAILURE; + } + break; + case 'u': + calc_utc_offset(); + break; + case 'v': + be_verbose = true; + break; + default: + usage(); + return EXIT_FAILURE; + } + } + argv += optind; + argc -= optind; + + if (argc == 0) { + fprintf(stderr, "%s: No pin number specified.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + if (argc == 1) { + fprintf(stderr, "%s: No trigger type specified.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + if (argc % 2 == 1) { + fprintf(stderr, "%s: Invalid number of pin intr-conf pairs.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + handle = gpio_open_device(file); + if (handle == GPIO_INVALID_HANDLE) + err(EXIT_FAILURE, "Cannot open %s", file); + + if (report_format == GPIO_EVENT_REPORT_SUMMARY) { + struct gpio_event_config cfg = + {GPIO_EVENT_REPORT_SUMMARY, 0}; + + res = ioctl(handle, GPIOCONFIGEVENTS, &cfg); + if (res < 0) + err(EXIT_FAILURE, "GPIOCONFIGEVENTS failed on %s", file); + } + + if (nonblock == true) { + flags = fcntl(handle, F_GETFL); + flags |= O_NONBLOCK; + res = fcntl(handle, F_SETFL, flags); + if (res < 0) + err(EXIT_FAILURE, "cannot set O_NONBLOCK on %s", file); + } + + for (int i = 0; i <= argc - 2; i += 2) { + + errno = 0; + pin_config.g_pin = strtol(argv[i], NULL, 10); + if (errno != 0) { + warn("Invalid pin number"); + usage(); + return EXIT_FAILURE; + } + + if (strnlen(argv[i + 1], 2) < 2) { + fprintf(stderr, "%s: Invalid trigger type (argument " + "too short).\n", getprogname()); + usage(); + return EXIT_FAILURE; + } + + switch((argv[i + 1][0] << 8) + argv[i + 1][1]) { + case ('n' << 8) + 'o': + pin_config.g_flags = GPIO_INTR_NONE; + break; + case ('e' << 8) + 'r': + pin_config.g_flags = GPIO_INTR_EDGE_RISING; + break; + case ('e' << 8) + 'f': + pin_config.g_flags = GPIO_INTR_EDGE_FALLING; + break; + case ('e' << 8) + 'b': + pin_config.g_flags = GPIO_INTR_EDGE_BOTH; + break; + default: + fprintf(stderr, "%s: Invalid trigger type.\n", + getprogname()); + usage(); + return EXIT_FAILURE; + } + + pin_config.g_flags |= GPIO_PIN_INPUT | GPIO_PIN_PULLUP; + + res = gpio_pin_set_flags(handle, &pin_config); + if (res < 0) + err(EXIT_FAILURE, "configuration of pin %d on %s " + "failed (flags=%d)", pin_config.g_pin, file, + pin_config.g_flags); + } + + switch (method) { + case 'r': + run_read(loop, handle, file, delayus); + break; + case 'p': + run_poll(loop, handle, file, timeout, delayus); + break; + case 's': + run_select(loop, handle, file, timeout, delayus); + break; + case 'k': + run_kqueue(loop, handle, file, timeout, delayus); + break; + case 'a': + run_aio_read(loop, handle, file, delayus); + break; + case 'i': + run_sigio(loop, handle, file); + break; + default: + fprintf(stderr, "%s: Unknown method.\n", getprogname()); + usage(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} |