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
12 struct 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
timer_mmap_pre_process(struct libasound_state * state)22 static 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 < 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
get_buffer(struct libasound_state * state,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t frame_offset)94 static 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 < 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
timer_mmap_process_frames(struct libasound_state * state,unsigned int * frame_count,struct mapper_context * mapper,struct container_context * cntrs)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)
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 < 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
forward_appl_ptr(struct libasound_state * state)219 static 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
timer_mmap_r_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)244 static 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;
297 error:
298 *frame_count = 0;
299 return err;
300 }
301
rewind_appl_ptr(struct libasound_state * state)302 static 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
fill_buffer_with_zero_samples(struct libasound_state * state)336 static 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
timer_mmap_w_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)374 static 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;
426 error:
427 *frame_count = 0;
428 return err;
429 }
430
timer_mmap_post_process(struct libasound_state * state)431 static 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
444 const 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
451 const 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