dmaengine: shdma: fix DMA error handling.

Present DMA error ISR in shdma.c is bogus, it locks the system hard in multiple
ways. Fix it to abort all queued transactions on all channels on the affected
controller and giving submitters a chance to get a DMA_ERROR status for aborted
transactions. Afterwards further functionality is again possible without the
need to re-load the driver.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
This commit is contained in:
Guennadi Liakhovetski 2010-02-11 16:50:05 +00:00 committed by Paul Mundt
parent 920925f90f
commit 47a4dc26ee

View File

@ -73,7 +73,7 @@ static void sh_dmae_ctl_stop(int id)
{ {
unsigned short dmaor = dmaor_read_reg(id); unsigned short dmaor = dmaor_read_reg(id);
dmaor &= ~(DMAOR_NMIF | DMAOR_AE); dmaor &= ~(DMAOR_NMIF | DMAOR_AE | DMAOR_DME);
dmaor_write_reg(id, dmaor); dmaor_write_reg(id, dmaor);
} }
@ -86,7 +86,7 @@ static int sh_dmae_rst(int id)
dmaor_write_reg(id, dmaor); dmaor_write_reg(id, dmaor);
if (dmaor_read_reg(id) & (DMAOR_AE | DMAOR_NMIF)) { if (dmaor_read_reg(id) & (DMAOR_AE | DMAOR_NMIF)) {
pr_warning(KERN_ERR "dma-sh: Can't initialize DMAOR.\n"); pr_warning("dma-sh: Can't initialize DMAOR.\n");
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
@ -661,7 +661,7 @@ static void sh_dmae_chan_ld_cleanup(struct sh_dmae_chan *sh_chan, bool all)
static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan) static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
{ {
struct sh_desc *sd; struct sh_desc *desc;
spin_lock_bh(&sh_chan->desc_lock); spin_lock_bh(&sh_chan->desc_lock);
/* DMA work check */ /* DMA work check */
@ -671,10 +671,10 @@ static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
} }
/* Find the first not transferred desciptor */ /* Find the first not transferred desciptor */
list_for_each_entry(sd, &sh_chan->ld_queue, node) list_for_each_entry(desc, &sh_chan->ld_queue, node)
if (sd->mark == DESC_SUBMITTED) { if (desc->mark == DESC_SUBMITTED) {
/* Get the ld start address from ld_queue */ /* Get the ld start address from ld_queue */
dmae_set_reg(sh_chan, &sd->hw); dmae_set_reg(sh_chan, &desc->hw);
dmae_start(sh_chan); dmae_start(sh_chan);
break; break;
} }
@ -696,6 +696,7 @@ static enum dma_status sh_dmae_is_complete(struct dma_chan *chan,
struct sh_dmae_chan *sh_chan = to_sh_chan(chan); struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
dma_cookie_t last_used; dma_cookie_t last_used;
dma_cookie_t last_complete; dma_cookie_t last_complete;
enum dma_status status;
sh_dmae_chan_ld_cleanup(sh_chan, false); sh_dmae_chan_ld_cleanup(sh_chan, false);
@ -709,7 +710,27 @@ static enum dma_status sh_dmae_is_complete(struct dma_chan *chan,
if (used) if (used)
*used = last_used; *used = last_used;
return dma_async_is_complete(cookie, last_complete, last_used); spin_lock_bh(&sh_chan->desc_lock);
status = dma_async_is_complete(cookie, last_complete, last_used);
/*
* If we don't find cookie on the queue, it has been aborted and we have
* to report error
*/
if (status != DMA_SUCCESS) {
struct sh_desc *desc;
status = DMA_ERROR;
list_for_each_entry(desc, &sh_chan->ld_queue, node)
if (desc->cookie == cookie) {
status = DMA_IN_PROGRESS;
break;
}
}
spin_unlock_bh(&sh_chan->desc_lock);
return status;
} }
static irqreturn_t sh_dmae_interrupt(int irq, void *data) static irqreturn_t sh_dmae_interrupt(int irq, void *data)
@ -732,40 +753,36 @@ static irqreturn_t sh_dmae_interrupt(int irq, void *data)
#if defined(CONFIG_CPU_SH4) #if defined(CONFIG_CPU_SH4)
static irqreturn_t sh_dmae_err(int irq, void *data) static irqreturn_t sh_dmae_err(int irq, void *data)
{ {
int err = 0;
struct sh_dmae_device *shdev = (struct sh_dmae_device *)data; struct sh_dmae_device *shdev = (struct sh_dmae_device *)data;
int i;
/* IRQ Multi */ /* halt the dma controller */
if (shdev->pdata.mode & SHDMA_MIX_IRQ) { sh_dmae_ctl_stop(0);
int __maybe_unused cnt = 0; if (shdev->pdata.mode & SHDMA_DMAOR1)
switch (irq) { sh_dmae_ctl_stop(1);
#if defined(DMTE6_IRQ) && defined(DMAE1_IRQ)
case DMTE6_IRQ: /* We cannot detect, which channel caused the error, have to reset all */
cnt++; for (i = 0; i < MAX_DMA_CHANNELS; i++) {
#endif struct sh_dmae_chan *sh_chan = shdev->chan[i];
case DMTE0_IRQ: if (sh_chan) {
if (dmaor_read_reg(cnt) & (DMAOR_NMIF | DMAOR_AE)) { struct sh_desc *desc;
disable_irq(irq); /* Stop the channel */
return IRQ_HANDLED; dmae_halt(sh_chan);
/* Complete all */
list_for_each_entry(desc, &sh_chan->ld_queue, node) {
struct dma_async_tx_descriptor *tx = &desc->async_tx;
desc->mark = DESC_IDLE;
if (tx->callback)
tx->callback(tx->callback_param);
} }
default: list_splice_init(&sh_chan->ld_queue, &sh_chan->ld_free);
return IRQ_NONE;
} }
} else {
/* reset dma controller */
err = sh_dmae_rst(0);
if (err)
return err;
#ifdef SH_DMAC_BASE1
if (shdev->pdata.mode & SHDMA_DMAOR1) {
err = sh_dmae_rst(1);
if (err)
return err;
}
#endif
disable_irq(irq);
return IRQ_HANDLED;
} }
sh_dmae_rst(0);
if (shdev->pdata.mode & SHDMA_DMAOR1)
sh_dmae_rst(1);
return IRQ_HANDLED;
} }
#endif #endif