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
54static bool be_verbose = false;
55static int report_format = GPIO_EVENT_REPORT_DETAIL;
56static struct timespec utc_offset;
57
58static volatile sig_atomic_t sigio = 0;
59
60static void
61sigio_handler(int sig __unused){
62	sigio = 1;
63}
64
65static void
66usage()
67{
68	fprintf(stderr, "usage: %s [-f ctldev] [-m method] [-s] [-n] [-S] [-u]"
69	    "[-t timeout] [-d delay-usec] pin intr-config [pin intr-config ...]\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");
89}
90
91static void
92verbose(const char *fmt, ...)
93{
94	va_list args;
95
96	if (!be_verbose)
97		return;
98
99	va_start(args, fmt);
100	vprintf(fmt, args);
101	va_end(args);
102}
103
104static const char*
105poll_event_to_str(short event)
106{
107	switch (event) {
108	case POLLIN:
109		return "POLLIN";
110	case POLLPRI:
111		return "POLLPRI:";
112	case POLLOUT:
113		return "POLLOUT:";
114	case POLLRDNORM:
115		return "POLLRDNORM";
116	case POLLRDBAND:
117		return "POLLRDBAND";
118	case POLLWRBAND:
119		return "POLLWRBAND";
120	case POLLINIGNEOF:
121		return "POLLINIGNEOF";
122	case POLLERR:
123		return "POLLERR";
124	case POLLHUP:
125		return "POLLHUP";
126	case POLLNVAL:
127		return "POLLNVAL";
128	default:
129		return "unknown event";
130	}
131}
132
133static void
134print_poll_events(short event)
135{
136	short curr_event = 0;
137	bool first = true;
138
139	for (size_t i = 0; i <= sizeof(short) * CHAR_BIT - 1; i++) {
140		curr_event = 1 << i;
141		if ((event & curr_event) == 0)
142			continue;
143		if (!first) {
144			printf(" | ");
145		} else {
146			first = false;
147		}
148		printf("%s", poll_event_to_str(curr_event));
149	}
150}
151
152static void
153calc_utc_offset()
154{
155	struct timespec monotime, utctime;
156
157	clock_gettime(CLOCK_MONOTONIC, &monotime);
158	clock_gettime(CLOCK_REALTIME, &utctime);
159	timespecsub(&utctime, &monotime, &utc_offset);
160}
161
162static void
163print_timestamp(const char *str, sbintime_t timestamp)
164{
165	struct timespec ts;
166	char timebuf[32];
167
168	ts = sbttots(timestamp);
169
170	if (!timespecisset(&utc_offset)) {
171		printf("%s %jd.%09ld ", str, (intmax_t)ts.tv_sec, ts.tv_nsec);
172	} else {
173                timespecadd(&utc_offset, &ts, &ts);
174		strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S",
175		    gmtime(&ts.tv_sec));
176		printf("%s %s.%09ld ", str, timebuf, ts.tv_nsec);
177	}
178}
179
180static void
181print_event_detail(const struct gpio_event_detail *det)
182{
183	print_timestamp("time", det->gp_time);
184	printf("pin %hu state %u\n", det->gp_pin, det->gp_pinstate);
185}
186
187static void
188print_event_summary(const struct gpio_event_summary *sum)
189{
190	print_timestamp("first_time", sum->gp_first_time);
191	print_timestamp("last_time", sum->gp_last_time);
192	printf("pin %hu count %hu first state %u last state %u\n",
193	    sum->gp_pin, sum->gp_count,
194	    sum->gp_first_state, sum->gp_last_state);
195}
196
197static void
198print_gpio_event(const void *buf)
199{
200	if (report_format == GPIO_EVENT_REPORT_DETAIL)
201		print_event_detail((const struct gpio_event_detail *)buf);
202	else
203		print_event_summary((const struct gpio_event_summary *)buf);
204}
205
206static void
207run_read(bool loop, int handle, const char *file, u_int delayus)
208{
209	const size_t numrecs = 64;
210	union {
211		const struct gpio_event_summary sum[numrecs];
212		const struct gpio_event_detail  det[numrecs];
213		uint8_t                         data[1];
214	} buffer;
215	ssize_t reccount, recsize, res;
216
217	if (report_format == GPIO_EVENT_REPORT_DETAIL)
218		recsize = sizeof(struct gpio_event_detail);
219	else
220		recsize = sizeof(struct gpio_event_summary);
221
222	do {
223		if (delayus != 0) {
224			verbose("sleep %f seconds before read()\n",
225			    delayus / 1000000.0);
226			usleep(delayus);
227		}
228		verbose("read into %zd byte buffer\n", sizeof(buffer));
229		res = read(handle, buffer.data, sizeof(buffer));
230		if (res < 0)
231			err(EXIT_FAILURE, "Cannot read from %s", file);
232
233		if ((res % recsize) != 0) {
234			fprintf(stderr, "%s: read() %zd bytes from %s; "
235			    "expected a multiple of %zu\n",
236			    getprogname(), res, file, recsize);
237		} else {
238			reccount = res / recsize;
239			verbose("read returned %zd bytes; %zd events\n", res,
240			    reccount);
241			for (ssize_t i = 0; i < reccount; ++i) {
242				if (report_format == GPIO_EVENT_REPORT_DETAIL)
243					print_event_detail(&buffer.det[i]);
244				else
245					print_event_summary(&buffer.sum[i]);
246			}
247		}
248	} while (loop);
249}
250
251static void
252run_poll(bool loop, int handle, const char *file, int timeout, u_int delayus)
253{
254	struct pollfd fds;
255	int res;
256
257        fds.fd = handle;
258        fds.events = POLLIN | POLLRDNORM;
259        fds.revents = 0;
260
261	do {
262		if (delayus != 0) {
263			verbose("sleep %f seconds before poll()\n",
264			    delayus / 1000000.0);
265			usleep(delayus);
266		}
267		res = poll(&fds, 1, timeout);
268		if (res < 0) {
269			err(EXIT_FAILURE, "Cannot poll() %s", file);
270		} else if (res == 0) {
271			printf("%s: poll() timed out on %s\n", getprogname(),
272			    file);
273		} else {
274			printf("%s: poll() returned %i (revents: ",
275			    getprogname(), res);
276			print_poll_events(fds.revents);
277			printf(") on %s\n", file);
278			if (fds.revents & (POLLHUP | POLLERR)) {
279				err(EXIT_FAILURE, "Recieved POLLHUP or POLLERR "
280				    "on %s", file);
281			}
282			run_read(false, handle, file, 0);
283		}
284	} while (loop);
285}
286
287static void
288run_select(bool loop, int handle, const char *file, int timeout, u_int delayus)
289{
290	fd_set readfds;
291	struct timeval tv;
292	struct timeval *tv_ptr;
293	int res;
294
295	FD_ZERO(&readfds);
296	FD_SET(handle, &readfds);
297	if (timeout != INFTIM) {
298		tv.tv_sec = timeout / 1000;
299		tv.tv_usec = (timeout % 1000) * 1000;
300		tv_ptr = &tv;
301	} else {
302		tv_ptr = NULL;
303	}
304
305	do {
306		if (delayus != 0) {
307			verbose("sleep %f seconds before select()\n",
308			    delayus / 1000000.0);
309			usleep(delayus);
310		}
311		res = select(FD_SETSIZE, &readfds, NULL, NULL, tv_ptr);
312		if (res < 0) {
313			err(EXIT_FAILURE, "Cannot select() %s", file);
314		} else if (res == 0) {
315			printf("%s: select() timed out on %s\n", getprogname(),
316			    file);
317		} else {
318			printf("%s: select() returned %i on %s\n",
319			    getprogname(), res, file);
320			run_read(false, handle, file, 0);
321		}
322	} while (loop);
323}
324
325static void
326run_kqueue(bool loop, int handle, const char *file, int timeout, u_int delayus)
327{
328	struct kevent event[1];
329	struct kevent tevent[1];
330	int kq = -1;
331	int nev = -1;
332	struct timespec tv;
333	struct timespec *tv_ptr;
334
335	if (timeout != INFTIM) {
336		tv.tv_sec = timeout / 1000;
337		tv.tv_nsec = (timeout % 1000) * 10000000;
338		tv_ptr = &tv;
339	} else {
340		tv_ptr = NULL;
341	}
342
343	kq = kqueue();
344	if (kq == -1)
345		err(EXIT_FAILURE, "kqueue() %s", file);
346
347	EV_SET(&event[0], handle, EVFILT_READ, EV_ADD, 0, 0, NULL);
348	nev = kevent(kq, event, 1, NULL, 0, NULL);
349	if (nev == -1)
350		err(EXIT_FAILURE, "kevent() %s", file);
351
352	do {
353		if (delayus != 0) {
354			verbose("sleep %f seconds before kevent()\n",
355			    delayus / 1000000.0);
356			usleep(delayus);
357		}
358		nev = kevent(kq, NULL, 0, tevent, 1, tv_ptr);
359		if (nev == -1) {
360			err(EXIT_FAILURE, "kevent() %s", file);
361		} else if (nev == 0) {
362			printf("%s: kevent() timed out on %s\n", getprogname(),
363			    file);
364		} else {
365			printf("%s: kevent() returned %i events (flags: %d) on "
366			    "%s\n", getprogname(), nev, tevent[0].flags, file);
367			if (tevent[0].flags & EV_EOF) {
368				err(EXIT_FAILURE, "Recieved EV_EOF on %s",
369				    file);
370			}
371			run_read(false, handle, file, 0);
372		}
373	} while (loop);
374}
375
376static void
377run_aio_read(bool loop, int handle, const char *file, u_int delayus)
378{
379	uint8_t buffer[1024];
380	size_t recsize;
381	ssize_t res;
382	struct aiocb iocb;
383
384	/*
385	 * Note that async IO to character devices is no longer allowed by
386	 * default (since freebsd 11).  This code is still here (for now)
387	 * because you can use sysctl vfs.aio.enable_unsafe=1 to bypass the
388	 * prohibition and run this code.
389	 */
390
391	if (report_format == GPIO_EVENT_REPORT_DETAIL)
392		recsize = sizeof(struct gpio_event_detail);
393	else
394		recsize = sizeof(struct gpio_event_summary);
395
396	bzero(&iocb, sizeof(iocb));
397
398	iocb.aio_fildes = handle;
399	iocb.aio_nbytes = sizeof(buffer);
400	iocb.aio_offset = 0;
401	iocb.aio_buf = buffer;
402
403	do {
404		if (delayus != 0) {
405			verbose("sleep %f seconds before aio_read()\n",
406			    delayus / 1000000.0);
407			usleep(delayus);
408		}
409		res = aio_read(&iocb);
410		if (res < 0)
411			err(EXIT_FAILURE, "Cannot aio_read from %s", file);
412		do {
413			res = aio_error(&iocb);
414		} while (res == EINPROGRESS);
415		if (res < 0)
416			err(EXIT_FAILURE, "aio_error on %s", file);
417		res = aio_return(&iocb);
418		if (res < 0)
419			err(EXIT_FAILURE, "aio_return on %s", file);
420		if ((res % recsize) != 0) {
421			fprintf(stderr, "%s: aio_read() %zd bytes from %s; "
422			    "expected a multiple of %zu\n",
423			    getprogname(), res, file, recsize);
424		} else {
425			for (ssize_t i = 0; i < res; i += recsize)
426				print_gpio_event(&buffer[i]);
427		}
428	} while (loop);
429}
430
431
432static void
433run_sigio(bool loop, int handle, const char *file)
434{
435	int res;
436	struct sigaction sigact;
437	int flags;
438	int pid;
439
440	bzero(&sigact, sizeof(sigact));
441	sigact.sa_handler = sigio_handler;
442	if (sigaction(SIGIO, &sigact, NULL) < 0)
443		err(EXIT_FAILURE, "cannot set SIGIO handler on %s", file);
444	flags = fcntl(handle, F_GETFL);
445	flags |= O_ASYNC;
446	res = fcntl(handle, F_SETFL, flags);
447	if (res < 0)
448		err(EXIT_FAILURE, "fcntl(F_SETFL) on %s", file);
449	pid = getpid();
450	res = fcntl(handle, F_SETOWN, pid);
451	if (res < 0)
452		err(EXIT_FAILURE, "fnctl(F_SETOWN) on %s", file);
453
454	do {
455		if (sigio == 1) {
456			sigio = 0;
457			printf("%s: recieved SIGIO on %s\n", getprogname(),
458			    file);
459			run_read(false, handle, file, 0);
460		}
461		pause();
462	} while (loop);
463}
464
465int
466main(int argc, char *argv[])
467{
468	int ch;
469	const char *file = "/dev/gpioc0";
470	char method = 'r';
471	bool loop = true;
472	bool nonblock = false;
473	u_int delayus = 0;
474	int flags;
475	int timeout = INFTIM;
476	int handle;
477	int res;
478	gpio_config_t pin_config;
479
480	while ((ch = getopt(argc, argv, "d:f:m:sSnt:uv")) != -1) {
481		switch (ch) {
482		case 'd':
483			delayus = strtol(optarg, NULL, 10);
484			if (errno != 0) {
485				warn("Invalid delay value");
486				usage();
487				return EXIT_FAILURE;
488			}
489			break;
490		case 'f':
491			file = optarg;
492			break;
493		case 'm':
494			method = optarg[0];
495			break;
496		case 's':
497			loop = false;
498			break;
499		case 'S':
500			report_format = GPIO_EVENT_REPORT_SUMMARY;
501			break;
502		case 'n':
503			nonblock= true;
504			break;
505		case 't':
506			errno = 0;
507			timeout = strtol(optarg, NULL, 10);
508			if (errno != 0) {
509				warn("Invalid timeout value");
510				usage();
511				return EXIT_FAILURE;
512			}
513			break;
514		case 'u':
515			calc_utc_offset();
516			break;
517		case 'v':
518			be_verbose = true;
519			break;
520		default:
521			usage();
522			return EXIT_FAILURE;
523		}
524	}
525	argv += optind;
526	argc -= optind;
527
528	if (argc == 0) {
529		fprintf(stderr, "%s: No pin number specified.\n",
530		    getprogname());
531		usage();
532		return EXIT_FAILURE;
533	}
534
535	if (argc == 1) {
536		fprintf(stderr, "%s: No trigger type specified.\n",
537		    getprogname());
538		usage();
539		return EXIT_FAILURE;
540	}
541
542	if (argc % 2 == 1) {
543		fprintf(stderr, "%s: Invalid number of pin intr-conf pairs.\n",
544		    getprogname());
545		usage();
546		return EXIT_FAILURE;
547	}
548
549	handle = gpio_open_device(file);
550	if (handle == GPIO_INVALID_HANDLE)
551		err(EXIT_FAILURE, "Cannot open %s", file);
552
553	if (report_format == GPIO_EVENT_REPORT_SUMMARY) {
554		struct gpio_event_config cfg =
555		    {GPIO_EVENT_REPORT_SUMMARY, 0};
556
557		res = ioctl(handle, GPIOCONFIGEVENTS, &cfg);
558		if (res < 0)
559			err(EXIT_FAILURE, "GPIOCONFIGEVENTS failed on %s", file);
560	}
561
562	if (nonblock == true) {
563		flags = fcntl(handle, F_GETFL);
564		flags |= O_NONBLOCK;
565		res = fcntl(handle, F_SETFL, flags);
566		if (res < 0)
567			err(EXIT_FAILURE, "cannot set O_NONBLOCK on %s", file);
568	}
569
570	for (int i = 0; i <= argc - 2; i += 2) {
571
572		errno = 0;
573		pin_config.g_pin = strtol(argv[i], NULL, 10);
574		if (errno != 0) {
575			warn("Invalid pin number");
576			usage();
577			return EXIT_FAILURE;
578		}
579
580		if (strnlen(argv[i + 1], 2) < 2) {
581			fprintf(stderr, "%s: Invalid trigger type (argument "
582			    "too short).\n", getprogname());
583			usage();
584			return EXIT_FAILURE;
585		}
586
587		switch((argv[i + 1][0] << 8) + argv[i + 1][1]) {
588		case ('n' << 8) + 'o':
589			pin_config.g_flags = GPIO_INTR_NONE;
590			break;
591		case ('e' << 8) + 'r':
592			pin_config.g_flags = GPIO_INTR_EDGE_RISING;
593			break;
594		case ('e' << 8) + 'f':
595			pin_config.g_flags = GPIO_INTR_EDGE_FALLING;
596			break;
597		case ('e' << 8) + 'b':
598			pin_config.g_flags = GPIO_INTR_EDGE_BOTH;
599			break;
600		default:
601			fprintf(stderr, "%s: Invalid trigger type.\n",
602			    getprogname());
603			usage();
604			return EXIT_FAILURE;
605		}
606
607		pin_config.g_flags |= GPIO_PIN_INPUT | GPIO_PIN_PULLUP;
608
609		res = gpio_pin_set_flags(handle, &pin_config);
610		if (res < 0)
611			err(EXIT_FAILURE, "configuration of pin %d on %s "
612			    "failed (flags=%d)", pin_config.g_pin, file,
613			    pin_config.g_flags);
614	}
615
616	switch (method) {
617	case 'r':
618		run_read(loop, handle, file, delayus);
619		break;
620	case 'p':
621		run_poll(loop, handle, file, timeout, delayus);
622		break;
623	case 's':
624		run_select(loop, handle, file, timeout, delayus);
625		break;
626	case 'k':
627		run_kqueue(loop, handle, file, timeout, delayus);
628		break;
629	case 'a':
630		run_aio_read(loop, handle, file, delayus);
631		break;
632	case 'i':
633		run_sigio(loop, handle, file);
634		break;
635	default:
636		fprintf(stderr, "%s: Unknown method.\n", getprogname());
637		usage();
638		return EXIT_FAILURE;
639	}
640
641	return EXIT_SUCCESS;
642}
643