1// SPDX-License-Identifier: GPL-2.0 2// 3// xfer-libasound-irq-mmap.c - Timer-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 bool need_forward_or_rewind; 15 char **vector; 16 17 unsigned int frames_per_second; 18 unsigned int samples_per_frame; 19 unsigned int frames_per_buffer; 20}; 21 22static int timer_mmap_pre_process(struct libasound_state *state) 23{ 24 struct map_layout *layout = state->private_data; 25 snd_pcm_access_t access; 26 snd_pcm_uframes_t frame_offset; 27 snd_pcm_uframes_t avail = 0; 28 snd_pcm_uframes_t frames_per_buffer; 29 int i; 30 int err; 31 32 // This parameter, 'period event', is a software feature in alsa-lib. 33 // This switch a handler in 'hw' PCM plugin from irq-based one to 34 // timer-based one. This handler has two file descriptors for 35 // ALSA PCM character device and ALSA timer device. The latter is used 36 // to catch suspend/resume events as wakeup event. 37 err = snd_pcm_sw_params_set_period_event(state->handle, 38 state->sw_params, 1); 39 if (err < 0) 40 return err; 41 42 err = snd_pcm_status_malloc(&layout->status); 43 if (err < 0) 44 return err; 45 46 err = snd_pcm_hw_params_get_access(state->hw_params, &access); 47 if (err < 0) 48 return err; 49 50 err = snd_pcm_hw_params_get_channels(state->hw_params, 51 &layout->samples_per_frame); 52 if (err < 0) 53 return err; 54 55 err = snd_pcm_hw_params_get_rate(state->hw_params, 56 &layout->frames_per_second, NULL); 57 if (err < 0) 58 return err; 59 60 err = snd_pcm_hw_params_get_buffer_size(state->hw_params, 61 &frames_per_buffer); 62 if (err < 0) 63 return err; 64 layout->frames_per_buffer = (unsigned int)frames_per_buffer; 65 66 if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { 67 layout->vector = calloc(layout->samples_per_frame, 68 sizeof(*layout->vector)); 69 if (layout->vector == NULL) 70 return err; 71 } 72 73 if (state->verbose) { 74 const snd_pcm_channel_area_t *areas; 75 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, 76 &avail); 77 if (err < 0) 78 return err; 79 80 logging(state, "attributes for mapped page frame:\n"); 81 for (i = 0; i < (int)layout->samples_per_frame; ++i) { 82 const snd_pcm_channel_area_t *area = areas + i; 83 84 logging(state, " sample number: %d\n", i); 85 logging(state, " address: %p\n", area->addr); 86 logging(state, " bits for offset: %u\n", area->first); 87 logging(state, " bits/frame: %u\n", area->step); 88 } 89 } 90 91 return 0; 92} 93 94static void *get_buffer(struct libasound_state *state, 95 const snd_pcm_channel_area_t *areas, 96 snd_pcm_uframes_t frame_offset) 97{ 98 struct map_layout *layout = state->private_data; 99 void *frame_buf; 100 101 if (layout->vector == NULL) { 102 char *buf; 103 buf = areas[0].addr; 104 buf += snd_pcm_frames_to_bytes(state->handle, frame_offset); 105 frame_buf = buf; 106 } else { 107 int i; 108 for (i = 0; i < (int)layout->samples_per_frame; ++i) { 109 layout->vector[i] = areas[i].addr; 110 layout->vector[i] += snd_pcm_samples_to_bytes( 111 state->handle, frame_offset); 112 } 113 frame_buf = layout->vector; 114 } 115 116 return frame_buf; 117} 118 119static int timer_mmap_process_frames(struct libasound_state *state, 120 unsigned int *frame_count, 121 struct mapper_context *mapper, 122 struct container_context *cntrs) 123{ 124 struct map_layout *layout = state->private_data; 125 snd_pcm_uframes_t planned_count; 126 snd_pcm_sframes_t avail; 127 snd_pcm_uframes_t avail_count; 128 const snd_pcm_channel_area_t *areas; 129 snd_pcm_uframes_t frame_offset; 130 void *frame_buf; 131 snd_pcm_sframes_t consumed_count; 132 int err; 133 134 // Retrieve avail space on PCM buffer between kernel/user spaces. 135 // On cache incoherent architectures, still care of data 136 // synchronization. 137 avail = snd_pcm_avail_update(state->handle); 138 if (avail < 0) 139 return (int)avail; 140 141 // Retrieve pointers of the buffer and left space up to the boundary. 142 avail_count = (snd_pcm_uframes_t)avail; 143 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, 144 &avail_count); 145 if (err < 0) 146 return err; 147 148 // MEMO: Use the amount of data frames as you like. 149 planned_count = layout->frames_per_buffer * random() / RAND_MAX; 150 if (frame_offset + planned_count > layout->frames_per_buffer) 151 planned_count = layout->frames_per_buffer - frame_offset; 152 153 // Trim up to expected frame count. 154 if (*frame_count < planned_count) 155 planned_count = *frame_count; 156 157 // Yield this CPU till planned amount of frames become available. 158 if (avail_count < planned_count) { 159 unsigned short revents; 160 int timeout_msec; 161 162 // TODO; precise granularity of timeout; e.g. ppoll(2). 163 // Furthermore, wrap up according to granularity of reported 164 // value for hw_ptr. 165 timeout_msec = ((planned_count - avail_count) * 1000 + 166 layout->frames_per_second - 1) / 167 layout->frames_per_second; 168 169 // TODO: However, experimentally, the above is not enough to 170 // keep planned amount of frames when waking up. I don't know 171 // exactly the mechanism yet. 172 err = xfer_libasound_wait_event(state, timeout_msec, 173 &revents); 174 // MEMO: timeout is expected since the above call is just to measure time elapse. 175 if (err < 0 && err != -ETIMEDOUT) 176 return err; 177 if (revents & POLLERR) { 178 // TODO: error reporting. 179 return -EIO; 180 } 181 if (!(revents & (POLLIN | POLLOUT))) 182 return -EAGAIN; 183 184 // MEMO: Need to perform hwsync explicitly because hwptr is not 185 // synchronized to actual position of data frame transmission 186 // on hardware because IRQ handlers are not used in this 187 // scheduling strategy. 188 avail = snd_pcm_avail(state->handle); 189 if (avail < 0) 190 return (int)avail; 191 if (avail < (snd_pcm_sframes_t)planned_count) { 192 logging(state, 193 "Wake up but not enough space: %lu %lu %u\n", 194 planned_count, avail, timeout_msec); 195 planned_count = avail; 196 } 197 } 198 199 // Let's process data frames. 200 *frame_count = planned_count; 201 frame_buf = get_buffer(state, areas, frame_offset); 202 err = mapper_context_process_frames(mapper, frame_buf, frame_count, 203 cntrs); 204 if (err < 0) 205 return err; 206 207 consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, 208 *frame_count); 209 if (consumed_count != *frame_count) { 210 logging(state, 211 "A bug of 'hw' PCM plugin or driver for this PCM " 212 "node.\n"); 213 } 214 *frame_count = consumed_count; 215 216 return 0; 217} 218 219static int forward_appl_ptr(struct libasound_state *state) 220{ 221 struct map_layout *layout = state->private_data; 222 snd_pcm_uframes_t forwardable_count; 223 snd_pcm_sframes_t forward_count; 224 225 forward_count = snd_pcm_forwardable(state->handle); 226 if (forward_count < 0) 227 return (int)forward_count; 228 forwardable_count = forward_count; 229 230 // No need to add safe-gurard because hwptr goes ahead. 231 forward_count = snd_pcm_forward(state->handle, forwardable_count); 232 if (forward_count < 0) 233 return (int)forward_count; 234 235 if (state->verbose) { 236 logging(state, 237 " forwarded: %lu/%u\n", 238 forward_count, layout->frames_per_buffer); 239 } 240 241 return 0; 242} 243 244static int timer_mmap_r_process_frames(struct libasound_state *state, 245 unsigned *frame_count, 246 struct mapper_context *mapper, 247 struct container_context *cntrs) 248{ 249 struct map_layout *layout = state->private_data; 250 snd_pcm_state_t s; 251 int err; 252 253 // SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP suppresses any IRQ to notify 254 // period elapse for data transmission, therefore no need to care of 255 // concurrent access by IRQ context and process context, unlike 256 // IRQ-based operations. 257 // Here, this is just to query current status to hardware, for later 258 // processing. 259 err = snd_pcm_status(state->handle, layout->status); 260 if (err < 0) 261 goto error; 262 s = snd_pcm_status_get_state(layout->status); 263 264 // TODO: if reporting something, do here with the status data. 265 266 if (s == SND_PCM_STATE_RUNNING) { 267 // Reduce delay between sampling on hardware and handling by 268 // this program. 269 if (layout->need_forward_or_rewind) { 270 err = forward_appl_ptr(state); 271 if (err < 0) 272 goto error; 273 layout->need_forward_or_rewind = false; 274 } 275 276 err = timer_mmap_process_frames(state, frame_count, mapper, 277 cntrs); 278 if (err < 0) 279 goto error; 280 } else { 281 if (s == SND_PCM_STATE_PREPARED) { 282 // For capture direction, need to start stream 283 // explicitly. 284 err = snd_pcm_start(state->handle); 285 if (err < 0) 286 goto error; 287 layout->need_forward_or_rewind = true; 288 // Not yet. 289 *frame_count = 0; 290 } else { 291 err = -EPIPE; 292 goto error; 293 } 294 } 295 296 return 0; 297error: 298 *frame_count = 0; 299 return err; 300} 301 302static int rewind_appl_ptr(struct libasound_state *state) 303{ 304 struct map_layout *layout = state->private_data; 305 snd_pcm_uframes_t rewindable_count; 306 snd_pcm_sframes_t rewind_count; 307 308 rewind_count = snd_pcm_rewindable(state->handle); 309 if (rewind_count < 0) 310 return (int)rewind_count; 311 rewindable_count = rewind_count; 312 313 // If appl_ptr were rewound just to position of hw_ptr, at next time, 314 // hw_ptr could catch up appl_ptr. This is overrun. We need a space 315 // between these two pointers to prevent this XRUN. 316 // This space is largely affected by time to process data frames later. 317 // 318 // TODO: a generous way to estimate a good value. 319 if (rewindable_count < 32) 320 return 0; 321 rewindable_count -= 32; 322 323 rewind_count = snd_pcm_rewind(state->handle, rewindable_count); 324 if (rewind_count < 0) 325 return (int)rewind_count; 326 327 if (state->verbose) { 328 logging(state, 329 " rewound: %lu/%u\n", 330 rewind_count, layout->frames_per_buffer); 331 } 332 333 return 0; 334} 335 336static int fill_buffer_with_zero_samples(struct libasound_state *state) 337{ 338 struct map_layout *layout = state->private_data; 339 const snd_pcm_channel_area_t *areas; 340 snd_pcm_uframes_t frame_offset; 341 snd_pcm_uframes_t avail_count; 342 snd_pcm_format_t sample_format; 343 snd_pcm_uframes_t consumed_count; 344 int err; 345 346 err = snd_pcm_hw_params_get_buffer_size(state->hw_params, 347 &avail_count); 348 if (err < 0) 349 return err; 350 351 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, 352 &avail_count); 353 if (err < 0) 354 return err; 355 356 err = snd_pcm_hw_params_get_format(state->hw_params, &sample_format); 357 if (err < 0) 358 return err; 359 360 err = snd_pcm_areas_silence(areas, frame_offset, 361 layout->samples_per_frame, avail_count, 362 sample_format); 363 if (err < 0) 364 return err; 365 366 consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset, 367 avail_count); 368 if (consumed_count != avail_count) 369 logging(state, "A bug of access plugin for this PCM node.\n"); 370 371 return 0; 372} 373 374static int timer_mmap_w_process_frames(struct libasound_state *state, 375 unsigned *frame_count, 376 struct mapper_context *mapper, 377 struct container_context *cntrs) 378{ 379 struct map_layout *layout = state->private_data; 380 snd_pcm_state_t s; 381 int err; 382 383 // Read my comment in 'timer_mmap_w_process_frames()'. 384 err = snd_pcm_status(state->handle, layout->status); 385 if (err < 0) 386 goto error; 387 s = snd_pcm_status_get_state(layout->status); 388 389 // TODO: if reporting something, do here with the status data. 390 391 if (s == SND_PCM_STATE_RUNNING) { 392 // Reduce delay between queueing by this program and presenting 393 // on hardware. 394 if (layout->need_forward_or_rewind) { 395 err = rewind_appl_ptr(state); 396 if (err < 0) 397 goto error; 398 layout->need_forward_or_rewind = false; 399 } 400 401 err = timer_mmap_process_frames(state, frame_count, mapper, 402 cntrs); 403 if (err < 0) 404 goto error; 405 } else { 406 // Need to start playback stream explicitly 407 if (s == SND_PCM_STATE_PREPARED) { 408 err = fill_buffer_with_zero_samples(state); 409 if (err < 0) 410 goto error; 411 412 err = snd_pcm_start(state->handle); 413 if (err < 0) 414 goto error; 415 416 layout->need_forward_or_rewind = true; 417 // Not yet. 418 *frame_count = 0; 419 } else { 420 err = -EPIPE; 421 goto error; 422 } 423 } 424 425 return 0; 426error: 427 *frame_count = 0; 428 return err; 429} 430 431static void timer_mmap_post_process(struct libasound_state *state) 432{ 433 struct map_layout *layout = state->private_data; 434 435 if (layout->status) 436 snd_pcm_status_free(layout->status); 437 layout->status = NULL; 438 439 if (layout->vector) 440 free(layout->vector); 441 layout->vector = NULL; 442} 443 444const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops = { 445 .pre_process = timer_mmap_pre_process, 446 .process_frames = timer_mmap_w_process_frames, 447 .post_process = timer_mmap_post_process, 448 .private_size = sizeof(struct map_layout), 449}; 450 451const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops = { 452 .pre_process = timer_mmap_pre_process, 453 .process_frames = timer_mmap_r_process_frames, 454 .post_process = timer_mmap_post_process, 455 .private_size = sizeof(struct map_layout), 456}; 457