[alsa-devel] [PATCH 28/35] axfer: add a common interface of waiter for I/O event notification

Takashi Sakamoto o-takashi at sakamocchi.jp
Tue Nov 13 07:41:40 CET 2018


There're several types of system calls for multiplexed I/O. They're used to
receive notifications of I/O events. Typically, userspace applications call
them against file descriptor to yield CPU. When I/O is enabled on any of
the descriptors, a task of the application is rescheduled, then the
application execute I/O calls.

This commit adds a common interface for this type of system calls, named as
'waiter'. This is expected to be used with non-blocking file operation and
operations on mapped page frame.

Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
 axfer/Makefile.am               |  5 +-
 axfer/waiter.c                  | 99 +++++++++++++++++++++++++++++++++
 axfer/waiter.h                  | 54 ++++++++++++++++++
 axfer/xfer-libasound-irq-mmap.c | 14 ++++-
 axfer/xfer-libasound-irq-rw.c   | 26 ++++++++-
 axfer/xfer-libasound.c          | 76 +++++++++++++++++++++++++
 axfer/xfer-libasound.h          |  7 +++
 7 files changed, 277 insertions(+), 4 deletions(-)
 create mode 100644 axfer/waiter.c
 create mode 100644 axfer/waiter.h

diff --git a/axfer/Makefile.am b/axfer/Makefile.am
index 960811e..e425a28 100644
--- a/axfer/Makefile.am
+++ b/axfer/Makefile.am
@@ -22,6 +22,7 @@ noinst_HEADERS = \
 	xfer.h \
 	xfer-libasound.h \
 	frame-cache.h
+	waiter.h
 
 axfer_SOURCES = \
 	misc.h \
@@ -47,4 +48,6 @@ axfer_SOURCES = \
 	frame-cache.c \
 	xfer-libasound-irq-rw.c \
 	subcmd-transfer.c \
