--- /dev/null
+// 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;
+ snd_pcm_uframes_t avail;
+ unsigned int avail_count;
+ void *frame_buf;
+ snd_pcm_sframes_t consumed_count;
+ int err;
+
+ // Wait for hardware IRQ when no avail space in buffer.
+ err = snd_pcm_wait(state->handle, -1);
+ if (err < 0)
+ return err;
+
+ // 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);
+ if (avail < 0)
+ 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) {
+ frame_buf = areas[0].addr;
+ frame_buf += snd_pcm_frames_to_bytes(state->handle,
+ frame_offset);
+ } 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),
+};
OPT_FATAL_ERRORS = 200,
};
-#define S_OPTS "D:N"
+#define S_OPTS "D:NM"
static const struct option l_opts[] = {
{"device", 1, 0, 'D'},
{"nonblock", 0, 0, 'N'},
+ {"mmap", 0, 0, 'M'},
// For debugging.
{"fatal-errors", 0, 0, OPT_FATAL_ERRORS},
};
state->node_literal = arg_duplicate_string(optarg, &err);
else if (key == 'N')
state->nonblock = true;
+ else if (key == 'M')
+ state->mmap = true;
else if (key == OPT_FATAL_ERRORS)
state->finish_at_xrun = true;
else
return -ENOMEM;
}
+ if (state->mmap && state->nonblock) {
+ fprintf(stderr,
+ "An option for mmap operation should not be used with "
+ "nonblocking option.\n");
+ return -EINVAL;
+ }
+
return err;
}
if (err < 0)
return err;
snd_pcm_access_mask_none(mask);
- snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED);
- snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED);
+ if (state->mmap) {
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+ } else {
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED);
+ }
err = snd_pcm_hw_params_set_access_mask(state->handle, state->hw_params,
mask);
snd_pcm_access_mask_free(mask);
if (*access == SND_PCM_ACCESS_RW_INTERLEAVED ||
*access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
state->ops = &xfer_libasound_irq_rw_ops;
+ } else if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+ *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
+ if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE)
+ state->ops = &xfer_libasound_irq_mmap_r_ops;
+ else
+ state->ops = &xfer_libasound_irq_mmap_w_ops;
} else {
return -ENXIO;
}