Add dma support for i.MX23/28
Subsystems (4)
v2 cover Add dma support for i.MX23/28
The v2 of the patch set is to address the v1 comments given by Russell, Sascha, and Lothar.
The patch number is down to 3 from 5 (v1) due to the change of dma device dynamical allocation to the static.
[PATCH v2 1/3] dmaengine: mxs-dma: add dma support for i.MX23/28 [PATCH v2 2/3] ARM: mxs: add dma channel definitions [PATCH v2 3/3] ARM: mxs: add dma device
Thanks for review.
Regards,
Shawn
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
This patch adds dma support for Freescale MXS-based SoC i.MX23/28, including apbh-dma and apbx-dma.
* apbh-dma and apbx-dma are supported in the driver as two instances, and have to be filtered by dma clients via device id. It becomes the convention that apbh-dma always gets registered prior to apbx-dma.
* apbh-dma is different between mx23 and mx28, hardware version register is used to handle the differences.
* mxs-dma supports pio function besides data transfer. The driver uses dma_data_direction DMA_NONE to identify the pio mode, and steals sgl and sg_len to get pio words and numbers from clients.
* mxs dmaengine has some very specific features, like sense function and the special NAND support (nand_lock, nand_wait4ready). These are too specific to implemented in generic dmaengine driver.
* The driver refers to imx-sdma and only a single descriptor is statically assigned to each channel.
--- arch/arm/mach-mxs/include/mach/dma.h | 26 ++ drivers/dma/Kconfig | 8 + drivers/dma/Makefile | 1 + drivers/dma/mxs-dma.c | 732 ++++++++++++++++++++++++++++++++++ 4 files changed, 767 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-mxs/include/mach/dma.h create mode 100644 drivers/dma/mxs-dma.c
arch/arm/mach-mxs/include/mach/dma.h new file +26 -0
@@ -0,0 +1,26 @@ +/* +* Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#ifndef __MACH_MXS_DMA_H__ +#define __MACH_MXS_DMA_H__ + +struct mxs_dma_data { +int chan_irq; +}; + +static inline int mxs_dma_is_apbh (struct dma_chan *chan) +{ +return !strcmp (dev_name (chan->device->dev),"mxs-dma-apbh" ); +} + +static inline int mxs_dma_is_apbx (struct dma_chan *chan) +{ +return !strcmp (dev_name (chan->device->dev),"mxs-dma-apbx" ); +} + +#endif /* __MACH_MXS_DMA_H__ */
drivers/dma/Kconfig +8 -0
@@ -227,6 +227,14 @@ config IMX_DMA Support the i.MX DMA engine. This engine is integrated into Freescale i.MX1/21/27 chips. +config MXS_DMA + bool "MXS DMA support" + depends on SOC_IMX23 || SOC_IMX28 + select DMA_ENGINE + help + Support the MXS DMA engine. This engine including APBH-DMA + and APBX-DMA is integrated into Freescale i.MX23/28 chips. + config DMA_ENGINE bool
drivers/dma/Makefile +1 -0
@@ -23,6 +23,7 @@ obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ obj-$(CONFIG_IMX_SDMA) += imx-sdma.o obj-$(CONFIG_IMX_DMA) += imx-dma.o +obj-$(CONFIG_MXS_DMA) += mxs-dma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_PL330_DMA) += pl330.o
drivers/dma/mxs-dma.c new file +732 -0
732 lines not on this page · raw message
Signature · 1 line
1.7.1
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
Quoted 15 lines · Shawn Guo
This patch adds dma support for Freescale MXS-based SoC i.MX23/28, including apbh-dma and apbx-dma.
* apbh-dma and apbx-dma are supported in the driver as two instances, and have to be filtered by dma clients via device id. It becomes the convention that apbh-dma always gets registered prior to apbx-dma.
* apbh-dma is different between mx23 and mx28, hardware version register is used to handle the differences.
* mxs-dma supports pio function besides data transfer. The driver uses
dma_data_directionDMA_NONEto identify the pio mode, and steals sgl andsg_lento get pio words and numbers from clients.
Quoted 6 lines
* mxs dmaengine has some very specific features, like sense function and the special NAND support (
nand_lock,nand_wait4ready). These are too specific to implemented in generic dmaengine driver.* The driver refers to imx-sdma and only a single descriptor is statically assigned to each channel.
Hi Shawn,
I took a look at this after our discussion about the MMC driver locking problem. My feeling is that you still need to get a better understanding of the locking interfaces in Linux.
drivers/dma/mxs-dma.c line 103
Quoted 9 lines
drivers/dma/mxs-dma.c new file +9
-0+struct mxs_dma_ccw { +u32 next; +u16 bits; +u16 xfer_bytes; +#define MAX_XFER_BYTES 0xff00 +dma_addr_t bufaddr; +#define MXS_PIO_WORDS 16 +u32 pio_words[MXS_PIO_WORDS ]; +};
Unrelated, but should bufaddr really be dma_addr_t? If this is a hardware structure, you should make it u32, because dma_addr_t may be either 32 or 64 bit in the future, as we get to multiplatform kernels.
Quoted 15 lines
drivers/dma/mxs-dma.c new file +15
-0+struct mxs_dma_chan { +struct mxs_dma_engine *mxs_dma; +struct dma_chan chan; +struct dma_async_tx_descriptor desc; +struct tasklet_struct tasklet; +spinlock_t lock; +int chan_irq; +struct mxs_dma_ccw *ccw; +dma_addr_t ccw_phys; +dma_cookie_t last_completed; +enum dma_status status; +unsigned int flags; +#define MXS_DMA_SG_LOOP (1 << 0) +}; +
drivers/dma/mxs-dma.c line 131
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+struct mxs_dma_engine { +int dev_id; +unsigned int version; +void __iomem *base; +struct clk *clk; +struct dma_device dma_device; +struct device_dma_parameters dma_parms; +#define MXS_DMA_CHANNELS 16 +struct mxs_dma_chan mxs_chans[MXS_DMA_CHANNELS ]; +};
So you have locking per channel, but there is no lock in the dma engine structure locking the global fields. This is probably fine.
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static void mxs_dma_reset_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_setl (1 << (chan_id +BP_APBH_CTRL0_RESET_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << (chan_id +BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL ), + mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); +} + +static void mxs_dma_enable_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* set cmd_addr up */ +__raw_writel (mxs_chan->ccw_phys, + mxs_dma->base +HW_APBHX_CHn_NXTCMDAR (chan_id)); + +/* enable apbh channel clock */ +if (dma_is_apbh ()) {
drivers/dma/mxs-dma.c line 166
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+if (apbh_is_old ()) +__mxs_clrl (1 << (chan_id +BP_APBH_CTRL0_CLKGATE_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); + } + +/* write 1 to SEMA to kick off the channel */ +__raw_writel (1 , mxs_dma->base +HW_APBHX_CHn_SEMA (chan_id)); +}
Just like the MMC driver, you also use __raw_writel(). If you have to do that, please encapsulate the access into a wrapper function that adds all the necessary serialization and add a comment explaining why you cannot use writel()/writel_relaxed().
Or just use writel().
Quoted 34 lines
drivers/dma/mxs-dma.c new file +34
-0+static void mxs_dma_disable_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* disable apbh channel clock */ +if (dma_is_apbh ()) { +if (apbh_is_old ()) +__mxs_setl (1 << (chan_id +BP_APBH_CTRL0_CLKGATE_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); + } + + mxs_chan->status =DMA_SUCCESS ; +} + +static void mxs_dma_pause_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* freeze the channel */ +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); + + mxs_chan->status =DMA_PAUSED ; +} + +static void mxs_dma_resume_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;
drivers/dma/mxs-dma.c line 211
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+int chan_id = mxs_chan->chan.chan_id; + +/* unfreeze the channel */ +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); + + mxs_chan->status =DMA_IN_PROGRESS ; +}
Should these take the spinlock? What if they are called simultaneously on multiple CPUs, or while the interrupt handler is running on another CPU?
Without a lock, another thread might see an incorrect value of mxs_chan->status.
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static dma_cookie_t mxs_dma_assign_cookie (struct mxs_dma_chan *mxs_chan) +{ +dma_cookie_t cookie = mxs_chan->chan.cookie; + +if (++cookie <0 ) + cookie =1 ; + + mxs_chan->chan.cookie = cookie; + mxs_chan->desc.cookie = cookie; + +return cookie; +} + +static struct mxs_dma_chan *to_mxs_dma_chan (struct dma_chan *chan) +{ + return container_of(chan, struct mxs_dma_chan, chan); +} + +static dma_cookie_t mxs_dma_tx_submit (struct dma_async_tx_descriptor *tx) +{ +struct mxs_dma_chan *mxs_chan =to_mxs_dma_chan (tx->chan); +dma_cookie_t cookie; +unsigned long flags; +
drivers/dma/mxs-dma.c line 246
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + + cookie =mxs_dma_assign_cookie (mxs_chan); + +mxs_dma_enable_chan (mxs_chan); + +spin_unlock_irqrestore (&mxs_chan->lock, flags); + +return cookie; +}
Just like the MMC driver, you use spin_lock_irqsave() in the functions that are called by other drivers, but then don't actually lock in the interrupt handler. This is clearly wrong, as I explained before:
The interrupt handler may already be running on another CPU, so it is not at all serialized with this code.
Quoted 5 lines
drivers/dma/mxs-dma.c new file +5
-0+static void mxs_dma_tasklet (unsigned long data) +{ +struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data; +unsigned long flags; +
drivers/dma/mxs-dma.c line 262
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + +if (mxs_chan->desc.callback) + mxs_chan->desc.callback (mxs_chan->desc.callback_param); + +if (mxs_chan->status ==DMA_SUCCESS ) + mxs_chan->last_completed = mxs_chan->desc.cookie; + +spin_unlock_irqrestore (&mxs_chan->lock, flags); +}
Here is a different problem. The entire tasklet has interrupts disabled and holds the spinlock that the interrupt handler does not hold.
This actually makes the tasklet a stricter context than the actual interrupt handler, defeating the purpose of tasklets (i.e. that you can run code without disabling interrupts).
I don't know what the rules for dmaengine callbacks are regarding the context in which they are called, but it seems you should either get rid of the spin_lock_irqsave here or do everything from the interrupt handler.
That leaves the last_completed variable. This apparently needs to be protected using a spinlock, because
it can be read from other tasks calling mxs_dma_tx_status, which in turn does not take the lock, so that is broken, too.
Arnd
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
Quoted 21 lines · Arnd Bergmann
Quoted 5 lines · Shawn Guo
drivers/dma/mxs-dma.c new file +5
-0+static void mxs_dma_tasklet (unsigned long data) +{ +struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data; +unsigned long flags; +drivers/dma/mxs-dma.c line 262
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + +if (mxs_chan->desc.callback) + mxs_chan->desc.callback (mxs_chan->desc.callback_param); + +if (mxs_chan->status ==DMA_SUCCESS ) + mxs_chan->last_completed = mxs_chan->desc.cookie; + +spin_unlock_irqrestore (&mxs_chan->lock, flags); +}Here is a different problem. The entire tasklet has interrupts disabled and holds the spinlock that the interrupt handler does not hold.
Quoted 8 lines
This actually makes the tasklet a stricter context than the actual interrupt handler, defeating the purpose of tasklets (i.e. that you can run code without disabling interrupts).
I don't know what the rules for dmaengine callbacks are regarding the context in which they are called, but it seems you should either get rid of the
spin_lock_irqsavehere or do everything from the interrupt handler.
dmaengine callbacks should be made from tasklet context with no locks held.
The generally accepted way to do this is:
dma_async_tx_callback callback; void *callback_param; spin_lock_irqsave(&mxs_chan->lock, flags); callback = mxs_chan->desc.callback; callback_param = mxs_chan->desc.callback_param; if (mxs_chan->status == DMA_SUCCESS) mxs_chan->last_completed = mxs_chan->desc.cookie; spin_unlock_irqrestore(&mxs_chan->lock, flags); if (callback) callback(callback_param);
which also avoids the locking issue when the callback starts the next MMC request.
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
In reply to Arnd BergmannQuoted 16 lines · Arnd Bergmann
Quoted 15 lines · Shawn Guo
This patch adds dma support for Freescale MXS-based SoC i.MX23/28, including apbh-dma and apbx-dma.
* apbh-dma and apbx-dma are supported in the driver as two instances, and have to be filtered by dma clients via device id. It becomes the convention that apbh-dma always gets registered prior to apbx-dma.
* apbh-dma is different between mx23 and mx28, hardware version register is used to handle the differences.
* mxs-dma supports pio function besides data transfer. The driver uses
dma_data_directionDMA_NONEto identify the pio mode, and steals sgl andsg_lento get pio words and numbers from clients.
Quoted 9 lines
Quoted 6 lines
* mxs dmaengine has some very specific features, like sense function and the special NAND support (
nand_lock,nand_wait4ready). These are too specific to implemented in generic dmaengine driver.* The driver refers to imx-sdma and only a single descriptor is statically assigned to each channel.
Hi Shawn,
Hi Arnd,
Quoted 1 line
I took a look at this after our discussion about the MMC driver locking
Thanks for reviewing the dma code too.
Quoted 3 lines
problem. My feeling is that you still need to get a better understanding of the locking interfaces in Linux.
Yes, I'm still learning Linux ;)
Quoted 5 lines
Quoted 5 lines
drivers/dma/mxs-dma.c new file +5
-0+struct mxs_dma_ccw { +u32 next; +u16 bits; +u16 xfer_bytes; +#define MAX_XFER_BYTES 0xff00
Quoted 9 lines
drivers/dma/mxs-dma.c line 103
Quoted 4 lines
drivers/dma/mxs-dma.c new file +4
-0+dma_addr_t bufaddr; +#define MXS_PIO_WORDS 16 +u32 pio_words[MXS_PIO_WORDS ]; +};Unrelated, but should bufaddr really be
dma_addr_t? If this is a hardware structure, you should make it u32, becausedma_addr_tmay be either 32 or 64 bit in the future, as we get to multiplatform kernels.
OK.
Quoted 69 lines
Quoted 15 lines
drivers/dma/mxs-dma.c new file +15
-0+struct mxs_dma_chan { +struct mxs_dma_engine *mxs_dma; +struct dma_chan chan; +struct dma_async_tx_descriptor desc; +struct tasklet_struct tasklet; +spinlock_t lock; +int chan_irq; +struct mxs_dma_ccw *ccw; +dma_addr_t ccw_phys; +dma_cookie_t last_completed; +enum dma_status status; +unsigned int flags; +#define MXS_DMA_SG_LOOP (1 << 0) +}; +drivers/dma/mxs-dma.c line 131
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+struct mxs_dma_engine { +int dev_id; +unsigned int version; +void __iomem *base; +struct clk *clk; +struct dma_device dma_device; +struct device_dma_parameters dma_parms; +#define MXS_DMA_CHANNELS 16 +struct mxs_dma_chan mxs_chans[MXS_DMA_CHANNELS ]; +};So you have locking per channel, but there is no lock in the dma engine structure locking the global fields. This is probably fine.
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static void mxs_dma_reset_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_setl (1 << (chan_id +BP_APBH_CTRL0_RESET_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << (chan_id +BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL ), + mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); +} + +static void mxs_dma_enable_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* set cmd_addr up */ +__raw_writel (mxs_chan->ccw_phys, + mxs_dma->base +HW_APBHX_CHn_NXTCMDAR (chan_id)); + +/* enable apbh channel clock */ +if (dma_is_apbh ()) {drivers/dma/mxs-dma.c line 166
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+if (apbh_is_old ()) +__mxs_clrl (1 << (chan_id +BP_APBH_CTRL0_CLKGATE_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); + } + +/* write 1 to SEMA to kick off the channel */ +__raw_writel (1 , mxs_dma->base +HW_APBHX_CHn_SEMA (chan_id)); +}Just like the MMC driver, you also use
__raw_writel(). If you have to do that, please encapsulate the access into a wrapper function that adds all the necessary serialization and add a comment explaining why you cannot use writel()/writel_relaxed().
Quoted 2 lines
Or just use
writel().
OK. Will change to readl/writel pair.
Quoted 48 lines
Quoted 34 lines
drivers/dma/mxs-dma.c new file +34
-0+static void mxs_dma_disable_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* disable apbh channel clock */ +if (dma_is_apbh ()) { +if (apbh_is_old ()) +__mxs_setl (1 << (chan_id +BP_APBH_CTRL0_CLKGATE_CHANNEL ), + mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); + } + + mxs_chan->status =DMA_SUCCESS ; +} + +static void mxs_dma_pause_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; +int chan_id = mxs_chan->chan.chan_id; + +/* freeze the channel */ +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_setl (1 << chan_id, mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); + + mxs_chan->status =DMA_PAUSED ; +} + +static void mxs_dma_resume_chan (struct mxs_dma_chan *mxs_chan) +{ +struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma;drivers/dma/mxs-dma.c line 211
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+int chan_id = mxs_chan->chan.chan_id; + +/* unfreeze the channel */ +if (dma_is_apbh () &&apbh_is_old ()) +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CTRL0 ); +else +__mxs_clrl (1 << chan_id, mxs_dma->base +HW_APBHX_CHANNEL_CTRL ); + + mxs_chan->status =DMA_IN_PROGRESS ; +}Should these take the spinlock? What if they are called simultaneously on multiple CPUs, or while the interrupt handler is running on another CPU?
Quoted 2 lines
Without a lock, another thread might see an incorrect value of
mxs_chan->status.
The mxs dma hardware somehow simplified the design to dedicate each channel to a specific client device, e.g. mmc 0 has to use channel 0. And I simplified the driver design accordingly with less locking since only that specific client device will access the channel, in which case the situation you are concerning very likely will not happen.
Quoted 39 lines
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static dma_cookie_t mxs_dma_assign_cookie (struct mxs_dma_chan *mxs_chan) +{ +dma_cookie_t cookie = mxs_chan->chan.cookie; + +if (++cookie <0 ) + cookie =1 ; + + mxs_chan->chan.cookie = cookie; + mxs_chan->desc.cookie = cookie; + +return cookie; +} + +static struct mxs_dma_chan *to_mxs_dma_chan (struct dma_chan *chan) +{ + return container_of(chan, struct mxs_dma_chan, chan); +} + +static dma_cookie_t mxs_dma_tx_submit (struct dma_async_tx_descriptor *tx) +{ +struct mxs_dma_chan *mxs_chan =to_mxs_dma_chan (tx->chan); +dma_cookie_t cookie; +unsigned long flags; +drivers/dma/mxs-dma.c line 246
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + + cookie =mxs_dma_assign_cookie (mxs_chan); + +mxs_dma_enable_chan (mxs_chan); + +spin_unlock_irqrestore (&mxs_chan->lock, flags); + +return cookie; +}Just like the MMC driver, you use
spin_lock_irqsave()in the functions that are called by other drivers, but then don't actually lock in the interrupt handler. This is clearly wrong, as I explained before:
Quoted 3 lines
The interrupt handler may already be running on another CPU, so it is not at all serialized with this code.
I may be wrong on this point, but spin_lock_irqsave/irqrestore pair is only for the protection you described? I learnt that the pair should be used to protect the critical region, where I'm not sure if it's called from interrupt context. Here I'm intending to protect the dma registers accessed in mxs_dma_enable_chan.
Quoted 29 lines
Quoted 5 lines
drivers/dma/mxs-dma.c new file +5
-0+static void mxs_dma_tasklet (unsigned long data) +{ +struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data; +unsigned long flags; +drivers/dma/mxs-dma.c line 262
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + +if (mxs_chan->desc.callback) + mxs_chan->desc.callback (mxs_chan->desc.callback_param); + +if (mxs_chan->status ==DMA_SUCCESS ) + mxs_chan->last_completed = mxs_chan->desc.cookie; + +spin_unlock_irqrestore (&mxs_chan->lock, flags); +}Here is a different problem. The entire tasklet has interrupts disabled and holds the spinlock that the interrupt handler does not hold.
This actually makes the tasklet a stricter context than the actual interrupt handler, defeating the purpose of tasklets (i.e. that you can run code without disabling interrupts).
I don't know what the rules for dmaengine callbacks are regarding the context in which they are called, but it seems you should either get rid of the
spin_lock_irqsavehere or do everything from the interrupt handler.
Quoted 5 lines
That leaves the
last_completedvariable. This apparently needs to be protected using a spinlock, because
it can be read from other tasks callingmxs_dma_tx_status, which in turn does not take the lock, so that is broken, too.
Thanks for teaching. I intend to take the fix that Russell offered. (Thanks, Russell). Does that look good to you, or spin_lock_bh version should be used than spin_lock_irqsave?
Signature · 2 lines
Regards, Shawn
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
Quoted 39 lines · Shawn Guo
Quoted 39 lines
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static dma_cookie_t mxs_dma_assign_cookie (struct mxs_dma_chan *mxs_chan) +{ +dma_cookie_t cookie = mxs_chan->chan.cookie; + +if (++cookie <0 ) + cookie =1 ; + + mxs_chan->chan.cookie = cookie; + mxs_chan->desc.cookie = cookie; + +return cookie; +} + +static struct mxs_dma_chan *to_mxs_dma_chan (struct dma_chan *chan) +{ + return container_of(chan, struct mxs_dma_chan, chan); +} + +static dma_cookie_t mxs_dma_tx_submit (struct dma_async_tx_descriptor *tx) +{ +struct mxs_dma_chan *mxs_chan =to_mxs_dma_chan (tx->chan); +dma_cookie_t cookie; +unsigned long flags; +drivers/dma/mxs-dma.c line 246
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + + cookie =mxs_dma_assign_cookie (mxs_chan); + +mxs_dma_enable_chan (mxs_chan); + +spin_unlock_irqrestore (&mxs_chan->lock, flags); + +return cookie; +}Just like the MMC driver, you use
spin_lock_irqsave()in the functions that are called by other drivers, but then don't actually lock in the interrupt handler. This is clearly wrong, as I explained before:
Quoted 8 lines
Quoted 3 lines
The interrupt handler may already be running on another CPU, so it is not at all serialized with this code.
I may be wrong on this point, but spin_lock_irqsave/irqrestore pair is only for the protection you described? I learnt that the pair should be used to protect the critical region, where I'm not sure if it's called from interrupt context. Here I'm intending to protect the dma registers accessed in
mxs_dma_enable_chan.
To generalize this further, a lock should protect specific data, not portions of the code, and ideally you should document exactly what you protect near the definition of the lock.
If you use the lock to protect all the DMA registers, you should use it consistently, including inside of the interrupt handler, where you access some other registers.
If you can prove that the registers used in the IRQ handler are totally independent of the other ones, you don't need to use the lock there, but then I'd advise documenting your assumptions.
Quoted 3 lines
Thanks for teaching. I intend to take the fix that Russell offered. (Thanks, Russell). Does that look good to you, or
spin_lock_bhversion should be used thanspin_lock_irqsave?
Russell's change looks correct to me.
Regarding the choice between spin_lock_bh and spin_lock_irqsave, I always prefer to use the one that expresses best to the reader what the intention is. spin_lock_irqsave() is stronger than spin_lock_bh(), and can always be used in its place.
A short form of the strict rules (there are better documentations out there) is:
* If all users are outside of interrupt or tasklet context, use a bare spin_lock().
* If one user is in a tasklet context, use spin_lock() inside the tasklet, but spin_lock_bh() outside, to prevent the tasklet from interrupting the critical section. Code that can be called in either tasklet or regular context needs to use spin_lock_bh() as well.
* If one user is in interrupt context, use spin_lock() inside of the interrupt handler, but spin_lock_irq() in tasklet and normal context (not spin_lock_irqsave()), to prevent the interrupt from happening during the critical section.
* Use spin_lock_irqsave() only for functions that can be called in either interrupt or non-interrupt context. Most drivers don't need this at all.
The simplified rule would be to always use spin_lock_irqsave(), because that does not require you to understand what you are doing.
My position is that it is better to use the stricter rules, because that documents that you actually do understand what you are doing ;-) It's also slightly more efficient, because it avoids having to save the interrupt status in a variable.
Arnd
PATCH v2 1/3 dmaengine: mxs-dma: add dma support for i.MX23/28
Quoted 53 lines · Arnd Bergmann
Quoted 39 lines · Shawn Guo
Quoted 39 lines
Quoted 24 lines
drivers/dma/mxs-dma.c new file +24
-0+static dma_cookie_t mxs_dma_assign_cookie (struct mxs_dma_chan *mxs_chan) +{ +dma_cookie_t cookie = mxs_chan->chan.cookie; + +if (++cookie <0 ) + cookie =1 ; + + mxs_chan->chan.cookie = cookie; + mxs_chan->desc.cookie = cookie; + +return cookie; +} + +static struct mxs_dma_chan *to_mxs_dma_chan (struct dma_chan *chan) +{ + return container_of(chan, struct mxs_dma_chan, chan); +} + +static dma_cookie_t mxs_dma_tx_submit (struct dma_async_tx_descriptor *tx) +{ +struct mxs_dma_chan *mxs_chan =to_mxs_dma_chan (tx->chan); +dma_cookie_t cookie; +unsigned long flags; +drivers/dma/mxs-dma.c line 246
Quoted 10 lines
drivers/dma/mxs-dma.c new file +10
-0+spin_lock_irqsave (&mxs_chan->lock, flags); + + cookie =mxs_dma_assign_cookie (mxs_chan); + +mxs_dma_enable_chan (mxs_chan); + +spin_unlock_irqrestore (&mxs_chan->lock, flags); + +return cookie; +}Just like the MMC driver, you use
spin_lock_irqsave()in the functions that are called by other drivers, but then don't actually lock in the interrupt handler. This is clearly wrong, as I explained before:Quoted 8 lines
Quoted 3 lines
The interrupt handler may already be running on another CPU, so it is not at all serialized with this code.
I may be wrong on this point, but spin_lock_irqsave/irqrestore pair is only for the protection you described? I learnt that the pair should be used to protect the critical region, where I'm not sure if it's called from interrupt context. Here I'm intending to protect the dma registers accessed in
mxs_dma_enable_chan.To generalize this further, a lock should protect specific data, not portions of the code, and ideally you should document exactly what you protect near the definition of the lock.
Quoted 8 lines
If you use the lock to protect all the DMA registers, you should use it consistently, including inside of the interrupt handler, where you access some other registers.
If you can prove that the registers used in the IRQ handler are totally independent of the other ones, you don't need to use the lock there, but then I'd advise documenting your assumptions.
When I change code to use readl/writel pair, I found the register protection in mxs_dma_enable_chan is actually unnecessary, because the registers are either channel specific or get set/clear address to avoid read-modify-write operation.
So I'm removing the locking there.
Quoted 32 lines
Quoted 3 lines
Thanks for teaching. I intend to take the fix that Russell offered. (Thanks, Russell). Does that look good to you, or
spin_lock_bhversion should be used thanspin_lock_irqsave?Russell's change looks correct to me.
Regarding the choice between
spin_lock_bhandspin_lock_irqsave, I always prefer to use the one that expresses best to the reader what the intention is.spin_lock_irqsave()is stronger thanspin_lock_bh(), and can always be used in its place.A short form of the strict rules (there are better documentations out there) is:
* If all users are outside of interrupt or tasklet context, use a bare
spin_lock().* If one user is in a tasklet context, use
spin_lock()inside the tasklet, butspin_lock_bh()outside, to prevent the tasklet from interrupting the critical section. Code that can be called in either tasklet or regular context needs to usespin_lock_bh()as well.* If one user is in interrupt context, use
spin_lock()inside of the interrupt handler, butspin_lock_irq()in tasklet and normal context (notspin_lock_irqsave()), to prevent the interrupt from happening during the critical section.* Use
spin_lock_irqsave()only for functions that can be called in either interrupt or non-interrupt context. Most drivers don't need this at all.
Quoted 8 lines
The simplified rule would be to always use
spin_lock_irqsave(), because that does not require you to understand what you are doing.My position is that it is better to use the stricter rules, because that documents that you actually do understand what you are doing ;-) It's also slightly more efficient, because it avoids having to save the interrupt status in a variable.
Thanks for this document. It helps.
I'm changing the tasklet to have callback only, and move others into interrupt handler, so that I can remove the locking completely from the driver.
Signature · 2 lines
Regards, Shawn
PATCH v2 2/3 ARM: mxs: add dma channel definitions
In reply to Shawn Guo--- arch/arm/mach-mxs/include/mach/mx23.h | 24 +++++++++++++++++++++ arch/arm/mach-mxs/include/mach/mx28.h | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 0 deletions(-)
arch/arm/mach-mxs/include/mach/mx23.h +24 -0
@@ -142,4 +142,28 @@#define MX23_INT_VDD5V_DROOP 64#define MX23_INT_DCDC4P2_BO 65 +/* +* APBH DMA +*/ +#define MX23_DMA_SSP1 1 +#define MX23_DMA_SSP2 2 +#define MX23_DMA_GPMI0 4 +#define MX23_DMA_GPMI1 5 +#define MX23_DMA_GPMI2 6 +#define MX23_DMA_GPMI3 7 + +/* +* APBX DMA +*/ +#define MX23_DMA_ADC 0 +#define MX23_DMA_DAC 1 +#define MX23_DMA_SPDIF 2 +#define MX23_DMA_I2C 3 +#define MX23_DMA_SAIF0 4 +#define MX23_DMA_UART0_RX 6 +#define MX23_DMA_UART0_TX 7 +#define MX23_DMA_UART1_RX 8 +#define MX23_DMA_UART1_TX 9 +#define MX23_DMA_SAIF1 10 +#endif /* __MACH_MX23_H__ */
arch/arm/mach-mxs/include/mach/mx28.h +37 -0
@@ -185,4 +185,41 @@#define MX28_INT_GPIO1 126#define MX28_INT_GPIO0 127 +/* +* APBH DMA +*/ +#define MX28_DMA_SSP0 0 +#define MX28_DMA_SSP1 1 +#define MX28_DMA_SSP2 2 +#define MX28_DMA_SSP3 3 +#define MX28_DMA_GPMI0 4 +#define MX28_DMA_GPMI1 5 +#define MX28_DMA_GPMI2 6 +#define MX28_DMA_GPMI3 7 +#define MX28_DMA_GPMI4 8 +#define MX28_DMA_GPMI5 9 +#define MX28_DMA_GPMI6 10 +#define MX28_DMA_GPMI7 11 +#define MX28_DMA_HSADC 12 +#define MX28_DMA_LCDIF 13 + +/* +* APBX DMA +*/ +#define MX28_DMA_AUART4_RX 0 +#define MX28_DMA_AUART4_TX 1 +#define MX28_DMA_SPDIF_TX 2 +#define MX28_DMA_SAIF0 4 +#define MX28_DMA_SAIF1 5 +#define MX28_DMA_I2C0 6 +#define MX28_DMA_I2C1 7 +#define MX28_DMA_AUART0_RX 8 +#define MX28_DMA_AUART0_TX 9 +#define MX28_DMA_AUART1_RX 10 +#define MX28_DMA_AUART1_TX 11 +#define MX28_DMA_AUART2_RX 12 +#define MX28_DMA_AUART2_TX 13 +#define MX28_DMA_AUART3_RX 14 +#define MX28_DMA_AUART3_TX 15 +#endif /* __MACH_MX28_H__ */
Signature · 1 line
1.7.1
PATCH v2 3/3 ARM: mxs: add dma device
In reply to Shawn Guo--- arch/arm/mach-mxs/clock-mx23.c | 3 +- arch/arm/mach-mxs/clock-mx28.c | 4 +- arch/arm/mach-mxs/devices/Makefile | 1 + arch/arm/mach-mxs/devices/platform-dma.c | 99 ++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-mxs/devices/platform-dma.c
arch/arm/mach-mxs/clock-mx23.c +2 -1
@@ -443,7 +443,8 @@ static struct clk_lookup lookups[] = {/* for amba-pl011 driver */ _REGISTER_CLOCK("duart", NULL, uart_clk) _REGISTER_CLOCK("rtc", NULL, rtc_clk) - _REGISTER_CLOCK(NULL, "hclk", hbus_clk) + _REGISTER_CLOCK("mxs-dma-apbh", NULL, hbus_clk) + _REGISTER_CLOCK("mxs-dma-apbx", NULL, xbus_clk) _REGISTER_CLOCK(NULL, "usb", usb_clk) _REGISTER_CLOCK(NULL, "audio", audio_clk)_REGISTER_CLOCK (NULL ,"pwm" , pwm_clk)
arch/arm/mach-mxs/clock-mx28.c +2 -2
@@ -617,8 +617,8 @@ static struct clk_lookup lookups[] = { _REGISTER_CLOCK("fec.0", NULL, fec_clk) _REGISTER_CLOCK("rtc", NULL, rtc_clk) _REGISTER_CLOCK("pll2", NULL, pll2_clk) - _REGISTER_CLOCK(NULL, "hclk", hbus_clk) - _REGISTER_CLOCK(NULL, "xclk", xbus_clk) + _REGISTER_CLOCK("mxs-dma-apbh", NULL, hbus_clk) + _REGISTER_CLOCK("mxs-dma-apbx", NULL, xbus_clk) _REGISTER_CLOCK("flexcan.0", NULL, can0_clk) _REGISTER_CLOCK("flexcan.1", NULL, can1_clk)_REGISTER_CLOCK (NULL ,"usb0" , usb0_clk)
arch/arm/mach-mxs/devices/Makefile +1 -0
@@ -1,4 +1,5 @@ obj-$(CONFIG_MXS_HAVE_AMBA_DUART) += amba-duart.o obj-$(CONFIG_MXS_HAVE_PLATFORM_AUART) += platform-auart.o +obj-y += platform-dma.o obj-$(CONFIG_MXS_HAVE_PLATFORM_FEC) += platform-fec.o obj-$(CONFIG_MXS_HAVE_PLATFORM_FLEXCAN) += platform-flexcan.o
arch/arm/mach-mxs/devices/platform-dma.c new file +99 -0
99 lines not on this page · raw message
Signature · 1 line
1.7.1