| From 6aa8a70ce771e37202cd24726fd93e2f553a5b79 Mon Sep 17 00:00:00 2001 |
| From: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Date: Tue, 17 Sep 2019 14:38:16 +0200 |
| Subject: [PATCH] iio: adc: stm32-adc: fix a race when using several adcs with |
| dma and irq |
| |
| commit dcb10920179ab74caf88a6f2afadecfc2743b910 upstream. |
| |
| End of conversion may be handled by using IRQ or DMA. There may be a |
| race when two conversions complete at the same time on several ADCs. |
| EOC can be read as 'set' for several ADCs, with: |
| - an ADC configured to use IRQs. EOCIE bit is set. The handler is normally |
| called in this case. |
| - an ADC configured to use DMA. EOCIE bit isn't set. EOC triggers the DMA |
| request instead. It's then automatically cleared by DMA read. But the |
| handler gets called due to status bit is temporarily set (IRQ triggered |
| by the other ADC). |
| So both EOC status bit in CSR and EOCIE control bit must be checked |
| before invoking the interrupt handler (e.g. call ISR only for |
| IRQ-enabled ADCs). |
| |
| Fixes: 2763ea0585c9 ("iio: adc: stm32: add optional dma support") |
| |
| Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Cc: <Stable@vger.kernel.org> |
| Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c |
| index 8da517553356..6016a864d6d6 100644 |
| --- a/drivers/iio/adc/stm32-adc-core.c |
| +++ b/drivers/iio/adc/stm32-adc-core.c |
| @@ -31,6 +31,8 @@ |
| * @eoc1: adc1 end of conversion flag in @csr |
| * @eoc2: adc2 end of conversion flag in @csr |
| * @eoc3: adc3 end of conversion flag in @csr |
| + * @ier: interrupt enable register offset for each adc |
| + * @eocie_msk: end of conversion interrupt enable mask in @ier |
| */ |
| struct stm32_adc_common_regs { |
| u32 csr; |
| @@ -38,6 +40,8 @@ struct stm32_adc_common_regs { |
| u32 eoc1_msk; |
| u32 eoc2_msk; |
| u32 eoc3_msk; |
| + u32 ier; |
| + u32 eocie_msk; |
| }; |
| |
| struct stm32_adc_priv; |
| @@ -251,6 +255,8 @@ static const struct stm32_adc_common_regs stm32f4_adc_common_regs = { |
| .eoc1_msk = STM32F4_EOC1, |
| .eoc2_msk = STM32F4_EOC2, |
| .eoc3_msk = STM32F4_EOC3, |
| + .ier = STM32F4_ADC_CR1, |
| + .eocie_msk = STM32F4_EOCIE, |
| }; |
| |
| /* STM32H7 common registers definitions */ |
| @@ -259,8 +265,24 @@ static const struct stm32_adc_common_regs stm32h7_adc_common_regs = { |
| .ccr = STM32H7_ADC_CCR, |
| .eoc1_msk = STM32H7_EOC_MST, |
| .eoc2_msk = STM32H7_EOC_SLV, |
| + .ier = STM32H7_ADC_IER, |
| + .eocie_msk = STM32H7_EOCIE, |
| }; |
| |
| +static const unsigned int stm32_adc_offset[STM32_ADC_MAX_ADCS] = { |
| + 0, STM32_ADC_OFFSET, STM32_ADC_OFFSET * 2, |
| +}; |
| + |
| +static unsigned int stm32_adc_eoc_enabled(struct stm32_adc_priv *priv, |
| + unsigned int adc) |
| +{ |
| + u32 ier, offset = stm32_adc_offset[adc]; |
| + |
| + ier = readl_relaxed(priv->common.base + offset + priv->cfg->regs->ier); |
| + |
| + return ier & priv->cfg->regs->eocie_msk; |
| +} |
| + |
| /* ADC common interrupt for all instances */ |
| static void stm32_adc_irq_handler(struct irq_desc *desc) |
| { |
| @@ -271,13 +293,28 @@ static void stm32_adc_irq_handler(struct irq_desc *desc) |
| chained_irq_enter(chip, desc); |
| status = readl_relaxed(priv->common.base + priv->cfg->regs->csr); |
| |
| - if (status & priv->cfg->regs->eoc1_msk) |
| + /* |
| + * End of conversion may be handled by using IRQ or DMA. There may be a |
| + * race here when two conversions complete at the same time on several |
| + * ADCs. EOC may be read 'set' for several ADCs, with: |
| + * - an ADC configured to use DMA (EOC triggers the DMA request, and |
| + * is then automatically cleared by DR read in hardware) |
| + * - an ADC configured to use IRQs (EOCIE bit is set. The handler must |
| + * be called in this case) |
| + * So both EOC status bit in CSR and EOCIE control bit must be checked |
| + * before invoking the interrupt handler (e.g. call ISR only for |
| + * IRQ-enabled ADCs). |
| + */ |
| + if (status & priv->cfg->regs->eoc1_msk && |
| + stm32_adc_eoc_enabled(priv, 0)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 0)); |
| |
| - if (status & priv->cfg->regs->eoc2_msk) |
| + if (status & priv->cfg->regs->eoc2_msk && |
| + stm32_adc_eoc_enabled(priv, 1)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 1)); |
| |
| - if (status & priv->cfg->regs->eoc3_msk) |
| + if (status & priv->cfg->regs->eoc3_msk && |
| + stm32_adc_eoc_enabled(priv, 2)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 2)); |
| |
| chained_irq_exit(chip, desc); |
| diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h |
| index 94aa2d2577dc..2579d514c2a3 100644 |
| --- a/drivers/iio/adc/stm32-adc-core.h |
| +++ b/drivers/iio/adc/stm32-adc-core.h |
| @@ -25,6 +25,7 @@ |
| * -------------------------------------------------------- |
| */ |
| #define STM32_ADC_MAX_ADCS 3 |
| +#define STM32_ADC_OFFSET 0x100 |
| #define STM32_ADCX_COMN_OFFSET 0x300 |
| |
| /* STM32F4 - Registers for each ADC instance */ |
| -- |
| 2.7.4 |
| |