-	xfer-libasound-irq-mmap.c
+	xfer-libasound-irq-mmap.c \
+	waiter.h \
+	waiter.c
diff --git a/axfer/waiter.c b/axfer/waiter.c
new file mode 100644
index 00000000..0bc4740
--- /dev/null
+++ b/axfer/waiter.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// waiter.c - I/O event waiter.
+//
+// Copyright (c) 2018 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+//
+// Licensed under the terms of the GNU General Public License, version 2.
+
+#include "waiter.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "misc.h"
+
+static const char *const waiter_type_labels[] = {
+	[WAITER_TYPE_DEFAULT] = "default",
+};
+
+enum waiter_type waiter_type_from_label(const char *label)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(waiter_type_labels); ++i) {
+		if (!strcmp(waiter_type_labels[i], label))
+			return i;
+	}
+
+	return WAITER_TYPE_DEFAULT;
+}
+
+const char *waiter_label_from_type(enum waiter_type type)
+{
+	return waiter_type_labels[type];
+}
+
+int waiter_context_init(struct waiter_context *waiter,
+			enum waiter_type type, unsigned int pfd_count)
+{
+	struct {
+		enum waiter_type type;
+		const struct waiter_data *waiter;
+	} entries[] = {
+		{WAITER_TYPE_COUNT,	NULL},
+	};
+	int i;
+
+	if (pfd_count == 0)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+		if (entries[i].type == type)
+			break;
+	}
+	if (i == ARRAY_SIZE(entries))
+		return -EINVAL;
+
+	waiter->private_data = malloc(entries[i].waiter->private_size);
+	if (waiter->private_data == NULL)
+		return -ENOMEM;
+	memset(waiter->private_data, 0, entries[i].waiter->private_size);
+
+	waiter->type = type;
+	waiter->ops = &entries[i].waiter->ops;
+
+	waiter->pfds = calloc(pfd_count, sizeof(*waiter->pfds));
+	if (waiter->pfds == NULL)
+		return -ENOMEM;
+	waiter->pfd_count = pfd_count;
+
+	return 0;
+}
+
+int waiter_context_prepare(struct waiter_context *waiter)
+{
+	return waiter->ops->prepare(waiter);
+}
+
+int waiter_context_wait_event(struct waiter_context *waiter,
+				int timeout_msec)
+{
+	return waiter->ops->wait_event(waiter, timeout_msec);
+}
+
+void waiter_context_release(struct waiter_context *waiter)
+{
+	waiter->ops->release(waiter);
+}
+
+void waiter_context_destroy(struct waiter_context *waiter)
+{
+	if (waiter->pfds)
+		free(waiter->pfds);
+	waiter->pfd_count = 0;
+	if (waiter->private_data)
+		free(waiter->private_data);
+	waiter->private_data = NULL;
+}
diff --git a/axfer/waiter.h b/axfer/waiter.h
new file mode 100644
index 00000000..17e01cb
--- /dev/null
+++ b/axfer/waiter.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// waiter.h - a header for I/O event waiter.
+//
+// Copyright (c) 2018 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+//
+// Licensed under the terms of the GNU General Public License, version 2.
+
+#ifndef __ALSA_UTILS_AXFER_WAITER__H_
+#define __ALSA_UTILS_AXFER_WAITER__H_
+
+#include <poll.h>
+
+enum waiter_type {
+	WAITER_TYPE_DEFAULT = 0,
+	WAITER_TYPE_COUNT,
+};
+
+struct waiter_ops;
+
+struct waiter_context {
+	enum waiter_type type;
+	const struct waiter_ops *ops;
+	void *private_data;
+
+	struct pollfd *pfds;
+	unsigned int pfd_count;
+};
+
+enum waiter_type waiter_type_from_label(const char *label);
+const char *waiter_label_from_type(enum waiter_type type);
+
+int waiter_context_init(struct waiter_context *waiter,
+			enum waiter_type type, unsigned int pfd_count);
+int waiter_context_prepare(struct waiter_context *waiter);
+int waiter_context_wait_event(struct waiter_context *waiter,
+				int timeout_msec);
+void waiter_context_release(struct waiter_context *waiter);
+void waiter_context_destroy(struct waiter_context *waiter);
+
+// For internal use in 'waiter' module.
+
+struct waiter_ops {
+	int (*prepare)(struct waiter_context *waiter);
+	int (*wait_event)(struct waiter_context *waiter, int timeout_msec);
+	void (*release)(struct waiter_context *waiter);
+};
+
+struct waiter_data {
+	struct waiter_ops ops;
+	unsigned int private_size;
+};
+
+#endif
diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c
index 18f6dfe..0c96ee5 100644
--- a/axfer/xfer-libasound-irq-mmap.c
+++ b/axfer/xfer-libasound-irq-mmap.c
@@ -82,10 +82,22 @@ static int irq_mmap_process_frames(struct libasound_state *state,
 	int err;
 
 	if (state->use_waiter) {
+		unsigned short revents;
+
 		// Wait for hardware IRQ when no avail space in buffer.
-		err = snd_pcm_wait(state->handle, -1);
+		err = xfer_libasound_wait_event(state, -1, &revents);
 		if (err < 0)
 			return err;
+		if (revents & POLLERR) {
+			// TODO: error reporting?
+			return -EIO;
+		}
+		if (!(revents & (POLLIN | POLLOUT)))
+			return -EAGAIN;
+
+		// When rescheduled, current position of data transmission was
+		// queried to actual hardware by a handler of IRQ. No need to
+		// perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC.
 	}
 
 	// Sync cache in user space to data in kernel space to calculate avail
diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c
index 625c095..cf4155f 100644
--- a/axfer/xfer-libasound-irq-rw.c
+++ b/axfer/xfer-libasound-irq-rw.c
@@ -134,10 +134,21 @@ static int r_process_frames_nonblocking(struct libasound_state *state,
 	}
 
 	if (state->use_waiter) {
+		unsigned short revents;
+
 		// Wait for hardware IRQ when no available space.
-		err = snd_pcm_wait(state->handle, -1);
+		err = xfer_libasound_wait_event(state, -1, &revents);
 		if (err < 0)
 			goto error;
+		if (revents & POLLERR) {
+			// TODO: error reporting.
+			err = -EIO;
+			goto error;
+		}
+		if (!(revents & POLLIN)) {
+			err = -EAGAIN;
+			goto error;
+		}
 	}
 
 	// Check available space on the buffer.
@@ -289,10 +300,21 @@ static int w_process_frames_nonblocking(struct libasound_state *state,
 	int err;
 
 	if (state->use_waiter) {
+		unsigned short revents;
+
 		// Wait for hardware IRQ when no left space.
-		err = snd_pcm_wait(state->handle, -1);
+		err = xfer_libasound_wait_event(state, -1, &revents);
 		if (err < 0)
 			goto error;
+		if (revents & POLLERR) {
+			// TODO: error reporting.
+			err = -EIO;
+			goto error;
+		}
+		if (!(revents & POLLOUT)) {
+			err = -EAGAIN;
+			goto error;
+		}
 	}
 
 	// Check available space on the buffer.
diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c
index a731423..5cef9f1 100644
--- a/axfer/xfer-libasound.c
+++ b/axfer/xfer-libasound.c
@@ -201,6 +201,7 @@ static int open_handle(struct xfer_context *xfer)
 
 	if ((state->nonblock || state->mmap) && !state->test_nowait)
 		state->use_waiter = true;
+	state->waiter_type = WAITER_TYPE_DEFAULT;
 
 	err = snd_pcm_hw_params_any(state->handle, state->hw_params);
 	if (err < 0)
@@ -220,6 +221,66 @@ static int open_handle(struct xfer_context *xfer)
 	return set_access_hw_param(state);
 }
 
+static int prepare_waiter(struct libasound_state *state)
+{
+	unsigned int pfd_count;
+	int err;
+
+	// Nothing to do for dafault waiter (=snd_pcm_wait()).
+	if (state->waiter_type == WAITER_TYPE_DEFAULT)
+		return 0;
+
+	err = snd_pcm_poll_descriptors_count(state->handle);
+	if (err < 0)
+		return err;
+	if (err == 0)
+		return -ENXIO;
+	pfd_count = (unsigned int)err;
+
+	state->waiter = malloc(sizeof(*state->waiter));
+	if (state->waiter == NULL)
+		return -ENOMEM;
+
+	err = waiter_context_init(state->waiter, state->waiter_type, pfd_count);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_poll_descriptors(state->handle, state->waiter->pfds,
+				       pfd_count);
+	if (err < 0)
+		return err;
+
+	return waiter_context_prepare(state->waiter);
+}
+
+int xfer_libasound_wait_event(struct libasound_state *state, int timeout_msec,
+			      unsigned short *revents)
+{
+	int err;
+
+	if (state->waiter_type != WAITER_TYPE_DEFAULT) {
+		struct waiter_context *waiter = state->waiter;
+
+		err = waiter_context_wait_event(waiter, timeout_msec);
+		if (err < 0)
+			return err;
+
+		err = snd_pcm_poll_descriptors_revents(state->handle,
+				waiter->pfds, waiter->pfd_count, revents);
+	} else {
+		err = snd_pcm_wait(state->handle, timeout_msec);
+		if (err < 0)
+			return err;
+
+		if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_PLAYBACK)
+			*revents = POLLOUT;
+		else
+			*revents = POLLIN;
+	}
+
+	return err;
+}
+
 static int configure_hw_params(struct libasound_state *state,
 			       snd_pcm_format_t format,
 			       unsigned int samples_per_frame,
@@ -559,6 +620,21 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer,
 	if (xfer->verbose > 0)
 		snd_pcm_dump(state->handle, state->log);
 
+	if (state->use_waiter) {
+		// NOTE: This should be after configuring sw_params due to
+		// timer descriptor for time-based scheduling model.
+		err = prepare_waiter(state);
+		if (err < 0)
+			return err;
+
+		if (xfer->verbose > 0) {
+			logging(state, "Waiter type:\n");
+			logging(state,
+				"  %s\n",
+				waiter_label_from_type(state->waiter_type));
+		}
+	}
+
 	return 0;
 }
 
diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h
index cf940e5..0bcfb40 100644
--- a/axfer/xfer-libasound.h
+++ b/axfer/xfer-libasound.h
@@ -10,6 +10,7 @@
 #define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
 
 #include "xfer.h"
+#include "waiter.h"
 
 #define logging(state, ...) \
 	snd_output_printf(state->log, __VA_ARGS__)
@@ -49,6 +50,9 @@ struct libasound_state {
 	bool no_softvol:1;
 
 	bool use_waiter:1;
+
+	enum waiter_type waiter_type;
+	struct waiter_context *waiter;
 };
 
 // For internal use in 'libasound' module.
@@ -63,6 +67,9 @@ struct xfer_libasound_ops {
 	unsigned int private_size;
 };
 
+int xfer_libasound_wait_event(struct libasound_state *state, int timeout_msec,
+			      unsigned short *revents);
+
 extern const struct xfer_libasound_ops xfer_libasound_irq_rw_ops;
 
 extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops;
-- 
2.19.1



More information about the Alsa-devel mailing list