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>
This commit is contained in:
Jaroslav Kysela 2010-01-05 17:19:34 +01:00
parent 4d96eb255c
commit f240406bab
5 changed files with 121 additions and 195 deletions

View File

@ -271,7 +271,6 @@ 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 */
unsigned long hw_ptr_jiffies; /* Time when hw_ptr is updated */
snd_pcm_sframes_t delay; /* extra delay; typically FIFO size */

View File

@ -61,7 +61,7 @@ struct snd_pcm_oss_runtime {
struct snd_pcm_plugin *plugin_first;
struct snd_pcm_plugin *plugin_last;
#endif
unsigned int prev_hw_ptr_interrupt;
unsigned int prev_hw_ptr_period;
};
struct snd_pcm_oss_file {

View File

@ -632,6 +632,13 @@ static long snd_pcm_alsa_frames(struct snd_pcm_substream *substream, long bytes)
return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes);
}
static inline
snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime)
{
snd_pcm_uframes_t ptr = runtime->status->hw_ptr;
return ptr - (ptr % runtime->period_size);
}
/* define extended formats in the recent OSS versions (if any) */
/* linear formats */
#define AFMT_S32_LE 0x00001000
@ -1102,7 +1109,7 @@ static int snd_pcm_oss_prepare(struct snd_pcm_substream *substream)
return err;
}
runtime->oss.prepare = 0;
runtime->oss.prev_hw_ptr_interrupt = 0;
runtime->oss.prev_hw_ptr_period = 0;
runtime->oss.period_ptr = 0;
runtime->oss.buffer_used = 0;
@ -1950,7 +1957,8 @@ static int snd_pcm_oss_get_caps(struct snd_pcm_oss_file *pcm_oss_file)
return result;
}
static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, snd_pcm_uframes_t hw_ptr)
static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream,
snd_pcm_uframes_t hw_ptr)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t appl_ptr;
@ -1986,7 +1994,8 @@ static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int tr
if (runtime->oss.trigger)
goto _skip1;
if (atomic_read(&psubstream->mmap_count))
snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt);
snd_pcm_oss_simulate_fill(psubstream,
get_hw_ptr_period(runtime));
runtime->oss.trigger = 1;
runtime->start_threshold = 1;
cmd = SNDRV_PCM_IOCTL_START;
@ -2105,11 +2114,12 @@ static int snd_pcm_oss_get_ptr(struct snd_pcm_oss_file *pcm_oss_file, int stream
info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size);
if (atomic_read(&substream->mmap_count)) {
snd_pcm_sframes_t n;
n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt;
delay = get_hw_ptr_period(runtime);
n = delay - runtime->oss.prev_hw_ptr_period;
if (n < 0)
n += runtime->boundary;
info.blocks = n / runtime->period_size;
runtime->oss.prev_hw_ptr_interrupt = delay;
runtime->oss.prev_hw_ptr_period = delay;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_pcm_oss_simulate_fill(substream, delay);
info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX;
@ -2673,18 +2683,22 @@ static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
if (atomic_read(&substream->mmap_count))
return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
return runtime->oss.prev_hw_ptr_period !=
get_hw_ptr_period(runtime);
else
return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames;
return snd_pcm_playback_avail(runtime) >=
runtime->oss.period_frames;
}
static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
if (atomic_read(&substream->mmap_count))
return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
return runtime->oss.prev_hw_ptr_period !=
get_hw_ptr_period(runtime);
else
return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames;
return snd_pcm_capture_avail(runtime) >=
runtime->oss.period_frames;
}
static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)

View File

