git: a48bbef5eb32 - main - sound: Adjust mmap example to use kqueue

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Wed, 17 Jun 2026 10:52:51 UTC
The branch main has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=a48bbef5eb32508a8d7b3b986c9b1d28176d1694

commit a48bbef5eb32508a8d7b3b986c9b1d28176d1694
Author:     Goran Mekić <meka@tilda.center>
AuthorDate: 2026-06-17 10:36:12 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2026-06-17 10:50:44 +0000

    sound: Adjust mmap example to use kqueue
    
    Reviewed by:    christos
    Differential Revision:  https://reviews.freebsd.org/D57410
---
 share/examples/sound/mmap.c | 178 +++++++++++++-------------------------------
 1 file changed, 50 insertions(+), 128 deletions(-)

diff --git a/share/examples/sound/mmap.c b/share/examples/sound/mmap.c
index 7f165d417020..3710483361ac 100644
--- a/share/examples/sound/mmap.c
+++ b/share/examples/sound/mmap.c
@@ -26,114 +26,18 @@
  */
 
 /*
- * This program demonstrates low-latency audio pass-through using mmap.
- * Opens input and output audio devices using memory-mapped I/O,
- * synchronizes them in a sync group for simultaneous start,
- * then continuously copies audio data from input to output.
+ * This program demonstrates low-latency audio pass-through using mmap
+ * and kqueue.  It opens input and output audio devices using memory-
+ * mapped I/O, synchronizes them in a sync group for simultaneous start,
+ * then continuously copies audio data from input to output.  Buffer
+ * positions are obtained from kqueue's ext[0] (replacing GETIPTR/
+ * GETOPTR ioctls) and error counters from ext[1] (replacing GETERROR).
  */
 
-#include <time.h>
+#include <sys/event.h>
 
 #include "oss.h"
 
-/*
- * Get current time in nanoseconds using monotonic clock.
- * Monotonic clock is not affected by system time changes.
- */
-static int64_t
-gettime_ns(void)
-{
-	struct timespec ts;
-
-	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
-		err(1, "clock_gettime failed");
-	return ((int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec);
-}
-
-/*
- * Sleep until the specified absolute time (in nanoseconds).
- * Uses TIMER_ABSTIME for precise timing synchronization.
- */
-static void
-sleep_until_ns(int64_t target_ns)
-{
-	struct timespec ts;
-
-	ts.tv_sec = target_ns / 1000000000LL;
-	ts.tv_nsec = target_ns % 1000000000LL;
-	if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0)
-		err(1, "clock_nanosleep failed");
-}
-
-/*
- * Calculate the number of frames to process per iteration.
- * Higher sample rates require larger steps to maintain efficiency.
- */
-static unsigned
-frame_stepping(unsigned sample_rate)
-{
-	return (16U * (1U + (sample_rate / 50000U)));
-}
-
-/*
- * Update the mmap pointer and calculate progress.
- * Returns the absolute progress in bytes.
- *
- * fd: file descriptor for the audio device
- * request: ioctl request (SNDCTL_DSP_GETIPTR or SNDCTL_DSP_GETOPTR)
- * map_pointer: current pointer position in the ring buffer
- * map_progress: absolute progress in bytes
- * buffer_bytes: total size of the ring buffer
- * frag_size: size of each fragment
- * frame_size: size of one audio frame in bytes
- */
-static int64_t
-update_map_progress(int fd, unsigned long request, int *map_pointer,
-    int64_t *map_progress, int buffer_bytes, int frag_size, int frame_size)
-{
-	count_info info = {};
-	unsigned delta, max_bytes, cycles;
-	int fragments;
-
-	if (ioctl(fd, request, &info) < 0)
-		err(1, "Failed to get mmap pointer");
-	if (info.ptr < 0 || info.ptr >= buffer_bytes)
-		errx(1, "Pointer out of bounds: %d", info.ptr);
-	if ((info.ptr % frame_size) != 0)
-		errx(1, "Pointer %d not aligned to frame size %d", info.ptr,
-		    frame_size);
-	if (info.blocks < 0)
-		errx(1, "Invalid block count %d", info.blocks);
-
-	/*
-	 * Calculate delta: how many bytes have been processed since last check.
-	 * Handle ring buffer wraparound using modulo arithmetic.
-	 */
-	delta = (info.ptr + buffer_bytes - *map_pointer) % buffer_bytes;
-
-	/*
-	 * Adjust delta based on reported blocks available.
-	 * This accounts for cases where the pointer has wrapped multiple times.
-	 */
-	max_bytes = (info.blocks + 1) * frag_size - 1;
-	if (max_bytes >= delta) {
-		cycles = max_bytes - delta;
-		cycles -= cycles % buffer_bytes;
-		delta += cycles;
-	}
-
-	/* Verify fragment count matches expected value */
-	fragments = delta / frag_size;
-	if (info.blocks < fragments || info.blocks > fragments + 1)
-		warnx("Pointer block mismatch: ptr=%d blocks=%d delta=%u",
-		    info.ptr, info.blocks, delta);
-
-	/* Update pointer and progress tracking */
-	*map_pointer = info.ptr;
-	*map_progress += delta;
-	return (*map_progress);
-}
-
 /*
  * Copy data between ring buffers, handling wraparound.
  * The copy starts at 'offset' and copies 'length' bytes.
@@ -169,8 +73,6 @@ main(int argc, char *argv[])
 	int ch, bytes;
 	int frag_size, frame_size, verbose = 0;
 	int map_pointer = 0;
-	unsigned step_frames;
-	int64_t frame_ns, start_ns, next_wakeup_ns;
 	int64_t read_progress = 0, write_progress = 0;
 	oss_syncgroup sync_group = { 0, 0, { 0 } };
 	struct config config_in = {
@@ -187,6 +89,8 @@ main(int argc, char *argv[])
 		.sample_rate = 48000,
 		.mmap = 1,
 	};
+	struct kevent ev;
+	int kq;
 
 	while ((ch = getopt(argc, argv, "v")) != -1) {
 		switch (ch) {
@@ -228,10 +132,6 @@ main(int argc, char *argv[])
 		errx(1,
 		    "Input and output configurations have different fragment sizes");
 
-	/* Calculate timing parameters */
-	step_frames = frame_stepping(config_in.sample_rate);
-	frame_ns = 1000000000LL / config_in.sample_rate;
-
 	/* Clear output buffer to prevent noise on startup */
 	memset(config_out.buf, 0, bytes);
 
