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