summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorIan Lepore <ian@FreeBSD.org>2020-12-12 18:34:15 +0000
committerIan Lepore <ian@FreeBSD.org>2020-12-12 18:34:15 +0000
commitff3468ac94597efdcbc56f372528dfc98b114dac (patch)
tree1ce558fbc24334dc686318ca35a1e9dfc244e115 /tools
parent0d2dcf2166f77fab2af39d9e89bff157ffc0ca5b (diff)
downloadsrc-test-ff3468ac94597efdcbc56f372528dfc98b114dac.tar.gz
src-test-ff3468ac94597efdcbc56f372528dfc98b114dac.zip
Provide userland notification of gpio pin changes ("userland gpio interrupts").
This is an import of the Google Summer of Code 2018 project completed by Christian Kramer (and, sadly, ignored by us for two years now). The goals stated for that project were: FreeBSD already has support for interrupts implemented in the GPIO controller drivers of several SoCs, but there are no interfaces to take advantage of them out of user space yet. The goal of this work is to implement such an interface by providing descriptors which integrate with the common I/O system calls and multiplexing mechanisms. The initial imported code supports the following functionality: - A kernel driver that provides an interface to the user space; the existing gpioc(4) driver was enhanced with this functionality. - Implement support for the most common I/O system calls / multiplexing mechanisms: - read() Places the pin number on which the interrupt occurred in the buffer. Blocking and non-blocking behaviour supported. - poll()/select() - kqueue() - signal driven I/O. Posting SIGIO when the O_ASYNC was set. - Many-to-many relationship between pins and file descriptors. - A file descriptor can monitor several GPIO pins. - A GPIO pin can be monitored by multiple file descriptors. - Integration with gpioctl and libgpio. I added some fixes (mostly to locking) and feature enhancements on top of the original gsoc code. The feature ehancements allow the user to choose between detailed and summary event reporting. Detailed reporting provides a record describing each pin change event. Summary reporting provides the time of the first and last change of each pin, and a count of how many times it changed state since the last read(2) call. Another enhancement allows the recording of multiple state change events on multiple pins between each call to read(2) (the original code would track only a single event at a time). The phabricator review for these changes timed out without approval, but I cite it below anyway, because the review contains a series of diffs that show how I evolved the code from its original state in Christian's github repo for the gsoc project to what is being commited here. (In effect, the phab review extends the VC history back to the original code.) Submitted by: Christian Kramer Obtained from: https://github.com/ckraemer/freebsd/tree/gsoc2018 Differential Revision: https://reviews.freebsd.org/D27398
Notes
Notes: svn path=/head/; revision=368585
Diffstat (limited to 'tools')
-rw-r--r--tools/test/README1
-rw-r--r--tools/test/gpioevents/Makefile12
-rw-r--r--tools/test/gpioevents/gpioevents.c642
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;
+}