Message ID | 20190228123743.12431-1-alexandru.ardelean@analog.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [V2] dmaengine: axi-dmac: Split too large segments | expand |
On Thu, 2019-02-28 at 14:37 +0200, Alexandru Ardelean wrote: > From: Lars-Peter Clausen <lars@metafoo.de> > > The axi-dmac driver currently rejects transfers with segments that are > larger than what the hardware can handle. > > Re-work the driver so that these large segments are split into multiple > segments instead where each segment is smaller or equal to the maximum > segment size. > > This allows the driver to handle transfers with segments of arbitrary > size. This will be superseeded by a V3. Found a minor item in this patch that isn't needed. The `last` field addition is not needed. It isn't used later either. > > Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> > Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com> > --- > > Changelog V1->V2: > * reworked this patch to not use the `sg_nents_for_dma()` helper from > https://patchwork.kernel.org/patch/9389821/ > After some discussion with Andy Shevchenko and some thought about it, > it > probably does not make sense to have this helper for this driver only, > since a more generic solution could be implemented via DMAEngine API. > This generic solution is not yet implemented (as I understood it from > the > discussion). > > drivers/dma/dma-axi-dmac.c | 83 +++++++++++++++++++++++++++++--------- > 1 file changed, 63 insertions(+), 20 deletions(-) > > diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c > index ffc0adc2f6ce..17869132e05f 100644 > --- a/drivers/dma/dma-axi-dmac.c > +++ b/drivers/dma/dma-axi-dmac.c > @@ -83,6 +83,7 @@ struct axi_dmac_sg { > unsigned int dest_stride; > unsigned int src_stride; > unsigned int id; > + bool last; > bool schedule_when_free; > }; > > @@ -166,7 +167,7 @@ static int axi_dmac_dest_is_mem(struct axi_dmac_chan > *chan) > > static bool axi_dmac_check_len(struct axi_dmac_chan *chan, unsigned int > len) > { > - if (len == 0 || len > chan->max_length) > + if (len == 0) > return false; > if ((len & chan->align_mask) != 0) /* Not aligned */ > return false; > @@ -379,6 +380,50 @@ static struct axi_dmac_desc > *axi_dmac_alloc_desc(unsigned int num_sgs) > return desc; > } > > +static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan > *chan, > + enum dma_transfer_direction direction, dma_addr_t addr, > + unsigned int num_periods, unsigned int period_len, > + struct axi_dmac_sg *sg) > +{ > + unsigned int num_segments, i; > + unsigned int segment_size; > + unsigned int len; > + > + /* Split into multiple equally sized segments if necessary */ > + num_segments = DIV_ROUND_UP(period_len, chan->max_length); > + segment_size = DIV_ROUND_UP(period_len, num_segments); > + /* Take care of alignment */ > + segment_size = ((segment_size - 1) | chan->align_mask) + 1; > + > + for (i = 0; i < num_periods; i++) { > + len = period_len; > + > + while (len > segment_size) { > + if (direction == DMA_DEV_TO_MEM) > + sg->dest_addr = addr; > + else > + sg->src_addr = addr; > + sg->x_len = segment_size; > + sg->y_len = 1; > + sg++; > + addr += segment_size; > + len -= segment_size; > + } > + > + if (direction == DMA_DEV_TO_MEM) > + sg->dest_addr = addr; > + else > + sg->src_addr = addr; > + sg->x_len = len; > + sg->y_len = 1; > + sg->last = true; > + sg++; > + addr += len; > + } > + > + return sg; > +} > + > static struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( > struct dma_chan *c, struct scatterlist *sgl, > unsigned int sg_len, enum dma_transfer_direction direction, > @@ -386,16 +431,24 @@ static struct dma_async_tx_descriptor > *axi_dmac_prep_slave_sg( > { > struct axi_dmac_chan *chan = to_axi_dmac_chan(c); > struct axi_dmac_desc *desc; > + struct axi_dmac_sg *dsg; > struct scatterlist *sg; > + unsigned int num_sgs; > unsigned int i; > > if (direction != chan->direction) > return NULL; > > - desc = axi_dmac_alloc_desc(sg_len); > + num_sgs = 0; > + for_each_sg(sgl, sg, sg_len, i) > + num_sgs += DIV_ROUND_UP(sg_dma_len(sg), chan->max_length); > + > + desc = axi_dmac_alloc_desc(num_sgs); > if (!desc) > return NULL; > > + dsg = desc->sg; > + > for_each_sg(sgl, sg, sg_len, i) { > if (!axi_dmac_check_addr(chan, sg_dma_address(sg)) || > !axi_dmac_check_len(chan, sg_dma_len(sg))) { > @@ -403,12 +456,8 @@ static struct dma_async_tx_descriptor > *axi_dmac_prep_slave_sg( > return NULL; > } > > - if (direction == DMA_DEV_TO_MEM) > - desc->sg[i].dest_addr = sg_dma_address(sg); > - else > - desc->sg[i].src_addr = sg_dma_address(sg); > - desc->sg[i].x_len = sg_dma_len(sg); > - desc->sg[i].y_len = 1; > + dsg = axi_dmac_fill_linear_sg(chan, direction, > sg_dma_address(sg), 1, > + sg_dma_len(sg), dsg); > } > > desc->cyclic = false; > @@ -423,7 +472,7 @@ static struct dma_async_tx_descriptor > *axi_dmac_prep_dma_cyclic( > { > struct axi_dmac_chan *chan = to_axi_dmac_chan(c); > struct axi_dmac_desc *desc; > - unsigned int num_periods, i; > + unsigned int num_periods, num_segments; > > if (direction != chan->direction) > return NULL; > @@ -436,20 +485,14 @@ static struct dma_async_tx_descriptor > *axi_dmac_prep_dma_cyclic( > return NULL; > > num_periods = buf_len / period_len; > + num_segments = DIV_ROUND_UP(period_len, chan->max_length); > > - desc = axi_dmac_alloc_desc(num_periods); > + desc = axi_dmac_alloc_desc(num_periods * num_segments); > if (!desc) > return NULL; > > - for (i = 0; i < num_periods; i++) { > - if (direction == DMA_DEV_TO_MEM) > - desc->sg[i].dest_addr = buf_addr; > - else > - desc->sg[i].src_addr = buf_addr; > - desc->sg[i].x_len = period_len; > - desc->sg[i].y_len = 1; > - buf_addr += period_len; > - } > + axi_dmac_fill_linear_sg(chan, direction, buf_addr, num_periods, > + buf_len, desc->sg); > > desc->cyclic = true; > > @@ -647,7 +690,7 @@ static int axi_dmac_probe(struct platform_device > *pdev) > of_node_put(of_channels); > > pdev->dev.dma_parms = &dmac->dma_parms; > - dma_set_max_seg_size(&pdev->dev, dmac->chan.max_length); > + dma_set_max_seg_size(&pdev->dev, UINT_MAX); > > dma_dev = &dmac->dma_dev; > dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index ffc0adc2f6ce..17869132e05f 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -83,6 +83,7 @@ struct axi_dmac_sg { unsigned int dest_stride; unsigned int src_stride; unsigned int id; + bool last; bool schedule_when_free; }; @@ -166,7 +167,7 @@ static int axi_dmac_dest_is_mem(struct axi_dmac_chan *chan) static bool axi_dmac_check_len(struct axi_dmac_chan *chan, unsigned int len) { - if (len == 0 || len > chan->max_length) + if (len == 0) return false; if ((len & chan->align_mask) != 0) /* Not aligned */ return false; @@ -379,6 +380,50 @@ static struct axi_dmac_desc *axi_dmac_alloc_desc(unsigned int num_sgs) return desc; } +static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan, + enum dma_transfer_direction direction, dma_addr_t addr, + unsigned int num_periods, unsigned int period_len, + struct axi_dmac_sg *sg) +{ + unsigned int num_segments, i; + unsigned int segment_size; + unsigned int len; + + /* Split into multiple equally sized segments if necessary */ + num_segments = DIV_ROUND_UP(period_len, chan->max_length); + segment_size = DIV_ROUND_UP(period_len, num_segments); + /* Take care of alignment */ + segment_size = ((segment_size - 1) | chan->align_mask) + 1; + + for (i = 0; i < num_periods; i++) { + len = period_len; + + while (len > segment_size) { + if (direction == DMA_DEV_TO_MEM) + sg->dest_addr = addr; + else + sg->src_addr = addr; + sg->x_len = segment_size; + sg->y_len = 1; + sg++; + addr += segment_size; + len -= segment_size; + } + + if (direction == DMA_DEV_TO_MEM) + sg->dest_addr = addr; + else + sg->src_addr = addr; + sg->x_len = len; + sg->y_len = 1; + sg->last = true; + sg++; + addr += len; + } + + return sg; +} + static struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( struct dma_chan *c, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, @@ -386,16 +431,24 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( { struct axi_dmac_chan *chan = to_axi_dmac_chan(c); struct axi_dmac_desc *desc; + struct axi_dmac_sg *dsg; struct scatterlist *sg; + unsigned int num_sgs; unsigned int i; if (direction != chan->direction) return NULL; - desc = axi_dmac_alloc_desc(sg_len); + num_sgs = 0; + for_each_sg(sgl, sg, sg_len, i) + num_sgs += DIV_ROUND_UP(sg_dma_len(sg), chan->max_length); + + desc = axi_dmac_alloc_desc(num_sgs); if (!desc) return NULL; + dsg = desc->sg; + for_each_sg(sgl, sg, sg_len, i) { if (!axi_dmac_check_addr(chan, sg_dma_address(sg)) || !axi_dmac_check_len(chan, sg_dma_len(sg))) { @@ -403,12 +456,8 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( return NULL; } - if (direction == DMA_DEV_TO_MEM) - desc->sg[i].dest_addr = sg_dma_address(sg); - else - desc->sg[i].src_addr = sg_dma_address(sg); - desc->sg[i].x_len = sg_dma_len(sg); - desc->sg[i].y_len = 1; + dsg = axi_dmac_fill_linear_sg(chan, direction, sg_dma_address(sg), 1, + sg_dma_len(sg), dsg); } desc->cyclic = false; @@ -423,7 +472,7 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_dma_cyclic( { struct axi_dmac_chan *chan = to_axi_dmac_chan(c); struct axi_dmac_desc *desc; - unsigned int num_periods, i; + unsigned int num_periods, num_segments; if (direction != chan->direction) return NULL; @@ -436,20 +485,14 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_dma_cyclic( return NULL; num_periods = buf_len / period_len; + num_segments = DIV_ROUND_UP(period_len, chan->max_length); - desc = axi_dmac_alloc_desc(num_periods); + desc = axi_dmac_alloc_desc(num_periods * num_segments); if (!desc) return NULL; - for (i = 0; i < num_periods; i++) { - if (direction == DMA_DEV_TO_MEM) - desc->sg[i].dest_addr = buf_addr; - else - desc->sg[i].src_addr = buf_addr; - desc->sg[i].x_len = period_len; - desc->sg[i].y_len = 1; - buf_addr += period_len; - } + axi_dmac_fill_linear_sg(chan, direction, buf_addr, num_periods, + buf_len, desc->sg); desc->cyclic = true; @@ -647,7 +690,7 @@ static int axi_dmac_probe(struct platform_device *pdev) of_node_put(of_channels); pdev->dev.dma_parms = &dmac->dma_parms; - dma_set_max_seg_size(&pdev->dev, dmac->chan.max_length); + dma_set_max_seg_size(&pdev->dev, UINT_MAX); dma_dev = &dmac->dma_dev; dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);