All of lore.kernel.org
 help / color / mirror / Atom feed
From: arnd@arndb.de (Arnd Bergmann)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2 1/3] dmaengine: mxs-dma: add dma support for i.MX23/28
Date: Thu, 17 Feb 2011 15:38:30 +0100	[thread overview]
Message-ID: <201102171538.31061.arnd@arndb.de> (raw)
In-Reply-To: <1297650228-12762-2-git-send-email-shawn.guo@freescale.com>

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

  reply	other threads:[~2011-02-17 14:38 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-02-14  2:23 [PATCH v2 0/3] Add dma support for i.MX23/28 Shawn Guo
2011-02-14  2:23 ` [PATCH v2 1/3] dmaengine: mxs-dma: add " Shawn Guo
2011-02-17 14:38   ` Arnd Bergmann [this message]
2011-02-17 15:09     ` Russell King - ARM Linux
2011-02-18  8:45     ` Shawn Guo
2011-02-18 13:22       ` Arnd Bergmann
2011-02-18 13:41         ` Shawn Guo
2011-02-14  2:23 ` [PATCH v2 2/3] ARM: mxs: add dma channel definitions Shawn Guo
2011-02-14  2:23 ` [PATCH v2 3/3] ARM: mxs: add dma device Shawn Guo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=201102171538.31061.arnd@arndb.de \
    --to=arnd@arndb.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.