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