ALSA: x86: Fix racy access to chmap
authorTakashi Iwai <tiwai@suse.de>
Wed, 1 Feb 2017 16:24:02 +0000 (17:24 +0100)
committerTakashi Iwai <tiwai@suse.de>
Fri, 3 Feb 2017 16:35:07 +0000 (17:35 +0100)
The access to chmap can be racy against the hotplug process, where it
recreates the chmap on the fly.  For protecting against it, a mutex is
introduced in this patch.  It's also used for protecting the change /
reference of eld and state fields, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/x86/intel_hdmi_audio.c
sound/x86/intel_hdmi_audio.h

index 046af2367fba8eed2da0b22a2cfadbc0d2a3fef1..c0cb59e6a89b23f52cd7fe6389eb264b3eaf5879 100644 (file)
@@ -555,11 +555,17 @@ static int had_chmap_ctl_get(struct snd_kcontrol *kcontrol,
 
        if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED)
                return -ENODEV;
-       if (!intelhaddata->chmap->chmap)
+
+       mutex_lock(&intelhaddata->mutex);
+       if (!intelhaddata->chmap->chmap) {
+               mutex_unlock(&intelhaddata->mutex);
                return -ENODATA;
+       }
+
        chmap = intelhaddata->chmap->chmap;
        for (i = 0; i < chmap->channels; i++)
                ucontrol->value.integer.value[i] = chmap->map[i];
+       mutex_unlock(&intelhaddata->mutex);
 
        return 0;
 }
@@ -1352,6 +1358,7 @@ static int snd_intelhad_pcm_mmap(struct snd_pcm_substream *substream,
                        vma->vm_end - vma->vm_start, vma->vm_page_prot);
 }
 
+/* process mode change of the running stream; called in mutex */
 static int hdmi_audio_mode_change(struct snd_intelhad *intelhaddata)
 {
        struct snd_pcm_substream *substream;
@@ -1642,7 +1649,7 @@ static int had_process_buffer_underrun(struct snd_intelhad *intelhaddata)
        return 0;
 }
 
-/* process hot plug, called from wq */
+/* process hot plug, called from wq with mutex locked */
 static int had_process_hot_plug(struct snd_intelhad *intelhaddata)
 {
        enum intel_had_aud_buf_type buf_id;
@@ -1682,7 +1689,7 @@ static int had_process_hot_plug(struct snd_intelhad *intelhaddata)
        return 0;
 }
 
-/* process hot unplug, called from wq */
+/* process hot unplug, called from wq with mutex locked */
 static int had_process_hot_unplug(struct snd_intelhad *intelhaddata)
 {
        enum intel_had_aud_buf_type buf_id;
@@ -1751,12 +1758,14 @@ static int had_iec958_get(struct snd_kcontrol *kcontrol,
 {
        struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol);
 
+       mutex_lock(&intelhaddata->mutex);
        ucontrol->value.iec958.status[0] = (intelhaddata->aes_bits >> 0) & 0xff;
        ucontrol->value.iec958.status[1] = (intelhaddata->aes_bits >> 8) & 0xff;
        ucontrol->value.iec958.status[2] =
                                        (intelhaddata->aes_bits >> 16) & 0xff;
        ucontrol->value.iec958.status[3] =
                                        (intelhaddata->aes_bits >> 24) & 0xff;
+       mutex_unlock(&intelhaddata->mutex);
        return 0;
 }
 
@@ -1775,16 +1784,19 @@ static int had_iec958_put(struct snd_kcontrol *kcontrol,
 {
        unsigned int val;
        struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol);
+       int changed = 0;
 
        val = (ucontrol->value.iec958.status[0] << 0) |
                (ucontrol->value.iec958.status[1] << 8) |
                (ucontrol->value.iec958.status[2] << 16) |
                (ucontrol->value.iec958.status[3] << 24);
+       mutex_lock(&intelhaddata->mutex);
        if (intelhaddata->aes_bits != val) {
                intelhaddata->aes_bits = val;
-               return 1;
+               changed = 1;
        }
-       return 1;
+       mutex_unlock(&intelhaddata->mutex);
+       return changed;
 }
 
 static struct snd_kcontrol_new had_control_iec958_mask = {
@@ -1837,6 +1849,7 @@ static void had_audio_wq(struct work_struct *work)
                container_of(work, struct snd_intelhad, hdmi_audio_wq);
        struct intel_hdmi_lpe_audio_pdata *pdata = ctx->dev->platform_data;
 
+       mutex_lock(&ctx->mutex);
        if (!pdata->hdmi_connected) {
                dev_dbg(ctx->dev, "%s: Event: HAD_NOTIFY_HOT_UNPLUG\n",
                        __func__);
@@ -1844,12 +1857,11 @@ static void had_audio_wq(struct work_struct *work)
                if (ctx->state != hdmi_connector_status_connected) {
                        dev_dbg(ctx->dev, "%s: Already Unplugged!\n",
                                __func__);
-                       return;
+               } else {
+                       ctx->state = hdmi_connector_status_disconnected;
+                       had_process_hot_unplug(ctx);
                }
 
-               ctx->state = hdmi_connector_status_disconnected;
-               had_process_hot_unplug(ctx);
-
        } else {
                struct intel_hdmi_lpe_audio_eld *eld = &pdata->eld;
 
@@ -1888,6 +1900,7 @@ static void had_audio_wq(struct work_struct *work)
                                hdmi_audio_mode_change(ctx);
                }
        }
+       mutex_unlock(&ctx->mutex);
 }
 
 /* release resources */
@@ -1948,6 +1961,7 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev)
 
        ctx = card->private_data;
        spin_lock_init(&ctx->had_spinlock);
+       mutex_init(&ctx->mutex);
        ctx->drv_status = HAD_DRV_DISCONNECTED;
        ctx->dev = &pdev->dev;
        ctx->card = card;
index 8b85a5668d839b2c376e28ec4a8391ecec58839a..be24682e3946864942478c42ac57a9722b9ee5e5 100644 (file)
@@ -139,6 +139,7 @@ struct snd_intelhad {
        void __iomem *mmio_start;
        unsigned int had_config_offset;
        struct work_struct hdmi_audio_wq;
+       struct mutex mutex; /* for protecting chmap, state and eld */
 };
 
 #endif /* _INTEL_HDMI_AUDIO_ */