2026-08-18
fbsd gpio interrupt

so, a how to use gpio interrupts under freebsd?

e.g. on armv7

i had a massive battle

even resorted to chatgpt

which then found me others

still hard. very hard

i have now-iirc-nonworking code from 2014 which i designed to be paired with perl (then poe, now anyevent, quite nice)

so i found

https://reviews.freebsd.org/D52102

https://github.com/cyclaero/shutdd

https://forums.freebsd.org/threads/gpio-api-general-orange-pi-h3.83950/page-3#post-556741

the 2014 code

http://ketas.si.pri.ee/bbb/bbb-poe-daemon/util/gpio-intr-kqueue.c

/*
 * Copyright (c) 2014  Luiz Otavio O Souza <loos.br@gmail.com>
 * Copyright (c) 2014  Sulev-Madis Silber <ketas@si.pri.ee>
 * All rights reserved.
 *
 * 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.
 */


#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/event.h>


int main(int argc, char **argv)
{
	char *gpioc;
	int ch, fd, kq, nev, pin;
	struct kevent chlist, evlist;
	
	/* kqueue example program for userland GPIO interrupt notification. */
	
	gpioc = "/dev/gpioc0";
	while ((ch = getopt(argc, argv, "f:")) != -1)
	{
		switch (ch)
		{
			case 'f':
				gpioc = optarg;
				break;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc < 1)
	{
		printf("Usage:\n");
		printf("gpio-intr-kqueue [-f /dev/gpiocX] interrupt-pin-number\n");
		exit(1);
	}
	
	pin = atoi(argv[0]);
	printf("Awaiting interrupts on pin: %d\n", pin);
	
	kq = kqueue();
	if (kq < 0)
	{
		err(1, "kqueue");
	}
	
	fd = open(gpioc, O_RDONLY);
	if (fd == -1)
	{
		err(1, "open");
	}
	
	/*
	 * Initialize our changes list with the pins we want to be notified
	 * about.
	 */
	EV_SET(&chlist, fd, EVFILT_READ, EV_ADD, 0, pin, NULL);
	
	for (;;)
	{
		/* Wait for an event. */
		nev = kevent(kq, &chlist, 1, &evlist, 1, NULL);
		if (nev < 0)
		{
			err(2, "kevent()");
		}
		if (nev == 0)
		{
			continue;
		}
		
		if (evlist.flags & EV_ERROR)
		{
			printf("error: %s\n", strerror(evlist.data));
			break;
		}
		
		printf("notified of isr on pin: %d\n", (int)evlist.data);
	}
	
	close(fd);
	close(kq);
	
	exit(0);
}

http://ketas.si.pri.ee/bbb/bbb-poe-daemon/util/gpio-irq-event.c

/*
 * Copyright (c) 2015  Sulev-Madis Silber <ketas@si.pri.ee>
 * Copyright (c) 2015  Luiz Otavio O Souza <loos.br@gmail.com>
 * All rights reserved.
 *
 * 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.
 *
 *
 * Example program to read GPIO IRQ events.
 *
 * Build with:
 * cc -Wall -o gpio-irq-event gpio-irq-event.c -lgpio
 */


#include <sys/types.h>
#include <sys/filio.h>
#include <sys/gpio.h>

#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libgpio.h>


#define MAXIRQS 32


static gpio_handle_t handle;
static int irq[MAXIRQS], nirqs;
static struct pollfd fds[MAXIRQS];


static void
usage(void)
{
	printf("usage:\n");
	printf("gpio-irq-event [-f /dev/gpioN] [-n] pin_number [pin] [..] [pin]\n");
	exit(1);
}


static void
sig_die(int sig)
{
	int i;
	
	
	for (i = 0; i < nirqs; i++)
	{
		if (fds[i].fd != -1)
		{
			gpio_close(fds[i].fd);
		}
		
		if (gpio_irq_unregister(handle, irq[i]) < 0)
		{
			perror("gpio_irq_unregister");
		}
	}
	
	gpio_close(handle);
	
	printf("\n");
	
	exit(1);
}


static int
read_ev(int fd, char *buf, ssize_t buflen, ssize_t *offset)
{
	ssize_t len;
	struct gpio_irq_event *ev;
	
	
	len = read(fd, buf + *offset, buflen - *offset);
	
	if (len == -1)
	{
		perror("read");
		return len;
	}
	else if (len == 0)
	{
		printf("nothing to read: %d\n", len);
		return len;
	}
	
	ev = (struct gpio_irq_event *)buf;
	*offset = len;
	
	while (*offset >= sizeof(*ev))
	{
		printf("gpiointerrupt ts=%lld.%06ld ts_last=%lld.%06ld pin=%d level=%d\n",
			ev->ev_ts.tv_sec, ev->ev_ts.tv_usec,
			ev->ev_ts_last.tv_sec, ev->ev_ts_last.tv_usec,
			ev->ev_pin,
			ev->ev_level);
		
		ev++;
		*offset -= sizeof(*ev);
	}
	
	if (offset > 0)
	{
		bcopy(buf + len - *offset, buf, *offset);
	}
	
	return 1;
}


static void
poll_irq_ev(struct pollfd *fds, int nfds)
{
	char *buf;
	int i, rv;
	ssize_t buflen, offset;
	
	
	offset = 0;
	buflen = sizeof(struct gpio_irq_event) * 128;
	buf = malloc(buflen);
	
	for (;;)
	{
		rv = poll(fds, nfds, -1);
		
		if (rv == -1)
		{
			perror("poll");
			break;
		}
		else if (rv == 0)
		{
			printf("invalid poll return\n");
			break;
		}
		
		for (i = 0; i < nfds; i++)
		{
			if (fds[i].revents & POLLIN)
			{
				if (read_ev(fds[i].fd, buf, buflen, &offset) <= 0)
				{
					break;
				}
			}
		}
	}
	
	free(buf);
}


static void
read_irq_ev(int fd)
{
	char *buf;
	ssize_t buflen, offset;
	
	
	offset = 0;
	buflen = sizeof(struct gpio_irq_event) * 128;
	buf = malloc(buflen);
	
	for (;;)
	{
		if (read_ev(fd, buf, buflen, &offset) <= 0)
		{
			break;
		}
	}
	
	free(buf);
}


int
main(int argc, char **argv)
{
	char *ctlfile;
	int ch, flags, i;
	unsigned int unit;
	
	
	flags = 0;
	ctlfile = NULL;
	
	while ((ch = getopt(argc, argv, "f:n")) != -1)
	{
		switch (ch)
		{
			case 'f':
				ctlfile = optarg;
				break;
			
			case 'n':
				flags |= O_NONBLOCK;
				break;
			
			default:
				usage();
		}
	}
	
	argc -= optind;
	argv += optind;
	
	if (argc < 1)
	{
		usage();
	}
	
	nirqs = 0;
	
	while (argc > 0 && nirqs < MAXIRQS)
	{
		irq[nirqs] = atoi(argv[0]);
		argc--;
		argv++;
		nirqs++;
	}
	
	if (argc > 0 || nirqs > MAXIRQS)
	{
		printf("too many pins at once, try increase MAXIRQS.\n");
		exit(1);
	}
	
	if (nirqs > 1 && (flags & O_NONBLOCK) == 0)
	{
		printf("multiple pins implies the use of nonblock mode.\n");
		flags |= O_NONBLOCK;
	}
	
	if (flags & O_NONBLOCK)
	{
		printf("using nonblock mode.\n");
	}
	
	if (ctlfile == NULL)
	{
		handle = gpio_open(0);
	}
	else
	{
		handle = gpio_open_device(ctlfile);
	}
	
	if (handle == GPIO_INVALID_HANDLE)
	{
		perror("gpio_open");
		exit(1);
	}
	
	for (i = 0; i < MAXIRQS; i++)
	{
		fds[i].fd = -1;
	}
	
	signal(SIGINT, sig_die);
	signal(SIGHUP, sig_die);
	signal(SIGTERM, sig_die);
	
	for (i = 0; i < nirqs; i++)
	{
		if (gpio_irq_register(handle, irq[i], &unit) < 0)
		{
			perror("gpio_irq_register");
			goto out;
		}
		
		/* Open irq event device. */
		fds[i].fd = gpio_irq_open(unit, flags);
		
		if (fds[i].fd == GPIO_INVALID_HANDLE)
		{
			perror("gpio_irq_open");
			goto out;
		}
		
		fds[i].events = POLLIN;
	}
	
	fprintf(stderr, "READY\n");
	
	if (flags & O_NONBLOCK)
	{
		poll_irq_ev(fds, nirqs);
	}
	else
	{
		read_irq_ev(fds[0].fd);
	}
	
	out:
	
	for (i = 0; i < nirqs; i++)
	{
		if (fds[i].fd != -1)
		{
			gpio_close(fds[i].fd);
		}
		
		if (gpio_irq_unregister(handle, irq[i]) < 0)
		{
			perror("gpio_irq_unregister");
		}
	}
	
	gpio_close(handle);
	
	exit(0);
}

the chatgpt rev 4 of this

/*
 * gpio-watch.c
 *
 * FreeBSD userland GPIO interrupt watcher with trailing-edge debounce.
 *
 * Real GPIO mode:
 *
 *     gpio-watch <controller> <edge> <debounce-us> <pin> [pin ...]
 *
 * Test mode:
 *
 *     gpio-watch --test
 *
 * Build:
 *
 *     cc -Wall -Wextra -O2 -o gpio-watch gpio-watch.c -lgpio
 */

#include <sys/types.h>
#include <sys/gpio.h>

#include <libgpio.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define MAX_PINS	128
#define EVENT_FIFO	256

struct watched_pin {
	int		pin;

	/* Last state that was actually reported. */
	uint32_t	last_state;

	/* Most recent event during the debounce period. */
	uint32_t	pending_state;
	sbintime_t	pending_time;

	/*
	 * Monotonic time at which the pending event
	 * becomes reportable.
	 */
	struct timespec	deadline;

	int		pending;
};

static volatile sig_atomic_t running = 1;

static void
sig_handler(int sig __unused)
{
	running = 0;
}

static int
parse_int(const char *s, const char *what)
{
	char *end;
	long v;

	errno = 0;
	v = strtol(s, &end, 0);

	if (errno != 0 || *end != '\0' || v < 0)
		errx(1, "invalid %s: %s", what, s);

	return ((int)v);
}

static uint64_t
parse_uint64(const char *s, const char *what)
{
	char *end;
	unsigned long long v;

	errno = 0;
	v = strtoull(s, &end, 10);

	if (errno != 0 || *end != '\0')
		errx(1, "invalid %s: %s", what, s);

	return ((uint64_t)v);
}

static uint32_t
parse_edge(const char *s)
{
	if (strcmp(s, "rising") == 0)
		return (GPIO_INTR_EDGE_RISING);

	if (strcmp(s, "falling") == 0)
		return (GPIO_INTR_EDGE_FALLING);

	if (strcmp(s, "both") == 0)
		return (GPIO_INTR_EDGE_BOTH);

	errx(1, "edge must be rising, falling or both");
}

static struct watched_pin *
find_pin(struct watched_pin *pins, int npins, int pin)
{
	int i;

	for (i = 0; i < npins; i++) {
		if (pins[i].pin == pin)
			return (&pins[i]);
	}

	return (NULL);
}

static const char *
edge_string(uint32_t oldstate, uint32_t newstate)
{
	if (oldstate == 0 && newstate != 0)
		return ("rising");

	if (oldstate != 0 && newstate == 0)
		return ("falling");

	return ("stable");
}

/*
 * Convert sbintime_t to seconds/nanoseconds for display.
 */
static void
print_timestamp(sbintime_t t)
{
	uint64_t sec;
	uint64_t frac;
	uint64_t nsec;

	sec = (uint64_t)t >> 32;
	frac = (uint32_t)t;

	nsec = (frac * 1000000000ULL) >> 32;

	printf("%ju.%09ju",
	    (uintmax_t)sec,
	    (uintmax_t)nsec);
}

/*
 * Convert sbintime_t to microseconds.
 *
 * Round instead of truncating so synthetic timestamps such as
 * 104000 us don't turn into 103999 us.
 */
static uint64_t
sbt_to_us(sbintime_t t)
{
	uint64_t sec;
	uint64_t frac;

	sec = (uint64_t)t >> 32;
	frac = (uint32_t)t;

	return ((sec * 1000000ULL) +
	    ((frac * 1000000ULL + (1ULL << 31)) >> 32));
}

/*
 * Add microseconds to a timespec.
 */
static struct timespec
timespec_add_us(struct timespec t, uint64_t us)
{
	uint64_t nsec;

	if (us > UINT64_MAX / 1000ULL)
		errx(1, "debounce interval too large");

	nsec = us * 1000ULL;

	t.tv_sec += (time_t)(nsec / 1000000000ULL);
	t.tv_nsec += (long)(nsec % 1000000000ULL);

	if (t.tv_nsec >= 1000000000L) {
		t.tv_sec++;
		t.tv_nsec -= 1000000000L;
	}

	return (t);
}

static int
timespec_cmp(const struct timespec *a,
    const struct timespec *b)
{
	if (a->tv_sec < b->tv_sec)
		return (-1);

	if (a->tv_sec > b->tv_sec)
		return (1);

	if (a->tv_nsec < b->tv_nsec)
		return (-1);

	if (a->tv_nsec > b->tv_nsec)
		return (1);

	return (0);
}

/*
 * Return milliseconds until deadline.
 *
 * poll(2) takes milliseconds, so round upward.
 */
static int
deadline_timeout(const struct timespec *now,
    const struct timespec *deadline)
{
	int64_t sec;
	int64_t nsec;
	int64_t ms;

	if (timespec_cmp(deadline, now) <= 0)
		return (0);

	sec = (int64_t)deadline->tv_sec -
	    (int64_t)now->tv_sec;

	nsec = (int64_t)deadline->tv_nsec -
	    (int64_t)now->tv_nsec;

	if (nsec < 0) {
		sec--;
		nsec += 1000000000LL;
	}

	ms = sec * 1000LL;

	ms += (nsec + 999999LL) / 1000000LL;

	if (ms > INT_MAX)
		return (INT_MAX);

	return ((int)ms);
}

/*
 * Find the earliest pending debounce deadline.
 */
static int
next_timeout(struct watched_pin *pins, int npins)
{
	struct timespec now;
	struct timespec earliest;
	int have_earliest;
	int i;

	if (clock_gettime(CLOCK_MONOTONIC, &now) != 0)
		err(1, "clock_gettime");

	have_earliest = 0;

	for (i = 0; i < npins; i++) {
		if (!pins[i].pending)
			continue;

		if (!have_earliest ||
		    timespec_cmp(&pins[i].deadline,
		    &earliest) < 0) {
			earliest = pins[i].deadline;
			have_earliest = 1;
		}
	}

	if (!have_earliest)
		return (-1);

	return (deadline_timeout(&now, &earliest));
}

/*
 * Report expired debounce timers.
 */
static void
flush_expired(struct watched_pin *pins, int npins,
    const struct timespec *now)
{
	int i;

	for (i = 0; i < npins; i++) {
		struct watched_pin *wp;

		wp = &pins[i];

		if (!wp->pending)
			continue;

		if (timespec_cmp(now, &wp->deadline) < 0)
			continue;

		print_timestamp(wp->pending_time);

		printf(" GPIO%d %-7s state=%u\n",
		    wp->pin,
		    edge_string(wp->last_state,
		    wp->pending_state),
		    wp->pending_state);

		fflush(stdout);

		wp->last_state = wp->pending_state;
		wp->pending = 0;
	}
}

/*
 * Common debounce engine.
 *
 * Every new event:
 *
 *     - replaces pending_state
 *     - replaces pending_time
 *     - restarts this pin's debounce timer
 */
static void
debounce_event(struct watched_pin *wp,
    uint32_t state,
    sbintime_t event_time,
    const struct timespec *now,
    uint64_t debounce_us)
{
	wp->pending_state = state;
	wp->pending_time = event_time;
	wp->pending = 1;

	wp->deadline =
	    timespec_add_us(*now, debounce_us);
}

/*
 * Convert simulated microseconds to sbintime_t.
 */
static sbintime_t
us_to_sbt(uint64_t us)
{
	uint64_t sec;
	uint64_t rem;
	uint64_t frac;

	sec = us / 1000000ULL;
	rem = us % 1000000ULL;

	frac = (rem * (1ULL << 32)) / 1000000ULL;

	return ((sbintime_t)((sec << 32) | frac));
}

static void
print_test_time(uint64_t us)
{
	printf("+%06ju us", (uintmax_t)us);
}

/*
 * Reset test pins to known initial states.
 */
static void
test_reset(struct watched_pin *pins, int npins)
{
	int i;

	for (i = 0; i < npins; i++) {
		pins[i].pending = 0;
		pins[i].pending_state = 0;
		pins[i].pending_time = 0;
		memset(&pins[i].deadline, 0,
		    sizeof(pins[i].deadline));
	}

	pins[0].last_state = 1;
	pins[1].last_state = 0;
	pins[2].last_state = 1;
}

static void
test_event(struct watched_pin *pins,
    int npins,
    int pin,
    uint32_t state,
    uint64_t event_us,
    uint64_t debounce_us)
{
	struct watched_pin *wp;
	struct timespec now;

	wp = find_pin(pins, npins, pin);

	if (wp == NULL)
		errx(1, "test pin %d not found", pin);

	now.tv_sec = (time_t)(event_us / 1000000ULL);
	now.tv_nsec =
	    (long)((event_us % 1000000ULL) * 1000ULL);

	printf("RAW       ");
	print_test_time(event_us);

	printf("  GPIO%d %-7s state=%u\n",
	    pin,
	    edge_string(
	        wp->pending ?
	        wp->pending_state :
	        wp->last_state,
	        state),
	    state);

	debounce_event(wp,
	    state,
	    us_to_sbt(event_us),
	    &now,
	    debounce_us);
}

static void
test_flush(struct watched_pin *pins,
    int npins,
    uint64_t now_us)
{
	struct timespec now;
	int i;

	now.tv_sec = (time_t)(now_us / 1000000ULL);
	now.tv_nsec =
	    (long)((now_us % 1000000ULL) * 1000ULL);

	for (i = 0; i < npins; i++) {
		struct watched_pin *wp;

		wp = &pins[i];

		if (!wp->pending)
			continue;

		if (timespec_cmp(&now, &wp->deadline) < 0)
			continue;

		printf("DEBOUNCED ");
		print_test_time(now_us);

		printf("  GPIO%d %-7s state=%u",
		    wp->pin,
		    edge_string(wp->last_state,
		    wp->pending_state),
		    wp->pending_state);

		printf("  (last event ");

		print_test_time(sbt_to_us(wp->pending_time));

		printf(")\n");

		wp->last_state = wp->pending_state;
		wp->pending = 0;
	}
}

static int
run_test(void)
{
	struct watched_pin pins[3];
	const uint64_t debounce_us = 5000;
	int npins = 3;
	int i;

	memset(pins, 0, sizeof(pins));

	pins[0].pin = 20;
	pins[1].pin = 35;
	pins[2].pin = 71;

	printf("GPIO WATCH TEST\n");
	printf("debounce: %ju us\n\n",
	    (uintmax_t)debounce_us);

	/*
	 * Test 1
	 */
	printf("TEST 1: single pin bounce\n\n");

	test_reset(pins, npins);

	test_event(pins, npins, 20, 0,
	    100000, debounce_us);

	test_event(pins, npins, 20, 1,
	    101000, debounce_us);

	test_event(pins, npins, 20, 0,
	    102000, debounce_us);

	test_event(pins, npins, 20, 1,
	    104000, debounce_us);

	test_flush(pins, npins, 109000);

	printf("\n");

	/*
	 * Test 2
	 */
	printf("TEST 2: two pins simultaneously\n\n");

	test_reset(pins, npins);

	test_event(pins, npins, 20, 0,
	    200000, debounce_us);

	test_event(pins, npins, 35, 1,
	    201000, debounce_us);

	test_event(pins, npins, 20, 1,
	    202000, debounce_us);

	test_event(pins, npins, 35, 0,
	    203000, debounce_us);

	test_event(pins, npins, 20, 0,
	    204000, debounce_us);

	test_event(pins, npins, 35, 1,
	    205000, debounce_us);

	test_flush(pins, npins, 209000);
	test_flush(pins, npins, 210000);

	printf("\n");

	/*
	 * Test 3
	 */
	printf("TEST 3: independent timers\n\n");

	test_reset(pins, npins);

	test_event(pins, npins, 20, 0,
	    300000, debounce_us);

	test_event(pins, npins, 20, 1,
	    302000, debounce_us);

	test_event(pins, npins, 71, 0,
	    303000, debounce_us);

	/*
	 * GPIO20 deadline = 307000
	 * GPIO71 deadline = 308000
	 */
	test_flush(pins, npins, 307000);
	test_flush(pins, npins, 308000);

	printf("\n");

	/*
	 * Test 4
	 */
	printf("TEST 4: last event wins\n\n");

	test_reset(pins, npins);

	test_event(pins, npins, 35, 0,
	    400000, debounce_us);

	test_event(pins, npins, 35, 1,
	    401000, debounce_us);

	test_event(pins, npins, 35, 0,
	    402000, debounce_us);

	test_flush(pins, npins, 407000);

	printf("\n");

	printf("FINAL STATES\n");

	for (i = 0; i < npins; i++)
		printf("GPIO%d = %u\n",
		    pins[i].pin,
		    pins[i].last_state);

	printf("\nTEST COMPLETE\n");

	return (0);
}

static int
run_gpio(int argc, char **argv)
{
	gpio_handle_t gpio;
	struct watched_pin pins[MAX_PINS];
	struct gpio_event_detail events[EVENT_FIFO];
	struct pollfd pfd;

	uint32_t intr;
	uint64_t debounce_us;

	int controller;
	int npins;
	int i;
	int flags;

	if (argc < 5) {
		fprintf(stderr,
		    "usage: %s <controller> "
		    "<rising|falling|both> "
		    "<debounce-us> <pin> [pin ...]\n",
		    argv[0]);

		return (1);
	}

	controller = parse_int(argv[1], "controller");
	intr = parse_edge(argv[2]);

	debounce_us =
	    parse_uint64(argv[3], "debounce interval");

	npins = argc - 4;

	if (npins > MAX_PINS)
		errx(1, "too many GPIO pins");

	for (i = 0; i < npins; i++) {
		pins[i].pin =
		    parse_int(argv[i + 4], "GPIO pin");

		pins[i].last_state = 0;
		pins[i].pending_state = 0;
		pins[i].pending_time = 0;
		pins[i].pending = 0;

		if (find_pin(pins, i, pins[i].pin) != NULL)
			errx(1, "GPIO pin specified twice: %d",
			    pins[i].pin);
	}

	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);

	gpio = gpio_open((unsigned)controller);

	if (gpio == GPIO_INVALID_HANDLE)
		errx(1, "gpio_open(%d)", controller);

	if (gpio_configure_events(gpio,
	    GPIO_EVENT_REPORT_DETAIL,
	    EVENT_FIFO) != 0)
		err(1, "gpio_configure_events");

	for (i = 0; i < npins; i++) {
		gpio_config_t cfg;

		memset(&cfg, 0, sizeof(cfg));

		cfg.g_pin = pins[i].pin;
		cfg.g_flags = GPIO_PIN_INPUT | intr;

		if (gpio_pin_set_flags(gpio, &cfg) != 0)
			err(1, "cannot configure GPIO %d",
			    pins[i].pin);

		pins[i].last_state =
		    gpio_pin_get(gpio, pins[i].pin);
	}

	pfd.fd = gpio_fileno(gpio);
	pfd.events = POLLIN;
	pfd.revents = 0;

	flags = fcntl(pfd.fd, F_GETFL, 0);

	if (flags == -1)
		err(1, "fcntl(F_GETFL)");

	if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
		err(1, "fcntl(F_SETFL)");

	printf("watching %d GPIO pins, %s edge, "
	    "trailing debounce %ju us\n",
	    npins,
	    argv[2],
	    (uintmax_t)debounce_us);

	for (i = 0; i < npins; i++) {
		printf("  GPIO%d initial=%u\n",
		    pins[i].pin,
		    pins[i].last_state);
	}

	fflush(stdout);

	while (running) {
		int timeout;
		int ret;

		timeout = next_timeout(pins, npins);

		ret = poll(&pfd, 1, timeout);

		if (ret < 0) {
			if (errno == EINTR)
				continue;

			err(1, "poll");
		}

		if (pfd.revents & POLLIN) {
			for (;;) {
				ssize_t bytes;
				size_t nevents;
				size_t j;

				bytes = read(pfd.fd,
				    events, sizeof(events));

				if (bytes < 0) {
					if (errno == EAGAIN ||
					    errno == EWOULDBLOCK)
						break;

					if (errno == EINTR)
						continue;

					err(1, "read");
				}

				if (bytes == 0)
					break;

				if ((size_t)bytes %
				    sizeof(struct gpio_event_detail) != 0)
					errx(1,
					    "partial GPIO event");

				nevents = (size_t)bytes /
				    sizeof(struct gpio_event_detail);

				for (j = 0; j < nevents; j++) {
					struct gpio_event_detail *ev;
					struct watched_pin *wp;
					struct timespec now;

					ev = &events[j];

					wp = find_pin(
					    pins,
					    npins,
					    ev->gp_pin);

					if (wp == NULL)
						continue;

					if (clock_gettime(
					    CLOCK_MONOTONIC,
					    &now) != 0)
						err(1,
						    "clock_gettime");

					debounce_event(
					    wp,
					    ev->gp_pinstate,
					    ev->gp_time,
					    &now,
					    debounce_us);
				}
			}
		}

		{
			struct timespec now;

			if (clock_gettime(
			    CLOCK_MONOTONIC, &now) != 0)
				err(1, "clock_gettime");

			flush_expired(
			    pins,
			    npins,
			    &now);
		}
	}

	gpio_close(gpio);

	return (0);
}

int
main(int argc, char **argv)
{
	if (argc == 2 &&
	    strcmp(argv[1], "--test") == 0)
		return (run_test());

	return (run_gpio(argc, argv));
}

also

https://wiki.freebsd.org/SummerOfCode2018Projects/UserSpaceGPIOinterrupts

anyway this is hell

if i make it working again i'll put that somewhere so nobody else would need to pull their hair out since they don't know c. and it's only available from your custom c code!!!

created [1787046839] 2026-08-18 12:53:59 +0300/EEST, modified [1787048242] 2026-08-18 13:17:22 +0300/EEST