1 // SPDX-License-Identifier: GPL-2.0
3 // xfer-libasound-irq-mmap.c - Timer-based scheduling model for mmap operation.
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
7 // Licensed under the terms of the GNU General Public License, version 2.
9 #include "xfer-libasound.h"
13 snd_pcm_status_t *status;
14 bool need_forward_or_rewind;
17 unsigned int frames_per_second;
18 unsigned int samples_per_frame;
19 unsigned int frames_per_buffer;
22 static int timer_mmap_pre_process(struct libasound_state *state)
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;
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,
42 err = snd_pcm_status_malloc(&layout->status);
46 err = snd_pcm_hw_params_get_access(state->hw_params, &access);
50 err = snd_pcm_hw_params_get_channels(state->hw_params,
51 &layout->samples_per_frame);
55 err = snd_pcm_hw_params_get_rate(state->hw_params,
56 &layout->frames_per_second, NULL);
60 err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
64 layout->frames_per_buffer = (unsigned int)frames_per_buffer;
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)
74 const snd_pcm_channel_area_t *areas;
75 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
80 logging(state, "attributes for mapped page frame:\n");
81 for (i = 0; i < layout->samples_per_frame; ++i) {
82 const snd_pcm_channel_area_t *area = areas + i;
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);
94 static void *get_buffer(struct libasound_state *state,
95 const snd_pcm_channel_area_t *areas,
96 snd_pcm_uframes_t frame_offset)
98 struct map_layout *layout = state->private_data;
101 if (layout->vector == NULL) {
103 buf = areas[0].addr + snd_pcm_frames_to_bytes(state->handle,
108 for (i = 0; i < 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);
113 frame_buf = layout->vector;
119 static int timer_mmap_process_frames(struct libasound_state *state,
120 unsigned int *frame_count,
121 struct mapper_context *mapper,
122 struct container_context *cntrs)
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;
131 snd_pcm_sframes_t consumed_count;
134 // Retrieve avail space on PCM buffer between kernel/user spaces.
135 // On cache incoherent architectures, still care of data
137 avail = snd_pcm_avail_update(state->handle);
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,
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;
153 // Trim up to expected frame count.
154 if (*frame_count < planned_count)
155 planned_count = *frame_count;
157 // Yield this CPU till planned amount of frames become available.
158 if (avail_count < planned_count) {
159 unsigned short revents;
162 // TODO; precise granularity of timeout; e.g. ppoll(2).
163 // Furthermore, wrap up according to granularity of reported
165 timeout_msec = ((planned_count - avail_count) * 1000 +
166 layout->frames_per_second - 1) /
167 layout->frames_per_second;
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,
176 if (revents & POLLERR) {
177 // TODO: error reporting.
180 if (!(revents & (POLLIN | POLLOUT)))
183 // MEMO: Need to perform hwsync explicitly because hwptr is not
184 // synchronized to actual position of data frame transmission
185 // on hardware because IRQ handlers are not used in this
186 // scheduling strategy.
187 avail = snd_pcm_avail(state->handle);
190 if (avail < planned_count) {
192 "Wake up but not enough space: %lu %lu %u\n",
193 planned_count, avail, timeout_msec);
194 planned_count = avail;
198 // Let's process data frames.
199 *frame_count = planned_count;
200 frame_buf = get_buffer(state, areas, frame_offset);
201 err = mapper_context_process_frames(mapper, frame_buf, frame_count,
206 consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
208 if (consumed_count != *frame_count) {
210 "A bug of 'hw' PCM plugin or driver for this PCM "
213 *frame_count = consumed_count;
218 static int forward_appl_ptr(struct libasound_state *state)
220 struct map_layout *layout = state->private_data;
221 snd_pcm_uframes_t forwardable_count;
222 snd_pcm_sframes_t forward_count;
224 forward_count = snd_pcm_forwardable(state->handle);
225 if (forward_count < 0)
226 return (int)forward_count;
227 forwardable_count = forward_count;
229 // No need to add safe-gurard because hwptr goes ahead.
230 forward_count = snd_pcm_forward(state->handle, forwardable_count);
231 if (forward_count < 0)
232 return (int)forward_count;
234 if (state->verbose) {
236 " forwarded: %lu/%u\n",
237 forward_count, layout->frames_per_buffer);
243 static int timer_mmap_r_process_frames(struct libasound_state *state,
244 unsigned *frame_count,
245 struct mapper_context *mapper,
246 struct container_context *cntrs)
248 struct map_layout *layout = state->private_data;
252 // SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP suppresses any IRQ to notify
253 // period elapse for data transmission, therefore no need to care of
254 // concurrent access by IRQ context and process context, unlike
255 // IRQ-based operations.
256 // Here, this is just to query current status to hardware, for later
258 err = snd_pcm_status(state->handle, layout->status);
261 s = snd_pcm_status_get_state(layout->status);
263 // TODO: if reporting something, do here with the status data.
265 if (s == SND_PCM_STATE_RUNNING) {
266 // Reduce delay between sampling on hardware and handling by
268 if (layout->need_forward_or_rewind) {
269 err = forward_appl_ptr(state);
272 layout->need_forward_or_rewind = false;
275 err = timer_mmap_process_frames(state, frame_count, mapper,
280 if (s == SND_PCM_STATE_PREPARED) {
281 // For capture direction, need to start stream
283 err = snd_pcm_start(state->handle);
286 layout->need_forward_or_rewind = true;
301 static int rewind_appl_ptr(struct libasound_state *state)
303 struct map_layout *layout = state->private_data;
304 snd_pcm_uframes_t rewindable_count;
305 snd_pcm_sframes_t rewind_count;
307 rewind_count = snd_pcm_rewindable(state->handle);
308 if (rewind_count < 0)
309 return (int)rewind_count;
310 rewindable_count = rewind_count;
312 // If appl_ptr were rewound just to position of hw_ptr, at next time,
313 // hw_ptr could catch up appl_ptr. This is overrun. We need a space
314 // between these two pointers to prevent this XRUN.
315 // This space is largely affected by time to process data frames later.
317 // TODO: a generous way to estimate a good value.
318 if (rewindable_count < 32)
320 rewindable_count -= 32;
322 rewind_count = snd_pcm_rewind(state->handle, rewindable_count);
323 if (rewind_count < 0)
324 return (int)rewind_count;
326 if (state->verbose) {
328 " rewound: %lu/%u\n",
329 rewind_count, layout->frames_per_buffer);
335 static int fill_buffer_with_zero_samples(struct libasound_state *state)
337 struct map_layout *layout = state->private_data;
338 const snd_pcm_channel_area_t *areas;
339 snd_pcm_uframes_t frame_offset;
340 snd_pcm_uframes_t avail_count;
341 snd_pcm_format_t sample_format;
342 snd_pcm_uframes_t consumed_count;
345 err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
350 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
355 err = snd_pcm_hw_params_get_format(state->hw_params, &sample_format);
359 err = snd_pcm_areas_silence(areas, frame_offset,
360 layout->samples_per_frame, avail_count,
365 consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
367 if (consumed_count != avail_count)
368 logging(state, "A bug of access plugin for this PCM node.\n");
373 static int timer_mmap_w_process_frames(struct libasound_state *state,
374 unsigned *frame_count,
375 struct mapper_context *mapper,
376 struct container_context *cntrs)
378 struct map_layout *layout = state->private_data;
382 // Read my comment in 'timer_mmap_w_process_frames()'.
383 err = snd_pcm_status(state->handle, layout->status);
386 s = snd_pcm_status_get_state(layout->status);
388 // TODO: if reporting something, do here with the status data.
390 if (s == SND_PCM_STATE_RUNNING) {
391 // Reduce delay between queueing by this program and presenting
393 if (layout->need_forward_or_rewind) {
394 err = rewind_appl_ptr(state);
397 layout->need_forward_or_rewind = false;
400 err = timer_mmap_process_frames(state, frame_count, mapper,
405 // Need to start playback stream explicitly
406 if (s == SND_PCM_STATE_PREPARED) {
407 err = fill_buffer_with_zero_samples(state);
411 err = snd_pcm_start(state->handle);
415 layout->need_forward_or_rewind = true;
430 static void timer_mmap_post_process(struct libasound_state *state)
432 struct map_layout *layout = state->private_data;
435 snd_pcm_status_free(layout->status);
436 layout->status = NULL;
439 free(layout->vector);
440 layout->vector = NULL;
443 const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops = {
444 .pre_process = timer_mmap_pre_process,
445 .process_frames = timer_mmap_w_process_frames,
446 .post_process = timer_mmap_post_process,
447 .private_size = sizeof(struct map_layout),
450 const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops = {
451 .pre_process = timer_mmap_pre_process,
452 .process_frames = timer_mmap_r_process_frames,
453 .post_process = timer_mmap_post_process,
454 .private_size = sizeof(struct map_layout),