tizen 2.3.1 release
[external/alsa-lib.git] / src / pcm / pcm_mmap_emul.c
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 */
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  */
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  */
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(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
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
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
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
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
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
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
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
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
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         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
345 static 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
358 static 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
373 static 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
405 int __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
443 pcm.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  */
469 int _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
511 SND_DLSYM_BUILD_VERSION(_snd_pcm_mmap_emul_open, SND_PCM_DLSYM_VERSION);
512 #endif