// 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; if (state->use_waiter) { unsigned int msec_per_buffer; unsigned short revents; // 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; // Wait for hardware IRQ when no avail space in buffer. 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; } 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 // 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 ((snd_pcm_sframes_t)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) { char *buf; buf = areas[0].addr; buf += snd_pcm_frames_to_bytes(state->handle, frame_offset); frame_buf = buf; } 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), };