ALSA: hda: Fix potential deadlock at codec unbinding
[sfrench/cifs-2.6.git] / sound / pci / hda / hda_codec.c
index eda70814369bd628676833352f3de2c3c1c3472e..7016b48227bf28d5f35ed54b656d7217eae467d9 100644 (file)
@@ -703,20 +703,10 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
 /*
  * PCM device
  */
-static void release_pcm(struct kref *kref)
-{
-       struct hda_pcm *pcm = container_of(kref, struct hda_pcm, kref);
-
-       if (pcm->pcm)
-               snd_device_free(pcm->codec->card, pcm->pcm);
-       clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
-       kfree(pcm->name);
-       kfree(pcm);
-}
-
 void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
 {
-       kref_put(&pcm->kref, release_pcm);
+       if (refcount_dec_and_test(&pcm->codec->pcm_ref))
+               wake_up(&pcm->codec->remove_sleep);
 }
 EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);
 
@@ -731,7 +721,6 @@ struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
                return NULL;
 
        pcm->codec = codec;
-       kref_init(&pcm->kref);
        va_start(args, fmt);
        pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
        va_end(args);
@@ -741,6 +730,7 @@ struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
        }
 
        list_add_tail(&pcm->list, &codec->pcm_list_head);
+       refcount_inc(&codec->pcm_ref);
        return pcm;
 }
 EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
@@ -748,15 +738,31 @@ EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
 /*
  * codec destructor
  */
+void snd_hda_codec_disconnect_pcms(struct hda_codec *codec)
+{
+       struct hda_pcm *pcm;
+
+       list_for_each_entry(pcm, &codec->pcm_list_head, list) {
+               if (pcm->disconnected)
+                       continue;
+               if (pcm->pcm)
+                       snd_device_disconnect(codec->card, pcm->pcm);
+               snd_hda_codec_pcm_put(pcm);
+               pcm->disconnected = 1;
+       }
+}
+
 static void codec_release_pcms(struct hda_codec *codec)
 {
        struct hda_pcm *pcm, *n;
 
        list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
-               list_del_init(&pcm->list);
+               list_del(&pcm->list);
                if (pcm->pcm)
-                       snd_device_disconnect(codec->card, pcm->pcm);
-               snd_hda_codec_pcm_put(pcm);
+                       snd_device_free(pcm->codec->card, pcm->pcm);
+               clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
+               kfree(pcm->name);
+               kfree(pcm);
        }
 }
 
@@ -769,6 +775,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
                codec->registered = 0;
        }
 
+       snd_hda_codec_disconnect_pcms(codec);
        cancel_delayed_work_sync(&codec->jackpoll_work);
        if (!codec->in_freeing)
                snd_hda_ctls_clear(codec);
@@ -792,6 +799,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
        remove_conn_list(codec);
        snd_hdac_regmap_exit(&codec->core);
        codec->configured = 0;
+       refcount_set(&codec->pcm_ref, 1); /* reset refcount */
 }
 EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind);
 
@@ -958,6 +966,8 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card,
        snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
        INIT_LIST_HEAD(&codec->conn_list);
        INIT_LIST_HEAD(&codec->pcm_list_head);
+       refcount_set(&codec->pcm_ref, 1);
+       init_waitqueue_head(&codec->remove_sleep);
 
        INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
        codec->depop_delay = -1;