2018-11-13 07:41:35 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//
|
|
|
|
// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation.
|
|
|
|
//
|
|
|
|
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
|
|
|
//
|
|
|
|
// Licensed under the terms of the GNU General Public License, version 2.
|
|
|
|
|
|
|
|
#include "xfer-libasound.h"
|
|
|
|
#include "misc.h"
|
|
|
|
|
|
|
|
struct map_layout {
|
|
|
|
snd_pcm_status_t *status;
|
|
|
|
|
|
|
|
char **vector;
|
|
|
|
unsigned int samples_per_frame;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int irq_mmap_pre_process(struct libasound_state *state)
|
|
|
|
{
|
|
|
|
struct map_layout *layout = state->private_data;
|
|
|
|
snd_pcm_access_t access;
|
|
|
|
snd_pcm_uframes_t frame_offset;
|
|
|
|
snd_pcm_uframes_t avail = 0;
|
|
|
|
int i;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_pcm_status_malloc(&layout->status);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = snd_pcm_hw_params_get_access(state->hw_params, &access);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = snd_pcm_hw_params_get_channels(state->hw_params,
|
|
|
|
&layout->samples_per_frame);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
|
|
|
|
layout->vector = calloc(layout->samples_per_frame,
|
|
|
|
sizeof(*layout->vector));
|
|
|
|
if (layout->vector == NULL)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->verbose) {
|
|
|
|
const snd_pcm_channel_area_t *areas;
|
|
|
|
err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
|
|
|
|
&avail);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
logging(state, "attributes for mapped page frame:\n");
|
|
|
|
for (i = 0; i < layout->samples_per_frame; ++i) {
|
|
|
|
const snd_pcm_channel_area_t *area = areas + i;
|
|
|
|
|
|
|
|
logging(state, " sample number: %d\n", i);
|
|
|
|
logging(state, " address: %p\n", area->addr);
|
|
|
|
logging(state, " bits for offset: %u\n", area->first);
|
|
|
|
logging(state, " bits/frame: %u\n", area->step);
|
|
|
|
}
|
|
|
|
logging(state, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int irq_mmap_process_frames(struct libasound_state *state,
|
|
|
|
unsigned int *frame_count,
|
|
|
|
struct mapper_context *mapper,
|
|
|
|
struct container_context *cntrs)
|
|
|
|
{
|
|
|
|
struct map_layout *layout = state->private_data;
|
|
|
|
const snd_pcm_channel_area_t *areas;
|
|
|
|
snd_pcm_uframes_t frame_offset;
|
2019-03-13 14:51:12 +01:00
|
|
|
snd_pcm_uframes_t avail;
|
2018-11-13 07:41:35 +01:00
|
|
|
unsigned int avail_count;
|
|
|
|
void *frame_buf;
|
|
|
|
snd_pcm_sframes_t consumed_count;
|
|
|
|
int err;
|
|
|
|
|
2018-11-13 07:41:36 +01:00
|
|
|
if (state->use_waiter) {
|
2019-10-29 16:14:14 +01:00
|
|
|
unsigned int msec_per_buffer;
|
2018-11-13 07:41:40 +01:00
|
|
|
unsigned short revents;
|
|
|
|
|
2019-10-29 16:14:14 +01:00
|
|
|
// Wait during msec equivalent to all audio data frames in
|
|
|
|
// buffer instead of period, for safe.
|
|
|
|
err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
|
|
|
|
&msec_per_buffer, NULL);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
msec_per_buffer /= 1000;
|
|
|
|
|
2018-11-13 07:41:36 +01:00
|
|
|
// Wait for hardware IRQ when no avail space in buffer.
|
2019-10-29 16:14:14 +01:00
|
|
|
err = xfer_libasound_wait_event(state, msec_per_buffer,
|
|
|
|
&revents);
|
|
|
|
if (err == -ETIMEDOUT) {
|
|
|
|
logging(state,
|
|
|
|
"No event occurs for PCM substream during %u "
|
|
|
|
"msec. The implementaion of kernel driver or "
|
|
|
|
"userland backend causes this issue.\n",
|
|
|
|
msec_per_buffer);
|
|
|
|
return err;
|
|
|
|
}
|
2018-11-13 07:41:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2018-11-13 07:41:40 +01:00
|
|
|
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.
|
2018-11-13 07:41:36 +01:00
|
|
|
}
|
2018-11-13 07:41:35 +01:00
|
|
|
|
|
|
|
// Sync cache in user space to data in kernel space to calculate avail
|
|
|
|
// frames according to the latest positions on PCM buffer.
|
|
|
|
//
|
|
|
|
// This has an additional advantage to handle libasound PCM plugins.
|
|
|
|
// Most of libasound PCM plugins perform resampling in .avail_update()
|
|
|
|
// callback for capture PCM substream, then update positions on buffer.
|
|
|
|
//
|
|
|
|
// MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
|
|
|
|
// return the same number of available frames.
|
|
|
|
avail = snd_pcm_avail_update(state->handle);
|
2019-03-13 14:51:12 +01:00
|
|
|
if ((snd_pcm_sframes_t)avail < 0)
|
2018-11-13 07:41:35 +01:00
|
|
|
return (int)avail;
|
|
|
|
if (*frame_count < avail)
|
|
|
|
avail = *frame_count;
|
|
|
|
|
|
|
|
err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
// Trim according up to expected frame count.
|
|
|
|
if (*frame_count < avail)
|
|
|
|
avail_count = *frame_count;
|
|
|
|
else
|
|
|
|
avail_count = (unsigned int)avail;
|
|
|
|
|
|
|
|
// TODO: Perhaps, the complex layout can be supported as a variation of
|
|
|
|
// vector type. However, there's no driver with this layout.
|
|
|
|
if (layout->vector == NULL) {
|
2020-02-05 09:12:19 +01:00
|
|
|
char *buf;
|
|
|
|
buf = areas[0].addr;
|
|
|
|
buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
|
|
|
|
frame_buf = buf;
|
2018-11-13 07:41:35 +01:00
|
|
|
} else {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < layout->samples_per_frame; ++i) {
|
|
|
|
layout->vector[i] = areas[i].addr;
|
|
|
|
layout->vector[i] += snd_pcm_samples_to_bytes(
|
|
|
|
state->handle, frame_offset);
|
|
|
|
}
|
|
|
|
frame_buf = layout->vector;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
|
|
|
|
cntrs);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (avail_count == 0) {
|
|
|
|
*frame_count = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
|
|
|
|
avail_count);
|
|
|
|
if (consumed_count < 0)
|
|
|
|
return (int)consumed_count;
|
|
|
|
if (consumed_count != avail_count)
|
|
|
|
logging(state, "A bug of access plugin for this PCM node.\n");
|
|
|
|
|
|
|
|
*frame_count = consumed_count;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int irq_mmap_r_process_frames(struct libasound_state *state,
|
|
|
|
unsigned *frame_count,
|
|
|
|
struct mapper_context *mapper,
|
|
|
|
struct container_context *cntrs)
|
|
|
|
{
|
|
|
|
struct map_layout *layout = state->private_data;
|
|
|
|
snd_pcm_state_t s;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
// To querying current status of hardware, we need to care of
|
|
|
|
// synchronization between 3 levels:
|
|
|
|
// 1. status to actual hardware by driver.
|
|
|
|
// 2. status data in kernel space.
|
|
|
|
// 3. status data in user space.
|
|
|
|
//
|
|
|
|
// Kernel driver query 1 and sync 2, according to requests of some
|
|
|
|
// ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
|
|
|
|
// supports mmap(2) operation on cache coherent architectures, some
|
|
|
|
// ioctl(2) commands on cache incoherent architecture. In usage of the
|
|
|
|
// former mechanism, we need to care of concurrent access by IRQ context
|
|
|
|
// and process context to the mapped page frame.
|
|
|
|
// In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
|
|
|
|
// SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
|
|
|
|
// mapped page frame is unused regardless of architectures in a point of
|
|
|
|
// cache coherency.
|
|
|
|
err = snd_pcm_status(state->handle, layout->status);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
s = snd_pcm_status_get_state(layout->status);
|
|
|
|
|
|
|
|
// TODO: if reporting something, do here with the status data.
|
|
|
|
|
|
|
|
// For capture direction, need to start stream explicitly.
|
|
|
|
if (s != SND_PCM_STATE_RUNNING) {
|
|
|
|
if (s != SND_PCM_STATE_PREPARED) {
|
|
|
|
err = -EPIPE;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = snd_pcm_start(state->handle);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
|
|
|
*frame_count = 0;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int irq_mmap_w_process_frames(struct libasound_state *state,
|
|
|
|
unsigned *frame_count,
|
|
|
|
struct mapper_context *mapper,
|
|
|
|
struct container_context *cntrs)
|
|
|
|
{
|
|
|
|
struct map_layout *layout = state->private_data;
|
|
|
|
snd_pcm_state_t s;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
// Read my comment in 'irq_mmap_r_process_frames().
|
|
|
|
err = snd_pcm_status(state->handle, layout->status);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
s = snd_pcm_status_get_state(layout->status);
|
|
|
|
|
|
|
|
// TODO: if reporting something, do here with the status data.
|
|
|
|
|
|
|
|
err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
// Need to start playback stream explicitly
|
|
|
|
if (s != SND_PCM_STATE_RUNNING) {
|
|
|
|
if (s != SND_PCM_STATE_PREPARED) {
|
|
|
|
err = -EPIPE;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = snd_pcm_start(state->handle);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
|
|
|
*frame_count = 0;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void irq_mmap_post_process(struct libasound_state *state)
|
|
|
|
{
|
|
|
|
struct map_layout *layout = state->private_data;
|
|
|
|
|
|
|
|
if (layout->status)
|
|
|
|
snd_pcm_status_free(layout->status);
|
|
|
|
layout->status = NULL;
|
|
|
|
|
|
|
|
free(layout->vector);
|
|
|
|
layout->vector = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
|
|
|
|
.pre_process = irq_mmap_pre_process,
|
|
|
|
.process_frames = irq_mmap_w_process_frames,
|
|
|
|
.post_process = irq_mmap_post_process,
|
|
|
|
.private_size = sizeof(struct map_layout),
|
|
|
|
};
|
|
|
|
|
|
|
|
const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
|
|
|
|
.pre_process = irq_mmap_pre_process,
|
|
|
|
.process_frames = irq_mmap_r_process_frames,
|
|
|
|
.post_process = irq_mmap_post_process,
|
|
|
|
.private_size = sizeof(struct map_layout),
|
|
|
|
};
|