aboutsummaryrefslogtreecommitdiffstats
path: root/sound/drivers/pcsp
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2008-08-11 04:18:39 -0400
committerTakashi Iwai <tiwai@suse.de>2008-10-20 08:47:15 -0400
commit96c7d478efad594e483ee8a826395b1342404885 (patch)
tree8ecb6f20f1f72f9d406b3c949d9b811347dcbb3e /sound/drivers/pcsp
parent76a4d10e522bfc238ddf70f35272088d420d2dcf (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/pcsp')
-rw-r--r--sound/drivers/pcsp/pcsp.c8
-rw-r--r--sound/drivers/pcsp/pcsp.h1
-rw-r--r--sound/drivers/pcsp/pcsp_lib.c95
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
189static void pcsp_stop_beep(struct snd_pcsp *chip) 189static 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 {
77extern struct snd_pcsp pcsp_chip; 77extern struct snd_pcsp pcsp_chip;
78 78
79extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle); 79extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle);
80extern void pcsp_sync_stop(struct snd_pcsp *chip);
80 81
81extern int snd_pcsp_new_pcm(struct snd_pcsp *chip); 82extern int snd_pcsp_new_pcm(struct snd_pcsp *chip);
82extern int snd_pcsp_new_mixer(struct snd_pcsp *chip); 83extern 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 */
27static 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
37static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
38
22enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) 39enum 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
124exit_nr_unlock2: 122 stop:
125 snd_pcm_stream_unlock(substream);
126exit_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 */
165void 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
167static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) 174static 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
183static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, 185static 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
194static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) 198static 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
247static struct snd_pcm_hardware snd_pcsp_playback = { 258static 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