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