1c72fcc34Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 2c72fcc34Sopenharmony_ci// 3c72fcc34Sopenharmony_ci// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation. 4c72fcc34Sopenharmony_ci// 5c72fcc34Sopenharmony_ci// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp> 6c72fcc34Sopenharmony_ci// 7c72fcc34Sopenharmony_ci// Licensed under the terms of the GNU General Public License, version 2. 8c72fcc34Sopenharmony_ci 9c72fcc34Sopenharmony_ci#include "xfer-libasound.h" 10c72fcc34Sopenharmony_ci#include "misc.h" 11c72fcc34Sopenharmony_ci 12c72fcc34Sopenharmony_cistruct map_layout { 13c72fcc34Sopenharmony_ci snd_pcm_status_t *status; 14c72fcc34Sopenharmony_ci 15c72fcc34Sopenharmony_ci char **vector; 16c72fcc34Sopenharmony_ci unsigned int samples_per_frame; 17c72fcc34Sopenharmony_ci}; 18c72fcc34Sopenharmony_ci 19c72fcc34Sopenharmony_cistatic int irq_mmap_pre_process(struct libasound_state *state) 20c72fcc34Sopenharmony_ci{ 21c72fcc34Sopenharmony_ci struct map_layout *layout = state->private_data; 22c72fcc34Sopenharmony_ci snd_pcm_access_t access; 23c72fcc34Sopenharmony_ci snd_pcm_uframes_t frame_offset; 24c72fcc34Sopenharmony_ci snd_pcm_uframes_t avail = 0; 25c72fcc34Sopenharmony_ci int i; 26c72fcc34Sopenharmony_ci int err; 27c72fcc34Sopenharmony_ci 28c72fcc34Sopenharmony_ci err = snd_pcm_status_malloc(&layout->status); 29c72fcc34Sopenharmony_ci if (err < 0) 30c72fcc34Sopenharmony_ci return err; 31c72fcc34Sopenharmony_ci 32c72fcc34Sopenharmony_ci err = snd_pcm_hw_params_get_access(state->hw_params, &access); 33c72fcc34Sopenharmony_ci if (err < 0) 34c72fcc34Sopenharmony_ci return err; 35c72fcc34Sopenharmony_ci 36c72fcc34Sopenharmony_ci err = snd_pcm_hw_params_get_channels(state->hw_params, 37c72fcc34Sopenharmony_ci &layout->samples_per_frame); 38c72fcc34Sopenharmony_ci if (err < 0) 39c72fcc34Sopenharmony_ci return err; 40c72fcc34Sopenharmony_ci 41c72fcc34Sopenharmony_ci if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { 42c72fcc34Sopenharmony_ci layout->vector = calloc(layout->samples_per_frame, 43c72fcc34Sopenharmony_ci sizeof(*layout->vector)); 44c72fcc34Sopenharmony_ci if (layout->vector == NULL) 45c72fcc34Sopenharmony_ci return err; 46c72fcc34Sopenharmony_ci } 47c72fcc34Sopenharmony_ci 48c72fcc34Sopenharmony_ci if (state->verbose) { 49c72fcc34Sopenharmony_ci const snd_pcm_channel_area_t *areas; 50c72fcc34Sopenharmony_ci err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, 51c72fcc34Sopenharmony_ci &avail); 52c72fcc34Sopenharmony_ci if (err < 0) 53c72fcc34Sopenharmony_ci return err; 54c72fcc34Sopenharmony_ci 55c72fcc34Sopenharmony_ci logging(state, "attributes for mapped page frame:\n"); 56c72fcc34Sopenharmony_ci for (i = 0; i < (int)layout->samples_per_frame; ++i) { 57c72fcc34Sopenharmony_ci const snd_pcm_channel_area_t *area = areas + i; 58c72fcc34Sopenharmony_ci 59c72fcc34Sopenharmony_ci logging(state, " sample number: %d\n", i); 60c72fcc34Sopenharmony_ci logging(state, " address: %p\n", area->addr); 61c72fcc34Sopenharmony_ci logging(state, " bits for offset: %u\n", area->first); 62c72fcc34Sopenharmony_ci logging(state, " bits/frame: %u\n", area->step); 63c72fcc34Sopenharmony_ci } 64c72fcc34Sopenharmony_ci logging(state, "\n"); 65c72fcc34Sopenharmony_ci } 66c72fcc34Sopenharmony_ci 67c72fcc34Sopenharmony_ci return 0; 68c72fcc34Sopenharmony_ci} 69c72fcc34Sopenharmony_ci 70c72fcc34Sopenharmony_cistatic int irq_mmap_process_frames(struct libasound_state *state, 71c72fcc34Sopenharmony_ci unsigned int *frame_count, 72c72fcc34Sopenharmony_ci struct mapper_context *mapper, 73c72fcc34Sopenharmony_ci struct container_context *cntrs) 74c72fcc34Sopenharmony_ci{ 75c72fcc34Sopenharmony_ci struct map_layout *layout = state->private_data; 76c72fcc34Sopenharmony_ci const snd_pcm_channel_area_t *areas; 77c72fcc34Sopenharmony_ci snd_pcm_uframes_t frame_offset; 78c72fcc34Sopenharmony_ci snd_pcm_uframes_t avail; 79c72fcc34Sopenharmony_ci unsigned int avail_count; 80c72fcc34Sopenharmony_ci void *frame_buf; 81c72fcc34Sopenharmony_ci snd_pcm_sframes_t consumed_count; 82c72fcc34Sopenharmony_ci int err; 83c72fcc34Sopenharmony_ci 84c72fcc34Sopenharmony_ci if (state->use_waiter) { 85c72fcc34Sopenharmony_ci unsigned int msec_per_buffer; 86c72fcc34Sopenharmony_ci unsigned short revents; 87c72fcc34Sopenharmony_ci 88c72fcc34Sopenharmony_ci // Wait during msec equivalent to all audio data frames in 89c72fcc34Sopenharmony_ci // buffer instead of period, for safe. 90c72fcc34Sopenharmony_ci err = snd_pcm_hw_params_get_buffer_time(state->hw_params, 91c72fcc34Sopenharmony_ci &msec_per_buffer, NULL); 92c72fcc34Sopenharmony_ci if (err < 0) 93c72fcc34Sopenharmony_ci return err; 94c72fcc34Sopenharmony_ci msec_per_buffer /= 1000; 95c72fcc34Sopenharmony_ci 96c72fcc34Sopenharmony_ci // Wait for hardware IRQ when no avail space in buffer. 97c72fcc34Sopenharmony_ci err = xfer_libasound_wait_event(state, msec_per_buffer, 98c72fcc34Sopenharmony_ci &revents); 99c72fcc34Sopenharmony_ci if (err == -ETIMEDOUT) { 100c72fcc34Sopenharmony_ci logging(state, 101c72fcc34Sopenharmony_ci "No event occurs for PCM substream during %u " 102c72fcc34Sopenharmony_ci "msec. The implementaion of kernel driver or " 103c72fcc34Sopenharmony_ci "userland backend causes this issue.\n", 104c72fcc34Sopenharmony_ci msec_per_buffer); 105c72fcc34Sopenharmony_ci return err; 106c72fcc34Sopenharmony_ci } 107c72fcc34Sopenharmony_ci if (err < 0) 108c72fcc34Sopenharmony_ci return err; 109c72fcc34Sopenharmony_ci if (revents & POLLERR) { 110c72fcc34Sopenharmony_ci // TODO: error reporting? 111c72fcc34Sopenharmony_ci return -EIO; 112c72fcc34Sopenharmony_ci } 113c72fcc34Sopenharmony_ci if (!(revents & (POLLIN | POLLOUT))) 114c72fcc34Sopenharmony_ci return -EAGAIN; 115c72fcc34Sopenharmony_ci 116c72fcc34Sopenharmony_ci // When rescheduled, current position of data transmission was 117c72fcc34Sopenharmony_ci // queried to actual hardware by a handler of IRQ. No need to 118c72fcc34Sopenharmony_ci // perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC. 119c72fcc34Sopenharmony_ci } 120c72fcc34Sopenharmony_ci 121c72fcc34Sopenharmony_ci // Sync cache in user space to data in kernel space to calculate avail 122c72fcc34Sopenharmony_ci // frames according to the latest positions on PCM buffer. 123c72fcc34Sopenharmony_ci // 124c72fcc34Sopenharmony_ci // This has an additional advantage to handle libasound PCM plugins. 125c72fcc34Sopenharmony_ci // Most of libasound PCM plugins perform resampling in .avail_update() 126c72fcc34Sopenharmony_ci // callback for capture PCM substream, then update positions on buffer. 127c72fcc34Sopenharmony_ci // 128c72fcc34Sopenharmony_ci // MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can 129c72fcc34Sopenharmony_ci // return the same number of available frames. 130c72fcc34Sopenharmony_ci avail = snd_pcm_avail_update(state->handle); 131c72fcc34Sopenharmony_ci if ((snd_pcm_sframes_t)avail < 0) 132c72fcc34Sopenharmony_ci return (int)avail; 133c72fcc34Sopenharmony_ci if (*frame_count < avail) 134c72fcc34Sopenharmony_ci avail = *frame_count; 135c72fcc34Sopenharmony_ci 136c72fcc34Sopenharmony_ci err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail); 137c72fcc34Sopenharmony_ci if (err < 0) 138c72fcc34Sopenharmony_ci return err; 139c72fcc34Sopenharmony_ci 140c72fcc34Sopenharmony_ci // Trim according up to expected frame count. 141c72fcc34Sopenharmony_ci if (*frame_count < avail) 142c72fcc34Sopenharmony_ci avail_count = *frame_count; 143c72fcc34Sopenharmony_ci else 144c72fcc34Sopenharmony_ci avail_count = (unsigned int)avail; 145c72fcc34Sopenharmony_ci 146c72fcc34Sopenharmony_ci // TODO: Perhaps, the complex layout can be supported as a variation of 147c72fcc34Sopenharmony_ci // vector type. However, there's no driver with this layout. 148c72fcc34Sopenharmony_ci if (layout->vector == NULL) { 149c72fcc34Sopenharmony_ci char *buf; 150c72fcc34Sopenharmony_ci buf = areas[0].addr; 151c72fcc34Sopenharmony_ci buf += snd_pcm_frames_to_bytes(state->handle, frame_offset); 152c72fcc34Sopenharmony_ci frame_buf = buf; 153c72fcc34Sopenharmony_ci } else { 154c72fcc34Sopenharmony_ci int i; 155c72fcc34Sopenharmony_ci for (i = 0; i < (int)layout->samples_per_frame; ++i) { 156c72fcc34Sopenharmony_ci layout->vector[i] = areas[i].addr; 157c72fcc34Sopenharmony_ci layout->vector[i] += snd_pcm_samples_to_bytes( 158c72fcc34Sopenharmony_ci state->handle, frame_offset); 159c72fcc34Sopenharmony_ci } 160c72fcc34Sopenharmony_ci frame_buf = layout->vector; 161c72fcc34Sopenharmony_ci } 162c72fcc34Sopenharmony_ci 163c72fcc34Sopenharmony_ci err = mapper_context_process_frames(mapper, frame_buf, &avail_count, 164c72fcc34Sopenharmony_ci cntrs); 165c72fcc34Sopenharmony_ci if (err < 0) 166c72fcc34Sopenharmony_ci return err; 167c72fcc34Sopenharmony_ci if (avail_count == 0) { 168c72fcc34Sopenharmony_ci *frame_count = 0; 169c72fcc34Sopenharmony_ci return 0; 170c72fcc34Sopenharmony_ci } 171c72fcc34Sopenharmony_ci 172c72fcc34Sopenharmony_ci consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, 173c72fcc34Sopenharmony_ci avail_count); 174c72fcc34Sopenharmony_ci if (consumed_count < 0) 175c72fcc34Sopenharmony_ci return (int)consumed_count; 176c72fcc34Sopenharmony_ci if (consumed_count != avail_count) 177c72fcc34Sopenharmony_ci logging(state, "A bug of access plugin for this PCM node.\n"); 178c72fcc34Sopenharmony_ci 179c72fcc34Sopenharmony_ci *frame_count = consumed_count; 180c72fcc34Sopenharmony_ci 181c72fcc34Sopenharmony_ci return 0; 182c72fcc34Sopenharmony_ci} 183c72fcc34Sopenharmony_ci 184c72fcc34Sopenharmony_cistatic int irq_mmap_r_process_frames(struct libasound_state *state, 185c72fcc34Sopenharmony_ci unsigned *frame_count, 186c72fcc34Sopenharmony_ci struct mapper_context *mapper, 187c72fcc34Sopenharmony_ci struct container_context *cntrs) 188c72fcc34Sopenharmony_ci{ 189c72fcc34Sopenharmony_ci struct map_layout *layout = state->private_data; 190c72fcc34Sopenharmony_ci snd_pcm_state_t s; 191c72fcc34Sopenharmony_ci int err; 192c72fcc34Sopenharmony_ci 193c72fcc34Sopenharmony_ci // To querying current status of hardware, we need to care of 194c72fcc34Sopenharmony_ci // synchronization between 3 levels: 195c72fcc34Sopenharmony_ci // 1. status to actual hardware by driver. 196c72fcc34Sopenharmony_ci // 2. status data in kernel space. 197c72fcc34Sopenharmony_ci // 3. status data in user space. 198c72fcc34Sopenharmony_ci // 199c72fcc34Sopenharmony_ci // Kernel driver query 1 and sync 2, according to requests of some 200c72fcc34Sopenharmony_ci // ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core 201c72fcc34Sopenharmony_ci // supports mmap(2) operation on cache coherent architectures, some 202c72fcc34Sopenharmony_ci // ioctl(2) commands on cache incoherent architecture. In usage of the 203c72fcc34Sopenharmony_ci // former mechanism, we need to care of concurrent access by IRQ context 204c72fcc34Sopenharmony_ci // and process context to the mapped page frame. 205c72fcc34Sopenharmony_ci // In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and 206c72fcc34Sopenharmony_ci // SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because 207c72fcc34Sopenharmony_ci // mapped page frame is unused regardless of architectures in a point of 208c72fcc34Sopenharmony_ci // cache coherency. 209c72fcc34Sopenharmony_ci err = snd_pcm_status(state->handle, layout->status); 210c72fcc34Sopenharmony_ci if (err < 0) 211c72fcc34Sopenharmony_ci goto error; 212c72fcc34Sopenharmony_ci s = snd_pcm_status_get_state(layout->status); 213c72fcc34Sopenharmony_ci 214c72fcc34Sopenharmony_ci // TODO: if reporting something, do here with the status data. 215c72fcc34Sopenharmony_ci 216c72fcc34Sopenharmony_ci // For capture direction, need to start stream explicitly. 217c72fcc34Sopenharmony_ci if (s != SND_PCM_STATE_RUNNING) { 218c72fcc34Sopenharmony_ci if (s != SND_PCM_STATE_PREPARED) { 219c72fcc34Sopenharmony_ci err = -EPIPE; 220c72fcc34Sopenharmony_ci goto error; 221c72fcc34Sopenharmony_ci } 222c72fcc34Sopenharmony_ci 223c72fcc34Sopenharmony_ci err = snd_pcm_start(state->handle); 224c72fcc34Sopenharmony_ci if (err < 0) 225c72fcc34Sopenharmony_ci goto error; 226c72fcc34Sopenharmony_ci } 227c72fcc34Sopenharmony_ci 228c72fcc34Sopenharmony_ci err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); 229c72fcc34Sopenharmony_ci if (err < 0) 230c72fcc34Sopenharmony_ci goto error; 231c72fcc34Sopenharmony_ci 232c72fcc34Sopenharmony_ci return 0; 233c72fcc34Sopenharmony_cierror: 234c72fcc34Sopenharmony_ci *frame_count = 0; 235c72fcc34Sopenharmony_ci return err; 236c72fcc34Sopenharmony_ci} 237c72fcc34Sopenharmony_ci 238c72fcc34Sopenharmony_cistatic int irq_mmap_w_process_frames(struct libasound_state *state, 239c72fcc34Sopenharmony_ci unsigned *frame_count, 240c72fcc34Sopenharmony_ci struct mapper_context *mapper, 241c72fcc34Sopenharmony_ci struct container_context *cntrs) 242c72fcc34Sopenharmony_ci{ 243c72fcc34Sopenharmony_ci struct map_layout *layout = state->private_data; 244c72fcc34Sopenharmony_ci snd_pcm_state_t s; 245c72fcc34Sopenharmony_ci int err; 246c72fcc34Sopenharmony_ci 247c72fcc34Sopenharmony_ci // Read my comment in 'irq_mmap_r_process_frames(). 248c72fcc34Sopenharmony_ci err = snd_pcm_status(state->handle, layout->status); 249c72fcc34Sopenharmony_ci if (err < 0) 250c72fcc34Sopenharmony_ci goto error; 251c72fcc34Sopenharmony_ci s = snd_pcm_status_get_state(layout->status); 252c72fcc34Sopenharmony_ci 253c72fcc34Sopenharmony_ci // TODO: if reporting something, do here with the status data. 254c72fcc34Sopenharmony_ci 255c72fcc34Sopenharmony_ci err = irq_mmap_process_frames(state, frame_count, mapper, cntrs); 256c72fcc34Sopenharmony_ci if (err < 0) 257c72fcc34Sopenharmony_ci goto error; 258c72fcc34Sopenharmony_ci 259c72fcc34Sopenharmony_ci // Need to start playback stream explicitly 260c72fcc34Sopenharmony_ci if (s != SND_PCM_STATE_RUNNING) { 261c72fcc34Sopenharmony_ci if (s != SND_PCM_STATE_PREPARED) { 262c72fcc34Sopenharmony_ci err = -EPIPE; 263c72fcc34Sopenharmony_ci goto error; 264c72fcc34Sopenharmony_ci } 265c72fcc34Sopenharmony_ci 266c72fcc34Sopenharmony_ci err = snd_pcm_start(state->handle); 267c72fcc34Sopenharmony_ci if (err < 0) 268c72fcc34Sopenharmony_ci goto error; 269c72fcc34Sopenharmony_ci } 270c72fcc34Sopenharmony_ci 271c72fcc34Sopenharmony_ci return 0; 272c72fcc34Sopenharmony_cierror: 273c72fcc34Sopenharmony_ci *frame_count = 0; 274c72fcc34Sopenharmony_ci return err; 275c72fcc34Sopenharmony_ci} 276c72fcc34Sopenharmony_ci 277c72fcc34Sopenharmony_cistatic void irq_mmap_post_process(struct libasound_state *state) 278c72fcc34Sopenharmony_ci{ 279c72fcc34Sopenharmony_ci struct map_layout *layout = state->private_data; 280c72fcc34Sopenharmony_ci 281c72fcc34Sopenharmony_ci if (layout->status) 282c72fcc34Sopenharmony_ci snd_pcm_status_free(layout->status); 283c72fcc34Sopenharmony_ci layout->status = NULL; 284c72fcc34Sopenharmony_ci 285c72fcc34Sopenharmony_ci free(layout->vector); 286c72fcc34Sopenharmony_ci layout->vector = NULL; 287c72fcc34Sopenharmony_ci} 288c72fcc34Sopenharmony_ci 289c72fcc34Sopenharmony_ciconst struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = { 290c72fcc34Sopenharmony_ci .pre_process = irq_mmap_pre_process, 291c72fcc34Sopenharmony_ci .process_frames = irq_mmap_w_process_frames, 292c72fcc34Sopenharmony_ci .post_process = irq_mmap_post_process, 293c72fcc34Sopenharmony_ci .private_size = sizeof(struct map_layout), 294c72fcc34Sopenharmony_ci}; 295c72fcc34Sopenharmony_ci 296c72fcc34Sopenharmony_ciconst struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = { 297c72fcc34Sopenharmony_ci .pre_process = irq_mmap_pre_process, 298c72fcc34Sopenharmony_ci .process_frames = irq_mmap_r_process_frames, 299c72fcc34Sopenharmony_ci .post_process = irq_mmap_post_process, 300c72fcc34Sopenharmony_ci .private_size = sizeof(struct map_layout), 301c72fcc34Sopenharmony_ci}; 302