diff options
| author | Takashi Iwai <tiwai@suse.de> | 2008-08-11 04:18:39 -0400 |
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2008-10-20 08:47:15 -0400 |
| commit | 96c7d478efad594e483ee8a826395b1342404885 (patch) | |
| tree | 8ecb6f20f1f72f9d406b3c949d9b811347dcbb3e /sound/drivers | |
| parent | 76a4d10e522bfc238ddf70f35272088d420d2dcf (diff) | |
ALSA: pcsp - Fix locking messes in snd-pcsp
snd-pcsp driver takes chip->substream_lock together with PCM substream
lock. These are even mixed up with hrtimer's lock, resulting in messy
lock depencies. Right now, snd-pcsp driver resolves the deadlock by
using HRTIMER_CB_SOFTIRQ. However, this isn't nice for a really fast
path like bit-flipping.
This patch introduces a tasklet for PCM period handling so that the
hrtimer callback can be handled fast. This also reduce the use of
chip->substream_lock to avoid deadlocks. It's still used in pointer
callback, but even this could be removed with a proper barrier.
Another good solution is to introduce async trigger callback. But,
this will involve with a major rewrite of the PCM core code, so I
take first this easy fix.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/drivers')
| -rw-r--r-- | sound/drivers/pcsp/pcsp.c | 8 | ||||
| -rw-r--r-- | sound/drivers/pcsp/pcsp.h | 1 | ||||
| -rw-r--r-- | sound/drivers/pcsp/pcsp_lib.c | 95 |
3 files changed, 56 insertions, 48 deletions
diff --git a/sound/drivers/pcsp/pcsp.c b/sound/drivers/pcsp/pcsp.c index 1899cf0685bc..87219bf0a35e 100644 --- a/sound/drivers/pcsp/pcsp.c +++ b/sound/drivers/pcsp/pcsp.c | |||
| @@ -96,7 +96,7 @@ static int __devinit snd_card_pcsp_probe(int devnum, struct device *dev) | |||
| 96 | return -EINVAL; | 96 | return -EINVAL; |
| 97 | 97 | ||
| 98 | hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | 98 | hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| 99 | pcsp_chip.timer.cb_mode = HRTIMER_CB_SOFTIRQ; | 99 | pcsp_chip.timer.cb_mode = HRTIMER_CB_IRQSAFE; |
| 100 | pcsp_chip.timer.function = pcsp_do_timer; | 100 | pcsp_chip.timer.function = pcsp_do_timer; |
| 101 | 101 | ||
| 102 | card = snd_card_new(index, id, THIS_MODULE, 0); | 102 | card = snd_card_new(index, id, THIS_MODULE, 0); |
| @@ -188,10 +188,8 @@ static int __devexit pcsp_remove(struct platform_device *dev) | |||
| 188 | 188 | ||
| 189 | static void pcsp_stop_beep(struct snd_pcsp *chip) | 189 | static void pcsp_stop_beep(struct snd_pcsp *chip) |
| 190 | { | 190 | { |
| 191 | spin_lock_irq(&chip->substream_lock); | 191 | pcsp_sync_stop(chip); |
| 192 | if (!chip->playback_substream) | 192 | pcspkr_stop_sound(); |
| 193 | pcspkr_stop_sound(); | ||
| 194 | spin_unlock_irq(&chip->substream_lock); | ||
| 195 | } | 193 | } |
| 196 | 194 | ||
| 197 | #ifdef CONFIG_PM | 195 | #ifdef CONFIG_PM |
diff --git a/sound/drivers/pcsp/pcsp.h b/sound/drivers/pcsp/pcsp.h index 1d661f795e8c..70533a333b5b 100644 --- a/sound/drivers/pcsp/pcsp.h +++ b/sound/drivers/pcsp/pcsp.h | |||
| @@ -77,6 +77,7 @@ struct snd_pcsp { | |||
| 77 | extern struct snd_pcsp pcsp_chip; | 77 | extern struct snd_pcsp pcsp_chip; |
| 78 | 78 | ||
| 79 | extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle); | 79 | extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle); |
| 80 | extern void pcsp_sync_stop(struct snd_pcsp *chip); | ||
| 80 | 81 | ||
| 81 | extern int snd_pcsp_new_pcm(struct snd_pcsp *chip); | 82 | extern int snd_pcsp_new_pcm(struct snd_pcsp *chip); |
| 82 | extern int snd_pcsp_new_mixer(struct snd_pcsp *chip); | 83 | extern int snd_pcsp_new_mixer(struct snd_pcsp *chip); |
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index e341f3f83b6a..40f95f549d2b 100644 --- a/sound/drivers/pcsp/pcsp_lib.c +++ b/sound/drivers/pcsp/pcsp_lib.c | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | 8 | ||
| 9 | #include <linux/module.h> | 9 | #include <linux/module.h> |
| 10 | #include <linux/moduleparam.h> | 10 | #include <linux/moduleparam.h> |
| 11 | #include <linux/interrupt.h> | ||
| 11 | #include <sound/pcm.h> | 12 | #include <sound/pcm.h> |
| 12 | #include <asm/io.h> | 13 | #include <asm/io.h> |
| 13 | #include "pcsp.h" | 14 | #include "pcsp.h" |
| @@ -19,6 +20,22 @@ MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " | |||
| 19 | 20 | ||
| 20 | #define DMIX_WANTS_S16 1 | 21 | #define DMIX_WANTS_S16 1 |
| 21 | 22 | ||
| 23 | /* | ||
| 24 | * Call snd_pcm_period_elapsed in a tasklet | ||
| 25 | * This avoids spinlock messes and long-running irq contexts | ||
| 26 | */ | ||
| 27 | static void pcsp_call_pcm_elapsed(unsigned long priv) | ||
| 28 | { | ||
| 29 | if (atomic_read(&pcsp_chip.timer_active)) { | ||
| 30 | struct snd_pcm_substream *substream; | ||
| 31 | substream = pcsp_chip.playback_substream; | ||
| 32 | if (substream) | ||
| 33 | snd_pcm_period_elapsed(substream); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0); | ||
| 38 | |||
| 22 | enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | 39 | enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) |
| 23 | { | 40 | { |
| 24 | unsigned char timer_cnt, val; | 41 | unsigned char timer_cnt, val; |
| @@ -28,41 +45,23 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | |||
| 28 | struct snd_pcm_substream *substream; | 45 | struct snd_pcm_substream *substream; |
| 29 | struct snd_pcm_runtime *runtime; | 46 | struct snd_pcm_runtime *runtime; |
| 30 | struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); | 47 | struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); |
| 48 | unsigned long flags; | ||
| 31 | 49 | ||
| 32 | if (chip->thalf) { | 50 | if (chip->thalf) { |
| 33 | outb(chip->val61, 0x61); | 51 | outb(chip->val61, 0x61); |
| 34 | chip->thalf = 0; | 52 | chip->thalf = 0; |
| 35 | if (!atomic_read(&chip->timer_active)) | 53 | if (!atomic_read(&chip->timer_active)) |
| 36 | return HRTIMER_NORESTART; | 54 | goto stop; |
| 37 | hrtimer_forward(&chip->timer, chip->timer.expires, | 55 | hrtimer_forward(&chip->timer, chip->timer.expires, |
| 38 | ktime_set(0, chip->ns_rem)); | 56 | ktime_set(0, chip->ns_rem)); |
| 39 | return HRTIMER_RESTART; | 57 | return HRTIMER_RESTART; |
| 40 | } | 58 | } |
| 41 | 59 | ||
| 42 | spin_lock_irq(&chip->substream_lock); | ||
| 43 | /* Takashi Iwai says regarding this extra lock: | ||
| 44 | |||
| 45 | If the irq handler handles some data on the DMA buffer, it should | ||
| 46 | do snd_pcm_stream_lock(). | ||
| 47 | That protects basically against all races among PCM callbacks, yes. | ||
| 48 | However, there are two remaining issues: | ||
| 49 | 1. The substream pointer you try to lock isn't protected _before_ | ||
| 50 | this lock yet. | ||
| 51 | 2. snd_pcm_period_elapsed() itself acquires the lock. | ||
| 52 | The requirement of another lock is because of 1. When you get | ||
| 53 | chip->playback_substream, it's not protected. | ||
| 54 | Keeping this lock while snd_pcm_period_elapsed() assures the substream | ||
| 55 | is still protected (at least, not released). And the other status is | ||
| 56 | handled properly inside snd_pcm_stream_lock() in | ||
| 57 | snd_pcm_period_elapsed(). | ||
| 58 | |||
| 59 | */ | ||
| 60 | if (!chip->playback_substream) | ||
| 61 | goto exit_nr_unlock1; | ||
| 62 | substream = chip->playback_substream; | ||
| 63 | snd_pcm_stream_lock(substream); | ||
| 64 | if (!atomic_read(&chip->timer_active)) | 60 | if (!atomic_read(&chip->timer_active)) |
| 65 | goto exit_nr_unlock2; | 61 | goto stop; |
| 62 | substream = chip->playback_substream; | ||
| 63 | if (!substream) | ||
| 64 | goto stop; | ||
| 66 | 65 | ||
| 67 | runtime = substream->runtime; | 66 | runtime = substream->runtime; |
| 68 | fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3; | 67 | fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3; |
| @@ -87,6 +86,8 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | |||
| 87 | 86 | ||
| 88 | period_bytes = snd_pcm_lib_period_bytes(substream); | 87 | period_bytes = snd_pcm_lib_period_bytes(substream); |
| 89 | buffer_bytes = snd_pcm_lib_buffer_bytes(substream); | 88 | buffer_bytes = snd_pcm_lib_buffer_bytes(substream); |
| 89 | |||
| 90 | spin_lock_irqsave(&chip->substream_lock, flags); | ||
| 90 | chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; | 91 | chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; |
| 91 | periods_elapsed = chip->playback_ptr - chip->period_ptr; | 92 | periods_elapsed = chip->playback_ptr - chip->period_ptr; |
| 92 | if (periods_elapsed < 0) { | 93 | if (periods_elapsed < 0) { |
| @@ -102,18 +103,15 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | |||
| 102 | * or ALSA will BUG on us. */ | 103 | * or ALSA will BUG on us. */ |
| 103 | chip->playback_ptr %= buffer_bytes; | 104 | chip->playback_ptr %= buffer_bytes; |
| 104 | 105 | ||
| 105 | snd_pcm_stream_unlock(substream); | ||
| 106 | |||
| 107 | if (periods_elapsed) { | 106 | if (periods_elapsed) { |
| 108 | snd_pcm_period_elapsed(substream); | ||
| 109 | chip->period_ptr += periods_elapsed * period_bytes; | 107 | chip->period_ptr += periods_elapsed * period_bytes; |
| 110 | chip->period_ptr %= buffer_bytes; | 108 | chip->period_ptr %= buffer_bytes; |
| 109 | tasklet_schedule(&pcsp_pcm_tasklet); | ||
| 111 | } | 110 | } |
| 112 | 111 | spin_unlock_irqrestore(&chip->substream_lock, flags); | |
| 113 | spin_unlock_irq(&chip->substream_lock); | ||
| 114 | 112 | ||
| 115 | if (!atomic_read(&chip->timer_active)) | 113 | if (!atomic_read(&chip->timer_active)) |
| 116 | return HRTIMER_NORESTART; | 114 | goto stop; |
| 117 | 115 | ||
| 118 | chip->ns_rem = PCSP_PERIOD_NS(); | 116 | chip->ns_rem = PCSP_PERIOD_NS(); |
| 119 | ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); | 117 | ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); |
| @@ -121,10 +119,7 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | |||
| 121 | hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns)); | 119 | hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns)); |
| 122 | return HRTIMER_RESTART; | 120 | return HRTIMER_RESTART; |
| 123 | 121 | ||
| 124 | exit_nr_unlock2: | 122 | stop: |
| 125 | snd_pcm_stream_unlock(substream); | ||
| 126 | exit_nr_unlock1: | ||
| 127 | spin_unlock_irq(&chip->substream_lock); | ||
| 128 | return HRTIMER_NORESTART; | 123 | return HRTIMER_NORESTART; |
| 129 | } | 124 | } |
| 130 | 125 | ||
| @@ -164,26 +159,35 @@ static void pcsp_stop_playing(struct snd_pcsp *chip) | |||
| 164 | spin_unlock(&i8253_lock); | 159 | spin_unlock(&i8253_lock); |
| 165 | } | 160 | } |
| 166 | 161 | ||
| 162 | /* | ||
| 163 | * Force to stop and sync the stream | ||
| 164 | */ | ||
| 165 | void pcsp_sync_stop(struct snd_pcsp *chip) | ||
| 166 | { | ||
| 167 | local_irq_disable(); | ||
| 168 | pcsp_stop_playing(chip); | ||
| 169 | local_irq_enable(); | ||
| 170 | hrtimer_cancel(&chip->timer); | ||
| 171 | tasklet_kill(&pcsp_pcm_tasklet); | ||
| 172 | } | ||
| 173 | |||
| 167 | static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) | 174 | static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) |
| 168 | { | 175 | { |
| 169 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | 176 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); |
| 170 | #if PCSP_DEBUG | 177 | #if PCSP_DEBUG |
| 171 | printk(KERN_INFO "PCSP: close called\n"); | 178 | printk(KERN_INFO "PCSP: close called\n"); |
| 172 | #endif | 179 | #endif |
| 173 | if (atomic_read(&chip->timer_active)) { | 180 | pcsp_sync_stop(chip); |
| 174 | printk(KERN_ERR "PCSP: timer still active\n"); | ||
| 175 | pcsp_stop_playing(chip); | ||
| 176 | } | ||
| 177 | spin_lock_irq(&chip->substream_lock); | ||
| 178 | chip->playback_substream = NULL; | 181 | chip->playback_substream = NULL; |
| 179 | spin_unlock_irq(&chip->substream_lock); | ||
| 180 | return 0; | 182 | return 0; |
| 181 | } | 183 | } |
| 182 | 184 | ||
| 183 | static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, | 185 | static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, |
| 184 | struct snd_pcm_hw_params *hw_params) | 186 | struct snd_pcm_hw_params *hw_params) |
| 185 | { | 187 | { |
| 188 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
| 186 | int err; | 189 | int err; |
| 190 | pcsp_sync_stop(chip); | ||
| 187 | err = snd_pcm_lib_malloc_pages(substream, | 191 | err = snd_pcm_lib_malloc_pages(substream, |
| 188 | params_buffer_bytes(hw_params)); | 192 | params_buffer_bytes(hw_params)); |
| 189 | if (err < 0) | 193 | if (err < 0) |
| @@ -193,9 +197,11 @@ static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, | |||
| 193 | 197 | ||
| 194 | static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) | 198 | static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) |
| 195 | { | 199 | { |
| 200 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
| 196 | #if PCSP_DEBUG | 201 | #if PCSP_DEBUG |
| 197 | printk(KERN_INFO "PCSP: hw_free called\n"); | 202 | printk(KERN_INFO "PCSP: hw_free called\n"); |
| 198 | #endif | 203 | #endif |
| 204 | pcsp_sync_stop(chip); | ||
| 199 | return snd_pcm_lib_free_pages(substream); | 205 | return snd_pcm_lib_free_pages(substream); |
| 200 | } | 206 | } |
| 201 | 207 | ||
| @@ -211,6 +217,7 @@ static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) | |||
| 211 | snd_pcm_lib_period_bytes(substream), | 217 | snd_pcm_lib_period_bytes(substream), |
| 212 | substream->runtime->periods); | 218 | substream->runtime->periods); |
| 213 | #endif | 219 | #endif |
| 220 | pcsp_sync_stop(chip); | ||
| 214 | chip->playback_ptr = 0; | 221 | chip->playback_ptr = 0; |
| 215 | chip->period_ptr = 0; | 222 | chip->period_ptr = 0; |
| 216 | return 0; | 223 | return 0; |
| @@ -241,7 +248,11 @@ static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream | |||
| 241 | *substream) | 248 | *substream) |
| 242 | { | 249 | { |
| 243 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | 250 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); |
| 244 | return bytes_to_frames(substream->runtime, chip->playback_ptr); | 251 | unsigned int pos; |
| 252 | spin_lock(&chip->substream_lock); | ||
| 253 | pos = chip->playback_ptr; | ||
| 254 | spin_unlock(&chip->substream_lock); | ||
| 255 | return bytes_to_frames(substream->runtime, pos); | ||
| 245 | } | 256 | } |
| 246 | 257 | ||
| 247 | static struct snd_pcm_hardware snd_pcsp_playback = { | 258 | static struct snd_pcm_hardware snd_pcsp_playback = { |
| @@ -278,9 +289,7 @@ static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) | |||
| 278 | return -EBUSY; | 289 | return -EBUSY; |
| 279 | } | 290 | } |
| 280 | runtime->hw = snd_pcsp_playback; | 291 | runtime->hw = snd_pcsp_playback; |
| 281 | spin_lock_irq(&chip->substream_lock); | ||
| 282 | chip->playback_substream = substream; | 292 | chip->playback_substream = substream; |
| 283 | spin_unlock_irq(&chip->substream_lock); | ||
| 284 | return 0; | 293 | return 0; |
| 285 | } | 294 | } |
| 286 | 295 | ||
