• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * \file pcm/pcm_mmap_emul.c
3  * \ingroup PCM_Plugins
4  * \brief PCM Mmap-Emulation Plugin Interface
5  * \author Takashi Iwai <tiwai@suse.de>
6  * \date 2007
7  */
8 /*
9  *  PCM - Mmap-Emulation
10  *  Copyright (c) 2007 by Takashi Iwai <tiwai@suse.de>
11  *
12  *
13  *   This library is free software; you can redistribute it and/or modify
14  *   it under the terms of the GNU Lesser General Public License as
15  *   published by the Free Software Foundation; either version 2.1 of
16  *   the License, or (at your option) any later version.
17  *
18  *   This program is distributed in the hope that it will be useful,
19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *   GNU Lesser General Public License for more details.
22  *
23  *   You should have received a copy of the GNU Lesser General Public
24  *   License along with this library; if not, write to the Free Software
25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26  *
27  */
28 
29 #include "pcm_local.h"
30 #include "pcm_generic.h"
31 
32 #ifndef PIC
33 /* entry for static linking */
34 const char *_snd_module_pcm_mmap_emul = "";
35 #endif
36 
37 #ifndef DOC_HIDDEN
38 /*
39  *
40  */
41 
42 typedef struct {
43 	snd_pcm_generic_t gen;
44 	unsigned int mmap_emul :1;
45 	snd_pcm_uframes_t hw_ptr;
46 	snd_pcm_uframes_t appl_ptr;
47 	snd_pcm_uframes_t start_threshold;
48 } mmap_emul_t;
49 #endif
50 
51 /*
52  * here goes a really tricky part; hw_refine falls back to ACCESS_RW_* type
53  * when ACCESS_MMAP_* isn't supported by the hardware.
54  */
snd_pcm_mmap_emul_hw_refine(snd_pcm_t * pcm,snd_pcm_hw_params_t * params)55 static int snd_pcm_mmap_emul_hw_refine(snd_pcm_t *pcm,
56 				       snd_pcm_hw_params_t *params)
57 {
58 	mmap_emul_t *map = pcm->private_data;
59 	int err = 0;
60 	snd_pcm_access_mask_t oldmask =
61 		*snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
62 	snd_pcm_access_mask_t mask;
63 	const snd_mask_t *pmask;
64 
65 	snd_mask_none(&mask);
66 	err = snd_pcm_hw_refine(map->gen.slave, params);
67 	if (err < 0) {
68 		snd_pcm_hw_params_t new = *params;
69 
70 		/* try to use RW_* */
71 		if (snd_pcm_access_mask_test(&oldmask,
72 					     SND_PCM_ACCESS_MMAP_INTERLEAVED) &&
73 		    !snd_pcm_access_mask_test(&oldmask,
74 					      SND_PCM_ACCESS_RW_INTERLEAVED))
75 			snd_pcm_access_mask_set(&mask,
76 						SND_PCM_ACCESS_RW_INTERLEAVED);
77 		if (snd_pcm_access_mask_test(&oldmask,
78 					     SND_PCM_ACCESS_MMAP_NONINTERLEAVED) &&
79 		    !snd_pcm_access_mask_test(&oldmask,
80 					      SND_PCM_ACCESS_RW_NONINTERLEAVED))
81 			snd_pcm_access_mask_set(&mask,
82 						SND_PCM_ACCESS_RW_NONINTERLEAVED);
83 		if (snd_pcm_access_mask_empty(&mask))
84 			return err;
85 		pmask = snd_pcm_hw_param_get_mask(&new,
86 						  SND_PCM_HW_PARAM_ACCESS);
87 		*(snd_mask_t *)pmask = mask;
88 		err = snd_pcm_hw_refine(map->gen.slave, &new);
89 		if (err < 0)
90 			return err;
91 		*params = new;
92 	}
93 
94 	pmask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
95 	if (snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_INTERLEAVED) ||
96 	    snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) ||
97 	    snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_COMPLEX))
98 		return 0;
99 	if (snd_pcm_access_mask_test(&mask, SND_PCM_ACCESS_RW_INTERLEAVED)) {
100 		if (snd_pcm_access_mask_test(pmask,
101 					     SND_PCM_ACCESS_RW_INTERLEAVED))
102 			snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
103 						SND_PCM_ACCESS_MMAP_INTERLEAVED);
104 		snd_pcm_access_mask_reset((snd_pcm_access_mask_t *)pmask,
105 					  SND_PCM_ACCESS_RW_INTERLEAVED);
106 		params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
107 	}
108 	if (snd_pcm_access_mask_test(&mask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
109 		if (snd_pcm_access_mask_test(pmask,
110 					     SND_PCM_ACCESS_RW_NONINTERLEAVED))
111 			snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
112 						SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
113 		snd_pcm_access_mask_reset((snd_pcm_access_mask_t *)pmask,
114 					  SND_PCM_ACCESS_RW_NONINTERLEAVED);
115 		params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
116 	}
117 	if (snd_pcm_access_mask_test(&oldmask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) {
118 		if (snd_pcm_access_mask_test(&oldmask,
119 					     SND_PCM_ACCESS_RW_INTERLEAVED)) {
120 			if (snd_pcm_access_mask_test(pmask,
121 						     SND_PCM_ACCESS_RW_INTERLEAVED)) {
122 				snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
123 							SND_PCM_ACCESS_MMAP_INTERLEAVED);
124 				params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
125 			}
126 		}
127 	}
128 	if (snd_pcm_access_mask_test(&oldmask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
129 		if (snd_pcm_access_mask_test(&oldmask,
130 					     SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
131 			if (snd_pcm_access_mask_test(pmask,
132 						     SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
133 				snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
134 							SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
135 				params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
136 			}
137 		}
138 	}
139 	return 0;
140 }
141 
142 /*
143  * hw_params needs a similar hack like hw_refine, but it's much simpler
144  * because now snd_pcm_hw_params_t takes only one choice for each item.
145  *
146  * Here, when the normal hw_params call fails, it turns on the mmap_emul
147  * flag and tries to use ACCESS_RW_* mode.
148  *
149  * In mmap_emul mode, the appl_ptr and hw_ptr are handled individually
150  * from the layering slave PCM, and they are sync'ed appropriately in
151  * each read/write or avail_update/commit call.
152  */
snd_pcm_mmap_emul_hw_params(snd_pcm_t * pcm,snd_pcm_hw_params_t * params)153 static int snd_pcm_mmap_emul_hw_params(snd_pcm_t *pcm,
154 				       snd_pcm_hw_params_t *params)
155 {
156 	mmap_emul_t *map = pcm->private_data;
157 	snd_pcm_hw_params_t old = *params;
158 	snd_pcm_access_t access;
159 	snd_pcm_access_mask_t oldmask;
160 	snd_pcm_access_mask_t *pmask;
161 	int err;
162 
163 	err = _snd_pcm_hw_params_internal(map->gen.slave, params);
164 	if (err >= 0) {
165 		map->mmap_emul = 0;
166 		return err;
167 	}
168 
169 	*params = old;
170 	pmask = (snd_pcm_access_mask_t *)snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
171 	oldmask = *pmask;
172 	if (INTERNAL(snd_pcm_hw_params_get_access)(params, &access) < 0)
173 		goto _err;
174 	switch (access) {
175 	case SND_PCM_ACCESS_MMAP_INTERLEAVED:
176 		snd_pcm_access_mask_reset(pmask,
177 					  SND_PCM_ACCESS_MMAP_INTERLEAVED);
178 		snd_pcm_access_mask_set(pmask, SND_PCM_ACCESS_RW_INTERLEAVED);
179 		break;
180 	case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
181 		snd_pcm_access_mask_reset(pmask,
182 					  SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
183 		snd_pcm_access_mask_set(pmask,
184 					SND_PCM_ACCESS_RW_NONINTERLEAVED);
185 		break;
186 	default:
187 		goto _err;
188 	}
189 	err = _snd_pcm_hw_params_internal(map->gen.slave, params);
190 	if (err < 0)
191 		goto _err;
192 
193 	/* need to back the access type to relieve apps */
194 	*pmask = oldmask;
195 
196 	/* OK, we do fake */
197 	map->mmap_emul = 1;
198 	map->appl_ptr = 0;
199 	map->hw_ptr = 0;
200 	snd_pcm_set_hw_ptr(pcm, &map->hw_ptr, -1, 0);
201 	snd_pcm_set_appl_ptr(pcm, &map->appl_ptr, -1, 0);
202 	return 0;
203 
204  _err:
205 	err = -errno;
206 	return err;
207 }
208 
snd_pcm_mmap_emul_sw_params(snd_pcm_t * pcm,snd_pcm_sw_params_t * params)209 static int snd_pcm_mmap_emul_sw_params(snd_pcm_t *pcm,
210 				       snd_pcm_sw_params_t *params)
211 {
212 	mmap_emul_t *map = pcm->private_data;
213 	int err;
214 
215 	if (!map->mmap_emul)
216 		return snd_pcm_generic_sw_params(pcm, params);
217 
218 	map->start_threshold = params->start_threshold;
219 
220 	/* HACK: don't auto-start in the slave PCM */
221 	params->start_threshold = pcm->boundary;
222 	err = snd_pcm_generic_sw_params(pcm, params);
223 	if (err < 0)
224 		return err;
225 	/* restore the value for this PCM */
226 	params->start_threshold = map->start_threshold;
227 	return err;
228 }
229 
snd_pcm_mmap_emul_prepare(snd_pcm_t * pcm)230 static int snd_pcm_mmap_emul_prepare(snd_pcm_t *pcm)
231 {
232 	mmap_emul_t *map = pcm->private_data;
233 	int err;
234 
235 	err = snd_pcm_generic_prepare(pcm);
236 	if (err < 0)
237 		return err;
238 	map->hw_ptr = map->appl_ptr = 0;
239 	return err;
240 }
241 
snd_pcm_mmap_emul_reset(snd_pcm_t * pcm)242 static int snd_pcm_mmap_emul_reset(snd_pcm_t *pcm)
243 {
244 	mmap_emul_t *map = pcm->private_data;
245 	int err;
246 
247 	err = snd_pcm_generic_reset(pcm);
248 	if (err < 0)
249 		return err;
250 	map->hw_ptr = map->appl_ptr = 0;
251 	return err;
252 }
253 
254 static snd_pcm_sframes_t
snd_pcm_mmap_emul_rewind(snd_pcm_t * pcm,snd_pcm_uframes_t frames)255 snd_pcm_mmap_emul_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
256 {
257 	frames = snd_pcm_generic_rewind(pcm, frames);
258 	if (frames > 0)
259 		snd_pcm_mmap_appl_backward(pcm, frames);
260 	return frames;
261 }
262 
263 static snd_pcm_sframes_t
snd_pcm_mmap_emul_forward(snd_pcm_t * pcm,snd_pcm_uframes_t frames)264 snd_pcm_mmap_emul_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
265 {
266 	frames = snd_pcm_generic_forward(pcm, frames);
267 	if (frames > 0)
268 		snd_pcm_mmap_appl_forward(pcm, frames);
269 	return frames;
270 }
271 
272 /* write out the uncommitted chunk on mmap buffer to the slave PCM */
273 static snd_pcm_sframes_t
sync_slave_write(snd_pcm_t * pcm)274 sync_slave_write(snd_pcm_t *pcm)
275 {
276 	mmap_emul_t *map = pcm->private_data;
277 	snd_pcm_t *slave = map->gen.slave;
278 	snd_pcm_uframes_t offset;
279 	snd_pcm_sframes_t size;
280 
281 	/* HACK: don't start stream automatically at commit in mmap mode */
282 	pcm->start_threshold = pcm->boundary;
283 
284 	size = map->appl_ptr - *slave->appl.ptr;
285 	if (size < 0)
286 		size += pcm->boundary;
287 	if (size) {
288 		offset = *slave->appl.ptr % pcm->buffer_size;
289 		size = snd_pcm_write_mmap(pcm, offset, size);
290 	}
291 	pcm->start_threshold = map->start_threshold; /* restore */
292 	return size;
293 }
294 
295 /* read the available chunk on the slave PCM to mmap buffer */
296 static snd_pcm_sframes_t
sync_slave_read(snd_pcm_t * pcm)297 sync_slave_read(snd_pcm_t *pcm)
298 {
299 	mmap_emul_t *map = pcm->private_data;
300 	snd_pcm_t *slave = map->gen.slave;
301 	snd_pcm_uframes_t offset;
302 	snd_pcm_sframes_t size;
303 
304 	size = *slave->hw.ptr - map->hw_ptr;
305 	if (size < 0)
306 		size += pcm->boundary;
307 	if (!size)
308 		return 0;
309 	offset = map->hw_ptr % pcm->buffer_size;
310 	size = snd_pcm_read_mmap(pcm, offset, size);
311 	if (size > 0)
312 		snd_pcm_mmap_hw_forward(pcm, size);
313 	return 0;
314 }
315 
316 static snd_pcm_sframes_t
snd_pcm_mmap_emul_mmap_commit(snd_pcm_t * pcm,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)317 snd_pcm_mmap_emul_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
318 			      snd_pcm_uframes_t size)
319 {
320 	mmap_emul_t *map = pcm->private_data;
321 	snd_pcm_t *slave = map->gen.slave;
322 
323 	snd_pcm_mmap_appl_forward(pcm, size);
324 	if (!map->mmap_emul)
325 		return snd_pcm_mmap_commit(slave, offset, size);
326 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
327 		sync_slave_write(pcm);
328 	return size;
329 }
330 
snd_pcm_mmap_emul_avail_update(snd_pcm_t * pcm)331 static snd_pcm_sframes_t snd_pcm_mmap_emul_avail_update(snd_pcm_t *pcm)
332 {
333 	mmap_emul_t *map = pcm->private_data;
334 	snd_pcm_t *slave = map->gen.slave;
335 
336 	if (!map->mmap_emul || pcm->stream == SND_PCM_STREAM_PLAYBACK)
337 		map->hw_ptr = *slave->hw.ptr;
338 	else
339 		sync_slave_read(pcm);
340 	return snd_pcm_mmap_avail(pcm);
341 }
342 
snd_pcm_mmap_emul_dump(snd_pcm_t * pcm,snd_output_t * out)343 static void snd_pcm_mmap_emul_dump(snd_pcm_t *pcm, snd_output_t *out)
344 {
345 	mmap_emul_t *map = pcm->private_data;
346 
347 	snd_output_printf(out, "Mmap emulation PCM\n");
348 	if (pcm->setup) {
349 		snd_output_printf(out, "Its setup is:\n");
350 		snd_pcm_dump_setup(pcm, out);
351 	}
352 	snd_output_printf(out, "Slave: ");
353 	snd_pcm_dump(map->gen.slave, out);
354 }
355 
356 static const snd_pcm_ops_t snd_pcm_mmap_emul_ops = {
357 	.close = snd_pcm_generic_close,
358 	.info = snd_pcm_generic_info,
359 	.hw_refine = snd_pcm_mmap_emul_hw_refine,
360 	.hw_params = snd_pcm_mmap_emul_hw_params,
361 	.hw_free = snd_pcm_generic_hw_free,
362 	.sw_params = snd_pcm_mmap_emul_sw_params,
363 	.channel_info = snd_pcm_generic_channel_info,
364 	.dump = snd_pcm_mmap_emul_dump,
365 	.nonblock = snd_pcm_generic_nonblock,
366 	.async = snd_pcm_generic_async,
367 	.mmap = snd_pcm_generic_mmap,
368 	.munmap = snd_pcm_generic_munmap,
369 	.query_chmaps = snd_pcm_generic_query_chmaps,
370 	.get_chmap = snd_pcm_generic_get_chmap,
371 	.set_chmap = snd_pcm_generic_set_chmap,
372 };
373 
374 static const snd_pcm_fast_ops_t snd_pcm_mmap_emul_fast_ops = {
375 	.status = snd_pcm_generic_status,
376 	.state = snd_pcm_generic_state,
377 	.hwsync = snd_pcm_generic_hwsync,
378 	.delay = snd_pcm_generic_delay,
379 	.prepare = snd_pcm_mmap_emul_prepare,
380 	.reset = snd_pcm_mmap_emul_reset,
381 	.start = snd_pcm_generic_start,
382 	.drop = snd_pcm_generic_drop,
383 	.drain = snd_pcm_generic_drain,
384 	.pause = snd_pcm_generic_pause,
385 	.rewindable = snd_pcm_generic_rewindable,
386 	.rewind = snd_pcm_mmap_emul_rewind,
387 	.forwardable = snd_pcm_generic_forwardable,
388 	.forward = snd_pcm_mmap_emul_forward,
389 	.resume = snd_pcm_generic_resume,
390 	.link = snd_pcm_generic_link,
391 	.link_slaves = snd_pcm_generic_link_slaves,
392 	.unlink = snd_pcm_generic_unlink,
393 	.writei = snd_pcm_generic_writei,
394 	.writen = snd_pcm_generic_writen,
395 	.readi = snd_pcm_generic_readi,
396 	.readn = snd_pcm_generic_readn,
397 	.avail_update = snd_pcm_mmap_emul_avail_update,
398 	.mmap_commit = snd_pcm_mmap_emul_mmap_commit,
399 	.htimestamp = snd_pcm_generic_htimestamp,
400 	.poll_descriptors = snd_pcm_generic_poll_descriptors,
401 	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
402 	.poll_revents = snd_pcm_generic_poll_revents,
403 	.may_wait_for_avail_min = snd_pcm_generic_may_wait_for_avail_min,
404 };
405 
406 #ifndef DOC_HIDDEN
__snd_pcm_mmap_emul_open(snd_pcm_t ** pcmp,const char * name,snd_pcm_t * slave,int close_slave)407 int __snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
408 			     snd_pcm_t *slave, int close_slave)
409 {
410 	snd_pcm_t *pcm;
411 	mmap_emul_t *map;
412 	int err;
413 
414 	map = calloc(1, sizeof(*map));
415 	if (!map)
416 		return -ENOMEM;
417 	map->gen.slave = slave;
418 	map->gen.close_slave = close_slave;
419 
420 	err = snd_pcm_new(&pcm, SND_PCM_TYPE_MMAP_EMUL, name,
421 			  slave->stream, slave->mode);
422 	if (err < 0) {
423 		free(map);
424 		return err;
425 	}
426 	pcm->ops = &snd_pcm_mmap_emul_ops;
427 	pcm->fast_ops = &snd_pcm_mmap_emul_fast_ops;
428 	pcm->private_data = map;
429 	pcm->poll_fd = slave->poll_fd;
430 	pcm->poll_events = slave->poll_events;
431 	pcm->tstamp_type = slave->tstamp_type;
432 	snd_pcm_set_hw_ptr(pcm, &map->hw_ptr, -1, 0);
433 	snd_pcm_set_appl_ptr(pcm, &map->appl_ptr, -1, 0);
434 	*pcmp = pcm;
435 
436 	return 0;
437 }
438 #endif
439 
440 /*! \page pcm_plugins
441 
442 \section pcm_plugins_mmap_emul Plugin: mmap_emul
443 
444 \code
445 pcm.name {
446 	type mmap_emul
447 	slave PCM
448 }
449 \endcode
450 
451 \subsection pcm_plugins_mmap_emul_funcref Function reference
452 
453 <UL>
454   <LI>_snd_pcm_hw_open()
455 </UL>
456 
457 */
458 
459 /**
460  * \brief Creates a new mmap_emul PCM
461  * \param pcmp Returns created PCM handle
462  * \param name Name of PCM
463  * \param root Root configuration node
464  * \param conf Configuration node with hw PCM description
465  * \param stream PCM Stream
466  * \param mode PCM Mode
467  * \warning Using of this function might be dangerous in the sense
468  *          of compatibility reasons. The prototype might be freely
469  *          changed in future.
470  */
_snd_pcm_mmap_emul_open(snd_pcm_t ** pcmp,const char * name,snd_config_t * root ATTRIBUTE_UNUSED,snd_config_t * conf,snd_pcm_stream_t stream,int mode)471 int _snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
472 			    snd_config_t *root ATTRIBUTE_UNUSED,
473 			    snd_config_t *conf,
474 			    snd_pcm_stream_t stream, int mode)
475 {
476 	snd_config_iterator_t i, next;
477 	int err;
478 	snd_pcm_t *spcm;
479 	snd_config_t *slave = NULL, *sconf;
480 
481 	snd_config_for_each(i, next, conf) {
482 		snd_config_t *n = snd_config_iterator_entry(i);
483 		const char *id;
484 		if (snd_config_get_id(n, &id) < 0)
485 			continue;
486 		if (snd_pcm_conf_generic_id(id))
487 			continue;
488 		if (strcmp(id, "slave") == 0) {
489 			slave = n;
490 			continue;
491 		}
492 		SNDERR("Unknown field %s", id);
493 		return -EINVAL;
494 	}
495 	if (!slave) {
496 		SNDERR("slave is not defined");
497 		return -EINVAL;
498 	}
499 	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
500 	if (err < 0)
501 		return err;
502 	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
503 	snd_config_delete(sconf);
504 	if (err < 0)
505 		return err;
506 	err = __snd_pcm_mmap_emul_open(pcmp, name, spcm, 1);
507 	if (err < 0)
508 		snd_pcm_close(spcm);
509 	return err;
510 }
511 
512 #ifndef DOC_HIDDEN
513 SND_DLSYM_BUILD_VERSION(_snd_pcm_mmap_emul_open, SND_PCM_DLSYM_VERSION);
514 #endif
515