[PATCH v2 1/3] dmaengine: mxs-dma: add dma support for i.MX23/28

Arnd Bergmann arnd at arndb.de
Thu Feb 17 09:38:30 EST 2011


On Monday 14 February 2011, Shawn Guo wrote:
> 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.

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.

> +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.

> +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)
> +};
> +
> +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.

> +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()) {
> +		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().

> +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;
> +	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.

> +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;
> +
> +	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.

> +static void mxs_dma_tasklet(unsigned long data)
> +{
> +	struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data;
> +	unsigned long flags;
> +
> +	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



More information about the linux-arm-kernel mailing list