Commit f04cd407 authored by Ira Snyder's avatar Ira Snyder Committed by Dan Williams

fsldma: fix controller lockups

Enabling poisoning in the dmapool API quickly showed that the DMA
controller was fetching descriptors that should not have been in use.
This has caused intermittent controller lockups during testing.

I have been unable to figure out the exact set of conditions which cause
this to happen. However, I believe it is related to the driver using the
hardware registers to track whether the controller is busy or not. The
code can incorrectly decide that the hardware is idle due to lag between
register writes and the hardware actually becoming busy.

To fix this, the driver has been reworked to explicitly track the state
of the hardware, rather than try to guess what it is doing based on the
register values.

This has passed dmatest with 10 threads per channel, 100000 iterations
per thread several times without error. Previously, this would fail
within a few seconds.
Signed-off-by: default avatarIra W. Snyder <iws@ovro.caltech.edu>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
parent 31f4306c
...@@ -68,11 +68,6 @@ static dma_addr_t get_cdar(struct fsldma_chan *chan) ...@@ -68,11 +68,6 @@ static dma_addr_t get_cdar(struct fsldma_chan *chan)
return DMA_IN(chan, &chan->regs->cdar, 64) & ~FSL_DMA_SNEN; return DMA_IN(chan, &chan->regs->cdar, 64) & ~FSL_DMA_SNEN;
} }
static dma_addr_t get_ndar(struct fsldma_chan *chan)
{
return DMA_IN(chan, &chan->regs->ndar, 64);
}
static u32 get_bcr(struct fsldma_chan *chan) static u32 get_bcr(struct fsldma_chan *chan)
{ {
return DMA_IN(chan, &chan->regs->bcr, 32); return DMA_IN(chan, &chan->regs->bcr, 32);
...@@ -143,13 +138,11 @@ static void dma_init(struct fsldma_chan *chan) ...@@ -143,13 +138,11 @@ static void dma_init(struct fsldma_chan *chan)
case FSL_DMA_IP_85XX: case FSL_DMA_IP_85XX:
/* Set the channel to below modes: /* Set the channel to below modes:
* EIE - Error interrupt enable * EIE - Error interrupt enable
* EOSIE - End of segments interrupt enable (basic mode)
* EOLNIE - End of links interrupt enable * EOLNIE - End of links interrupt enable
* BWC - Bandwidth sharing among channels * BWC - Bandwidth sharing among channels
*/ */
DMA_OUT(chan, &chan->regs->mr, FSL_DMA_MR_BWC DMA_OUT(chan, &chan->regs->mr, FSL_DMA_MR_BWC
| FSL_DMA_MR_EIE | FSL_DMA_MR_EOLNIE | FSL_DMA_MR_EIE | FSL_DMA_MR_EOLNIE, 32);
| FSL_DMA_MR_EOSIE, 32);
break; break;
case FSL_DMA_IP_83XX: case FSL_DMA_IP_83XX:
/* Set the channel to below modes: /* Set the channel to below modes:
...@@ -168,25 +161,32 @@ static int dma_is_idle(struct fsldma_chan *chan) ...@@ -168,25 +161,32 @@ static int dma_is_idle(struct fsldma_chan *chan)
return (!(sr & FSL_DMA_SR_CB)) || (sr & FSL_DMA_SR_CH); return (!(sr & FSL_DMA_SR_CB)) || (sr & FSL_DMA_SR_CH);
} }
/*
* Start the DMA controller
*
* Preconditions:
* - the CDAR register must point to the start descriptor
* - the MRn[CS] bit must be cleared
*/
static void dma_start(struct fsldma_chan *chan) static void dma_start(struct fsldma_chan *chan)
{ {
u32 mode; u32 mode;
mode = DMA_IN(chan, &chan->regs->mr, 32); mode = DMA_IN(chan, &chan->regs->mr, 32);
if ((chan->feature & FSL_DMA_IP_MASK) == FSL_DMA_IP_85XX) { if (chan->feature & FSL_DMA_CHAN_PAUSE_EXT) {
if (chan->feature & FSL_DMA_CHAN_PAUSE_EXT) { DMA_OUT(chan, &chan->regs->bcr, 0, 32);
DMA_OUT(chan, &chan->regs->bcr, 0, 32); mode |= FSL_DMA_MR_EMP_EN;
mode |= FSL_DMA_MR_EMP_EN; } else {
} else { mode &= ~FSL_DMA_MR_EMP_EN;
mode &= ~FSL_DMA_MR_EMP_EN;
}
} }
if (chan->feature & FSL_DMA_CHAN_START_EXT) if (chan->feature & FSL_DMA_CHAN_START_EXT) {
mode |= FSL_DMA_MR_EMS_EN; mode |= FSL_DMA_MR_EMS_EN;
else } else {
mode &= ~FSL_DMA_MR_EMS_EN;
mode |= FSL_DMA_MR_CS; mode |= FSL_DMA_MR_CS;
}
DMA_OUT(chan, &chan->regs->mr, mode, 32); DMA_OUT(chan, &chan->regs->mr, mode, 32);
} }
...@@ -760,14 +760,15 @@ static int fsl_dma_device_control(struct dma_chan *dchan, ...@@ -760,14 +760,15 @@ static int fsl_dma_device_control(struct dma_chan *dchan,
switch (cmd) { switch (cmd) {
case DMA_TERMINATE_ALL: case DMA_TERMINATE_ALL:
spin_lock_irqsave(&chan->desc_lock, flags);
/* Halt the DMA engine */ /* Halt the DMA engine */
dma_halt(chan); dma_halt(chan);
spin_lock_irqsave(&chan->desc_lock, flags);
/* Remove and free all of the descriptors in the LD queue */ /* Remove and free all of the descriptors in the LD queue */
fsldma_free_desc_list(chan, &chan->ld_pending); fsldma_free_desc_list(chan, &chan->ld_pending);
fsldma_free_desc_list(chan, &chan->ld_running); fsldma_free_desc_list(chan, &chan->ld_running);
chan->idle = true;
spin_unlock_irqrestore(&chan->desc_lock, flags); spin_unlock_irqrestore(&chan->desc_lock, flags);
return 0; return 0;
...@@ -805,76 +806,43 @@ static int fsl_dma_device_control(struct dma_chan *dchan, ...@@ -805,76 +806,43 @@ static int fsl_dma_device_control(struct dma_chan *dchan,
} }
/** /**
* fsl_dma_update_completed_cookie - Update the completed cookie. * fsl_chan_ld_cleanup - Clean up link descriptors
* @chan : Freescale DMA channel * @chan : Freescale DMA channel
* *
* CONTEXT: hardirq * This function is run after the queue of running descriptors has been
* executed by the DMA engine. It will run any callbacks, and then free
* the descriptors.
*
* HARDWARE STATE: idle
*/ */
static void fsl_dma_update_completed_cookie(struct fsldma_chan *chan) static void fsl_chan_ld_cleanup(struct fsldma_chan *chan)
{ {
struct fsl_desc_sw *desc; struct fsl_desc_sw *desc, *_desc;
unsigned long flags; unsigned long flags;
dma_cookie_t cookie;
spin_lock_irqsave(&chan->desc_lock, flags); spin_lock_irqsave(&chan->desc_lock, flags);
/* if the ld_running list is empty, there is nothing to do */
if (list_empty(&chan->ld_running)) { if (list_empty(&chan->ld_running)) {
chan_dbg(chan, "no running descriptors\n"); chan_dbg(chan, "no descriptors to cleanup\n");
goto out_unlock; goto out_unlock;
} }
/* Get the last descriptor, update the cookie to that */ /*
* Get the last descriptor, update the cookie to it
*
* This is done before callbacks run so that clients can check the
* status of their DMA transfer inside the callback.
*/
desc = to_fsl_desc(chan->ld_running.prev); desc = to_fsl_desc(chan->ld_running.prev);
if (dma_is_idle(chan)) chan->completed_cookie = desc->async_tx.cookie;
cookie = desc->async_tx.cookie; chan_dbg(chan, "completed_cookie = %d\n", chan->completed_cookie);
else {
cookie = desc->async_tx.cookie - 1;
if (unlikely(cookie < DMA_MIN_COOKIE))
cookie = DMA_MAX_COOKIE;
}
chan->completed_cookie = cookie;
out_unlock:
spin_unlock_irqrestore(&chan->desc_lock, flags);
}
/**
* fsldma_desc_status - Check the status of a descriptor
* @chan: Freescale DMA channel
* @desc: DMA SW descriptor
*
* This function will return the status of the given descriptor
*/
static enum dma_status fsldma_desc_status(struct fsldma_chan *chan,
struct fsl_desc_sw *desc)
{
return dma_async_is_complete(desc->async_tx.cookie,
chan->completed_cookie,
chan->common.cookie);
}
/**
* fsl_chan_ld_cleanup - Clean up link descriptors
* @chan : Freescale DMA channel
*
* This function clean up the ld_queue of DMA channel.
*/
static void fsl_chan_ld_cleanup(struct fsldma_chan *chan)
{
struct fsl_desc_sw *desc, *_desc;
unsigned long flags;
spin_lock_irqsave(&chan->desc_lock, flags);
chan_dbg(chan, "chan completed_cookie = %d\n", chan->completed_cookie); /* Run the callback for each descriptor, in order */
list_for_each_entry_safe(desc, _desc, &chan->ld_running, node) { list_for_each_entry_safe(desc, _desc, &chan->ld_running, node) {
dma_async_tx_callback callback; dma_async_tx_callback callback;
void *callback_param; void *callback_param;
if (fsldma_desc_status(chan, desc) == DMA_IN_PROGRESS)
break;
/* Remove from the list of running transactions */ /* Remove from the list of running transactions */
list_del(&desc->node); list_del(&desc->node);
...@@ -898,6 +866,7 @@ static void fsl_chan_ld_cleanup(struct fsldma_chan *chan) ...@@ -898,6 +866,7 @@ static void fsl_chan_ld_cleanup(struct fsldma_chan *chan)
dma_pool_free(chan->desc_pool, desc, desc->async_tx.phys); dma_pool_free(chan->desc_pool, desc, desc->async_tx.phys);
} }
out_unlock:
spin_unlock_irqrestore(&chan->desc_lock, flags); spin_unlock_irqrestore(&chan->desc_lock, flags);
} }
...@@ -905,10 +874,7 @@ static void fsl_chan_ld_cleanup(struct fsldma_chan *chan) ...@@ -905,10 +874,7 @@ static void fsl_chan_ld_cleanup(struct fsldma_chan *chan)
* fsl_chan_xfer_ld_queue - transfer any pending transactions * fsl_chan_xfer_ld_queue - transfer any pending transactions
* @chan : Freescale DMA channel * @chan : Freescale DMA channel
* *
* This will make sure that any pending transactions will be run. * HARDWARE STATE: idle
* If the DMA controller is idle, it will be started. Otherwise,
* the DMA controller's interrupt handler will start any pending
* transactions when it becomes idle.
*/ */
static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan) static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan)
{ {
...@@ -927,22 +893,15 @@ static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan) ...@@ -927,22 +893,15 @@ static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan)
} }
/* /*
* The DMA controller is not idle, which means the interrupt * The DMA controller is not idle, which means that the interrupt
* handler will start any queued transactions when it runs * handler will start any queued transactions when it runs after
* at the end of the current transaction * this transaction finishes
*/ */
if (!dma_is_idle(chan)) { if (!chan->idle) {
chan_dbg(chan, "DMA controller still busy\n"); chan_dbg(chan, "DMA controller still busy\n");
goto out_unlock; goto out_unlock;
} }
/*
* TODO:
* make sure the dma_halt() function really un-wedges the
* controller as much as possible
*/
dma_halt(chan);
/* /*
* If there are some link descriptors which have not been * If there are some link descriptors which have not been
* transferred, we need to start the controller * transferred, we need to start the controller
...@@ -952,15 +911,32 @@ static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan) ...@@ -952,15 +911,32 @@ static void fsl_chan_xfer_ld_queue(struct fsldma_chan *chan)
* Move all elements from the queue of pending transactions * Move all elements from the queue of pending transactions
* onto the list of running transactions * onto the list of running transactions
*/ */
chan_dbg(chan, "idle, starting controller\n");
desc = list_first_entry(&chan->ld_pending, struct fsl_desc_sw, node); desc = list_first_entry(&chan->ld_pending, struct fsl_desc_sw, node);
list_splice_tail_init(&chan->ld_pending, &chan->ld_running); list_splice_tail_init(&chan->ld_pending, &chan->ld_running);
/*
* The 85xx DMA controller doesn't clear the channel start bit
* automatically at the end of a transfer. Therefore we must clear
* it in software before starting the transfer.
*/
if ((chan->feature & FSL_DMA_IP_MASK) == FSL_DMA_IP_85XX) {
u32 mode;
mode = DMA_IN(chan, &chan->regs->mr, 32);
mode &= ~FSL_DMA_MR_CS;
DMA_OUT(chan, &chan->regs->mr, mode, 32);
}
/* /*
* Program the descriptor's address into the DMA controller, * Program the descriptor's address into the DMA controller,
* then start the DMA transaction * then start the DMA transaction
*/ */
set_cdar(chan, desc->async_tx.phys); set_cdar(chan, desc->async_tx.phys);
get_cdar(chan);
dma_start(chan); dma_start(chan);
chan->idle = false;
out_unlock: out_unlock:
spin_unlock_irqrestore(&chan->desc_lock, flags); spin_unlock_irqrestore(&chan->desc_lock, flags);
...@@ -985,16 +961,18 @@ static enum dma_status fsl_tx_status(struct dma_chan *dchan, ...@@ -985,16 +961,18 @@ static enum dma_status fsl_tx_status(struct dma_chan *dchan,
struct dma_tx_state *txstate) struct dma_tx_state *txstate)
{ {
struct fsldma_chan *chan = to_fsl_chan(dchan); struct fsldma_chan *chan = to_fsl_chan(dchan);
dma_cookie_t last_used;
dma_cookie_t last_complete; dma_cookie_t last_complete;
dma_cookie_t last_used;
unsigned long flags;
fsl_chan_ld_cleanup(chan); spin_lock_irqsave(&chan->desc_lock, flags);
last_used = dchan->cookie;
last_complete = chan->completed_cookie; last_complete = chan->completed_cookie;
last_used = dchan->cookie;
dma_set_tx_state(txstate, last_complete, last_used, 0); spin_unlock_irqrestore(&chan->desc_lock, flags);
dma_set_tx_state(txstate, last_complete, last_used, 0);
return dma_async_is_complete(cookie, last_complete, last_used); return dma_async_is_complete(cookie, last_complete, last_used);
} }
...@@ -1005,8 +983,6 @@ static enum dma_status fsl_tx_status(struct dma_chan *dchan, ...@@ -1005,8 +983,6 @@ static enum dma_status fsl_tx_status(struct dma_chan *dchan,
static irqreturn_t fsldma_chan_irq(int irq, void *data) static irqreturn_t fsldma_chan_irq(int irq, void *data)
{ {
struct fsldma_chan *chan = data; struct fsldma_chan *chan = data;
int update_cookie = 0;
int xfer_ld_q = 0;
u32 stat; u32 stat;
/* save and clear the status register */ /* save and clear the status register */
...@@ -1014,6 +990,7 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data) ...@@ -1014,6 +990,7 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)
set_sr(chan, stat); set_sr(chan, stat);
chan_dbg(chan, "irq: stat = 0x%x\n", stat); chan_dbg(chan, "irq: stat = 0x%x\n", stat);
/* check that this was really our device */
stat &= ~(FSL_DMA_SR_CB | FSL_DMA_SR_CH); stat &= ~(FSL_DMA_SR_CB | FSL_DMA_SR_CH);
if (!stat) if (!stat)
return IRQ_NONE; return IRQ_NONE;
...@@ -1028,28 +1005,9 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data) ...@@ -1028,28 +1005,9 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)
*/ */
if (stat & FSL_DMA_SR_PE) { if (stat & FSL_DMA_SR_PE) {
chan_dbg(chan, "irq: Programming Error INT\n"); chan_dbg(chan, "irq: Programming Error INT\n");
if (get_bcr(chan) == 0) {
/* BCR register is 0, this is a DMA_INTERRUPT async_tx.
* Now, update the completed cookie, and continue the
* next uncompleted transfer.
*/
update_cookie = 1;
xfer_ld_q = 1;
}
stat &= ~FSL_DMA_SR_PE; stat &= ~FSL_DMA_SR_PE;
} if (get_bcr(chan) != 0)
chan_err(chan, "Programming Error!\n");
/*
* If the link descriptor segment transfer finishes,
* we will recycle the used descriptor.
*/
if (stat & FSL_DMA_SR_EOSI) {
chan_dbg(chan, "irq: End-of-segments INT\n");
chan_dbg(chan, "irq: clndar 0x%llx, nlndar 0x%llx\n",
(unsigned long long)get_cdar(chan),
(unsigned long long)get_ndar(chan));
stat &= ~FSL_DMA_SR_EOSI;
update_cookie = 1;
} }
/* /*
...@@ -1059,8 +1017,6 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data) ...@@ -1059,8 +1017,6 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)
if (stat & FSL_DMA_SR_EOCDI) { if (stat & FSL_DMA_SR_EOCDI) {
chan_dbg(chan, "irq: End-of-Chain link INT\n"); chan_dbg(chan, "irq: End-of-Chain link INT\n");
stat &= ~FSL_DMA_SR_EOCDI; stat &= ~FSL_DMA_SR_EOCDI;
update_cookie = 1;
xfer_ld_q = 1;
} }
/* /*
...@@ -1071,25 +1027,44 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data) ...@@ -1071,25 +1027,44 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)
if (stat & FSL_DMA_SR_EOLNI) { if (stat & FSL_DMA_SR_EOLNI) {
chan_dbg(chan, "irq: End-of-link INT\n"); chan_dbg(chan, "irq: End-of-link INT\n");
stat &= ~FSL_DMA_SR_EOLNI; stat &= ~FSL_DMA_SR_EOLNI;
xfer_ld_q = 1;
} }
if (update_cookie) /* check that the DMA controller is really idle */
fsl_dma_update_completed_cookie(chan); if (!dma_is_idle(chan))
if (xfer_ld_q) chan_err(chan, "irq: controller not idle!\n");
fsl_chan_xfer_ld_queue(chan);
/* check that we handled all of the bits */
if (stat) if (stat)
chan_dbg(chan, "irq: unhandled sr 0x%08x\n", stat); chan_err(chan, "irq: unhandled sr 0x%08x\n", stat);
chan_dbg(chan, "irq: Exit\n"); /*
* Schedule the tasklet to handle all cleanup of the current
* transaction. It will start a new transaction if there is
* one pending.
*/
tasklet_schedule(&chan->tasklet); tasklet_schedule(&chan->tasklet);
chan_dbg(chan, "irq: Exit\n");
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static void dma_do_tasklet(unsigned long data) static void dma_do_tasklet(unsigned long data)
{ {
struct fsldma_chan *chan = (struct fsldma_chan *)data; struct fsldma_chan *chan = (struct fsldma_chan *)data;
unsigned long flags;
chan_dbg(chan, "tasklet entry\n");
/* run all callbacks, free all used descriptors */
fsl_chan_ld_cleanup(chan); fsl_chan_ld_cleanup(chan);
/* the channel is now idle */
spin_lock_irqsave(&chan->desc_lock, flags);
chan->idle = true;
spin_unlock_irqrestore(&chan->desc_lock, flags);
/* start any pending transactions automatically */
fsl_chan_xfer_ld_queue(chan);
chan_dbg(chan, "tasklet exit\n");
} }
static irqreturn_t fsldma_ctrl_irq(int irq, void *data) static irqreturn_t fsldma_ctrl_irq(int irq, void *data)
...@@ -1269,6 +1244,7 @@ static int __devinit fsl_dma_chan_probe(struct fsldma_device *fdev, ...@@ -1269,6 +1244,7 @@ static int __devinit fsl_dma_chan_probe(struct fsldma_device *fdev,
spin_lock_init(&chan->desc_lock); spin_lock_init(&chan->desc_lock);
INIT_LIST_HEAD(&chan->ld_pending); INIT_LIST_HEAD(&chan->ld_pending);
INIT_LIST_HEAD(&chan->ld_running); INIT_LIST_HEAD(&chan->ld_running);
chan->idle = true;
chan->common.device = &fdev->common; chan->common.device = &fdev->common;
......
...@@ -148,6 +148,7 @@ struct fsldma_chan { ...@@ -148,6 +148,7 @@ struct fsldma_chan {
int id; /* Raw id of this channel */ int id; /* Raw id of this channel */
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
u32 feature; u32 feature;
bool idle; /* DMA controller is idle */
void (*toggle_ext_pause)(struct fsldma_chan *fsl_chan, int enable); void (*toggle_ext_pause)(struct fsldma_chan *fsl_chan, int enable);
void (*toggle_ext_start)(struct fsldma_chan *fsl_chan, int enable); void (*toggle_ext_start)(struct fsldma_chan *fsl_chan, int enable);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment