mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-23 07:46:32 +01:00
axfer: add a common interface of waiter for I/O event notification
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>
This commit is contained in:
parent
5eea6d346f
commit
cd211d8c22
7 changed files with 277 additions and 4 deletions
|
@ -22,6 +22,7 @@ noinst_HEADERS = \
|
||||||
xfer.h \
|
xfer.h \
|
||||||
xfer-libasound.h \
|
xfer-libasound.h \
|
||||||
frame-cache.h
|
frame-cache.h
|
||||||
|
waiter.h
|
||||||
|
|
||||||
axfer_SOURCES = \
|
axfer_SOURCES = \
|
||||||
misc.h \
|
misc.h \
|
||||||
|
@ -47,4 +48,6 @@ axfer_SOURCES = \
|
||||||
frame-cache.c \
|
frame-cache.c \
|
||||||
xfer-libasound-irq-rw.c \
|
xfer-libasound-irq-rw.c \
|
||||||
subcmd-transfer.c \
|
subcmd-transfer.c \
|
||||||
xfer-libasound-irq-mmap.c
|
xfer-libasound-irq-mmap.c \
|
||||||
|
waiter.h \
|
||||||
|
waiter.c
|
||||||
|
|
99
axfer/waiter.c
Normal file
99
axfer/waiter.c
Normal file
|
@ -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;
|
||||||
|
}
|
54
axfer/waiter.h
Normal file
54
axfer/waiter.h
Normal file
|
@ -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
|
|
@ -82,10 +82,22 @@ static int irq_mmap_process_frames(struct libasound_state *state,
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (state->use_waiter) {
|
if (state->use_waiter) {
|
||||||
|
unsigned short revents;
|
||||||
|
|
||||||
// Wait for hardware IRQ when no avail space in buffer.
|
// 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)
|
if (err < 0)
|
||||||
return err;
|
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
|
// Sync cache in user space to data in kernel space to calculate avail
|
||||||
|
|
|
@ -134,10 +134,21 @@ static int r_process_frames_nonblocking(struct libasound_state *state,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state->use_waiter) {
|
if (state->use_waiter) {
|
||||||
|
unsigned short revents;
|
||||||
|
|
||||||
// Wait for hardware IRQ when no available space.
|
// 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)
|
if (err < 0)
|
||||||
goto error;
|
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.
|
// Check available space on the buffer.
|
||||||
|
@ -289,10 +300,21 @@ static int w_process_frames_nonblocking(struct libasound_state *state,
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (state->use_waiter) {
|
if (state->use_waiter) {
|
||||||
|
unsigned short revents;
|
||||||
|
|
||||||
// Wait for hardware IRQ when no left space.
|
// 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)
|
if (err < 0)
|
||||||
goto error;
|
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.
|
// Check available space on the buffer.
|
||||||
|
|
|
@ -201,6 +201,7 @@ static int open_handle(struct xfer_context *xfer)
|
||||||
|
|
||||||
if ((state->nonblock || state->mmap) && !state->test_nowait)
|
if ((state->nonblock || state->mmap) && !state->test_nowait)
|
||||||
state->use_waiter = true;
|
state->use_waiter = true;
|
||||||
|
state->waiter_type = WAITER_TYPE_DEFAULT;
|
||||||
|
|
||||||
err = snd_pcm_hw_params_any(state->handle, state->hw_params);
|
err = snd_pcm_hw_params_any(state->handle, state->hw_params);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -220,6 +221,66 @@ static int open_handle(struct xfer_context *xfer)
|
||||||
return set_access_hw_param(state);
|
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,
|
static int configure_hw_params(struct libasound_state *state,
|
||||||
snd_pcm_format_t format,
|
snd_pcm_format_t format,
|
||||||
unsigned int samples_per_frame,
|
unsigned int samples_per_frame,
|
||||||
|
@ -559,6 +620,21 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer,
|
||||||
if (xfer->verbose > 0)
|
if (xfer->verbose > 0)
|
||||||
snd_pcm_dump(state->handle, state->log);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
|
#define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
|
||||||
|
|
||||||
#include "xfer.h"
|
#include "xfer.h"
|
||||||
|
#include "waiter.h"
|
||||||
|
|
||||||
#define logging(state, ...) \
|
#define logging(state, ...) \
|
||||||
snd_output_printf(state->log, __VA_ARGS__)
|
snd_output_printf(state->log, __VA_ARGS__)
|
||||||
|
@ -49,6 +50,9 @@ struct libasound_state {
|
||||||
bool no_softvol:1;
|
bool no_softvol:1;
|
||||||
|
|
||||||
bool use_waiter:1;
|
bool use_waiter:1;
|
||||||
|
|
||||||
|
enum waiter_type waiter_type;
|
||||||
|
struct waiter_context *waiter;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For internal use in 'libasound' module.
|
// For internal use in 'libasound' module.
|
||||||
|
@ -63,6 +67,9 @@ struct xfer_libasound_ops {
|
||||||
unsigned int private_size;
|
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_rw_ops;
|
||||||
|
|
||||||
extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops;
|
extern const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops;
|
||||||
|
|
Loading…
Reference in a new issue