@ -172,6 +172,7 @@ static void xrun(struct snd_pcm_substream *substream)
#define hw_ptr_error(substream, fmt, args...) \
do { \
if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \
xrun_log_show(substream); \
if (printk_ratelimit()) { \
snd_printd("PCM: " fmt, ##args); \
} \
@ -188,7 +189,6 @@ struct hwptr_log_entry {
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t old_hw_ptr;
snd_pcm_uframes_t hw_ptr_base;
snd_pcm_uframes_t hw_ptr_interrupt;
};
struct snd_pcm_hwptr_log {
@ -220,7 +220,6 @@ static void xrun_log(struct snd_pcm_substream *substream,
entry->buffer_size = runtime->buffer_size;;
entry->old_hw_ptr = runtime->status->hw_ptr;
entry->hw_ptr_base = runtime->hw_ptr_base;
entry->hw_ptr_interrupt = runtime->hw_ptr_interrupt;;
log->idx = (log->idx + 1) % XRUN_LOG_CNT;
}
@ -241,14 +240,13 @@ static void xrun_log_show(struct snd_pcm_substream *substream)
entry = &log->entries[idx];
if (entry->period_size == 0)
break;
snd_printd("hwptr log: %s: j=%lu, pos=0x%lx/0x%lx/0x%lx, "
"hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n",
snd_printd("hwptr log: %s: j=%lu, pos=%ld/%ld/%ld, "
"hwptr=%ld/%ld\n",
name, entry->jiffies, (unsigned long)entry->pos,
(unsigned long)entry->period_size,
(unsigned long)entry->buffer_size,
(unsigned long)entry->old_hw_ptr,
(unsigned long)entry->hw_ptr_base,
(unsigned long)entry->hw_ptr_interrupt);
(unsigned long)entry->hw_ptr_base);
idx++;
idx %= XRUN_LOG_CNT;
}
@ -265,33 +263,6 @@ static void xrun_log_show(struct snd_pcm_substream *substream)
#endif
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;
pos = substream->ops->pointer(substream);
if (pos == SNDRV_PCM_POS_XRUN)
return pos; /* XRUN */
if (pos >= runtime->buffer_size) {
if (printk_ratelimit()) {
char name[16];
pcm_debug_name(substream, name, sizeof(name));
xrun_log_show(substream);
snd_printd(KERN_ERR "BUG: %s, pos = 0x%lx, "
"buffer size = 0x%lx, period size = 0x%lx\n",
name, pos, runtime->buffer_size,
runtime->period_size);
}
pos = 0;
}
pos -= pos % runtime->min_align;
if (xrun_debug(substream, XRUN_DEBUG_LOG))
xrun_log(substream, pos);
return pos;
}
static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime)
{
@ -319,71 +290,87 @@ static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
return 0;
}
static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
unsigned int in_interrupt)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t pos;
snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_ptr_interrupt, hw_base;
snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, 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);
pos = substream->ops->pointer(substream);
if (pos == SNDRV_PCM_POS_XRUN) {
xrun(substream);
return -EPIPE;
}
if (xrun_debug(substream, XRUN_DEBUG_PERIODUPDATE)) {
if (pos >= runtime->buffer_size) {
if (printk_ratelimit()) {
char name[16];
pcm_debug_name(substream, name, sizeof(name));
snd_printd("period_update: %s: pos=0x%x/0x%x/0x%x, "
"hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n",
name, (unsigned int)pos,
(unsigned int)runtime->period_size,
(unsigned int)runtime->buffer_size,
(unsigned long)old_hw_ptr,
(unsigned long)runtime->hw_ptr_base,
(unsigned long)runtime->hw_ptr_interrupt);
xrun_log_show(substream);
snd_printd(KERN_ERR "BUG: %s, pos = %ld, "
"buffer size = %ld, period size = %ld\n",
name, pos, runtime->buffer_size,
runtime->period_size);
}
pos = 0;
}
pos -= pos % runtime->min_align;
if (xrun_debug(substream, XRUN_DEBUG_LOG))
xrun_log(substream, pos);
hw_base = runtime->hw_ptr_base;
new_hw_ptr = hw_base + pos;
hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size;
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 (in_interrupt) {
/* we know that one period was processed */
/* delta = "expected next hw_ptr" for in_interrupt != 0 */
delta = old_hw_ptr - (old_hw_ptr % runtime->period_size)
+ runtime->period_size;
if (delta > new_hw_ptr) {
hw_base += runtime->buffer_size;
if (hw_base >= runtime->boundary)
hw_base = 0;
new_hw_ptr = hw_base + pos;
goto __delta;
}
if (delta < 0) {
if (runtime->periods == 1 || new_hw_ptr < old_hw_ptr)
delta += runtime->buffer_size;
if (delta < 0) {
xrun_log_show(substream);
hw_ptr_error(substream,
"Unexpected hw_pointer value "
"(stream=%i, pos=%ld, intr_ptr=%ld)\n",
substream->stream, (long)pos,
(long)hw_ptr_interrupt);
#if 1
/* simply skipping the hwptr update seems more
* robust in some cases, e.g. on VMware with
* inaccurate timer source
*/
return 0; /* skip this update */
#else
/* 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;
#endif
} else {
}
/* new_hw_ptr might be lower than old_hw_ptr in case when */
/* pointer crosses the end of the ring buffer */
if (new_hw_ptr < old_hw_ptr) {
hw_base += runtime->buffer_size;
if (hw_base >= runtime->boundary)
hw_base = 0;
new_hw_ptr = hw_base + pos;
}
__delta:
delta = (new_hw_ptr - old_hw_ptr) % runtime->boundary;
if (xrun_debug(substream, in_interrupt ?
XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) {
char name[16];
pcm_debug_name(substream, name, sizeof(name));
snd_printd("%s_update: %s: pos=%u/%u/%u, "
"hwptr=%ld/%ld/%ld/%ld\n",
in_interrupt ? "period" : "hwptr",
name,
(unsigned int)pos,
(unsigned int)runtime->period_size,
(unsigned int)runtime->buffer_size,
(unsigned long)delta,
(unsigned long)old_hw_ptr,
(unsigned long)new_hw_ptr,
(unsigned long)runtime->hw_ptr_base);
}
/* something must be really wrong */
if (delta >= runtime->buffer_size) {
hw_ptr_error(substream,
"Unexpected hw_pointer value %s"
"(stream=%i, pos=%ld, new_hw_ptr=%ld, "
"old_hw_ptr=%ld)\n",
in_interrupt ? "[Q] " : "[P]",
substream->stream, (long)pos,
(long)new_hw_ptr, (long)old_hw_ptr);
return 0;
}
/* Do jiffies check only in xrun_debug mode */
@ -396,7 +383,7 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
*/
if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
goto no_jiffies_check;
hdelta = new_hw_ptr - old_hw_ptr;
hdelta = delta;
if (hdelta < runtime->delay)
goto no_jiffies_check;
hdelta -= runtime->delay;
@ -405,45 +392,49 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
delta = jdelta /
(((runtime->period_size * HZ) / runtime->rate)
+ HZ/100);
xrun_log_show(substream);
/* move new_hw_ptr according jiffies not pos variable */
new_hw_ptr = old_hw_ptr;
/* use loop to avoid checks for delta overflows */
/* the delta value is small or zero in most cases */
while (delta > 0) {
new_hw_ptr += runtime->period_size;
if (new_hw_ptr >= runtime->boundary)
new_hw_ptr -= runtime->boundary;
delta--;
}
/* align hw_base to buffer_size */
hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
delta = 0;
hw_ptr_error(substream,
"hw_ptr skipping! [Q] "
"hw_ptr skipping! %s"
"(pos=%ld, delta=%ld, period=%ld, "
"jdelta=%lu/%lu/%lu)\n",
"jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
in_interrupt ? "[Q] " : "",
(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;
((hdelta * HZ) / runtime->rate), delta,
(unsigned long)old_hw_ptr,
(unsigned long)new_hw_ptr);
}
no_jiffies_check:
if (delta > runtime->period_size + runtime->period_size / 2) {
xrun_log_show(substream);
hw_ptr_error(substream,
"Lost interrupts? "
"(stream=%i, delta=%ld, intr_ptr=%ld)\n",
"Lost interrupts? %s"
"(stream=%i, delta=%ld, new_hw_ptr=%ld, "
"old_hw_ptr=%ld)\n",
in_interrupt ? "[Q] " : "",
substream->stream, (long)delta,
(long)hw_ptr_interrupt);
/* rebase hw_ptr_interrupt */
hw_ptr_interrupt =
new_hw_ptr - new_hw_ptr % runtime->period_size;
(long)new_hw_ptr,
(long)old_hw_ptr);
}
runtime->hw_ptr_interrupt = hw_ptr_interrupt;
if (runtime->status->hw_ptr == new_hw_ptr)
return 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, new_hw_ptr);
if (runtime->status->hw_ptr == new_hw_ptr)
return 0;
runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
runtime->hw_ptr_jiffies = jiffies;
@ -456,83 +447,7 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
/* CAUTION: call it with irq disabled */
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, 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);
if (pos == SNDRV_PCM_POS_XRUN) {
xrun(substream);
return -EPIPE;
}
if (xrun_debug(substream, XRUN_DEBUG_HWPTRUPDATE)) {
char name[16];
pcm_debug_name(substream, name, sizeof(name));
snd_printd("hw_update: %s: pos=0x%x/0x%x/0x%x, "
"hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n",
name, (unsigned int)pos,
(unsigned int)runtime->period_size,
(unsigned int)runtime->buffer_size,
(unsigned long)old_hw_ptr,
(unsigned long)runtime->hw_ptr_base,
(unsigned long)runtime->hw_ptr_interrupt);
}
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) {
xrun_log_show(substream);
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;
}
hw_base += runtime->buffer_size;
if (hw_base >= runtime->boundary)
hw_base = 0;
new_hw_ptr = hw_base + pos;
}
/* Do jiffies check only in xrun_debug mode */
if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
goto no_jiffies_check;
if (delta < runtime->delay)
goto no_jiffies_check;
delta -= runtime->delay;
if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) {
xrun_log_show(substream);
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;
}
no_jiffies_check:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, new_hw_ptr);
if (runtime->status->hw_ptr == new_hw_ptr)
return 0;
runtime->hw_ptr_base = hw_base;
runtime->status->hw_ptr = new_hw_ptr;
runtime->hw_ptr_jiffies = jiffies;
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
return snd_pcm_update_hw_ptr_post(substream, runtime);
return snd_pcm_update_hw_ptr0(substream, 0);
}
/**
@ -1744,7 +1659,7 @@ void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
snd_pcm_stream_lock_irqsave(substream, flags);
if (!snd_pcm_running(substream) ||
snd_pcm_update_hw_ptr_interrupt(substream) < 0)
snd_pcm_update_hw_ptr0(substream, 1) < 0)
goto _end;
if (substream->timer_running)

View File

@ -1247,8 +1247,6 @@ static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state)
if (err < 0)
return err;
runtime->hw_ptr_base = 0;
runtime->hw_ptr_interrupt = runtime->status->hw_ptr -
runtime->status->hw_ptr % runtime->period_size;
runtime->silence_start = runtime->status->hw_ptr;
runtime->silence_filled = 0;
return 0;