From 4e1eb70caa1965002d5e13522a5332e9908c447b Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sun, 27 Sep 2026 16:36:56 +0000 Subject: [PATCH] udev: finish the kqueue code path so the BSDs no longer need libepoll-shim udev_input.c had a half-written kqueue alternative to epoll behind a "#if 1 / TODO" that hard-selected epoll everywhere, which is why the FreeBSD games/retroarch port has to link the udev input driver against libepoll-shim (UDEV_LIB_DEPENDS/UDEV_CFLAGS/UDEV_LDFLAGS in the port). Complete it: on FreeBSD, OpenBSD, NetBSD, DragonFly and macOS the evdev fds are registered with EV_SET(EVFILT_READ, EV_ADD, udata = device) (the placeholder referenced an undefined LISTENSOCKET), the poll uses a zero timespec like the epoll_wait(..., 0) it mirrors (the placeholder referenced an undefined `timeout`), and the event loop tests filter == EVFILT_READ && !(flags & EV_ERROR) instead of the epoll-only `events & EPOLLIN`. kqueue is level-triggered by default so the drain-until-EAGAIN read loop is unchanged, and closing a device fd removes it from the kqueue just as it does from the epoll set. NetBSD's kevent.udata is intptr_t rather than void *, hence the UDEV_KQ_UDATA cast. Linux still uses epoll; that object is unchanged. The kqueue variant was compiled with libkqueue's to catch type errors. CI: the FreeBSD job now fails if the linked retroarch binary still imports any epoll_* symbol. --- .github/workflows/FreeBSD.yml | 5 +++++ input/drivers/udev_input.c | 34 ++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index 15a7f5cba4..22a5dbf8c1 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -71,6 +71,11 @@ jobs: gmake -j$NCPU clean gmake -j$NCPU info all ./retroarch --version + # udev input must multiplex with kqueue here, so the binary + # must not need libepoll-shim's epoll_* at all. + if nm -u retroarch | grep -q 'epoll_'; then + echo "retroarch still references epoll (libepoll-shim)"; exit 1 + fi # With v4l_compat the V4L2 camera and video processor core # must still compile; only those two objects are rebuilt. diff --git a/input/drivers/udev_input.c b/input/drivers/udev_input.c index 2dd760b67e..eeb1306da3 100644 --- a/input/drivers/udev_input.c +++ b/input/drivers/udev_input.c @@ -14,18 +14,12 @@ * If not, see . */ -/* TODO/FIXME - set this once the kqueue codepath is implemented and working properly, - * also remove libepoll-shim from the Makefile when that happens. */ -#if 1 -#define HAVE_EPOLL -#else -#ifdef __linux__ -#define HAVE_EPOLL 1 -#endif - -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) +/* Linux multiplexes the evdev fds with epoll; the BSDs and macOS use + * kqueue natively so no libepoll-shim is needed there. */ +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #define HAVE_KQUEUE 1 -#endif +#else +#define HAVE_EPOLL 1 #endif #include @@ -43,6 +37,13 @@ #include #elif defined(HAVE_KQUEUE) #include +#include +/* NetBSD declares kevent.udata as intptr_t, everyone else as void *. */ +#if defined(__NetBSD__) +#define UDEV_KQ_UDATA(ptr) ((intptr_t)(ptr)) +#else +#define UDEV_KQ_UDATA(ptr) ((void*)(ptr)) +#endif #endif #include @@ -3295,7 +3296,7 @@ static int udev_input_add_device(udev_input_t *udev, fd, strerror(errno)); } #elif defined(HAVE_KQUEUE) - EV_SET(&event, fd, EVFILT_READ, EV_ADD, 0, 0, LISTENSOCKET); + EV_SET(&event, fd, EVFILT_READ, EV_ADD, 0, 0, UDEV_KQ_UDATA(device)); if (kevent(udev->fd, &event, 1, NULL, 0, NULL) == -1) { RARCH_ERR("[udev] Failed to add FD (%d) to kqueue list (%s).\n", @@ -3540,8 +3541,9 @@ static void udev_input_poll(void *data) ret = epoll_wait(udev->fd, events, ARRAY_SIZE(events), 0); #elif defined(HAVE_KQUEUE) { + /* Zero timeout: drain what is pending, never block the frame. */ struct timespec timeoutspec; - timeoutspec.tv_sec = timeout; + timeoutspec.tv_sec = 0; timeoutspec.tv_nsec = 0; ret = kevent(udev->fd, NULL, 0, events, ARRAY_SIZE(events), &timeoutspec); @@ -3550,8 +3552,12 @@ static void udev_input_poll(void *data) for (i = 0; i < ret; i++) { - /* TODO/FIXME - add HAVE_EPOLL/HAVE_KQUEUE codepaths here */ +#if defined(HAVE_EPOLL) if (events[i].events & EPOLLIN) +#elif defined(HAVE_KQUEUE) + if ( events[i].filter == EVFILT_READ + && !(events[i].flags & EV_ERROR)) +#endif { int j, len; struct input_event input_events[32];