• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
12 struct map_layout {
13 	snd_pcm_status_t *status;
14 
15 	char **vector;
16 	unsigned int samples_per_frame;
17 };
18 
irq_mmap_pre_process(struct libasound_state * state)19 static 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 < 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 
irq_mmap_process_frames(struct libasound_state * state,unsigned int * frame_count,struct mapper_context * mapper,struct container_context * cntrs)70 static 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 < 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 
irq_mmap_r_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)184 static 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;
233 error:
234 	*frame_count = 0;
235 	return err;
236 }
237 
irq_mmap_w_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)238 static 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;
272 error:
273 	*frame_count = 0;
274 	return err;
275 }
276 
irq_mmap_post_process(struct libasound_state * state)277 static 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 
289 const 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 
296 const 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