diff options
author | Jaroslav Kysela <perex@perex.cz> | 2010-01-27 18:10:13 +0100 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2010-01-27 18:17:27 +0100 |
commit | 7910b4a1db63fefc3d291853d33c34c5b6352e8e (patch) | |
tree | e68f8581906f00f4cdefa2f1017555dd9fdb4d74 /sound/core/pcm_native.c | |
parent | e7636925789b042ff9d98c51d48392e8c5549480 (diff) | |
download | linux-7910b4a1db63fefc3d291853d33c34c5b6352e8e.tar.gz linux-7910b4a1db63fefc3d291853d33c34c5b6352e8e.tar.bz2 linux-7910b4a1db63fefc3d291853d33c34c5b6352e8e.zip |
ALSA: pcm_native - fix runtime->boundary calculation
The code in pcm_lib updating runtime->hw_ptr_interrupt expects
that runtime->boundary is divisible with runtime->period_size.
Thanks are going to Clemens Ladisch for the notice.
Fix the runtime->boundary calculation using buffer_size * period_size
as base and find a least common multiple for 32bit platforms when
the expression might overflow.
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/core/pcm_native.c')
-rw-r--r-- | sound/core/pcm_native.c | 39 |
1 files changed, 36 insertions, 3 deletions
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 7a002db512b4..9cbaf90d3d88 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -27,6 +27,7 @@ #include <linux/pm_qos_params.h> #include <linux/uio.h> #include <linux/dma-mapping.h> +#include <linux/math64.h> #include <sound/core.h> #include <sound/control.h> #include <sound/info.h> @@ -366,6 +367,38 @@ static int period_to_usecs(struct snd_pcm_runtime *runtime) return usecs; } +static int calc_boundary(struct snd_pcm_runtime *runtime) +{ + u_int64_t boundary; + + boundary = (u_int64_t)runtime->buffer_size * + (u_int64_t)runtime->period_size; +#if BITS_PER_LONG < 64 + /* try to find lowest common multiple for buffer and period */ + if (boundary > LONG_MAX - runtime->buffer_size) { + u_int32_t remainder = -1; + u_int32_t divident = runtime->buffer_size; + u_int32_t divisor = runtime->period_size; + while (remainder) { + remainder = divident % divisor; + if (remainder) { + divident = divisor; + divisor = remainder; + } + } + boundary = div_u64(boundary, divisor); + if (boundary > LONG_MAX - runtime->buffer_size) + return -ERANGE; + } +#endif + if (boundary == 0) + return -ERANGE; + runtime->boundary = boundary; + while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) + runtime->boundary *= 2; + return 0; +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -441,9 +474,9 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, runtime->stop_threshold = runtime->buffer_size; runtime->silence_threshold = 0; runtime->silence_size = 0; - runtime->boundary = runtime->buffer_size; - while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) - runtime->boundary *= 2; + err = calc_boundary(runtime); + if (err < 0) + goto _error; snd_pcm_timer_resolution_change(substream); runtime->status->state = SNDRV_PCM_STATE_SETUP; |