fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / sound / core / init.c
index 4d92588..a4cc6b1 100644 (file)
 #include <sound/control.h>
 #include <sound/info.h>
 
-struct snd_shutdown_f_ops {
-       struct file_operations f_ops;
-       struct snd_shutdown_f_ops *next;
-};
+static DEFINE_SPINLOCK(shutdown_lock);
+static LIST_HEAD(shutdown_files);
+
+static struct file_operations snd_shutdown_f_ops;
 
 static unsigned int snd_cards_lock;    /* locked for registering/using */
 struct snd_card *snd_cards[SNDRV_CARDS];
@@ -81,8 +81,6 @@ static inline int init_info_for_card(struct snd_card *card)
 #define init_info_for_card(card)
 #endif
 
-static void snd_card_free_thread(void * __card);
-
 /**
  *  snd_card_new - create and initialize a soundcard structure
  *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
@@ -145,7 +143,6 @@ struct snd_card *snd_card_new(int idx, const char *xid,
        INIT_LIST_HEAD(&card->ctl_files);
        spin_lock_init(&card->files_lock);
        init_waitqueue_head(&card->shutdown_sleep);
-       INIT_WORK(&card->free_workq, snd_card_free_thread, card);
 #ifdef CONFIG_PM
        mutex_init(&card->power_lock);
        init_waitqueue_head(&card->power_sleep);
@@ -201,6 +198,25 @@ static ssize_t snd_disconnect_write(struct file *file, const char __user *buf,
        return -ENODEV;
 }
 
+static int snd_disconnect_release(struct inode *inode, struct file *file)
+{
+       struct snd_monitor_file *df = NULL, *_df;
+
+       spin_lock(&shutdown_lock);
+       list_for_each_entry(_df, &shutdown_files, shutdown_list) {
+               if (_df->file == file) {
+                       df = _df;
+                       break;
+               }
+       }
+       spin_unlock(&shutdown_lock);
+
+       if (likely(df))
+               return df->disconnected_f_op->release(inode, file);
+
+       panic("%s(%p, %p) failed!", __FUNCTION__, inode, file);
+}
+
 static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
 {
        return POLLERR | POLLNVAL;
@@ -222,6 +238,22 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on)
        return -ENODEV;
 }
 
+static struct file_operations snd_shutdown_f_ops =
+{
+       .owner =        THIS_MODULE,
+       .llseek =       snd_disconnect_llseek,
+       .read =         snd_disconnect_read,
+       .write =        snd_disconnect_write,
+       .release =      snd_disconnect_release,
+       .poll =         snd_disconnect_poll,
+       .unlocked_ioctl = snd_disconnect_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = snd_disconnect_ioctl,
+#endif
+       .mmap =         snd_disconnect_mmap,
+       .fasync =       snd_disconnect_fasync
+};
+
 /**
  *  snd_card_disconnect - disconnect all APIs from the file-operations (user space)
  *  @card: soundcard structure
@@ -237,9 +269,6 @@ int snd_card_disconnect(struct snd_card *card)
 {
        struct snd_monitor_file *mfile;
        struct file *file;
-       struct snd_shutdown_f_ops *s_f_ops;
-       struct file_operations *f_ops;
-       const struct file_operations *old_f_ops;
        int err;
 
        spin_lock(&card->files_lock);
@@ -264,34 +293,14 @@ int snd_card_disconnect(struct snd_card *card)
 
                /* it's critical part, use endless loop */
                /* we have no room to fail */
-               s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC);
-               if (s_f_ops == NULL)
-                       panic("Atomic allocation failed for snd_shutdown_f_ops!");
-
-               f_ops = &s_f_ops->f_ops;
-
-               memset(f_ops, 0, sizeof(*f_ops));
-               f_ops->owner = file->f_op->owner;
-               f_ops->release = file->f_op->release;
-               f_ops->llseek = snd_disconnect_llseek;
-               f_ops->read = snd_disconnect_read;
-               f_ops->write = snd_disconnect_write;
-               f_ops->poll = snd_disconnect_poll;
-               f_ops->unlocked_ioctl = snd_disconnect_ioctl;
-#ifdef CONFIG_COMPAT
-               f_ops->compat_ioctl = snd_disconnect_ioctl;
-#endif
-               f_ops->mmap = snd_disconnect_mmap;
-               f_ops->fasync = snd_disconnect_fasync;
+               mfile->disconnected_f_op = mfile->file->f_op;
 
