Skip to content

Commit

Permalink
[ALSA] pcm-midlevel: Add more strict buffer position checks based on …
Browse files Browse the repository at this point in the history
…jiffies

Some drivers like Intel8x0 or Intel HDA are broken for some hardware variants.
This patch adds more strict buffer position checks based on jiffies when
internal hw_ptr is updated. Enable xrun_debug to see mangling of wrong
positions.

As a side effect, the hw_ptr interrupt update routine might do slightly better
job when many interrupts are lost.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
  • Loading branch information
Jaroslav Kysela committed Apr 10, 2009
1 parent fa00e04 commit bbf6ad1
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 9 deletions.
3 changes: 2 additions & 1 deletion include/sound/pcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ struct snd_pcm_runtime {
int overrange;
snd_pcm_uframes_t avail_max;
snd_pcm_uframes_t hw_ptr_base; /* Position at buffer restart */
snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time*/
snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
unsigned long hw_ptr_jiffies; /* Time when hw_ptr is updated */

/* -- HW params -- */
snd_pcm_access_t access; /* access mode */
Expand Down
47 changes: 39 additions & 8 deletions sound/core/pcm_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,11 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t pos;
snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt, hw_base;
snd_pcm_sframes_t delta;
snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_ptr_interrupt, hw_base;
snd_pcm_sframes_t hdelta, delta;
unsigned long jdelta;

old_hw_ptr = runtime->status->hw_ptr;
pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
if (pos == SNDRV_PCM_POS_XRUN) {
xrun(substream);
Expand Down Expand Up @@ -247,7 +249,30 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
new_hw_ptr = hw_base + pos;
}
}
if (delta > runtime->period_size) {
hdelta = new_hw_ptr - old_hw_ptr;
jdelta = jiffies - runtime->hw_ptr_jiffies;
if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
delta = jdelta /
(((runtime->period_size * HZ) / runtime->rate)
+ HZ/100);
hw_ptr_error(substream,
"hw_ptr skipping! [Q] "
"(pos=%ld, delta=%ld, period=%ld, "
"jdelta=%lu/%lu/%lu)\n",
(long)pos, (long)hdelta,
(long)runtime->period_size, jdelta,
((hdelta * HZ) / runtime->rate), delta);
hw_ptr_interrupt = runtime->hw_ptr_interrupt +
runtime->period_size * delta;
if (hw_ptr_interrupt >= runtime->boundary)
hw_ptr_interrupt -= runtime->boundary;
/* rebase to interrupt position */
hw_base = new_hw_ptr = hw_ptr_interrupt;
/* align hw_base to buffer_size */
hw_base -= hw_base % runtime->buffer_size;
delta = 0;
}
if (delta > runtime->period_size + runtime->period_size / 2) {
hw_ptr_error(substream,
"Lost interrupts? "
"(stream=%i, delta=%ld, intr_ptr=%ld)\n",
Expand All @@ -263,6 +288,7 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)

runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
runtime->hw_ptr_jiffies = jiffies;
runtime->hw_ptr_interrupt = hw_ptr_interrupt;

return snd_pcm_update_hw_ptr_post(substream, runtime);
Expand All @@ -275,6 +301,7 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
snd_pcm_uframes_t pos;
snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
snd_pcm_sframes_t delta;
unsigned long jdelta;

old_hw_ptr = runtime->status->hw_ptr;
pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
Expand All @@ -286,27 +313,29 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
new_hw_ptr = hw_base + pos;

delta = new_hw_ptr - old_hw_ptr;
jdelta = jiffies - runtime->hw_ptr_jiffies;
if (delta < 0) {
delta += runtime->buffer_size;
if (delta < 0) {
hw_ptr_error(substream,
"Unexpected hw_pointer value [2] "
"(stream=%i, pos=%ld, old_ptr=%ld)\n",
"(stream=%i, pos=%ld, old_ptr=%ld, jdelta=%li)\n",
substream->stream, (long)pos,
(long)old_hw_ptr);
(long)old_hw_ptr, jdelta);
return 0;
}
hw_base += runtime->buffer_size;
if (hw_base >= runtime->boundary)
hw_base = 0;
new_hw_ptr = hw_base + pos;
}
if (delta > runtime->period_size && runtime->periods > 1) {
if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) {
hw_ptr_error(substream,
"hw_ptr skipping! "
"(pos=%ld, delta=%ld, period=%ld)\n",
"(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu)\n",
(long)pos, (long)delta,
(long)runtime->period_size);
(long)runtime->period_size, jdelta,
((delta * HZ) / runtime->rate));
return 0;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
Expand All @@ -315,6 +344,7 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)

runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
runtime->hw_ptr_jiffies = jiffies;

return snd_pcm_update_hw_ptr_post(substream, runtime);
}
Expand Down Expand Up @@ -1441,6 +1471,7 @@ static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream,
runtime->status->hw_ptr %= runtime->buffer_size;
else
runtime->status->hw_ptr = 0;
runtime->hw_ptr_jiffies = jiffies;
snd_pcm_stream_unlock_irqrestore(substream, flags);
return 0;
}
Expand Down

0 comments on commit bbf6ad1

Please sign in to comment.