]> git.alsa-project.org Git - alsa-utils.git/commitdiff
axfer: add a common interface of waiter for I/O event notification
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Tue, 13 Nov 2018 06:41:40 +0000 (15:41 +0900)
committerTakashi Iwai <tiwai@suse.de>
Tue, 13 Nov 2018 11:04:48 +0000 (12:04 +0100)
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@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
axfer/Makefile.am
axfer/waiter.c [new file with mode: 0644]
axfer/waiter.h [new file with mode: 0644]
axfer/xfer-libasound-irq-mmap.c
axfer/xfer-libasound-irq-rw.c
axfer/xfer-libasound.c
axfer/xfer-libasound.h

index 960811e2ffacdb800fd2333e2b4e1fa0411c8d09..e425a28fd24feb1a3afa9b1d1e68c2802d978ffe 100644 (file)
@@ -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 (file)
index 0000000..0bc4740
--- /dev/null
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// waiter.c - I/O event waiter.
+//
+// Copyright (c) 2018 Takashi Sakamoto <o-takashi@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 (file)
index 0000000..17e01cb
--- /dev/null
@@ -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@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
index 18f6dfe7b3afd0f1dfb13537a50a3b61c1287cf4..0c96ee5e870ac78a0ff54ebe7278619fdc4be64d 100644 (file)
@@ -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
index 625c095d95b424964d43b938a98ce1c1fe0f7494..cf4155fbc6de1390f2cfc28deb0785998c039740 100644 (file)
@@ -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.
index a731423d82f729be07d564dd573494c174cc2bd6..5cef9f1686e78b69108e28ef2ac1914c04dbd3af 100644 (file)
@@ -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;
 }
 
index cf940e54294a05b12667375f33a810fee686092a..0bcfb4074bc2ff43c5308300d2a8f1acfa2bfd6a 100644 (file)
@@ -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;