diff options
Diffstat (limited to 'sound/drivers/pcsp/pcsp_lib.c')
-rw-r--r-- | sound/drivers/pcsp/pcsp_lib.c | 95 |
1 files changed, 52 insertions, 43 deletions
diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index 1f42e4063118..f8d8470861da 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, hrtimer_get_expires(&chip->timer), | 55 | hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), |
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); |
@@ -122,10 +120,7 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) | |||
122 | ktime_set(0, ns)); | 120 | ktime_set(0, ns)); |
123 | return HRTIMER_RESTART; | 121 | return HRTIMER_RESTART; |
124 | 122 | ||
125 | exit_nr_unlock2: | 123 | stop: |
126 | snd_pcm_stream_unlock(substream); | ||
127 | exit_nr_unlock1: | ||
128 | spin_unlock_irq(&chip->substream_lock); | ||
129 | return HRTIMER_NORESTART; | 124 | return HRTIMER_NORESTART; |
130 | } | 125 | } |
131 | 126 | ||
@@ -165,26 +160,35 @@ static void pcsp_stop_playing(struct snd_pcsp *chip) | |||
165 | spin_unlock(&i8253_lock); | 160 | spin_unlock(&i8253_lock); |
166 | } | 161 | } |
167 | 162 | ||
163 | /* | ||
164 | * Force to stop and sync the stream | ||
165 | */ | ||
166 | void pcsp_sync_stop(struct snd_pcsp *chip) | ||
167 | { | ||
168 | local_irq_disable(); | ||
169 | pcsp_stop_playing(chip); | ||
170 | local_irq_enable(); | ||
171 | hrtimer_cancel(&chip->timer); | ||
172 | tasklet_kill(&pcsp_pcm_tasklet); | ||
173 | } | ||
174 | |||
168 | static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) | 175 | static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) |
169 | { | 176 | { |
170 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | 177 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); |
171 | #if PCSP_DEBUG | 178 | #if PCSP_DEBUG |
172 | printk(KERN_INFO "PCSP: close called\n"); | 179 | printk(KERN_INFO "PCSP: close called\n"); |
173 | #endif | 180 | #endif |
174 | if (atomic_read(&chip->timer_active)) { | 181 | pcsp_sync_stop(chip); |
175 | printk(KERN_ERR "PCSP: timer still active\n"); | ||
176 | pcsp_stop_playing(chip); | ||
177 | } | ||
178 | spin_lock_irq(&chip->substream_lock); | ||
179 | chip->playback_substream = NULL; | 182 | chip->playback_substream = NULL; |
180 | spin_unlock_irq(&chip->substream_lock); | ||
181 | return 0; | 183 | return 0; |
182 | } | 184 | } |
183 | 185 | ||
184 | static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, | 186 | static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, |
185 | struct snd_pcm_hw_params *hw_params) | 187 | struct snd_pcm_hw_params *hw_params) |
186 | { | 188 | { |
189 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
187 | int err; | 190 | int err; |
191 | pcsp_sync_stop(chip); | ||
188 | err = snd_pcm_lib_malloc_pages(substream, | 192 | err = snd_pcm_lib_malloc_pages(substream, |
189 | params_buffer_bytes(hw_params)); | 193 | params_buffer_bytes(hw_params)); |
190 | if (err < 0) | 194 | if (err < 0) |
@@ -194,9 +198,11 @@ static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, | |||
194 | 198 | ||
195 | static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) | 199 | static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) |
196 | { | 200 | { |
201 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | ||
197 | #if PCSP_DEBUG | 202 | #if PCSP_DEBUG |
198 | printk(KERN_INFO "PCSP: hw_free called\n"); | 203 | printk(KERN_INFO "PCSP: hw_free called\n"); |
199 | #endif | 204 | #endif |
205 | pcsp_sync_stop(chip); | ||
200 | return snd_pcm_lib_free_pages(substream); | 206 | return snd_pcm_lib_free_pages(substream); |
201 | } | 207 | } |
202 | 208 | ||
@@ -212,6 +218,7 @@ static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) | |||
212 | snd_pcm_lib_period_bytes(substream), | 218 | snd_pcm_lib_period_bytes(substream), |
213 | substream->runtime->periods); | 219 | substream->runtime->periods); |
214 | #endif | 220 | #endif |
221 | pcsp_sync_stop(chip); | ||
215 | chip->playback_ptr = 0; | 222 | chip->playback_ptr = 0; |
216 | chip->period_ptr = 0; | 223 | chip->period_ptr = 0; |
217 | return 0; | 224 | return 0; |
@@ -242,7 +249,11 @@ static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream | |||
242 | *substream) | 249 | *substream) |
243 | { | 250 | { |
244 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); | 251 | struct snd_pcsp *chip = snd_pcm_substream_chip(substream); |
245 | return bytes_to_frames(substream->runtime, chip->playback_ptr); | 252 | unsigned int pos; |
253 | spin_lock(&chip->substream_lock); | ||
254 | pos = chip->playback_ptr; | ||
255 | spin_unlock(&chip->substream_lock); | ||
256 | return bytes_to_frames(substream->runtime, pos); | ||
246 | } | 257 | } |
247 | 258 | ||
248 | static struct snd_pcm_hardware snd_pcsp_playback = { | 259 | static struct snd_pcm_hardware snd_pcsp_playback = { |
@@ -279,9 +290,7 @@ static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) | |||
279 | return -EBUSY; | 290 | return -EBUSY; |
280 | } | 291 | } |
281 | runtime->hw = snd_pcsp_playback; | 292 | runtime->hw = snd_pcsp_playback; |
282 | spin_lock_irq(&chip->substream_lock); | ||
283 | chip->playback_substream = substream; | 293 | chip->playback_substream = substream; |
284 | spin_unlock_irq(&chip->substream_lock); | ||
285 | return 0; | 294 | return 0; |
286 | } | 295 | } |
287 | 296 | ||