Commit 2dc66667 authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Paul Mundt

dmaengine: shdma: fix locking

Close multiple theoretical races, especially the one in
.device_free_chan_resources().
Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarPaul Mundt <lethal@linux-sh.org>
parent 5b02c51a
...@@ -48,7 +48,7 @@ enum sh_dmae_desc_status { ...@@ -48,7 +48,7 @@ enum sh_dmae_desc_status {
/* /*
* Used for write-side mutual exclusion for the global device list, * Used for write-side mutual exclusion for the global device list,
* read-side synchronization by way of RCU. * read-side synchronization by way of RCU, and per-controller data.
*/ */
static DEFINE_SPINLOCK(sh_dmae_lock); static DEFINE_SPINLOCK(sh_dmae_lock);
static LIST_HEAD(sh_dmae_devices); static LIST_HEAD(sh_dmae_devices);
...@@ -85,22 +85,35 @@ static void dmaor_write(struct sh_dmae_device *shdev, u16 data) ...@@ -85,22 +85,35 @@ static void dmaor_write(struct sh_dmae_device *shdev, u16 data)
*/ */
static void sh_dmae_ctl_stop(struct sh_dmae_device *shdev) static void sh_dmae_ctl_stop(struct sh_dmae_device *shdev)
{ {
unsigned short dmaor = dmaor_read(shdev); unsigned short dmaor;
unsigned long flags;
spin_lock_irqsave(&sh_dmae_lock, flags);
dmaor = dmaor_read(shdev);
dmaor_write(shdev, dmaor & ~(DMAOR_NMIF | DMAOR_AE | DMAOR_DME)); dmaor_write(shdev, dmaor & ~(DMAOR_NMIF | DMAOR_AE | DMAOR_DME));
spin_unlock_irqrestore(&sh_dmae_lock, flags);
} }
static int sh_dmae_rst(struct sh_dmae_device *shdev) static int sh_dmae_rst(struct sh_dmae_device *shdev)
{ {
unsigned short dmaor; unsigned short dmaor;
unsigned long flags;
sh_dmae_ctl_stop(shdev); spin_lock_irqsave(&sh_dmae_lock, flags);
dmaor = dmaor_read(shdev) | shdev->pdata->dmaor_init;
dmaor_write(shdev, dmaor); dmaor = dmaor_read(shdev) & ~(DMAOR_NMIF | DMAOR_AE | DMAOR_DME);
if (dmaor_read(shdev) & (DMAOR_AE | DMAOR_NMIF)) {
pr_warning("dma-sh: Can't initialize DMAOR.\n"); dmaor_write(shdev, dmaor | shdev->pdata->dmaor_init);
return -EINVAL;
dmaor = dmaor_read(shdev);
spin_unlock_irqrestore(&sh_dmae_lock, flags);
if (dmaor & (DMAOR_AE | DMAOR_NMIF)) {
dev_warn(shdev->common.dev, "Can't initialize DMAOR.\n");
return -EIO;
} }
return 0; return 0;
} }
...@@ -184,7 +197,7 @@ static void dmae_init(struct sh_dmae_chan *sh_chan) ...@@ -184,7 +197,7 @@ static void dmae_init(struct sh_dmae_chan *sh_chan)
static int dmae_set_chcr(struct sh_dmae_chan *sh_chan, u32 val) static int dmae_set_chcr(struct sh_dmae_chan *sh_chan, u32 val)
{ {
/* When DMA was working, can not set data to CHCR */ /* If DMA is active, cannot set CHCR. TODO: remove this superfluous check */
if (dmae_is_busy(sh_chan)) if (dmae_is_busy(sh_chan))
return -EBUSY; return -EBUSY;
...@@ -374,7 +387,12 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan) ...@@ -374,7 +387,12 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
LIST_HEAD(list); LIST_HEAD(list);
int descs = sh_chan->descs_allocated; int descs = sh_chan->descs_allocated;
/* Protect against ISR */
spin_lock_irq(&sh_chan->desc_lock);
dmae_halt(sh_chan); dmae_halt(sh_chan);
spin_unlock_irq(&sh_chan->desc_lock);
/* Now no new interrupts will occur */
/* Prepared and not submitted descriptors can still be on the queue */ /* Prepared and not submitted descriptors can still be on the queue */
if (!list_empty(&sh_chan->ld_queue)) if (!list_empty(&sh_chan->ld_queue))
...@@ -384,6 +402,7 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan) ...@@ -384,6 +402,7 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
/* The caller is holding dma_list_mutex */ /* The caller is holding dma_list_mutex */
struct sh_dmae_slave *param = chan->private; struct sh_dmae_slave *param = chan->private;
clear_bit(param->slave_id, sh_dmae_slave_used); clear_bit(param->slave_id, sh_dmae_slave_used);
chan->private = NULL;
} }
spin_lock_bh(&sh_chan->desc_lock); spin_lock_bh(&sh_chan->desc_lock);
...@@ -563,8 +582,6 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_memcpy( ...@@ -563,8 +582,6 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_memcpy(
if (!chan || !len) if (!chan || !len)
return NULL; return NULL;
chan->private = NULL;
sh_chan = to_sh_chan(chan); sh_chan = to_sh_chan(chan);
sg_init_table(&sg, 1); sg_init_table(&sg, 1);
...@@ -620,9 +637,9 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, ...@@ -620,9 +637,9 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
if (!chan) if (!chan)
return -EINVAL; return -EINVAL;
spin_lock_bh(&sh_chan->desc_lock);
dmae_halt(sh_chan); dmae_halt(sh_chan);
spin_lock_bh(&sh_chan->desc_lock);
if (!list_empty(&sh_chan->ld_queue)) { if (!list_empty(&sh_chan->ld_queue)) {
/* Record partial transfer */ /* Record partial transfer */
struct sh_desc *desc = list_entry(sh_chan->ld_queue.next, struct sh_desc *desc = list_entry(sh_chan->ld_queue.next,
...@@ -716,6 +733,14 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all ...@@ -716,6 +733,14 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
list_move(&desc->node, &sh_chan->ld_free); list_move(&desc->node, &sh_chan->ld_free);
} }
} }
if (all && !callback)
/*
* Terminating and the loop completed normally: forgive
* uncompleted cookies
*/
sh_chan->completed_cookie = sh_chan->common.cookie;
spin_unlock_bh(&sh_chan->desc_lock); spin_unlock_bh(&sh_chan->desc_lock);
if (callback) if (callback)
...@@ -733,10 +758,6 @@ static void sh_dmae_chan_ld_cleanup(struct sh_dmae_chan *sh_chan, bool all) ...@@ -733,10 +758,6 @@ static void sh_dmae_chan_ld_cleanup(struct sh_dmae_chan *sh_chan, bool all)
{ {
while (__ld_cleanup(sh_chan, all)) while (__ld_cleanup(sh_chan, all))
; ;
if (all)
/* Terminating - forgive uncompleted cookies */
sh_chan->completed_cookie = sh_chan->common.cookie;
} }
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)
...@@ -782,8 +803,10 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan, ...@@ -782,8 +803,10 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
sh_dmae_chan_ld_cleanup(sh_chan, false); sh_dmae_chan_ld_cleanup(sh_chan, false);
last_used = chan->cookie; /* First read completed cookie to avoid a skew */
last_complete = sh_chan->completed_cookie; last_complete = sh_chan->completed_cookie;
rmb();
last_used = chan->cookie;
BUG_ON(last_complete < 0); BUG_ON(last_complete < 0);
dma_set_tx_state(txstate, last_complete, last_used, 0); dma_set_tx_state(txstate, last_complete, last_used, 0);
...@@ -813,8 +836,12 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan, ...@@ -813,8 +836,12 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
static irqreturn_t sh_dmae_interrupt(int irq, void *data) static irqreturn_t sh_dmae_interrupt(int irq, void *data)
{ {
irqreturn_t ret = IRQ_NONE; irqreturn_t ret = IRQ_NONE;
struct sh_dmae_chan *sh_chan = (struct sh_dmae_chan *)data; struct sh_dmae_chan *sh_chan = data;
u32 chcr = sh_dmae_readl(sh_chan, CHCR); u32 chcr;
spin_lock(&sh_chan->desc_lock);
chcr = sh_dmae_readl(sh_chan, CHCR);
if (chcr & CHCR_TE) { if (chcr & CHCR_TE) {
/* DMA stop */ /* DMA stop */
...@@ -824,10 +851,13 @@ static irqreturn_t sh_dmae_interrupt(int irq, void *data) ...@@ -824,10 +851,13 @@ static irqreturn_t sh_dmae_interrupt(int irq, void *data)
tasklet_schedule(&sh_chan->tasklet); tasklet_schedule(&sh_chan->tasklet);
} }
spin_unlock(&sh_chan->desc_lock);
return ret; return ret;
} }
static unsigned int sh_dmae_reset(struct sh_dmae_device *shdev) /* Called from error IRQ or NMI */
static bool sh_dmae_reset(struct sh_dmae_device *shdev)
{ {
unsigned int handled = 0; unsigned int handled = 0;
int i; int i;
...@@ -839,22 +869,32 @@ static unsigned int sh_dmae_reset(struct sh_dmae_device *shdev) ...@@ -839,22 +869,32 @@ static unsigned int sh_dmae_reset(struct sh_dmae_device *shdev)
for (i = 0; i < SH_DMAC_MAX_CHANNELS; i++) { for (i = 0; i < SH_DMAC_MAX_CHANNELS; i++) {
struct sh_dmae_chan *sh_chan = shdev->chan[i]; struct sh_dmae_chan *sh_chan = shdev->chan[i];
struct sh_desc *desc; struct sh_desc *desc;
LIST_HEAD(dl);
if (!sh_chan) if (!sh_chan)
continue; continue;
spin_lock(&sh_chan->desc_lock);
/* Stop the channel */ /* Stop the channel */
dmae_halt(sh_chan); dmae_halt(sh_chan);
list_splice_init(&sh_chan->ld_queue, &dl);
spin_unlock(&sh_chan->desc_lock);
/* Complete all */ /* Complete all */
list_for_each_entry(desc, &sh_chan->ld_queue, node) { list_for_each_entry(desc, &dl, node) {
struct dma_async_tx_descriptor *tx = &desc->async_tx; struct dma_async_tx_descriptor *tx = &desc->async_tx;
desc->mark = DESC_IDLE; desc->mark = DESC_IDLE;
if (tx->callback) if (tx->callback)
tx->callback(tx->callback_param); tx->callback(tx->callback_param);
} }
list_splice_init(&sh_chan->ld_queue, &sh_chan->ld_free); spin_lock(&sh_chan->desc_lock);
list_splice(&dl, &sh_chan->ld_free);
spin_unlock(&sh_chan->desc_lock);
handled++; handled++;
} }
...@@ -867,10 +907,11 @@ static irqreturn_t sh_dmae_err(int irq, void *data) ...@@ -867,10 +907,11 @@ static irqreturn_t sh_dmae_err(int irq, void *data)
{ {
struct sh_dmae_device *shdev = data; struct sh_dmae_device *shdev = data;
if (dmaor_read(shdev) & DMAOR_AE) if (!(dmaor_read(shdev) & DMAOR_AE))
return IRQ_RETVAL(sh_dmae_reset(data));
else
return IRQ_NONE; return IRQ_NONE;
sh_dmae_reset(data);
return IRQ_HANDLED;
} }
static void dmae_do_tasklet(unsigned long data) static void dmae_do_tasklet(unsigned long data)
...@@ -902,17 +943,11 @@ static void dmae_do_tasklet(unsigned long data) ...@@ -902,17 +943,11 @@ static void dmae_do_tasklet(unsigned long data)
static bool sh_dmae_nmi_notify(struct sh_dmae_device *shdev) static bool sh_dmae_nmi_notify(struct sh_dmae_device *shdev)
{ {
unsigned int handled;
/* Fast path out if NMIF is not asserted for this controller */ /* Fast path out if NMIF is not asserted for this controller */
if ((dmaor_read(shdev) & DMAOR_NMIF) == 0) if ((dmaor_read(shdev) & DMAOR_NMIF) == 0)
return false; return false;
handled = sh_dmae_reset(shdev); return sh_dmae_reset(shdev);
if (handled)
return true;
return false;
} }
static int sh_dmae_nmi_handler(struct notifier_block *self, static int sh_dmae_nmi_handler(struct notifier_block *self,
...@@ -982,9 +1017,6 @@ static int __devinit sh_dmae_chan_probe(struct sh_dmae_device *shdev, int id, ...@@ -982,9 +1017,6 @@ static int __devinit sh_dmae_chan_probe(struct sh_dmae_device *shdev, int id,
tasklet_init(&new_sh_chan->tasklet, dmae_do_tasklet, tasklet_init(&new_sh_chan->tasklet, dmae_do_tasklet,
(unsigned long)new_sh_chan); (unsigned long)new_sh_chan);
/* Init the channel */
dmae_init(new_sh_chan);
spin_lock_init(&new_sh_chan->desc_lock); spin_lock_init(&new_sh_chan->desc_lock);
/* Init descripter manage list */ /* Init descripter manage list */
...@@ -1115,7 +1147,7 @@ static int __init sh_dmae_probe(struct platform_device *pdev) ...@@ -1115,7 +1147,7 @@ static int __init sh_dmae_probe(struct platform_device *pdev)
list_add_tail_rcu(&shdev->node, &sh_dmae_devices); list_add_tail_rcu(&shdev->node, &sh_dmae_devices);
spin_unlock_irqrestore(&sh_dmae_lock, flags); spin_unlock_irqrestore(&sh_dmae_lock, flags);
/* reset dma controller */ /* reset dma controller - only needed as a test */
err = sh_dmae_rst(shdev); err = sh_dmae_rst(shdev);
if (err) if (err)
goto rst_err; goto rst_err;
......
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