-               s_f_ops->next = card->s_f_ops;
-               card->s_f_ops = s_f_ops;
-               
-               f_ops = fops_get(f_ops);
+               spin_lock(&shutdown_lock);
+               list_add(&mfile->shutdown_list, &shutdown_files);
+               spin_unlock(&shutdown_lock);
 
-               old_f_ops = file->f_op;
-               file->f_op = f_ops;     /* must be atomic */
-               fops_put(old_f_ops);
+               fops_get(&snd_shutdown_f_ops);
+               mfile->file->f_op = &snd_shutdown_f_ops;
                
                mfile = mfile->next;
        }
@@ -310,6 +319,7 @@ int snd_card_disconnect(struct snd_card *card)
        if (err < 0)
                snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number);
 
+       snd_info_card_disconnect(card);
        return 0;       
 }
 
@@ -326,22 +336,8 @@ EXPORT_SYMBOL(snd_card_disconnect);
  *  Returns zero. Frees all associated devices and frees the control
  *  interface associated to given soundcard.
  */
-int snd_card_free(struct snd_card *card)
+static int snd_card_do_free(struct snd_card *card)
 {
-       struct snd_shutdown_f_ops *s_f_ops;
-
-       if (card == NULL)
-               return -EINVAL;
-       mutex_lock(&snd_card_mutex);
-       snd_cards[card->number] = NULL;
-       mutex_unlock(&snd_card_mutex);
-
-#ifdef CONFIG_PM
-       wake_up(&card->power_sleep);
-#endif
-       /* wait, until all devices are ready for the free operation */
-       wait_event(card->shutdown_sleep, card->files == NULL);
-
 #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
        if (snd_mixer_oss_notify_callback)
                snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
@@ -360,71 +356,68 @@ int snd_card_free(struct snd_card *card)
        }
        if (card->private_free)
                card->private_free(card);
-       snd_info_unregister(card->proc_id);
+       snd_info_free_entry(card->proc_id);
        if (snd_info_card_free(card) < 0) {
                snd_printk(KERN_WARNING "unable to free card info\n");
                /* Not fatal error */
        }
-       while (card->s_f_ops) {
-               s_f_ops = card->s_f_ops;
-               card->s_f_ops = s_f_ops->next;
-               kfree(s_f_ops);
-       }
+#ifndef CONFIG_SYSFS_DEPRECATED
+       if (card->card_dev)
+               device_unregister(card->card_dev);
+#endif
+       kfree(card);
+       return 0;
+}
+
+static int snd_card_free_prepare(struct snd_card *card)
+{
+       if (card == NULL)
+               return -EINVAL;
+       (void) snd_card_disconnect(card);
        mutex_lock(&snd_card_mutex);
+       snd_cards[card->number] = NULL;
        snd_cards_lock &= ~(1 << card->number);
        mutex_unlock(&snd_card_mutex);
-       kfree(card);
+#ifdef CONFIG_PM
+       wake_up(&card->power_sleep);
+#endif
        return 0;
 }
 
-EXPORT_SYMBOL(snd_card_free);
-
-static void snd_card_free_thread(void * __card)
+int snd_card_free_when_closed(struct snd_card *card)
 {
-       struct snd_card *card = __card;
-       struct module * module = card->module;
-
-       if (!try_module_get(module)) {
-               snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number);
-               module = NULL;
-       }
+       int free_now = 0;
+       int ret = snd_card_free_prepare(card);
+       if (ret)
+               return ret;
 
-       snd_card_free(card);
+       spin_lock(&card->files_lock);
+       if (card->files == NULL)
+               free_now = 1;
+       else
+               card->free_on_last_close = 1;
+       spin_unlock(&card->files_lock);
 
-       module_put(module);
+       if (free_now)
+               snd_card_do_free(card);
+       return 0;
 }
 
