Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ALSA: pcm_lib - cleanup & merge hw_ptr update functions

Do general cleanup in snd_pcm_update_hw_ptr*() routines and merge them.
The main change is hw_ptr_interrupt variable removal to simplify code
logic. This variable can be computed directly from hw_ptr.

Ensure that updated hw_ptr is not lower than previous one (it was possible
with old code in some obscure situations when interrupt was delayed or
the lowlevel driver returns wrong ring buffer position value).

Signed-off-by: Jaroslav Kysela <perex@perex.cz>

+121 -195
-1
include/sound/pcm.h
··· 271 271 int overrange; 272 272 snd_pcm_uframes_t avail_max; 273 273 snd_pcm_uframes_t hw_ptr_base; /* Position at buffer restart */ 274 - snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */ 275 274 unsigned long hw_ptr_jiffies; /* Time when hw_ptr is updated */ 276 275 snd_pcm_sframes_t delay; /* extra delay; typically FIFO size */ 277 276
+1 -1
include/sound/pcm_oss.h
··· 61 61 struct snd_pcm_plugin *plugin_first; 62 62 struct snd_pcm_plugin *plugin_last; 63 63 #endif 64 - unsigned int prev_hw_ptr_interrupt; 64 + unsigned int prev_hw_ptr_period; 65 65 }; 66 66 67 67 struct snd_pcm_oss_file {
+23 -9
sound/core/oss/pcm_oss.c
··· 632 632 return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes); 633 633 } 634 634 635 + static inline 636 + snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime) 637 + { 638 + snd_pcm_uframes_t ptr = runtime->status->hw_ptr; 639 + return ptr - (ptr % runtime->period_size); 640 + } 641 + 635 642 /* define extended formats in the recent OSS versions (if any) */ 636 643 /* linear formats */ 637 644 #define AFMT_S32_LE 0x00001000 ··· 1109 1102 return err; 1110 1103 } 1111 1104 runtime->oss.prepare = 0; 1112 - runtime->oss.prev_hw_ptr_interrupt = 0; 1105 + runtime->oss.prev_hw_ptr_period = 0; 1113 1106 runtime->oss.period_ptr = 0; 1114 1107 runtime->oss.buffer_used = 0; 1115 1108 ··· 1957 1950 return result; 1958 1951 } 1959 1952 1960 - static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, snd_pcm_uframes_t hw_ptr) 1953 + static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, 1954 + snd_pcm_uframes_t hw_ptr) 1961 1955 { 1962 1956 struct snd_pcm_runtime *runtime = substream->runtime; 1963 1957 snd_pcm_uframes_t appl_ptr; ··· 1994 1986 if (runtime->oss.trigger) 1995 1987 goto _skip1; 1996 1988 if (atomic_read(&psubstream->mmap_count)) 1997 - snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt); 1989 + snd_pcm_oss_simulate_fill(psubstream, 1990 + get_hw_ptr_period(runtime)); 1998 1991 runtime->oss.trigger = 1; 1999 1992 runtime->start_threshold = 1; 2000 1993 cmd = SNDRV_PCM_IOCTL_START; ··· 2114 2105 info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size); 2115 2106 if (atomic_read(&substream->mmap_count)) { 2116 2107 snd_pcm_sframes_t n; 2117 - n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt; 2108 + delay = get_hw_ptr_period(runtime); 2109 + n = delay - runtime->oss.prev_hw_ptr_period; 2118 2110 if (n < 0) 2119 2111 n += runtime->boundary; 2120 2112 info.blocks = n / runtime->period_size; 2121 - runtime->oss.prev_hw_ptr_interrupt = delay; 2113 + runtime->oss.prev_hw_ptr_period = delay; 2122 2114 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 2123 2115 snd_pcm_oss_simulate_fill(substream, delay); 2124 2116 info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX; ··· 2683 2673 { 2684 2674 struct snd_pcm_runtime *runtime = substream->runtime; 2685 2675 if (atomic_read(&substream->mmap_count)) 2686 - return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; 2676 + return runtime->oss.prev_hw_ptr_period != 2677 + get_hw_ptr_period(runtime); 2687 2678 else 2688 - return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames; 2679 + return snd_pcm_playback_avail(runtime) >= 2680 + runtime->oss.period_frames; 2689 2681 } 2690 2682 2691 2683 static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream) 2692 2684 { 2693 2685 struct snd_pcm_runtime *runtime = substream->runtime; 2694 2686 if (atomic_read(&substream->mmap_count)) 2695 - return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; 2687 + return runtime->oss.prev_hw_ptr_period != 2688 + get_hw_ptr_period(runtime); 2696 2689 else 2697 - return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames; 2690 + return snd_pcm_capture_avail(runtime) >= 2691 + runtime->oss.period_frames; 2698 2692 } 2699 2693 2700 2694 static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)
+97 -182
sound/core/pcm_lib.c
··· 172 172 #define hw_ptr_error(substream, fmt, args...) \ 173 173 do { \ 174 174 if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \ 175 + xrun_log_show(substream); \ 175 176 if (printk_ratelimit()) { \ 176 177 snd_printd("PCM: " fmt, ##args); \ 177 178 } \ ··· 189 188 snd_pcm_uframes_t buffer_size; 190 189 snd_pcm_uframes_t old_hw_ptr; 191 190 snd_pcm_uframes_t hw_ptr_base; 192 - snd_pcm_uframes_t hw_ptr_interrupt; 193 191 }; 194 192 195 193 struct snd_pcm_hwptr_log { ··· 220 220 entry->buffer_size = runtime->buffer_size;; 221 221 entry->old_hw_ptr = runtime->status->hw_ptr; 222 222 entry->hw_ptr_base = runtime->hw_ptr_base; 223 - entry->hw_ptr_interrupt = runtime->hw_ptr_interrupt;; 224 223 log->idx = (log->idx + 1) % XRUN_LOG_CNT; 225 224 } 226 225 ··· 240 241 entry = &log->entries[idx]; 241 242 if (entry->period_size == 0) 242 243 break; 243 - snd_printd("hwptr log: %s: j=%lu, pos=0x%lx/0x%lx/0x%lx, " 244 - "hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n", 244 + snd_printd("hwptr log: %s: j=%lu, pos=%ld/%ld/%ld, " 245 + "hwptr=%ld/%ld\n", 245 246 name, entry->jiffies, (unsigned long)entry->pos, 246 247 (unsigned long)entry->period_size, 247 248 (unsigned long)entry->buffer_size, 248 249 (unsigned long)entry->old_hw_ptr, 249 - (unsigned long)entry->hw_ptr_base, 250 - (unsigned long)entry->hw_ptr_interrupt); 250 + (unsigned long)entry->hw_ptr_base); 251 251 idx++; 252 252 idx %= XRUN_LOG_CNT; 253 253 } ··· 262 264 #define xrun_log_show(substream) do { } while (0) 263 265 264 266 #endif 265 - 266 - static snd_pcm_uframes_t 267 - snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream, 268 - struct snd_pcm_runtime *runtime) 269 - { 270 - snd_pcm_uframes_t pos; 271 - 272 - pos = substream->ops->pointer(substream); 273 - if (pos == SNDRV_PCM_POS_XRUN) 274 - return pos; /* XRUN */ 275 - if (pos >= runtime->buffer_size) { 276 - if (printk_ratelimit()) { 277 - char name[16]; 278 - pcm_debug_name(substream, name, sizeof(name)); 279 - xrun_log_show(substream); 280 - snd_printd(KERN_ERR "BUG: %s, pos = 0x%lx, " 281 - "buffer size = 0x%lx, period size = 0x%lx\n", 282 - name, pos, runtime->buffer_size, 283 - runtime->period_size); 284 - } 285 - pos = 0; 286 - } 287 - pos -= pos % runtime->min_align; 288 - if (xrun_debug(substream, XRUN_DEBUG_LOG)) 289 - xrun_log(substream, pos); 290 - return pos; 291 - } 292 267 293 268 static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream, 294 269 struct snd_pcm_runtime *runtime) ··· 290 319 return 0; 291 320 } 292 321 293 - static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) 322 + static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, 323 + unsigned int in_interrupt) 294 324 { 295 325 struct snd_pcm_runtime *runtime = substream->runtime; 296 326 snd_pcm_uframes_t pos; 297 - snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_ptr_interrupt, hw_base; 327 + snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base; 298 328 snd_pcm_sframes_t hdelta, delta; 299 329 unsigned long jdelta; 300 330 301 331 old_hw_ptr = runtime->status->hw_ptr; 302 - pos = snd_pcm_update_hw_ptr_pos(substream, runtime); 332 + pos = substream->ops->pointer(substream); 303 333 if (pos == SNDRV_PCM_POS_XRUN) { 304 334 xrun(substream); 305 335 return -EPIPE; 306 336 } 307 - if (xrun_debug(substream, XRUN_DEBUG_PERIODUPDATE)) { 308 - char name[16]; 309 - pcm_debug_name(substream, name, sizeof(name)); 310 - snd_printd("period_update: %s: pos=0x%x/0x%x/0x%x, " 311 - "hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n", 312 - name, (unsigned int)pos, 313 - (unsigned int)runtime->period_size, 314 - (unsigned int)runtime->buffer_size, 315 - (unsigned long)old_hw_ptr, 316 - (unsigned long)runtime->hw_ptr_base, 317 - (unsigned long)runtime->hw_ptr_interrupt); 337 + if (pos >= runtime->buffer_size) { 338 + if (printk_ratelimit()) { 339 + char name[16]; 340 + pcm_debug_name(substream, name, sizeof(name)); 341 + xrun_log_show(substream); 342 + snd_printd(KERN_ERR "BUG: %s, pos = %ld, " 343 + "buffer size = %ld, period size = %ld\n", 344 + name, pos, runtime->buffer_size, 345 + runtime->period_size); 346 + } 347 + pos = 0; 318 348 } 349 + pos -= pos % runtime->min_align; 350 + if (xrun_debug(substream, XRUN_DEBUG_LOG)) 351 + xrun_log(substream, pos); 319 352 hw_base = runtime->hw_ptr_base; 320 353 new_hw_ptr = hw_base + pos; 321 - hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size; 322 - delta = new_hw_ptr - hw_ptr_interrupt; 323 - if (hw_ptr_interrupt >= runtime->boundary) { 324 - hw_ptr_interrupt -= runtime->boundary; 325 - if (hw_base < runtime->boundary / 2) 326 - /* hw_base was already lapped; recalc delta */ 327 - delta = new_hw_ptr - hw_ptr_interrupt; 328 - } 329 - if (delta < 0) { 330 - if (runtime->periods == 1 || new_hw_ptr < old_hw_ptr) 331 - delta += runtime->buffer_size; 332 - if (delta < 0) { 333 - xrun_log_show(substream); 334 - hw_ptr_error(substream, 335 - "Unexpected hw_pointer value " 336 - "(stream=%i, pos=%ld, intr_ptr=%ld)\n", 337 - substream->stream, (long)pos, 338 - (long)hw_ptr_interrupt); 339 - #if 1 340 - /* simply skipping the hwptr update seems more 341 - * robust in some cases, e.g. on VMware with 342 - * inaccurate timer source 343 - */ 344 - return 0; /* skip this update */ 345 - #else 346 - /* rebase to interrupt position */ 347 - hw_base = new_hw_ptr = hw_ptr_interrupt; 348 - /* align hw_base to buffer_size */ 349 - hw_base -= hw_base % runtime->buffer_size; 350 - delta = 0; 351 - #endif 352 - } else { 354 + if (in_interrupt) { 355 + /* we know that one period was processed */ 356 + /* delta = "expected next hw_ptr" for in_interrupt != 0 */ 357 + delta = old_hw_ptr - (old_hw_ptr % runtime->period_size) 358 + + runtime->period_size; 359 + if (delta > new_hw_ptr) { 353 360 hw_base += runtime->buffer_size; 354 361 if (hw_base >= runtime->boundary) 355 362 hw_base = 0; 356 363 new_hw_ptr = hw_base + pos; 364 + goto __delta; 357 365 } 366 + } 367 + /* new_hw_ptr might be lower than old_hw_ptr in case when */ 368 + /* pointer crosses the end of the ring buffer */ 369 + if (new_hw_ptr < old_hw_ptr) { 370 + hw_base += runtime->buffer_size; 371 + if (hw_base >= runtime->boundary) 372 + hw_base = 0; 373 + new_hw_ptr = hw_base + pos; 374 + } 375 + __delta: 376 + delta = (new_hw_ptr - old_hw_ptr) % runtime->boundary; 377 + if (xrun_debug(substream, in_interrupt ? 378 + XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) { 379 + char name[16]; 380 + pcm_debug_name(substream, name, sizeof(name)); 381 + snd_printd("%s_update: %s: pos=%u/%u/%u, " 382 + "hwptr=%ld/%ld/%ld/%ld\n", 383 + in_interrupt ? "period" : "hwptr", 384 + name, 385 + (unsigned int)pos, 386 + (unsigned int)runtime->period_size, 387 + (unsigned int)runtime->buffer_size, 388 + (unsigned long)delta, 389 + (unsigned long)old_hw_ptr, 390 + (unsigned long)new_hw_ptr, 391 + (unsigned long)runtime->hw_ptr_base); 392 + } 393 + /* something must be really wrong */ 394 + if (delta >= runtime->buffer_size) { 395 + hw_ptr_error(substream, 396 + "Unexpected hw_pointer value %s" 397 + "(stream=%i, pos=%ld, new_hw_ptr=%ld, " 398 + "old_hw_ptr=%ld)\n", 399 + in_interrupt ? "[Q] " : "[P]", 400 + substream->stream, (long)pos, 401 + (long)new_hw_ptr, (long)old_hw_ptr); 402 + return 0; 358 403 } 359 404 360 405 /* Do jiffies check only in xrun_debug mode */ ··· 383 396 */ 384 397 if (runtime->hw.info & SNDRV_PCM_INFO_BATCH) 385 398 goto no_jiffies_check; 386 - hdelta = new_hw_ptr - old_hw_ptr; 399 + hdelta = delta; 387 400 if (hdelta < runtime->delay) 388 401 goto no_jiffies_check; 389 402 hdelta -= runtime->delay; ··· 392 405 delta = jdelta / 393 406 (((runtime->period_size * HZ) / runtime->rate) 394 407 + HZ/100); 395 - xrun_log_show(substream); 408 + /* move new_hw_ptr according jiffies not pos variable */ 409 + new_hw_ptr = old_hw_ptr; 410 + /* use loop to avoid checks for delta overflows */ 411 + /* the delta value is small or zero in most cases */ 412 + while (delta > 0) { 413 + new_hw_ptr += runtime->period_size; 414 + if (new_hw_ptr >= runtime->boundary) 415 + new_hw_ptr -= runtime->boundary; 416 + delta--; 417 + } 418 + /* align hw_base to buffer_size */ 419 + hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size); 420 + delta = 0; 396 421 hw_ptr_error(substream, 397 - "hw_ptr skipping! [Q] " 422 + "hw_ptr skipping! %s" 398 423 "(pos=%ld, delta=%ld, period=%ld, " 399 - "jdelta=%lu/%lu/%lu)\n", 424 + "jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n", 425 + in_interrupt ? "[Q] " : "", 400 426 (long)pos, (long)hdelta, 401 427 (long)runtime->period_size, jdelta, 402 - ((hdelta * HZ) / runtime->rate), delta); 403 - hw_ptr_interrupt = runtime->hw_ptr_interrupt + 404 - runtime->period_size * delta; 405 - if (hw_ptr_interrupt >= runtime->boundary) 406 - hw_ptr_interrupt -= runtime->boundary; 407 - /* rebase to interrupt position */ 408 - hw_base = new_hw_ptr = hw_ptr_interrupt; 409 - /* align hw_base to buffer_size */ 410 - hw_base -= hw_base % runtime->buffer_size; 411 - delta = 0; 428 + ((hdelta * HZ) / runtime->rate), delta, 429 + (unsigned long)old_hw_ptr, 430 + (unsigned long)new_hw_ptr); 412 431 } 413 432 no_jiffies_check: 414 433 if (delta > runtime->period_size + runtime->period_size / 2) { 415 - xrun_log_show(substream); 416 434 hw_ptr_error(substream, 417 - "Lost interrupts? " 418 - "(stream=%i, delta=%ld, intr_ptr=%ld)\n", 435 + "Lost interrupts? %s" 436 + "(stream=%i, delta=%ld, new_hw_ptr=%ld, " 437 + "old_hw_ptr=%ld)\n", 438 + in_interrupt ? "[Q] " : "", 419 439 substream->stream, (long)delta, 420 - (long)hw_ptr_interrupt); 421 - /* rebase hw_ptr_interrupt */ 422 - hw_ptr_interrupt = 423 - new_hw_ptr - new_hw_ptr % runtime->period_size; 440 + (long)new_hw_ptr, 441 + (long)old_hw_ptr); 424 442 } 425 - runtime->hw_ptr_interrupt = hw_ptr_interrupt; 443 + 444 + if (runtime->status->hw_ptr == new_hw_ptr) 445 + return 0; 426 446 427 447 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && 428 448 runtime->silence_size > 0) 429 449 snd_pcm_playback_silence(substream, new_hw_ptr); 430 - 431 - if (runtime->status->hw_ptr == new_hw_ptr) 432 - return 0; 433 450 434 451 runtime->hw_ptr_base = hw_base; 435 452 runtime->status->hw_ptr = new_hw_ptr; ··· 447 456 /* CAUTION: call it with irq disabled */ 448 457 int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) 449 458 { 450 - struct snd_pcm_runtime *runtime = substream->runtime; 451 - snd_pcm_uframes_t pos; 452 - snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base; 453 - snd_pcm_sframes_t delta; 454 - unsigned long jdelta; 455 - 456 - old_hw_ptr = runtime->status->hw_ptr; 457 - pos = snd_pcm_update_hw_ptr_pos(substream, runtime); 458 - if (pos == SNDRV_PCM_POS_XRUN) { 459 - xrun(substream); 460 - return -EPIPE; 461 - } 462 - if (xrun_debug(substream, XRUN_DEBUG_HWPTRUPDATE)) { 463 - char name[16]; 464 - pcm_debug_name(substream, name, sizeof(name)); 465 - snd_printd("hw_update: %s: pos=0x%x/0x%x/0x%x, " 466 - "hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n", 467 - name, (unsigned int)pos, 468 - (unsigned int)runtime->period_size, 469 - (unsigned int)runtime->buffer_size, 470 - (unsigned long)old_hw_ptr, 471 - (unsigned long)runtime->hw_ptr_base, 472 - (unsigned long)runtime->hw_ptr_interrupt); 473 - } 474 - 475 - hw_base = runtime->hw_ptr_base; 476 - new_hw_ptr = hw_base + pos; 477 - 478 - delta = new_hw_ptr - old_hw_ptr; 479 - jdelta = jiffies - runtime->hw_ptr_jiffies; 480 - if (delta < 0) { 481 - delta += runtime->buffer_size; 482 - if (delta < 0) { 483 - xrun_log_show(substream); 484 - hw_ptr_error(substream, 485 - "Unexpected hw_pointer value [2] " 486 - "(stream=%i, pos=%ld, old_ptr=%ld, jdelta=%li)\n", 487 - substream->stream, (long)pos, 488 - (long)old_hw_ptr, jdelta); 489 - return 0; 490 - } 491 - hw_base += runtime->buffer_size; 492 - if (hw_base >= runtime->boundary) 493 - hw_base = 0; 494 - new_hw_ptr = hw_base + pos; 495 - } 496 - /* Do jiffies check only in xrun_debug mode */ 497 - if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK)) 498 - goto no_jiffies_check; 499 - if (delta < runtime->delay) 500 - goto no_jiffies_check; 501 - delta -= runtime->delay; 502 - if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) { 503 - xrun_log_show(substream); 504 - hw_ptr_error(substream, 505 - "hw_ptr skipping! " 506 - "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu)\n", 507 - (long)pos, (long)delta, 508 - (long)runtime->period_size, jdelta, 509 - ((delta * HZ) / runtime->rate)); 510 - return 0; 511 - } 512 - no_jiffies_check: 513 - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && 514 - runtime->silence_size > 0) 515 - snd_pcm_playback_silence(substream, new_hw_ptr); 516 - 517 - if (runtime->status->hw_ptr == new_hw_ptr) 518 - return 0; 519 - 520 - runtime->hw_ptr_base = hw_base; 521 - runtime->status->hw_ptr = new_hw_ptr; 522 - runtime->hw_ptr_jiffies = jiffies; 523 - if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) 524 - snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); 525 - 526 - return snd_pcm_update_hw_ptr_post(substream, runtime); 459 + return snd_pcm_update_hw_ptr0(substream, 0); 527 460 } 528 461 529 462 /** ··· 1659 1744 1660 1745 snd_pcm_stream_lock_irqsave(substream, flags); 1661 1746 if (!snd_pcm_running(substream) || 1662 - snd_pcm_update_hw_ptr_interrupt(substream) < 0) 1747 + snd_pcm_update_hw_ptr0(substream, 1) < 0) 1663 1748 goto _end; 1664 1749 1665 1750 if (substream->timer_running)
-2
sound/core/pcm_native.c
··· 1247 1247 if (err < 0) 1248 1248 return err; 1249 1249 runtime->hw_ptr_base = 0; 1250 - runtime->hw_ptr_interrupt = runtime->status->hw_ptr - 1251 - runtime->status->hw_ptr % runtime->period_size; 1252 1250 runtime->silence_start = runtime->status->hw_ptr; 1253 1251 runtime->silence_filled = 0; 1254 1252 return 0;