aboutsummaryrefslogtreecommitdiff
path: root/sound/core/pcm_lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core/pcm_lib.c')
-rw-r--r--sound/core/pcm_lib.c195
1 files changed, 141 insertions, 54 deletions
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 921691080f3..a2a792c18c4 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -125,23 +125,32 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram
}
}
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+#define xrun_debug(substream) ((substream)->pstr->xrun_debug)
+#else
+#define xrun_debug(substream) 0
+#endif
+
+#define dump_stack_on_xrun(substream) do { \
+ if (xrun_debug(substream) > 1) \
+ dump_stack(); \
+ } while (0)
+
static void xrun(struct snd_pcm_substream *substream)
{
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
-#ifdef CONFIG_SND_PCM_XRUN_DEBUG
- if (substream->pstr->xrun_debug) {
+ if (xrun_debug(substream)) {
snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n",
substream->pcm->card->number,
substream->pcm->device,
substream->stream ? 'c' : 'p');
- if (substream->pstr->xrun_debug > 1)
- dump_stack();
+ dump_stack_on_xrun(substream);
}
-#endif
}
-static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream,
- struct snd_pcm_runtime *runtime)
+static snd_pcm_uframes_t
+snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream,
+ struct snd_pcm_runtime *runtime)
{
snd_pcm_uframes_t pos;
@@ -150,17 +159,21 @@ static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(struct snd_pcm_substre
pos = substream->ops->pointer(substream);
if (pos == SNDRV_PCM_POS_XRUN)
return pos; /* XRUN */
-#ifdef CONFIG_SND_DEBUG
if (pos >= runtime->buffer_size) {
- snd_printk(KERN_ERR "BUG: stream = %i, pos = 0x%lx, buffer size = 0x%lx, period size = 0x%lx\n", substream->stream, pos, runtime->buffer_size, runtime->period_size);
+ if (printk_ratelimit()) {
+ snd_printd(KERN_ERR "BUG: stream = %i, pos = 0x%lx, "
+ "buffer size = 0x%lx, period size = 0x%lx\n",
+ substream->stream, pos, runtime->buffer_size,
+ runtime->period_size);
+ }
+ pos = 0;
}
-#endif
pos -= pos % runtime->min_align;
return pos;
}
-static inline int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
- struct snd_pcm_runtime *runtime)
+static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
+ struct snd_pcm_runtime *runtime)
{
snd_pcm_uframes_t avail;
@@ -182,48 +195,108 @@ static inline int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream
return 0;
}
-static inline int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
+#define hw_ptr_error(substream, fmt, args...) \
+ do { \
+ if (xrun_debug(substream)) { \
+ if (printk_ratelimit()) { \
+ snd_printd("PCM: " fmt, ##args); \
+ } \
+ dump_stack_on_xrun(substream); \
+ } \
+ } while (0)
+
+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;
- 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);
return -EPIPE;
}
- if (runtime->period_size == runtime->buffer_size)
- goto __next_buf;
- new_hw_ptr = runtime->hw_ptr_base + pos;
+ hw_base = runtime->hw_ptr_base;
+ new_hw_ptr = hw_base + pos;
hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size;
-
- delta = hw_ptr_interrupt - new_hw_ptr;
- if (delta > 0) {
- if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
-#ifdef CONFIG_SND_PCM_XRUN_DEBUG
- if (runtime->periods > 1 && substream->pstr->xrun_debug) {
- snd_printd(KERN_ERR "Unexpected hw_pointer value [1] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
- if (substream->pstr->xrun_debug > 1)
- dump_stack();
- }
-#endif
- return 0;
+ delta = new_hw_ptr - hw_ptr_interrupt;
+ if (hw_ptr_interrupt >= runtime->boundary) {
+ hw_ptr_interrupt -= runtime->boundary;
+ if (hw_base < runtime->boundary / 2)
+ /* hw_base was already lapped; recalc delta */
+ delta = new_hw_ptr - hw_ptr_interrupt;
+ }
+ if (delta < 0) {
+ delta += runtime->buffer_size;
+ if (delta < 0) {
+ hw_ptr_error(substream,
+ "Unexpected hw_pointer value "
+ "(stream=%i, pos=%ld, intr_ptr=%ld)\n",
+ substream->stream, (long)pos,
+ (long)hw_ptr_interrupt);
+ /* 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;
+ } else {
+ hw_base += runtime->buffer_size;
+ if (hw_base >= runtime->boundary)
+ hw_base = 0;
+ new_hw_ptr = hw_base + pos;
}
- __next_buf:
- runtime->hw_ptr_base += runtime->buffer_size;
- if (runtime->hw_ptr_base == runtime->boundary)
- runtime->hw_ptr_base = 0;
- new_hw_ptr = runtime->hw_ptr_base + pos;
}
-
+ /* Skip the jiffies check for hardwares with BATCH flag.
+ * Such hardware usually just increases the position at each IRQ,
+ * thus it can't give any strange position.
+ */
+ if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
+ goto no_jiffies_check;
+ 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;
+ }
+ no_jiffies_check:
+ if (delta > runtime->period_size + runtime->period_size / 2) {
+ hw_ptr_error(substream,
+ "Lost interrupts? "
+ "(stream=%i, delta=%ld, intr_ptr=%ld)\n",
+ substream->stream, (long)delta,
+ (long)hw_ptr_interrupt);
+ /* rebase hw_ptr_interrupt */
+ hw_ptr_interrupt =
+ new_hw_ptr - new_hw_ptr % runtime->period_size;
+ }
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, new_hw_ptr);
+ runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
- runtime->hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size;
+ runtime->hw_ptr_jiffies = jiffies;
+ runtime->hw_ptr_interrupt = hw_ptr_interrupt;
return snd_pcm_update_hw_ptr_post(substream, runtime);
}
@@ -233,8 +306,9 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t pos;
- snd_pcm_uframes_t old_hw_ptr, new_hw_ptr;
+ 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);
@@ -242,30 +316,42 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
xrun(substream);
return -EPIPE;
}
- new_hw_ptr = runtime->hw_ptr_base + pos;
-
- delta = old_hw_ptr - new_hw_ptr;
- if (delta > 0) {
- if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
-#ifdef CONFIG_SND_PCM_XRUN_DEBUG
- if (runtime->periods > 2 && substream->pstr->xrun_debug) {
- snd_printd(KERN_ERR "Unexpected hw_pointer value [2] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
- if (substream->pstr->xrun_debug > 1)
- dump_stack();
- }
-#endif
+ hw_base = runtime->hw_ptr_base;
+ 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, jdelta=%li)\n",
+ substream->stream, (long)pos,
+ (long)old_hw_ptr, jdelta);
return 0;
}
- runtime->hw_ptr_base += runtime->buffer_size;
- if (runtime->hw_ptr_base == runtime->boundary)
- runtime->hw_ptr_base = 0;
- new_hw_ptr = runtime->hw_ptr_base + pos;
+ hw_base += runtime->buffer_size;
+ if (hw_base >= runtime->boundary)
+ hw_base = 0;
+ new_hw_ptr = hw_base + pos;
+ }
+ if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) {
+ hw_ptr_error(substream,
+ "hw_ptr skipping! "
+ "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu)\n",
+ (long)pos, (long)delta,
+ (long)runtime->period_size, jdelta,
+ ((delta * HZ) / runtime->rate));
+ return 0;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, new_hw_ptr);
+ 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);
}
@@ -1392,6 +1478,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;
}