diff options
Diffstat (limited to 'sound/drivers/pcsp/pcsp_lib.c')
-rw-r--r-- | sound/drivers/pcsp/pcsp_lib.c | 168 |
1 files changed, 101 insertions, 67 deletions
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index 1f42e4063118..84cc2658c05b 100644 --- a/sound/drivers/pcsp/pcsp_lib.c +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/interrupt.h> #include <sound/pcm.h> #include <asm/io.h> #include "pcsp.h" @@ -19,61 +20,57 @@ MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " #define DMIX_WANTS_S16 1 -enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) +/* + * Call snd_pcm_period_elapsed in a tasklet + * This avoids spinlock messes and long-running irq contexts + */ +static void pcsp_call_pcm_elapsed(unsigned long priv) +{ + if (atomic_read(&pcsp_chip.timer_active)) { + struct snd_pcm_substream *substream; + substream = pcsp_chip.playback_substream; + if (substream) + snd_pcm_period_elapsed(substream); + } +} + +static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0); + +/* write the port and returns the next expire time in ns; + * called at the trigger-start and in hrtimer callback + */ +static unsigned long pcsp_timer_update(struct hrtimer *handle) { unsigned char timer_cnt, val; - int fmt_size, periods_elapsed; u64 ns; - size_t period_bytes, buffer_bytes; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); + unsigned long flags; if (chip->thalf) { outb(chip->val61, 0x61); chip->thalf = 0; if (!atomic_read(&chip->timer_active)) - return HRTIMER_NORESTART; - hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), - ktime_set(0, chip->ns_rem)); - return HRTIMER_RESTART; + return 0; + return chip->ns_rem; } - spin_lock_irq(&chip->substream_lock); - /* Takashi Iwai says regarding this extra lock: - - If the irq handler handles some data on the DMA buffer, it should - do snd_pcm_stream_lock(). - That protects basically against all races among PCM callbacks, yes. - However, there are two remaining issues: - 1. The substream pointer you try to lock isn't protected _before_ - this lock yet. - 2. snd_pcm_period_elapsed() itself acquires the lock. - The requirement of another lock is because of 1. When you get - chip->playback_substream, it's not protected. - Keeping this lock while snd_pcm_period_elapsed() assures the substream - is still protected (at least, not released). And the other status is - handled properly inside snd_pcm_stream_lock() in - snd_pcm_period_elapsed(). - - */ - if (!chip->playback_substream) - goto exit_nr_unlock1; - substream = chip->playback_substream; - snd_pcm_stream_lock(substream); if (!atomic_read(&chip->timer_active)) - goto exit_nr_unlock2; + return 0; + substream = chip->playback_substream; + if (!substream) + return 0; runtime = substream->runtime; - fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3; /* assume it is mono! */ - val = runtime->dma_area[chip->playback_ptr + fmt_size - 1]; - if (snd_pcm_format_signed(runtime->format)) + val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1]; + if (chip->is_signed) val ^= 0x80; timer_cnt = val * CUR_DIV() / 256; if (timer_cnt && chip->enable) { - spin_lock(&i8253_lock); + spin_lock_irqsave(&i8253_lock, flags); if (!nforce_wa) { outb_p(chip->val61, 0x61); outb_p(timer_cnt, 0x42); @@ -82,12 +79,39 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) outb(chip->val61 ^ 2, 0x61); chip->thalf = 1; } - spin_unlock(&i8253_lock); + spin_unlock_irqrestore(&i8253_lock, flags); } + chip->ns_rem = PCSP_PERIOD_NS(); + ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); + chip->ns_rem -= ns; + return ns; +} + +enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) +{ + struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); + struct snd_pcm_substream *substream; + int periods_elapsed, pointer_update; + size_t period_bytes, buffer_bytes; + unsigned long ns; + unsigned long flags; + + pointer_update = !chip->thalf; + ns = pcsp_timer_update(handle); + if (!ns) + return HRTIMER_NORESTART; + + /* update the playback position */ + substream = chip->playback_substream; + if (!substream) + return HRTIMER_NORESTART; + period_bytes = snd_pcm_lib_period_bytes(substream); buffer_bytes = snd_pcm_lib_buffer_bytes(substream); - chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; + + spin_lock_irqsave(&chip->substream_lock, flags); + chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size; periods_elapsed = chip->playback_ptr - chip->period_ptr; if (periods_elapsed < 0) { #if PCSP_DEBUG @@ -102,41 +126,30 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) * or ALSA will BUG on us. */ chip->playback_ptr %= buffer_bytes; - snd_pcm_stream_unlock(substream); - if (periods_elapsed) { - snd_pcm_period_elapsed(substream); chip->period_ptr += periods_elapsed * period_bytes; chip->period_ptr %= buffer_bytes; } + spin_unlock_irqrestore(&chip->substream_lock, flags); - spin_unlock_irq(&chip->substream_lock); + if (periods_elapsed) + tasklet_schedule(&pcsp_pcm_tasklet); - if (!atomic_read(&chip->timer_active)) - return HRTIMER_NORESTART; + hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns)); - chip->ns_rem = PCSP_PERIOD_NS(); - ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); - chip->ns_rem -= ns; - hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), - ktime_set(0, ns)); return HRTIMER_RESTART; - -exit_nr_unlock2: - snd_pcm_stream_unlock(substream); -exit_nr_unlock1: - spin_unlock_irq(&chip->substream_lock); - return HRTIMER_NORESTART; } -static void pcsp_start_playing(struct snd_pcsp *chip) +static int pcsp_start_playing(struct snd_pcsp *chip) { + unsigned long ns; + #if PCSP_DEBUG printk(KERN_INFO "PCSP: start_playing called\n"); #endif if (atomic_read(&chip->timer_active)) { printk(KERN_ERR "PCSP: Timer already active\n"); - return; + return -EIO; } spin_lock(&i8253_lock); @@ -146,7 +159,12 @@ static void pcsp_start_playing(struct snd_pcsp *chip) atomic_set(&chip->timer_active, 1); chip->thalf = 0; - hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL); + ns = pcsp_timer_update(&pcsp_chip.timer); + if (!ns) + return -EIO; + + hrtimer_start(&pcsp_chip.timer, ktime_set(0, ns), HRTIMER_MODE_REL); + return 0; } static void pcsp_stop_playing(struct snd_pcsp *chip) @@ -165,26 +183,35 @@ static void pcsp_stop_playing(struct snd_pcsp *chip) spin_unlock(&i8253_lock); } +/* + * Force to stop and sync the stream + */ +void pcsp_sync_stop(struct snd_pcsp *chip) +{ + local_irq_disable(); + pcsp_stop_playing(chip); + local_irq_enable(); + hrtimer_cancel(&chip->timer); + tasklet_kill(&pcsp_pcm_tasklet); +} + static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) { struct snd_pcsp *chip = snd_pcm_substream_chip(substream); #if PCSP_DEBUG printk(KERN_INFO "PCSP: close called\n"); #endif - if (atomic_read(&chip->timer_active)) { - printk(KERN_ERR "PCSP: timer still active\n"); - pcsp_stop_playing(chip); - } - spin_lock_irq(&chip->substream_lock); + pcsp_sync_stop(chip); chip->playback_substream = NULL; - spin_unlock_irq(&chip->substream_lock); return 0; } static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); int err; + pcsp_sync_stop(chip); err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); if (err < 0) @@ -194,9 +221,11 @@ static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) { + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); #if PCSP_DEBUG printk(KERN_INFO "PCSP: hw_free called\n"); #endif + pcsp_sync_stop(chip); return snd_pcm_lib_free_pages(substream); } @@ -212,8 +241,12 @@ static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) snd_pcm_lib_period_bytes(substream), substream->runtime->periods); #endif + pcsp_sync_stop(chip); chip->playback_ptr = 0; chip->period_ptr = 0; + chip->fmt_size = + snd_pcm_format_physical_width(substream->runtime->format) >> 3; + chip->is_signed = snd_pcm_format_signed(substream->runtime->format); return 0; } @@ -226,8 +259,7 @@ static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - pcsp_start_playing(chip); - break; + return pcsp_start_playing(chip); case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: pcsp_stop_playing(chip); @@ -242,7 +274,11 @@ static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream *substream) { struct snd_pcsp *chip = snd_pcm_substream_chip(substream); - return bytes_to_frames(substream->runtime, chip->playback_ptr); + unsigned int pos; + spin_lock(&chip->substream_lock); + pos = chip->playback_ptr; + spin_unlock(&chip->substream_lock); + return bytes_to_frames(substream->runtime, pos); } static struct snd_pcm_hardware snd_pcsp_playback = { @@ -279,9 +315,7 @@ static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) return -EBUSY; } runtime->hw = snd_pcsp_playback; - spin_lock_irq(&chip->substream_lock); chip->playback_substream = substream; - spin_unlock_irq(&chip->substream_lock); return 0; } |