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