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