diff mbox

[v3,1/2] dma: mmp_pdma: add support for residue reporting

Message ID 1376497189-21298-1-git-send-email-zonque@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel Mack Aug. 14, 2013, 4:19 p.m. UTC
In order to report the channel's residue, we have to memorize the first
dma buffer position when the channel is configured.

Signed-off-by: Daniel Mack <zonque@gmail.com>
---
 drivers/dma/mmp_pdma.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 43 insertions(+), 2 deletions(-)

Comments

Russell King - ARM Linux Aug. 14, 2013, 4:23 p.m. UTC | #1
On Wed, Aug 14, 2013 at 06:19:48PM +0200, Daniel Mack wrote:
> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
> +{
> +	struct mmp_pdma_desc_sw *sw;
> +	u32 curr, done = 0;
> +
> +	if (chan->dir == DMA_DEV_TO_MEM)
> +		curr = readl(chan->phy->base + DTADR(chan->phy->idx));
> +	else
> +		curr = readl(chan->phy->base + DSADR(chan->phy->idx));
> +
> +	list_for_each_entry(sw, &chan->chain_running, node) {
> +		u32 start;
> +		u32 len = sw->desc.dcmd & DCMD_LENGTH;
> +
> +		if (chan->dir == DMA_DEV_TO_MEM)
> +			start = sw->desc.dtadr;
> +		else
> +			start = sw->desc.dsadr;
> +
> +		if (curr >= start && curr <= (start + len)) {
> +			done += curr - start;
> +			break;
> +		}
> +
> +		done += len;
> +	}
> +
> +	return chan->total_len - done;

Yep, that should be sufficient.  Thanks for fixing that.
Chao Xie Aug. 15, 2013, 1:57 a.m. UTC | #2
On Thu, Aug 15, 2013 at 12:19 AM, Daniel Mack <zonque@gmail.com> wrote:
> In order to report the channel's residue, we have to memorize the first
> dma buffer position when the channel is configured.
>
> Signed-off-by: Daniel Mack <zonque@gmail.com>
> ---
>  drivers/dma/mmp_pdma.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 43 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c
> index 579f79a..d66340a 100644
> --- a/drivers/dma/mmp_pdma.c
> +++ b/drivers/dma/mmp_pdma.c
> @@ -28,8 +28,8 @@
>  #define DALGN          0x00a0
>  #define DINT           0x00f0
>  #define DDADR          0x0200
> -#define DSADR          0x0204
> -#define DTADR          0x0208
> +#define DSADR(n)       (0x0204 + ((n) << 4))
> +#define DTADR(n)       (0x0208 + ((n) << 4))
>  #define DCMD           0x020c
>
>  #define DCSR_RUN       (1 << 31)       /* Run Bit (read / write) */
> @@ -97,6 +97,11 @@ struct mmp_pdma_chan {
>         struct dma_async_tx_descriptor desc;
>         struct mmp_pdma_phy *phy;
>         enum dma_transfer_direction dir;
> +       /*
> +        * We memorize the original total length so we can later determine the
> +        * channel's residue.
> +        */
> +       u32 total_len;
>
>         /* channel's basic info */
>         struct tasklet_struct tasklet;
> @@ -470,6 +475,8 @@ mmp_pdma_prep_memcpy(struct dma_chan *dchan,
>                 chan->dcmd |= DCMD_BURST32;
>         }
>
> +       chan->total_len = len;
> +
>         do {
>                 /* Allocate the link descriptor from DMA pool */
>                 new = mmp_pdma_alloc_descriptor(chan);
> @@ -541,11 +548,14 @@ mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
>                 return NULL;
>
>         chan->byte_align = false;
> +       chan->total_len = 0;
>
>         for_each_sg(sgl, sg, sg_len, i) {
>                 addr = sg_dma_address(sg);
>                 avail = sg_dma_len(sgl);
>
> +               chan->total_len += avail;
> +
>                 do {
>                         len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES);
>                         if (addr & 0x7)
> @@ -666,6 +676,36 @@ static int mmp_pdma_control(struct dma_chan *dchan, enum dma_ctrl_cmd cmd,
>         return ret;
>  }
>
> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
> +{
> +       struct mmp_pdma_desc_sw *sw;
> +       u32 curr, done = 0;
> +
> +       if (chan->dir == DMA_DEV_TO_MEM)
> +               curr = readl(chan->phy->base + DTADR(chan->phy->idx));
> +       else
> +               curr = readl(chan->phy->base + DSADR(chan->phy->idx));
> +
> +       list_for_each_entry(sw, &chan->chain_running, node) {
> +               u32 start;
> +               u32 len = sw->desc.dcmd & DCMD_LENGTH;
> +
> +               if (chan->dir == DMA_DEV_TO_MEM)
> +                       start = sw->desc.dtadr;
> +               else
> +                       start = sw->desc.dsadr;
> +
> +               if (curr >= start && curr <= (start + len)) {
> +                       done += curr - start;
> +                       break;
> +               }
> +
> +               done += len;
> +       }
> +
> +       return chan->total_len - done;
> +}
> +

It seems that you will get all the residue bytes in the channel.
For DMA transfer, user may submit many trasaction.
The transaction will be composed by mutiple desctiptors. So it means
that chan->chain_running includes
the descriptors from all ongoing transactions.
When user try to get the tx_status, user will pass the cookie to
identify which transaction it want to get the status.
So for mmp_pdma_residue, it need care about the cookie, and find out
the transaction, then calculate the residue bytes.

The orignal driver has a issue, it will assign cookie for all
descriptors, not the transaction. I think that need to be changed.

>  static enum dma_status mmp_pdma_tx_status(struct dma_chan *dchan,
>                         dma_cookie_t cookie, struct dma_tx_state *txstate)
>  {
> @@ -675,6 +715,7 @@ static enum dma_status mmp_pdma_tx_status(struct dma_chan *dchan,
>
>         spin_lock_irqsave(&chan->desc_lock, flags);
>         ret = dma_cookie_status(dchan, cookie, txstate);
> +       txstate->residue = mmp_pdma_residue(chan);
>         spin_unlock_irqrestore(&chan->desc_lock, flags);
>
>         return ret;
> --
> 1.8.3.1
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Daniel Mack Aug. 15, 2013, 3:44 a.m. UTC | #3
Hi Chao,

On 15.08.2013 03:57, Chao Xie wrote:
> On Thu, Aug 15, 2013 at 12:19 AM, Daniel Mack <zonque@gmail.com> wrote:

>> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
>> +{
>> +       struct mmp_pdma_desc_sw *sw;
>> +       u32 curr, done = 0;
>> +
>> +       if (chan->dir == DMA_DEV_TO_MEM)
>> +               curr = readl(chan->phy->base + DTADR(chan->phy->idx));
>> +       else
>> +               curr = readl(chan->phy->base + DSADR(chan->phy->idx));
>> +
>> +       list_for_each_entry(sw, &chan->chain_running, node) {
>> +               u32 start;
>> +               u32 len = sw->desc.dcmd & DCMD_LENGTH;
>> +
>> +               if (chan->dir == DMA_DEV_TO_MEM)
>> +                       start = sw->desc.dtadr;
>> +               else
>> +                       start = sw->desc.dsadr;
>> +
>> +               if (curr >= start && curr <= (start + len)) {
>> +                       done += curr - start;
>> +                       break;
>> +               }
>> +
>> +               done += len;
>> +       }
>> +
>> +       return chan->total_len - done;
>> +}
>> +
> 
> It seems that you will get all the residue bytes in the channel.
> For DMA transfer, user may submit many trasaction.
> The transaction will be composed by mutiple desctiptors. So it means
> that chan->chain_running includes
> the descriptors from all ongoing transactions.
> When user try to get the tx_status, user will pass the cookie to
> identify which transaction it want to get the status.
> So for mmp_pdma_residue, it need care about the cookie, and find out
> the transaction, then calculate the residue bytes.

Ok, right.

> The orignal driver has a issue, it will assign cookie for all
> descriptors, not the transaction. I think that need to be changed.

Not necessarily. If all the linked descriptors are assigned the same
cookie, we can as well just walk all entries in chain_running and ignore
the ones that don't match the cookie we're looking for,

The problem with your proposed approach is that the driver will
currently dispose desc->tx_list via list_splice_tail_init() once the
transaction has been submitted, so we can't access it later. But what I
have in mind should work equally well. I'll post an updated patch later.


Thanks for the review,
Daniel
Chao Xie Aug. 15, 2013, 8:08 a.m. UTC | #4
On Thu, Aug 15, 2013 at 11:44 AM, Daniel Mack <zonque@gmail.com> wrote:
> Hi Chao,
>
> On 15.08.2013 03:57, Chao Xie wrote:
>> On Thu, Aug 15, 2013 at 12:19 AM, Daniel Mack <zonque@gmail.com> wrote:
>
>>> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
>>> +{
>>> +       struct mmp_pdma_desc_sw *sw;
>>> +       u32 curr, done = 0;
>>> +
>>> +       if (chan->dir == DMA_DEV_TO_MEM)
>>> +               curr = readl(chan->phy->base + DTADR(chan->phy->idx));
>>> +       else
>>> +               curr = readl(chan->phy->base + DSADR(chan->phy->idx));
>>> +
>>> +       list_for_each_entry(sw, &chan->chain_running, node) {
>>> +               u32 start;
>>> +               u32 len = sw->desc.dcmd & DCMD_LENGTH;
>>> +
>>> +               if (chan->dir == DMA_DEV_TO_MEM)
>>> +                       start = sw->desc.dtadr;
>>> +               else
>>> +                       start = sw->desc.dsadr;
>>> +
>>> +               if (curr >= start && curr <= (start + len)) {
>>> +                       done += curr - start;
>>> +                       break;
>>> +               }
>>> +
>>> +               done += len;
>>> +       }
>>> +
>>> +       return chan->total_len - done;
>>> +}
>>> +
>>
>> It seems that you will get all the residue bytes in the channel.
>> For DMA transfer, user may submit many trasaction.
>> The transaction will be composed by mutiple desctiptors. So it means
>> that chan->chain_running includes
>> the descriptors from all ongoing transactions.
>> When user try to get the tx_status, user will pass the cookie to
>> identify which transaction it want to get the status.
>> So for mmp_pdma_residue, it need care about the cookie, and find out
>> the transaction, then calculate the residue bytes.
>
> Ok, right.
>
>> The orignal driver has a issue, it will assign cookie for all
>> descriptors, not the transaction. I think that need to be changed.
>
> Not necessarily. If all the linked descriptors are assigned the same
> cookie, we can as well just walk all entries in chain_running and ignore
> the ones that don't match the cookie we're looking for,
>

In the function mmp_pdma_tx_submit
It will assign cookie for every descriptors by
list_for_each_entry(child, &desc->tx_list, node) {
                cookie = dma_cookie_assign(&child->async_tx);
        }
So not all the descriptors in same transaction will share same cookie.

> The problem with your proposed approach is that the driver will
> currently dispose desc->tx_list via list_splice_tail_init() once the
> transaction has been submitted, so we can't access it later. But what I
> have in mind should work equally well. I'll post an updated patch later.
>
The desc->tx_list will be linked to chain_running, so in chain_running, you can
still get the mmp_pdma_desc_sw.
So there are two ways
1. When user submit a transaction, only the first descriptor will be
assgined the cookie, and
you can check chan->completed_cookie for whether the transaction is
done or not. For the
residue bytes, you can go through chain_running, and find out the last
descriptor for the
transaction by comparing the cookie. Then you can tell the residue bytes.
For all the desriptors in the chain_running, only the descriptor has
non-zero cookie
is the first descriptor of the transaction.

2. Assign cookie for all the desriptors in same transaction, and
record the first one, and last one.
Then you can calculate the residue bytes too.

>
> Thanks for the review,
> Daniel
>
Daniel Mack Aug. 15, 2013, 6:12 p.m. UTC | #5
On 15.08.2013 10:08, Chao Xie wrote:
> In the function mmp_pdma_tx_submit
> It will assign cookie for every descriptors by
> list_for_each_entry(child, &desc->tx_list, node) {
>                 cookie = dma_cookie_assign(&child->async_tx);
>         }
> So not all the descriptors in same transaction will share same cookie.

Hmm, yes. You're right. However, I see quite some more issues with the
driver as it stands, especially due to assumptions that are made for
multiple transactions on one channel.

For example, let's think about two transactions with multiple
descriptors each, and both transactions are present in the chain_running
list. Each transaction has the ENDIRQEN bit set on the last descriptor only.

Currently, when an interrupt is caught, the code will only look at the
very last entry in the running list and complete its cookie, and then
dispose the entire running chain. Hence, the other transaction's cookie
will never complete. Even worse: as an interrupt will always be for the
first descriptor in the chain with the ENDIRQEN bit set, we complete the
second transaction that is in fact still running (and call the
completion handler for it), while we don't do exactly that for the first
one.

Am I right with the above or do I make faulty assumptions? I'll think
about how the driver can be changed in order to fix this, but it will be
more invasive than I thought.


Thanks for sharing your ideas,
Daniel
Chao Xie Aug. 16, 2013, 2:21 a.m. UTC | #6
On Fri, Aug 16, 2013 at 2:12 AM, Daniel Mack <zonque@gmail.com> wrote:
> On 15.08.2013 10:08, Chao Xie wrote:
>> In the function mmp_pdma_tx_submit
>> It will assign cookie for every descriptors by
>> list_for_each_entry(child, &desc->tx_list, node) {
>>                 cookie = dma_cookie_assign(&child->async_tx);
>>         }
>> So not all the descriptors in same transaction will share same cookie.
>
> Hmm, yes. You're right. However, I see quite some more issues with the
> driver as it stands, especially due to assumptions that are made for
> multiple transactions on one channel.
>
> For example, let's think about two transactions with multiple
> descriptors each, and both transactions are present in the chain_running
> list. Each transaction has the ENDIRQEN bit set on the last descriptor only.
>
> Currently, when an interrupt is caught, the code will only look at the
> very last entry in the running list and complete its cookie, and then
> dispose the entire running chain. Hence, the other transaction's cookie
> will never complete. Even worse: as an interrupt will always be for the
> first descriptor in the chain with the ENDIRQEN bit set, we complete the
> second transaction that is in fact still running (and call the
> completion handler for it), while we don't do exactly that for the first
> one.
>
> Am I right with the above or do I make faulty assumptions? I'll think
> about how the driver can be changed in order to fix this, but it will be
> more invasive than I thought.
>
I think you are right. I include the orignal author for the driver, and maybe
he can comment at it.

>
> Thanks for sharing your ideas,
> Daniel
>
Xiang Wang Aug. 16, 2013, 7:57 a.m. UTC | #7
On 08/15/2013 12:19 AM, Daniel Mack wrote:
> In order to report the channel's residue, we have to memorize the first
> dma buffer position when the channel is configured.
>
> Signed-off-by: Daniel Mack <zonque@gmail.com>
> ---
>   drivers/dma/mmp_pdma.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
>   1 file changed, 43 insertions(+), 2 deletions(-)
>

> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
> +{
> +	struct mmp_pdma_desc_sw *sw;
> +	u32 curr, done = 0;
> +
> +	if (chan->dir == DMA_DEV_TO_MEM)
> +		curr = readl(chan->phy->base + DTADR(chan->phy->idx));
One concern for this patch:
If we call dmaengine_tx_status() after dma_do_tasklet@mmp_pdma.c, 
chan->phy will most likely to be NULL.
dma_do_tasklet() -> start_pending_queue() -> mmp_pdma_free_phy()

For why do we call dmaengine_tx_status() after a DMA interrupt:
briefly, if we use DMA to handle device trailing bytes, it'll report a 
DMA interrupt. We need to get to know how many bytes have been received 
in the IRQ handler.
We've discussed about this in the link below:
http://thread.gmane.org/gmane.linux.kernel/1500713
Daniel Mack Aug. 16, 2013, 3:40 p.m. UTC | #8
On 16.08.2013 09:57, Xiang Wang wrote:
> On 08/15/2013 12:19 AM, Daniel Mack wrote:
>> In order to report the channel's residue, we have to memorize the first
>> dma buffer position when the channel is configured.
>>
>> Signed-off-by: Daniel Mack <zonque@gmail.com>
>> ---
>>   drivers/dma/mmp_pdma.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
>>   1 file changed, 43 insertions(+), 2 deletions(-)
>>
> 
>> +static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
>> +{
>> +	struct mmp_pdma_desc_sw *sw;
>> +	u32 curr, done = 0;
>> +
>> +	if (chan->dir == DMA_DEV_TO_MEM)
>> +		curr = readl(chan->phy->base + DTADR(chan->phy->idx));
> One concern for this patch:
> If we call dmaengine_tx_status() after dma_do_tasklet@mmp_pdma.c, 
> chan->phy will most likely to be NULL.
> dma_do_tasklet() -> start_pending_queue() -> mmp_pdma_free_phy()

Yes, I noticed this, too. I'm not really sure what the reason is to call
start_pending_queue() _before_ the callbacks are done. A quick test
shows that it works as well the other way around.

> For why do we call dmaengine_tx_status() after a DMA interrupt:
> briefly, if we use DMA to handle device trailing bytes, it'll report a 
> DMA interrupt. We need to get to know how many bytes have been received 
> in the IRQ handler.
> We've discussed about this in the link below:
> http://thread.gmane.org/gmane.linux.kernel/1500713

Thanks for the link, I wasn't aware of that discussion. And I actually
second what Andy said: As long as the residue calculation algorithm can
cope with the chan->phy==NULL condition and return 0 in that case, it's
actually doing the right thing, because a completed transaction has a
residue of 0 bytes.

I'll send out another series later, that also aims to fix the tasklet
behaviour. I'd appreciate another round of reviews :)


Daniel
diff mbox

Patch

diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c
index 579f79a..d66340a 100644
--- a/drivers/dma/mmp_pdma.c
+++ b/drivers/dma/mmp_pdma.c
@@ -28,8 +28,8 @@ 
 #define DALGN		0x00a0
 #define DINT		0x00f0
 #define DDADR		0x0200
-#define DSADR		0x0204
-#define DTADR		0x0208
+#define DSADR(n)	(0x0204 + ((n) << 4))
+#define DTADR(n)	(0x0208 + ((n) << 4))
 #define DCMD		0x020c
 
 #define DCSR_RUN	(1 << 31)	/* Run Bit (read / write) */
@@ -97,6 +97,11 @@  struct mmp_pdma_chan {
 	struct dma_async_tx_descriptor desc;
 	struct mmp_pdma_phy *phy;
 	enum dma_transfer_direction dir;
+	/*
+	 * We memorize the original total length so we can later determine the
+	 * channel's residue.
+	 */
+	u32 total_len;
 
 	/* channel's basic info */
 	struct tasklet_struct tasklet;
@@ -470,6 +475,8 @@  mmp_pdma_prep_memcpy(struct dma_chan *dchan,
 		chan->dcmd |= DCMD_BURST32;
 	}
 
+	chan->total_len = len;
+
 	do {
 		/* Allocate the link descriptor from DMA pool */
 		new = mmp_pdma_alloc_descriptor(chan);
@@ -541,11 +548,14 @@  mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
 		return NULL;
 
 	chan->byte_align = false;
+	chan->total_len = 0;
 
 	for_each_sg(sgl, sg, sg_len, i) {
 		addr = sg_dma_address(sg);
 		avail = sg_dma_len(sgl);
 
+		chan->total_len += avail;
+
 		do {
 			len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES);
 			if (addr & 0x7)
@@ -666,6 +676,36 @@  static int mmp_pdma_control(struct dma_chan *dchan, enum dma_ctrl_cmd cmd,
 	return ret;
 }
 
+static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan)
+{
+	struct mmp_pdma_desc_sw *sw;
+	u32 curr, done = 0;
+
+	if (chan->dir == DMA_DEV_TO_MEM)
+		curr = readl(chan->phy->base + DTADR(chan->phy->idx));
+	else
+		curr = readl(chan->phy->base + DSADR(chan->phy->idx));
+
+	list_for_each_entry(sw, &chan->chain_running, node) {
+		u32 start;
+		u32 len = sw->desc.dcmd & DCMD_LENGTH;
+
+		if (chan->dir == DMA_DEV_TO_MEM)
+			start = sw->desc.dtadr;
+		else
+			start = sw->desc.dsadr;
+
+		if (curr >= start && curr <= (start + len)) {
+			done += curr - start;
+			break;
+		}
+
+		done += len;
+	}
+
+	return chan->total_len - done;
+}
+
 static enum dma_status mmp_pdma_tx_status(struct dma_chan *dchan,
 			dma_cookie_t cookie, struct dma_tx_state *txstate)
 {
@@ -675,6 +715,7 @@  static enum dma_status mmp_pdma_tx_status(struct dma_chan *dchan,
 
 	spin_lock_irqsave(&chan->desc_lock, flags);
 	ret = dma_cookie_status(dchan, cookie, txstate);
+	txstate->residue = mmp_pdma_residue(chan);
 	spin_unlock_irqrestore(&chan->desc_lock, flags);
 
 	return ret;