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