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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
26 *
27 */
28
29#include "pcm_local.h"
30#include "pcm_generic.h"
31
32#ifndef PIC
33/* entry for static linking */
34const char *_snd_module_pcm_mmap_emul = "";
35#endif
36
37#ifndef DOC_HIDDEN
38/*
39 *
40 */
41
42typedef 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 */
55static 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 */
153static 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(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(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
209static 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
230static 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
242static 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
254static snd_pcm_sframes_t
255snd_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
263static snd_pcm_sframes_t
264snd_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 */
273static snd_pcm_sframes_t
274sync_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 */
296static snd_pcm_sframes_t
297sync_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
316static snd_pcm_sframes_t
317snd_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
331static 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	snd_pcm_sframes_t avail;
336
337	avail = snd_pcm_avail_update(slave);
338	if (!map->mmap_emul || pcm->stream == SND_PCM_STREAM_PLAYBACK)
339		map->hw_ptr = *slave->hw.ptr;
340	else
341		sync_slave_read(pcm);
342	return snd_pcm_mmap_avail(pcm);
343}
344
345static void snd_pcm_mmap_emul_dump(snd_pcm_t *pcm, snd_output_t *out)
346{
347	mmap_emul_t *map = pcm->private_data;
348
349	snd_output_printf(out, "Mmap emulation PCM\n");
350	if (pcm->setup) {
351		snd_output_printf(out, "Its setup is:\n");
352		snd_pcm_dump_setup(pcm, out);
353	}
354	snd_output_printf(out, "Slave: ");
355	snd_pcm_dump(map->gen.slave, out);
356}
357
358static const snd_pcm_ops_t snd_pcm_mmap_emul_ops = {
359	.close = snd_pcm_generic_close,
360	.info = snd_pcm_generic_info,
361	.hw_refine = snd_pcm_mmap_emul_hw_refine,
362	.hw_params = snd_pcm_mmap_emul_hw_params,
363	.hw_free = snd_pcm_generic_hw_free,
364	.sw_params = snd_pcm_mmap_emul_sw_params,
365	.channel_info = snd_pcm_generic_channel_info,
366	.dump = snd_pcm_mmap_emul_dump,
367	.nonblock = snd_pcm_generic_nonblock,
368	.async = snd_pcm_generic_async,
369	.mmap = snd_pcm_generic_mmap,
370	.munmap = snd_pcm_generic_munmap,
371};
372
373static const snd_pcm_fast_ops_t snd_pcm_mmap_emul_fast_ops = {
374	.status = snd_pcm_generic_status,
375	.state = snd_pcm_generic_state,
376	.hwsync = snd_pcm_generic_hwsync,
377	.delay = snd_pcm_generic_delay,
378	.prepare = snd_pcm_mmap_emul_prepare,
379	.reset = snd_pcm_mmap_emul_reset,
380	.start = snd_pcm_generic_start,
381	.drop = snd_pcm_generic_drop,
382	.drain = snd_pcm_generic_drain,
383	.pause = snd_pcm_generic_pause,
384	.rewindable = snd_pcm_generic_rewindable,
385	.rewind = snd_pcm_mmap_emul_rewind,
386	.forwardable = snd_pcm_generic_forwardable,
387	.forward = snd_pcm_mmap_emul_forward,
388	.resume = snd_pcm_generic_resume,
389	.link = snd_pcm_generic_link,
390	.link_slaves = snd_pcm_generic_link_slaves,
391	.unlink = snd_pcm_generic_unlink,
392	.writei = snd_pcm_generic_writei,
393	.writen = snd_pcm_generic_writen,
394	.readi = snd_pcm_generic_readi,
395	.readn = snd_pcm_generic_readn,
396	.avail_update = snd_pcm_mmap_emul_avail_update,
397	.mmap_commit = snd_pcm_mmap_emul_mmap_commit,
398	.htimestamp = snd_pcm_generic_htimestamp,
399	.poll_descriptors = snd_pcm_generic_poll_descriptors,
400	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
401	.poll_revents = snd_pcm_generic_poll_revents,
402};
403
404#ifndef DOC_HIDDEN
405int __snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
406			     snd_pcm_t *slave, int close_slave)
407{
408	snd_pcm_t *pcm;
409	mmap_emul_t *map;
410	int err;
411
412	map = calloc(1, sizeof(*map));
413	if (!map)
414		return -ENOMEM;
415	map->gen.slave = slave;
416	map->gen.close_slave = close_slave;
417
418	err = snd_pcm_new(&pcm, SND_PCM_TYPE_MMAP_EMUL, name,
419			  slave->stream, slave->mode);
420	if (err < 0) {
421		free(map);
422		return err;
423	}
424	pcm->ops = &snd_pcm_mmap_emul_ops;
425	pcm->fast_ops = &snd_pcm_mmap_emul_fast_ops;
426	pcm->private_data = map;
427	pcm->poll_fd = slave->poll_fd;
428	pcm->poll_events = slave->poll_events;
429	pcm->monotonic = slave->monotonic;
430	snd_pcm_set_hw_ptr(pcm, &map->hw_ptr, -1, 0);
431	snd_pcm_set_appl_ptr(pcm, &map->appl_ptr, -1, 0);
432	*pcmp = pcm;
433
434	return 0;
435}
436#endif
437
438/*! \page pcm_plugins
439
440\section pcm_plugins_mmap_emul Plugin: mmap_emul
441
442\code
443pcm.name {
444	type mmap_emul
445	slave PCM
446}
447\endcode
448
449\subsection pcm_plugins_mmap_emul_funcref Function reference
450
451<UL>
452  <LI>_snd_pcm_hw_open()
453</UL>
454
455*/
456
457/**
458 * \brief Creates a new mmap_emul PCM
459 * \param pcmp Returns created PCM handle
460 * \param name Name of PCM
461 * \param root Root configuration node
462 * \param conf Configuration node with hw PCM description
463 * \param stream PCM Stream
464 * \param mode PCM Mode
465 * \warning Using of this function might be dangerous in the sense
466 *          of compatibility reasons. The prototype might be freely
467 *          changed in future.
468 */
469int _snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
470			    snd_config_t *root ATTRIBUTE_UNUSED,
471			    snd_config_t *conf,
472			    snd_pcm_stream_t stream, int mode)
473{
474	snd_config_iterator_t i, next;
475	int err;
476	snd_pcm_t *spcm;
477	snd_config_t *slave = NULL, *sconf;
478
479	snd_config_for_each(i, next, conf) {
480		snd_config_t *n = snd_config_iterator_entry(i);
481		const char *id;
482		if (snd_config_get_id(n, &id) < 0)
483			continue;
484		if (snd_pcm_conf_generic_id(id))
485			continue;
486		if (strcmp(id, "slave") == 0) {
487			slave = n;
488			continue;
489		}
490		SNDERR("Unknown field %s", id);
491		return -EINVAL;
492	}
493	if (!slave) {
494		SNDERR("slave is not defined");
495		return -EINVAL;
496	}
497	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
498	if (err < 0)
499		return err;
500	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
501	snd_config_delete(sconf);
502	if (err < 0)
503		return err;
504	err = __snd_pcm_mmap_emul_open(pcmp, name, spcm, 1);
505	if (err < 0)
506		snd_pcm_close(spcm);
507	return err;
508}
509
510#ifndef DOC_HIDDEN
511SND_DLSYM_BUILD_VERSION(_snd_pcm_mmap_emul_open, SND_PCM_DLSYM_VERSION);
512#endif
513