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 */ 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_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 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 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 343static 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 356static 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 374static 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 407int __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 445pcm.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 */ 471int _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 513SND_DLSYM_BUILD_VERSION(_snd_pcm_mmap_emul_open, SND_PCM_DLSYM_VERSION); 514#endif 515