@@ -245,25 +145,51 @@ main(int argc, char *argv[])
 	if (ioctl(config_in.fd, SNDCTL_DSP_SYNCSTART, &sync_group.id) < 0)
 		err(1, "Starting sync group failed");
 
-	/* Initialize timing and progress tracking */
-	start_ns = gettime_ns();
-	read_progress = update_map_progress(config_in.fd, SNDCTL_DSP_GETIPTR,
-	    &map_pointer, &read_progress, bytes, frag_size, frame_size);
-	write_progress = read_progress;
-	next_wakeup_ns = start_ns;
+	/* Create kqueue and register input device for read events */
+	kq = kqueue();
+	if (kq < 0)
+		err(1, "kqueue failed");
+	EV_SET(&ev, config_in.fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
+	if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0)
+		err(1, "kevent register failed");
 
 	/*
 	 * Main processing loop:
-	 * 1. Sleep until next scheduled wakeup
-	 * 2. Check how much new audio data is available
-	 * 3. Copy available data from input to output buffer
-	 * 4. Schedule next wakeup
+	 * Block on kevent() until input data is available.
+	 * ext[0] holds the current DMA pointer (GETIPTR/GETOPTR equivalent).
+	 * ext[1] holds the xrun count for the channel (GETERROR equivalent).
 	 */
 	for (;;) {
-		sleep_until_ns(next_wakeup_ns);
-		read_progress = update_map_progress(config_in.fd,
-		    SNDCTL_DSP_GETIPTR, &map_pointer, &read_progress, bytes,
-		    frag_size, frame_size);
+		int n;
+		int ptr;
+		unsigned delta;
+
+		n = kevent(kq, NULL, 0, &ev, 1, NULL);
+		if (n < 0)
+			err(1, "kevent failed");
+		if (n == 0)
+			continue;
+
+		ptr = (int)ev.ext[0];
+		if (ptr < 0 || ptr >= bytes)
+			errx(1, "Pointer out of bounds: %d", ptr);
+		if ((ptr % frame_size) != 0)
+			errx(1, "Pointer %d not aligned to frame size %d", ptr,
+			    frame_size);
+
+		/*
+		 * Calculate delta: how many bytes have been processed since
+		 * last check. Handle ring buffer wraparound.
+		 */
+		delta = (ptr + bytes - map_pointer) % bytes;
+
+		/* Update pointer and progress tracking */
+		map_pointer = ptr;
+		read_progress += delta;
+
+		/* Report xruns if any */
+		if (ev.ext[1] != 0 && verbose)
+			warnx("xruns: %llu", (unsigned long long)ev.ext[1]);
 
 		/* Copy new audio data if available */
 		if (read_progress > write_progress) {
@@ -277,13 +203,9 @@ main(int argc, char *argv[])
 				printf("copied %d bytes at %d (abs %lld)\n",
 				    length, offset, (long long)write_progress);
 		}
-
-		/* Schedule next wakeup based on frame timing */
-		next_wakeup_ns += (int64_t)step_frames * frame_ns;
-		if (next_wakeup_ns < gettime_ns())
-			next_wakeup_ns = gettime_ns();
 	}
 
+	close(kq);
 	if (munmap(config_in.buf, bytes) != 0)
 		err(1, "Memory unmap failed");
 	config_in.buf = NULL;