]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
ASoC: stm32: spdifrx: fix race condition in irq handler
authorOlivier Moysan <olivier.moysan@st.com>
Wed, 4 Dec 2019 15:43:32 +0000 (16:43 +0100)
committerKhalid Elmously <khalid.elmously@canonical.com>
Fri, 14 Feb 2020 05:29:37 +0000 (00:29 -0500)
BugLink: https://bugs.launchpad.net/bugs/1861934
commit 86e1956af4c863d653136fd6e5694adf2054dbaa upstream.

When snd_pcm_stop() is called in interrupt routine,
substream context may have already been released.
Add protection on substream context.

Fixes: 03e4d5d56fa5 ("ASoC: stm32: Add SPDIFRX support")
Signed-off-by: Olivier Moysan <olivier.moysan@st.com>
Link: https://lore.kernel.org/r/20191204154333.7152-3-olivier.moysan@st.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Khalid Elmously <khalid.elmously@canonical.com>
sound/soc/stm/stm32_spdifrx.c

index efcbee35743a990d8aaa114438e32c7a81e0c1c6..260729cf85572628189cadd25886293af90a081e 100644 (file)
  * @slave_config: dma slave channel runtime config pointer
  * @phys_addr: SPDIFRX registers physical base address
  * @lock: synchronization enabling lock
+ * @irq_lock: prevent race condition with IRQ on stream state
  * @cs: channel status buffer
  * @ub: user data buffer
  * @irq: SPDIFRX interrupt line
@@ -233,6 +234,7 @@ struct stm32_spdifrx_data {
        struct dma_slave_config slave_config;
        dma_addr_t phys_addr;
        spinlock_t lock;  /* Sync enabling lock */
+       spinlock_t irq_lock; /* Prevent race condition on stream state */
        unsigned char cs[SPDIFRX_CS_BYTES_NB];
        unsigned char ub[SPDIFRX_UB_BYTES_NB];
        int irq;
@@ -645,7 +647,6 @@ static const struct regmap_config stm32_h7_spdifrx_regmap_conf = {
 static irqreturn_t stm32_spdifrx_isr(int irq, void *devid)
 {
        struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)devid;
-       struct snd_pcm_substream *substream = spdifrx->substream;
        struct platform_device *pdev = spdifrx->pdev;
        unsigned int cr, mask, sr, imr;
        unsigned int flags;
@@ -713,14 +714,19 @@ static irqreturn_t stm32_spdifrx_isr(int irq, void *devid)
                regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR,
                                   SPDIFRX_CR_SPDIFEN_MASK, cr);
 
-               if (substream)
-                       snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+               spin_lock(&spdifrx->irq_lock);
+               if (spdifrx->substream)
+                       snd_pcm_stop(spdifrx->substream,
+                                    SNDRV_PCM_STATE_DISCONNECTED);
+               spin_unlock(&spdifrx->irq_lock);
 
                return IRQ_HANDLED;
        }
 
-       if (err_xrun && substream)
-               snd_pcm_stop_xrun(substream);
+       spin_lock(&spdifrx->irq_lock);
+       if (err_xrun && spdifrx->substream)
+               snd_pcm_stop_xrun(spdifrx->substream);
+       spin_unlock(&spdifrx->irq_lock);
 
        return IRQ_HANDLED;
 }
@@ -729,9 +735,12 @@ static int stm32_spdifrx_startup(struct snd_pcm_substream *substream,
                                 struct snd_soc_dai *cpu_dai)
 {
        struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai);
+       unsigned long flags;
        int ret;
 
+       spin_lock_irqsave(&spdifrx->irq_lock, flags);
        spdifrx->substream = substream;
+       spin_unlock_irqrestore(&spdifrx->irq_lock, flags);
 
        ret = clk_prepare_enable(spdifrx->kclk);
        if (ret)
@@ -807,8 +816,12 @@ static void stm32_spdifrx_shutdown(struct snd_pcm_substream *substream,
                                   struct snd_soc_dai *cpu_dai)
 {
        struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai);
+       unsigned long flags;
 
+       spin_lock_irqsave(&spdifrx->irq_lock, flags);
        spdifrx->substream = NULL;
+       spin_unlock_irqrestore(&spdifrx->irq_lock, flags);
+
        clk_disable_unprepare(spdifrx->kclk);
 }
 
@@ -913,6 +926,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
        spdifrx->pdev = pdev;
        init_completion(&spdifrx->cs_completion);
        spin_lock_init(&spdifrx->lock);
+       spin_lock_init(&spdifrx->irq_lock);
 
        platform_set_drvdata(pdev, spdifrx);