1// SPDX-License-Identifier: GPL-2.0
2//
3// xfer-libasound-irq-mmap.c - IRQ-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
15	char **vector;
16	unsigned int samples_per_frame;
17};
18
19static int irq_mmap_pre_process(struct libasound_state *state)
20{
21	struct map_layout *layout = state->private_data;
22	snd_pcm_access_t access;
23	snd_pcm_uframes_t frame_offset;
24	snd_pcm_uframes_t avail = 0;
25	int i;
26	int err;
27
28	err = snd_pcm_status_malloc(&layout->status);
29	if (err < 0)
30		return err;
31
32	err = snd_pcm_hw_params_get_access(state->hw_params, &access);
33	if (err < 0)
34		return err;
35
36	err = snd_pcm_hw_params_get_channels(state->hw_params,
37					     &layout->samples_per_frame);
38	if (err < 0)
39		return err;
40
41	if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
42		layout->vector = calloc(layout->samples_per_frame,
43					sizeof(*layout->vector));
44		if (layout->vector == NULL)
45			return err;
46	}
47
48	if (state->verbose) {
49		const snd_pcm_channel_area_t *areas;
50		err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
51					 &avail);
52		if (err < 0)
53			return err;
54
55		logging(state, "attributes for mapped page frame:\n");
56		for (i = 0; i < (int)layout->samples_per_frame; ++i) {
57			const snd_pcm_channel_area_t *area = areas + i;
58
59			logging(state, "  sample number: %d\n", i);
60			logging(state, "    address: %p\n", area->addr);
61			logging(state, "    bits for offset: %u\n", area->first);
62			logging(state, "    bits/frame: %u\n", area->step);
63		}
64		logging(state, "\n");
65	}
66
67	return 0;
68}
69
70static int irq_mmap_process_frames(struct libasound_state *state,
71				   unsigned int *frame_count,
72				   struct mapper_context *mapper,
73				   struct container_context *cntrs)
74{
75	struct map_layout *layout = state->private_data;
76	const snd_pcm_channel_area_t *areas;
77	snd_pcm_uframes_t frame_offset;
78	snd_pcm_uframes_t avail;
79	unsigned int avail_count;
80	void *frame_buf;
81	snd_pcm_sframes_t consumed_count;
82	int err;
83
84	if (state->use_waiter) {
85		unsigned int msec_per_buffer;
86		unsigned short revents;
87
88		// Wait during msec equivalent to all audio data frames in
89		// buffer instead of period, for safe.
90		err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
91							&msec_per_buffer, NULL);
92		if (err < 0)
93			return err;
94		msec_per_buffer /= 1000;
95
96		// Wait for hardware IRQ when no avail space in buffer.
97		err = xfer_libasound_wait_event(state, msec_per_buffer,
98						&revents);
99		if (err == -ETIMEDOUT) {
100			logging(state,
101				"No event occurs for PCM substream during %u "
102				"msec. The implementaion of kernel driver or "
103				"userland backend causes this issue.\n",
104				msec_per_buffer);
105			return err;
106		}
107		if (err < 0)
108			return err;
109		if (revents & POLLERR) {
110			// TODO: error reporting?
111			return -EIO;
112		}
113		if (!(revents & (POLLIN | POLLOUT)))
114			return -EAGAIN;
115
116		// When rescheduled, current position of data transmission was
117		// queried to actual hardware by a handler of IRQ. No need to
118		// perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC.
119	}
120
121	// Sync cache in user space to data in kernel space to calculate avail
122	// frames according to the latest positions on PCM buffer.
123	//
124	// This has an additional advantage to handle libasound PCM plugins.
125	// Most of libasound PCM plugins perform resampling in .avail_update()
126	// callback for capture PCM substream, then update positions on buffer.
127	//
128	// MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
129	// return the same number of available frames.
130	avail = snd_pcm_avail_update(state->handle);
131	if ((snd_pcm_sframes_t)avail < 0)
132		return (int)avail;
133	if (*frame_count < avail)
134		avail = *frame_count;
135
136	err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
137	if (err < 0)
138		return err;
139
140	// Trim according up to expected frame count.
141	if (*frame_count < avail)
142		avail_count = *frame_count;
143	else
144		avail_count = (unsigned int)avail;
145
146	// TODO: Perhaps, the complex layout can be supported as a variation of
147	// vector type. However, there's no driver with this layout.
148	if (layout->vector == NULL) {
149		char *buf;
150		buf = areas[0].addr;
151		buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
152		frame_buf = buf;
153	} else {
154		int i;
155		for (i = 0; i < (int)layout->samples_per_frame; ++i) {
156			layout->vector[i] = areas[i].addr;
157			layout->vector[i] += snd_pcm_samples_to_bytes(
158						state->handle, frame_offset);
159		}
160		frame_buf = layout->vector;
161	}
162
163	err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
164					    cntrs);
165	if (err < 0)
166		return err;
167	if (avail_count == 0) {
168		*frame_count = 0;
169		return 0;
170	}
171
172	consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
173					     avail_count);
174	if (consumed_count < 0)
175		return (int)consumed_count;
176	if (consumed_count != avail_count)
177		logging(state, "A bug of access plugin for this PCM node.\n");
178
179	*frame_count = consumed_count;
180
181	return 0;
182}
183
184static int irq_mmap_r_process_frames(struct libasound_state *state,
185				     unsigned *frame_count,
186				     struct mapper_context *mapper,
187				     struct container_context *cntrs)
188{
189	struct map_layout *layout = state->private_data;
190	snd_pcm_state_t s;
191	int err;
192
193	// To querying current status of hardware, we need to care of
194	// synchronization between 3 levels:
195	//  1. status to actual hardware by driver.
196	//  2. status data in kernel space.
197	//  3. status data in user space.
198	//
199	// Kernel driver query 1 and sync 2, according to requests of some
200	// ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
201	// supports mmap(2) operation on cache coherent architectures, some
202	// ioctl(2) commands on cache incoherent architecture. In usage of the
203	// former mechanism, we need to care of concurrent access by IRQ context
204	// and process context to the mapped page frame.
205	// In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
206	// SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
207	// mapped page frame is unused regardless of architectures in a point of
208	// cache coherency.
209	err = snd_pcm_status(state->handle, layout->status);
210	if (err < 0)
211		goto error;
212	s = snd_pcm_status_get_state(layout->status);
213
214	// TODO: if reporting something, do here with the status data.
215
216	// For capture direction, need to start stream explicitly.
217	if (s != SND_PCM_STATE_RUNNING) {
218		if (s != SND_PCM_STATE_PREPARED) {
219			err = -EPIPE;
220			goto error;
221		}
222
223		err = snd_pcm_start(state->handle);
224		if (err < 0)
225			goto error;
226	}
227
228	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
229	if (err < 0)
230		goto error;
231
232	return 0;
233error:
234	*frame_count = 0;
235	return err;
236}
237
238static int irq_mmap_w_process_frames(struct libasound_state *state,
239				     unsigned *frame_count,
240				     struct mapper_context *mapper,
241				     struct container_context *cntrs)
242{
243	struct map_layout *layout = state->private_data;
244	snd_pcm_state_t s;
245	int err;
246
247	// Read my comment in 'irq_mmap_r_process_frames().
248	err = snd_pcm_status(state->handle, layout->status);
249	if (err < 0)
250		goto error;
251	s = snd_pcm_status_get_state(layout->status);
252
253	// TODO: if reporting something, do here with the status data.
254
255	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
256	if (err < 0)
257		goto error;
258
259	// Need to start playback stream explicitly
260	if (s != SND_PCM_STATE_RUNNING) {
261		if (s != SND_PCM_STATE_PREPARED) {
262			err = -EPIPE;
263			goto error;
264		}
265
266		err = snd_pcm_start(state->handle);
267		if (err < 0)
268			goto error;
269	}
270
271	return 0;
272error:
273	*frame_count = 0;
274	return err;
275}
276
277static void irq_mmap_post_process(struct libasound_state *state)
278{
279	struct map_layout *layout = state->private_data;
280
281	if (layout->status)
282		snd_pcm_status_free(layout->status);
283	layout->status = NULL;
284
285	free(layout->vector);
286	layout->vector = NULL;
287}
288
289const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
290	.pre_process	= irq_mmap_pre_process,
291	.process_frames	= irq_mmap_w_process_frames,
292	.post_process	= irq_mmap_post_process,
293	.private_size	= sizeof(struct map_layout),
294};
295
296const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
297	.pre_process	= irq_mmap_pre_process,
298	.process_frames	= irq_mmap_r_process_frames,
299	.post_process	= irq_mmap_post_process,
300	.private_size	= sizeof(struct map_layout),
301};
302