Commit 25a49b8e authored by Russell King's avatar Russell King

[ARM] Fix DMA handler race condition.

This fixes a race condition in the RiscPC DMA code, which causes
DMA for a channel to halt due to a race condition between the
hardware state machine and the software programming the next DMA
buffer.
parent af7dbd5e
...@@ -57,7 +57,7 @@ static void iomd_get_next_sg(struct scatterlist *sg, dma_t *dma) ...@@ -57,7 +57,7 @@ static void iomd_get_next_sg(struct scatterlist *sg, dma_t *dma)
if (end > PAGE_SIZE) if (end > PAGE_SIZE)
end = PAGE_SIZE; end = PAGE_SIZE;
if (offset + (int) TRANSFER_SIZE > end) if (offset + TRANSFER_SIZE >= end)
flags |= DMA_END_L; flags |= DMA_END_L;
sg->length = end - TRANSFER_SIZE; sg->length = end - TRANSFER_SIZE;
...@@ -95,27 +95,31 @@ static irqreturn_t iomd_dma_handle(int irq, void *dev_id, struct pt_regs *regs) ...@@ -95,27 +95,31 @@ static irqreturn_t iomd_dma_handle(int irq, void *dev_id, struct pt_regs *regs)
if (!(status & DMA_ST_INT)) if (!(status & DMA_ST_INT))
return IRQ_HANDLED; return IRQ_HANDLED;
if (status & DMA_ST_OFL && !dma->sg) if ((dma->state ^ status) & DMA_ST_AB)
break; iomd_get_next_sg(&dma->cur_sg, dma);
iomd_get_next_sg(&dma->cur_sg, dma);
switch (status & (DMA_ST_OFL | DMA_ST_AB)) { switch (status & (DMA_ST_OFL | DMA_ST_AB)) {
case DMA_ST_OFL: /* OIA */ case DMA_ST_OFL: /* OIA */
case DMA_ST_AB: /* .IB */ case DMA_ST_AB: /* .IB */
iomd_writel(dma->cur_sg.dma_address, base + CURA); iomd_writel(dma->cur_sg.dma_address, base + CURA);
iomd_writel(dma->cur_sg.length, base + ENDA); iomd_writel(dma->cur_sg.length, base + ENDA);
dma->state = DMA_ST_AB;
break; break;
case DMA_ST_OFL | DMA_ST_AB: /* OIB */ case DMA_ST_OFL | DMA_ST_AB: /* OIB */
case 0: /* .IA */ case 0: /* .IA */
iomd_writel(dma->cur_sg.dma_address, base + CURB); iomd_writel(dma->cur_sg.dma_address, base + CURB);
iomd_writel(dma->cur_sg.length, base + ENDB); iomd_writel(dma->cur_sg.length, base + ENDB);
dma->state = 0;
break; break;
} }
if (status & DMA_ST_OFL &&
dma->cur_sg.length == (DMA_END_S|DMA_END_L))
break;
} while (1); } while (1);
iomd_writeb(0, dma->dma_base + CR); dma->state = ~DMA_ST_AB;
disable_irq(irq); disable_irq(irq);
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -152,6 +156,7 @@ static void iomd_enable_dma(dmach_t channel, dma_t *dma) ...@@ -152,6 +156,7 @@ static void iomd_enable_dma(dmach_t channel, dma_t *dma)
} }
iomd_writeb(DMA_CR_C, dma_base + CR); iomd_writeb(DMA_CR_C, dma_base + CR);
dma->state = DMA_ST_AB;
} }
if (dma->dma_mode == DMA_MODE_READ) if (dma->dma_mode == DMA_MODE_READ)
...@@ -165,13 +170,11 @@ static void iomd_disable_dma(dmach_t channel, dma_t *dma) ...@@ -165,13 +170,11 @@ static void iomd_disable_dma(dmach_t channel, dma_t *dma)
{ {
unsigned long dma_base = dma->dma_base; unsigned long dma_base = dma->dma_base;
unsigned long flags; unsigned long flags;
unsigned int ctrl;
local_irq_save(flags); local_irq_save(flags);
ctrl = iomd_readb(dma_base + CR); if (dma->state != ~DMA_ST_AB)
if (ctrl & DMA_CR_E)
disable_irq(dma->dma_irq); disable_irq(dma->dma_irq);
iomd_writeb(ctrl & ~DMA_CR_E, dma_base + CR); iomd_writeb(0, dma_base + CR);
local_irq_restore(flags); local_irq_restore(flags);
} }
......
...@@ -41,6 +41,7 @@ struct dma_struct { ...@@ -41,6 +41,7 @@ struct dma_struct {
unsigned int dma_base; /* Controller base address */ unsigned int dma_base; /* Controller base address */
int dma_irq; /* Controller IRQ */ int dma_irq; /* Controller IRQ */
struct scatterlist cur_sg; /* Current controller buffer */ struct scatterlist cur_sg; /* Current controller buffer */
unsigned int state;
struct dma_ops *d_ops; struct dma_ops *d_ops;
}; };
......
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