-/**
- *  snd_card_free_in_thread - call snd_card_free() in thread
- *  @card: soundcard structure
- *
- *  This function schedules the call of snd_card_free() function in a
- *  work queue.  When all devices are released (non-busy), the work
- *  is woken up and calls snd_card_free().
- *
- *  When a card can be disconnected at any time by hotplug service,
- *  this function should be used in disconnect (or detach) callback
- *  instead of calling snd_card_free() directly.
- *  
- *  Returns - zero otherwise a negative error code if the start of thread failed.
- */
-int snd_card_free_in_thread(struct snd_card *card)
-{
-       if (card->files == NULL) {
-               snd_card_free(card);
-               return 0;
-       }
+EXPORT_SYMBOL(snd_card_free_when_closed);
 
-       if (schedule_work(&card->free_workq))
-               return 0;
+int snd_card_free(struct snd_card *card)
+{
+       int ret = snd_card_free_prepare(card);
+       if (ret)
+               return ret;
 
-       snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number);
-       /* try to free the structure immediately */
-       snd_card_free(card);
-       return -EFAULT;
+       /* wait, until all devices are ready for the free operation */
+       wait_event(card->shutdown_sleep, card->files == NULL);
+       snd_card_do_free(card);
+       return 0;
 }
 
-EXPORT_SYMBOL(snd_card_free_in_thread);
+EXPORT_SYMBOL(snd_card_free);
 
 static void choose_default_id(struct snd_card *card)
 {
@@ -506,6 +499,14 @@ int snd_card_register(struct snd_card *card)
        int err;
 
        snd_assert(card != NULL, return -EINVAL);
+#ifndef CONFIG_SYSFS_DEPRECATED
+       if (!card->card_dev) {
+               card->card_dev = device_create(sound_class, card->dev, 0,
+                                              "card%i", card->number);
+               if (IS_ERR(card->card_dev))
+                       card->card_dev = NULL;
+       }
+#endif
        if ((err = snd_device_register_all(card)) < 0)
                return err;
        mutex_lock(&snd_card_mutex);
@@ -625,9 +626,9 @@ int __init snd_card_info_init(void)
 
 int __exit snd_card_info_done(void)
 {
-       snd_info_unregister(snd_card_info_entry);
+       snd_info_free_entry(snd_card_info_entry);
 #ifdef MODULE
-       snd_info_unregister(snd_card_module_info_entry);
+       snd_info_free_entry(snd_card_module_info_entry);
 #endif
        return 0;
 }
@@ -686,6 +687,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file)
        if (mfile == NULL)
                return -ENOMEM;
        mfile->file = file;
+       mfile->disconnected_f_op = NULL;
        mfile->next = NULL;
        spin_lock(&card->files_lock);
        if (card->shutdown) {
@@ -708,15 +710,16 @@ EXPORT_SYMBOL(snd_card_file_add);
  *
  *  This function removes the file formerly added to the card via
  *  snd_card_file_add() function.
- *  If all files are removed and the release of the card is
- *  scheduled, it will wake up the the thread to call snd_card_free()
- *  (see snd_card_free_in_thread() function).
+ *  If all files are removed and snd_card_free_when_closed() was
+ *  called beforehand, it processes the pending release of
+ *  resources.
  *
  *  Returns zero or a negative error code.
  */
 int snd_card_file_remove(struct snd_card *card, struct file *file)
 {
        struct snd_monitor_file *mfile, *pfile = NULL;
+       int last_close = 0;
 
        spin_lock(&card->files_lock);
        mfile = card->files;
@@ -731,9 +734,20 @@ int snd_card_file_remove(struct snd_card *card, struct file *file)
                pfile = mfile;
                mfile = mfile->next;
        }
-       spin_unlock(&card->files_lock);
+       if (mfile && mfile->disconnected_f_op) {
+               fops_put(mfile->disconnected_f_op);
+               spin_lock(&shutdown_lock);
+               list_del(&mfile->shutdown_list);
+               spin_unlock(&shutdown_lock);
+       }
        if (card->files == NULL)
+               last_close = 1;
+       spin_unlock(&card->files_lock);
+       if (last_close) {
                wake_up(&card->shutdown_sleep);
+               if (card->free_on_last_close)
+                       snd_card_do_free(card);
+       }
        if (!mfile) {
                snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
                return -ENOENT;