diff options
author | Karsten Wiese <fzu@wemgehoertderstaat.de> | 2006-10-06 10:08:27 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2006-10-06 14:23:04 -0400 |
commit | a9edfc60227a1dc5c741666ff252a6055b73b184 (patch) | |
tree | 15993e126624ec9e05f33f957ee4f69bd4787220 /sound/core | |
parent | 4130d59b1ac6e32c130bd59dbce5eb30fede0197 (diff) |
[ALSA] Handle file operations during snd_card disconnects using static file->f_op
Alsa used to kmalloc one file->f_op per file per disconnecting snd_card.
This led to oopses sometimes when file->f_op was freed before __fput()
finished.
Patch adds a virtual device for disconnect: VDD.
VDD consists of:
LIST_HEAD(shutdown_files)
protected by DEFINE_SPINLOCK(shutdown_mutex)
static struct file_operations snd_shutdown_f_ops
and functions assigned to it
Additions to struct snd_monitor_file
to specify if instance is hidden by VDD or not.
A VDD's instance is
created in snd_card_disconnect() under the card->files_lock.
cleaned up in snd_card_file_remove() under the card->files_lock.
Signed-off-by: Karsten Wiese <fzu@wemgehoertderstaat.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/init.c | 92 |
1 files changed, 52 insertions, 40 deletions
diff --git a/sound/core/init.c b/sound/core/init.c index d7607a25acdf..3058d626a90a 100644 --- a/sound/core/init.c +++ b/sound/core/init.c | |||
@@ -33,10 +33,10 @@ | |||
33 | #include <sound/control.h> | 33 | #include <sound/control.h> |
34 | #include <sound/info.h> | 34 | #include <sound/info.h> |
35 | 35 | ||
36 | struct snd_shutdown_f_ops { | 36 | static DEFINE_SPINLOCK(shutdown_lock); |
37 | struct file_operations f_ops; | 37 | static LIST_HEAD(shutdown_files); |
38 | struct snd_shutdown_f_ops *next; | 38 | |
39 | }; | 39 | static struct file_operations snd_shutdown_f_ops; |
40 | 40 | ||
41 | static unsigned int snd_cards_lock; /* locked for registering/using */ | 41 | static unsigned int snd_cards_lock; /* locked for registering/using */ |
42 | struct snd_card *snd_cards[SNDRV_CARDS]; | 42 | struct snd_card *snd_cards[SNDRV_CARDS]; |
@@ -198,6 +198,25 @@ static ssize_t snd_disconnect_write(struct file *file, const char __user *buf, | |||
198 | return -ENODEV; | 198 | return -ENODEV; |
199 | } | 199 | } |
200 | 200 | ||
201 | static int snd_disconnect_release(struct inode *inode, struct file *file) | ||
202 | { | ||
203 | struct snd_monitor_file *df = NULL, *_df; | ||
204 | |||
205 | spin_lock(&shutdown_lock); | ||
206 | list_for_each_entry(_df, &shutdown_files, shutdown_list) { | ||
207 | if (_df->file == file) { | ||
208 | df = _df; | ||
209 | break; | ||
210 | } | ||
211 | } | ||
212 | spin_unlock(&shutdown_lock); | ||
213 | |||
214 | if (likely(df)) | ||
215 | return df->disconnected_f_op->release(inode, file); | ||
216 | |||
217 | panic("%s(%p, %p) failed!", __FUNCTION__, inode, file); | ||
218 | } | ||
219 | |||
201 | static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait) | 220 | static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait) |
202 | { | 221 | { |
203 | return POLLERR | POLLNVAL; | 222 | return POLLERR | POLLNVAL; |
@@ -219,6 +238,22 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) | |||
219 | return -ENODEV; | 238 | return -ENODEV; |
220 | } | 239 | } |
221 | 240 | ||
241 | static struct file_operations snd_shutdown_f_ops = | ||
242 | { | ||
243 | .owner = THIS_MODULE, | ||
244 | .llseek = snd_disconnect_llseek, | ||
245 | .read = snd_disconnect_read, | ||
246 | .write = snd_disconnect_write, | ||
247 | .release = snd_disconnect_release, | ||
248 | .poll = snd_disconnect_poll, | ||
249 | .unlocked_ioctl = snd_disconnect_ioctl, | ||
250 | #ifdef CONFIG_COMPAT | ||
251 | .compat_ioctl = snd_disconnect_ioctl, | ||
252 | #endif | ||
253 | .mmap = snd_disconnect_mmap, | ||
254 | .fasync = snd_disconnect_fasync | ||
255 | }; | ||
256 | |||
222 | /** | 257 | /** |
223 | * snd_card_disconnect - disconnect all APIs from the file-operations (user space) | 258 | * snd_card_disconnect - disconnect all APIs from the file-operations (user space) |
224 | * @card: soundcard structure | 259 | * @card: soundcard structure |
@@ -234,9 +269,6 @@ int snd_card_disconnect(struct snd_card *card) | |||
234 | { | 269 | { |
235 | struct snd_monitor_file *mfile; | 270 | struct snd_monitor_file *mfile; |
236 | struct file *file; | 271 | struct file *file; |
237 | struct snd_shutdown_f_ops *s_f_ops; | ||
238 | struct file_operations *f_ops; | ||
239 | const struct file_operations *old_f_ops; | ||
240 | int err; | 272 | int err; |
241 | 273 | ||
242 | spin_lock(&card->files_lock); | 274 | spin_lock(&card->files_lock); |
@@ -261,34 +293,14 @@ int snd_card_disconnect(struct snd_card *card) | |||
261 | 293 | ||
262 | /* it's critical part, use endless loop */ | 294 | /* it's critical part, use endless loop */ |
263 | /* we have no room to fail */ | 295 | /* we have no room to fail */ |
264 | s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC); | 296 | mfile->disconnected_f_op = mfile->file->f_op; |
265 | if (s_f_ops == NULL) | ||
266 | panic("Atomic allocation failed for snd_shutdown_f_ops!"); | ||
267 | |||
268 | f_ops = &s_f_ops->f_ops; | ||
269 | |||
270 | memset(f_ops, 0, sizeof(*f_ops)); | ||
271 | f_ops->owner = file->f_op->owner; | ||
272 | f_ops->release = file->f_op->release; | ||
273 | f_ops->llseek = snd_disconnect_llseek; | ||
274 | f_ops->read = snd_disconnect_read; | ||
275 | f_ops->write = snd_disconnect_write; | ||
276 | f_ops->poll = snd_disconnect_poll; | ||
277 | f_ops->unlocked_ioctl = snd_disconnect_ioctl; | ||
278 | #ifdef CONFIG_COMPAT | ||
279 | f_ops->compat_ioctl = snd_disconnect_ioctl; | ||
280 | #endif | ||
281 | f_ops->mmap = snd_disconnect_mmap; | ||
282 | f_ops->fasync = snd_disconnect_fasync; | ||
283 | 297 | ||
284 | s_f_ops->next = card->s_f_ops; | 298 | spin_lock(&shutdown_lock); |
285 | card->s_f_ops = s_f_ops; | 299 | list_add(&mfile->shutdown_list, &shutdown_files); |
286 | 300 | spin_unlock(&shutdown_lock); | |
287 | f_ops = fops_get(f_ops); | ||
288 | 301 | ||
289 | old_f_ops = file->f_op; | 302 | fops_get(&snd_shutdown_f_ops); |
290 | file->f_op = f_ops; /* must be atomic */ | 303 | mfile->file->f_op = &snd_shutdown_f_ops; |
291 | fops_put(old_f_ops); | ||
292 | 304 | ||
293 | mfile = mfile->next; | 305 | mfile = mfile->next; |
294 | } | 306 | } |
@@ -326,8 +338,6 @@ EXPORT_SYMBOL(snd_card_disconnect); | |||
326 | */ | 338 | */ |
327 | static int snd_card_do_free(struct snd_card *card) | 339 | static int snd_card_do_free(struct snd_card *card) |
328 | { | 340 | { |
329 | struct snd_shutdown_f_ops *s_f_ops; | ||
330 | |||
331 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) | 341 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) |
332 | if (snd_mixer_oss_notify_callback) | 342 | if (snd_mixer_oss_notify_callback) |
333 | snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); | 343 | snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); |
@@ -351,11 +361,6 @@ static int snd_card_do_free(struct snd_card *card) | |||
351 | snd_printk(KERN_WARNING "unable to free card info\n"); | 361 | snd_printk(KERN_WARNING "unable to free card info\n"); |
352 | /* Not fatal error */ | 362 | /* Not fatal error */ |
353 | } | 363 | } |
354 | while (card->s_f_ops) { | ||
355 | s_f_ops = card->s_f_ops; | ||
356 | card->s_f_ops = s_f_ops->next; | ||
357 | kfree(s_f_ops); | ||
358 | } | ||
359 | kfree(card); | 364 | kfree(card); |
360 | return 0; | 365 | return 0; |
361 | } | 366 | } |
@@ -670,6 +675,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file) | |||
670 | if (mfile == NULL) | 675 | if (mfile == NULL) |
671 | return -ENOMEM; | 676 | return -ENOMEM; |
672 | mfile->file = file; | 677 | mfile->file = file; |
678 | mfile->disconnected_f_op = NULL; | ||
673 | mfile->next = NULL; | 679 | mfile->next = NULL; |
674 | spin_lock(&card->files_lock); | 680 | spin_lock(&card->files_lock); |
675 | if (card->shutdown) { | 681 | if (card->shutdown) { |
@@ -716,6 +722,12 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) | |||
716 | pfile = mfile; | 722 | pfile = mfile; |
717 | mfile = mfile->next; | 723 | mfile = mfile->next; |
718 | } | 724 | } |
725 | if (mfile && mfile->disconnected_f_op) { | ||
726 | fops_put(mfile->disconnected_f_op); | ||
727 | spin_lock(&shutdown_lock); | ||
728 | list_del(&mfile->shutdown_list); | ||
729 | spin_unlock(&shutdown_lock); | ||
730 | } | ||
719 | if (card->files == NULL) | 731 | if (card->files == NULL) |
720 | last_close = 1; | 732 | last_close = 1; |
721 | spin_unlock(&card->files_lock); | 733 | spin_unlock(&card->files_lock); |