• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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