diff --git a/axfer/Makefile.am b/axfer/Makefile.am index 386f8e2..960811e 100644 --- a/axfer/Makefile.am +++ b/axfer/Makefile.am @@ -46,4 +46,5 @@ axfer_SOURCES = \ frame-cache.h \ frame-cache.c \ xfer-libasound-irq-rw.c \ - subcmd-transfer.c + subcmd-transfer.c \ + xfer-libasound-irq-mmap.c diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c new file mode 100644 index 0000000..87ef7e0 --- /dev/null +++ b/axfer/xfer-libasound-irq-mmap.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation. +// +// Copyright (c) 2018 Takashi Sakamoto +// +// 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), +}; diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c index cb26b69..c2e1282 100644 --- a/axfer/xfer-libasound.c +++ b/axfer/xfer-libasound.c @@ -14,10 +14,11 @@ enum no_short_opts { 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}, }; @@ -49,6 +50,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key, 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 @@ -70,6 +73,13 @@ int xfer_libasound_validate_opts(struct xfer_context *xfer) 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; } @@ -82,8 +92,13 @@ static int set_access_hw_param(struct libasound_state *state) 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); @@ -279,6 +294,12 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer, 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; } diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h index 6656aeb..550b1c2 100644 --- a/axfer/xfer-libasound.h +++ b/axfer/xfer-libasound.h @@ -32,6 +32,7 @@ struct libasound_state { bool finish_at_xrun:1; bool nonblock:1; + bool mmap:1; }; // For internal use in 'libasound' module. @@ -48,4 +49,7 @@ struct xfer_libasound_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_w_ops; + #endif