diff options
Diffstat (limited to 'sound/drivers/pcsp/pcsp_lib.c')
-rw-r--r-- | sound/drivers/pcsp/pcsp_lib.c | 168 |
1 files changed, 67 insertions, 101 deletions
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index 84cc2658c05b..1f42e4063118 100644 --- a/sound/drivers/pcsp/pcsp_lib.c +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -8,7 +8,6 @@ #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/interrupt.h> #include <sound/pcm.h> #include <asm/io.h> #include "pcsp.h" @@ -20,57 +19,61 @@ MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " #define DMIX_WANTS_S16 1 -/* - * 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) +enum hrtimer_restart pcsp_do_timer(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 0; - return chip->ns_rem; + return HRTIMER_NORESTART; + hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), + ktime_set(0, chip->ns_rem)); + return HRTIMER_RESTART; } - if (!atomic_read(&chip->timer_active)) - return 0; + 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; - if (!substream) - return 0; + snd_pcm_stream_lock(substream); + if (!atomic_read(&chip->timer_active)) + goto exit_nr_unlock2; runtime = substream->runtime; + fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3; /* assume it is mono! */ - val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1]; - if (chip->is_signed) + val = runtime->dma_area[chip->playback_ptr + fmt_size - 1]; + if (snd_pcm_format_signed(runtime->format)) val ^= 0x80; timer_cnt = val * CUR_DIV() / 256; if (timer_cnt && chip->enable) { - spin_lock_irqsave(&i8253_lock, flags); + spin_lock(&i8253_lock); if (!nforce_wa) { outb_p(chip->val61, 0x61); outb_p(timer_cnt, 0x42); @@ -79,39 +82,12 @@ static unsigned long pcsp_timer_update(struct hrtimer *handle) outb(chip->val61 ^ 2, 0x61); chip->thalf = 1; } - spin_unlock_irqrestore(&i8253_lock, flags); + spin_unlock(&i8253_lock); } - 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); - - spin_lock_irqsave(&chip->substream_lock, flags); - chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size; + chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; periods_elapsed = chip->playback_ptr - chip->period_ptr; if (periods_elapsed < 0) { #if PCSP_DEBUG @@ -126,30 +102,41 @@ 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); - if (periods_elapsed) - tasklet_schedule(&pcsp_pcm_tasklet); + spin_unlock_irq(&chip->substream_lock); - hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns)); + if (!atomic_read(&chip->timer_active)) + return HRTIMER_NORESTART; + 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 int pcsp_start_playing(struct snd_pcsp *chip) +static void 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 -EIO; + return; } spin_lock(&i8253_lock); @@ -159,12 +146,7 @@ static int pcsp_start_playing(struct snd_pcsp *chip) atomic_set(&chip->timer_active, 1); chip->thalf = 0; - 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; + hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL); } static void pcsp_stop_playing(struct snd_pcsp *chip) @@ -183,35 +165,26 @@ 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 - pcsp_sync_stop(chip); + if (atomic_read(&chip->timer_active)) { + printk(KERN_ERR "PCSP: timer still active\n"); + pcsp_stop_playing(chip); + } + spin_lock_irq(&chip->substream_lock); 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) @@ -221,11 +194,9 @@ 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); } @@ -241,12 +212,8 @@ 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; } @@ -259,7 +226,8 @@ static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - return pcsp_start_playing(chip); + pcsp_start_playing(chip); + break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: pcsp_stop_playing(chip); @@ -274,11 +242,7 @@ static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream *substream) { struct snd_pcsp *chip = snd_pcm_substream_chip(substream); - unsigned int pos; - spin_lock(&chip->substream_lock); - pos = chip->playback_ptr; - spin_unlock(&chip->substream_lock); - return bytes_to_frames(substream->runtime, pos); + return bytes_to_frames(substream->runtime, chip->playback_ptr); } static struct snd_pcm_hardware snd_pcsp_playback = { @@ -315,7 +279,9 @@ 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; } |