Skip to contentSkip to conversation

usb: ehci: fix update qtd->token in qh_append_tds

PATCH New ming.lei at canonical.com 7 participants 26 messages Aug 27, 2011 – Aug 30, 2011

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

From: Ming Lei <[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

---
 drivers/usb/host/ehci-q.c |   14 ++++++++++++++
 1 files changed, 14 insertions(+), 0 deletions(-)
drivers/usb/host/ehci-q.c +14 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
 			wmb ();
 			dummy->hw_token = token;
 
+			/* The mb() below is added to make sure that
+			 * 'token' can be writen into qtd, so that ehci
+			 * HC can see the up-to-date qtd descriptor. On
+			 * some archs(at least on ARM Cortex A9 dual core),
+			 * writing into coherenet memory doesn't mean the
+			 * value written can reach physical memory
+			 * immediately, and the value may be buffered
+			 * inside L2 cache. 'dummy->hw_token = token;'
+			 * after mb() is added for obeying correct mb()
+			 * usage.
+			 * */
+			mb();
+			token = dummy->hw_token;
+
 			urb->hcpriv = qh_get (qh);
 		}
 	}
Signature · 1 line
1.7.4.1
#1 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 10 lines · ming.lei at canonical.com

From: Ming Lei<[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Who said "one mb() on ARM is enough to flush L2 cache" ? It's just a memory barrier and it doesn't flush any cache. What it cleans is the CPU write buffers and the L2 cache write buffers.

Quoted 4 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

Though number looks great, how is the below patch helping to get better numbers.

Quoted 17 lines
---
  drivers/usb/host/ehci-q.c |   14 ++++++++++++++
  1 files changed, 14 insertions(+), 0 deletions(-)
drivers/usb/host/ehci-q.c +4 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
  			wmb ();
  			dummy->hw_token = token;
 
+			/* The mb() below is added to make sure that
+			 * 'token' can be writen into qtd, so that ehci
+			 * HC can see the up-to-date qtd descriptor. On
+			 * some archs(at least on ARM Cortex A9 dual core),

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1098

Quoted 10 lines
drivers/usb/host/ehci-q.c +10 -0
+			 * writing into coherenet memory doesn't mean the
+			 * value written can reach physical memory
+			 * immediately, and the value may be buffered
+			 * inside L2 cache. 'dummy->hw_token = token;'
+			 * after mb() is added for obeying correct mb()
+			 * usage.
+			 * */
+			mb();
+			token = dummy->hw_token;
+

This patch at max fix some corruption if the memory buffer used is buffer-able. Infact I see there is already a write memory barrier above. So just pushing that down by one line should be enough.

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1100

Quoted 2 lines
drivers/usb/host/ehci-q.c +0 -0
  			dummy->hw_token = token;
  			wmb ();

Is there another patch along with this which removes, some cache clean on this buffer ?

Regards
Santosh

author
#2 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 11 lines · Santosh

Hi,

Quoted 8 lines · ming.lei at canonical.com

From: Ming Lei<[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...),
see details from link of https://bugs.launchpad.net/bugs/709245.

Quoted 8 lines
Quoted 4 lines

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Who said "one mb() on ARM is enough to flush L2 cache" ? It's just a memory barrier and it doesn't flush any cache. What it cleans is the CPU write buffers and the L2 cache write buffers

Yes, your description is more accurate, it should be L2 write buffer, I see mb() will call outer_sync() on ARM.

Quoted 7 lines
Quoted 5 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

Though number looks great, how is the below patch helping to get better numbers.

The patch can make ehci HC see the up-to-date qtd, so make usb transaction executed correctly. If a qtd->token is not updated, maybe IOC is not set or set very
late, so interrupt can't be triggered in time, also mistaken 'total bytes to transfer'
can make HC work badly.

In fact, I have traced the problem and found ehci irq is often delayed by ehci HC.
also sometimes ehci irq is lost, so I start to trace ehci driver and find the problem here.

Quoted 26 lines
Quoted 26 lines

---
?drivers/usb/host/ehci-q.c | ? 14 ++++++++++++++ ?1 files changed, 14 insertions(+), 0 deletions(-)

drivers/usb/host/ehci-q.c +11 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
 ? ? ? ? ? ? ? ? ? ? ? ?wmb ();
 ? ? ? ? ? ? ? ? ? ? ? ?dummy->hw_token = token;
 
+ ? ? ? ? ? ? ? ? ? ? ? /* The mb() below is added to make sure that
+ ? ? ? ? ? ? ? ? ? ? ? ?* 'token' can be writen into qtd, so that ehci
+ ? ? ? ? ? ? ? ? ? ? ? ?* HC can see the up-to-date qtd descriptor. On
+ ? ? ? ? ? ? ? ? ? ? ? ?* some archs(at least on ARM Cortex A9 dual
 core),
+ ? ? ? ? ? ? ? ? ? ? ? ?* writing into coherenet memory doesn't mean the
+ ? ? ? ? ? ? ? ? ? ? ? ?* value written can reach physical memory
+ ? ? ? ? ? ? ? ? ? ? ? ?* immediately, and the value may be buffered
+ ? ? ? ? ? ? ? ? ? ? ? ?* inside L2 cache. 'dummy->hw_token = token;'
+ ? ? ? ? ? ? ? ? ? ? ? ?* after mb() is added for obeying correct mb()
+ ? ? ? ? ? ? ? ? ? ? ? ?* usage.
+ ? ? ? ? ? ? ? ? ? ? ? ?* */
Quoted 8 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1099

Quoted 3 lines
drivers/usb/host/ehci-q.c +3 -0
+ ? ? ? ? ? ? ? ? ? ? ? mb();
+ ? ? ? ? ? ? ? ? ? ? ? token = dummy->hw_token;
+

This patch at max fix some corruption if the memory buffer used is buffer-able. Infact I see there is already a write memory barrier above. So just pushing that down by one line should be enough.

The above wmb is used to order updating qtd->hw_next and dummy->hw_token.

Quoted 6 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1101

Quoted 3 lines
drivers/usb/host/ehci-q.c +0 -0
 ? ? ? ? ? ? ? ? ? ? ? dummy->hw_token = token;
 ? ? ? ? ? ? ? ? ? ? ? wmb ();

Is there another patch along with this which removes, some cache clean on this buffer ?

No, I am not sure the wmb should be merged with mb().

thanks,
--
Ming Lei

#3 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 39 lines · Ming Lei
Quoted 11 lines · Santosh

Hi,

Quoted 8 lines · ming.lei at canonical.com

From: Ming Lei<[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...),
see details from link of https://bugs.launchpad.net/bugs/709245.

Quoted 8 lines
Quoted 4 lines

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Who said "one mb() on ARM is enough to flush L2 cache" ? It's just a memory barrier and it doesn't flush any cache. What it cleans is the CPU write buffers and the L2 cache write buffers

Yes, your description is more accurate, it should be L2 write buffer, I see mb() will call outer_sync() on ARM.

Quoted 7 lines
Quoted 5 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

Though number looks great, how is the below patch helping to get better numbers.

The patch can make ehci HC see the up-to-date qtd, so make usb transaction executed correctly. If a qtd->token is not updated, maybe IOC is not set or set very
late, so interrupt can't be triggered in time, also mistaken 'total bytes to transfer'
can make HC work badly.

Quoted 5 lines

In fact, I have traced the problem and found ehci irq is often delayed by ehci HC.
also sometimes ehci irq is lost, so I start to trace ehci driver and find the problem here.

You are missing all of this important description in change log.

Quoted 39 lines
Quoted 26 lines
Quoted 26 lines
---
  drivers/usb/host/ehci-q.c |   14 ++++++++++++++
  1 files changed, 14 insertions(+), 0 deletions(-)
drivers/usb/host/ehci-q.c +11 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
                        wmb ();
                        dummy->hw_token = token;
 
+                       /* The mb() below is added to make sure that
+                        * 'token' can be writen into qtd, so that ehci
+                        * HC can see the up-to-date qtd descriptor. On
+                        * some archs(at least on ARM Cortex A9 dual
 core),
+                        * writing into coherenet memory doesn't mean the
+                        * value written can reach physical memory
+                        * immediately, and the value may be buffered
+                        * inside L2 cache. 'dummy->hw_token = token;'
+                        * after mb() is added for obeying correct mb()
+                        * usage.
+                        * */
Quoted 8 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1098

Quoted 3 lines
drivers/usb/host/ehci-q.c +3 -0
+                       mb();
+                       token = dummy->hw_token;
+

This patch at max fix some corruption if the memory buffer used is buffer-able. Infact I see there is already a write memory barrier above. So just pushing that down by one line should be enough.

The above wmb is used to order updating qtd->hw_next and dummy->hw_token.

Quoted 1 line
Quoted 8 lines
Quoted 5 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1101

Quoted 2 lines
drivers/usb/host/ehci-q.c +0 -0
                       dummy->hw_token = token;
                       wmb ();

Is there another patch along with this which removes, some cache clean on this buffer ?

No, I am not sure the wmb should be merged with mb().

It will work but I leave that call to you since I don't understand much what that function is doing. Infact I see, excessive usage of wmb() in qh_append_tds()

Regards
Santosh

#4 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

In reply to ming.lei at canonical.com
Quoted 9 lines · ming.lei at canonical.com

From: Ming Lei <[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Really? A mb() should not be flushing any caches, it's just a memory barrier. Or is ARM somehow "special" in this way?

Quoted 3 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

That's impressive, but I don't think this is really the proper way to do this...

Quoted 16 lines
---
 drivers/usb/host/ehci-q.c |   14 ++++++++++++++
 1 files changed, 14 insertions(+), 0 deletions(-)
drivers/usb/host/ehci-q.c +3 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
 			wmb ();
 			dummy->hw_token = token;
 
+			/* The mb() below is added to make sure that
+			 * 'token' can be writen into qtd, so that ehci
+			 * HC can see the up-to-date qtd descriptor. On

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1097

Quoted 10 lines
drivers/usb/host/ehci-q.c +10 -0
+			 * some archs(at least on ARM Cortex A9 dual core),
+			 * writing into coherenet memory doesn't mean the
+			 * value written can reach physical memory
+			 * immediately, and the value may be buffered
+			 * inside L2 cache. 'dummy->hw_token = token;'
+			 * after mb() is added for obeying correct mb()
+			 * usage.
+			 * */
+			mb();
+			token = dummy->hw_token;

Your comment does not match the code, so something is wrong here.

greg k-h

author
#5 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 7 lines · Greg KH
Quoted 6 lines · ming.lei at canonical.com

From: Ming Lei <[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

Quoted 6 lines
Quoted 3 lines

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Really? ?A mb() should not be flushing any caches, it's just a memory barrier. ?Or is ARM somehow "special" in this way?

As Santosh pointed out, mb on ARM will flush L2 write buffer. The description here is wrong.

I think the below should make the writing reach into memory on all ARCH after ' token = dummy->hw_token;' is executed.

                       dummy->hw_token = token;
                       mb()
                       token = dummy->hw_token;

The above is the idea introduced to fix the problem.

Quoted 21 lines
Quoted 4 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

That's impressive, but I don't think this is really the proper way to do this...

Quoted 13 lines

---
?drivers/usb/host/ehci-q.c | ? 14 ++++++++++++++ ?1 files changed, 14 insertions(+), 0 deletions(-)

drivers/usb/host/ehci-q.c +0 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
 ? ? ? ? ? ? ? ? ? ? ? wmb ();
 ? ? ? ? ? ? ? ? ? ? ? dummy->hw_token = token;
 
Quoted 15 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1097

Quoted 13 lines
drivers/usb/host/ehci-q.c +13 -0
+ ? ? ? ? ? ? ? ? ? ? /* The mb() below is added to make sure that
+ ? ? ? ? ? ? ? ? ? ? ?* 'token' can be writen into qtd, so that ehci
+ ? ? ? ? ? ? ? ? ? ? ?* HC can see the up-to-date qtd descriptor. On
+ ? ? ? ? ? ? ? ? ? ? ?* some archs(at least on ARM Cortex A9 dual core),
+ ? ? ? ? ? ? ? ? ? ? ?* writing into coherenet memory doesn't mean the
+ ? ? ? ? ? ? ? ? ? ? ?* value written can reach physical memory
+ ? ? ? ? ? ? ? ? ? ? ?* immediately, and the value may be buffered
+ ? ? ? ? ? ? ? ? ? ? ?* inside L2 cache. 'dummy->hw_token = token;'
+ ? ? ? ? ? ? ? ? ? ? ?* after mb() is added for obeying correct mb()
+ ? ? ? ? ? ? ? ? ? ? ?* usage.
+ ? ? ? ? ? ? ? ? ? ? ?* */
+ ? ? ? ? ? ? ? ? ? ? mb();
+ ? ? ? ? ? ? ? ? ? ? token = dummy->hw_token;

Your comment does not match the code, so something is wrong here.

If you mean "L2 cache flush", I confess to the mistaken description, and will update it later. If you mean others, could you help to point it out?

thanks,
--
Ming Lei

#6 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 14 lines · Ming Lei

Hi,

Quoted 11 lines · Greg KH
Quoted 9 lines · ming.lei at canonical.com

From: Ming Lei <[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

Quoted 5 lines
Quoted 2 lines

Really? ?A mb() should not be flushing any caches, it's just a memory barrier. ?Or is ARM somehow "special" in this way?

As Santosh pointed out, mb on ARM will flush L2 write buffer. The description here is wrong.

Then this can't be accepted as-is :)

Quoted 8 lines

I think the below should make the writing reach into memory on all ARCH after ' token = dummy->hw_token;' is executed.

                       dummy->hw_token = token;
                       mb()
                       token = dummy->hw_token;

The above is the idea introduced to fix the problem.

Are you sure? Have you read the documentation about memory barriers to confirm this?

Quoted 36 lines
Quoted 20 lines
Quoted 3 lines

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

That's impressive, but I don't think this is really the proper way to do this...

Quoted 13 lines

---
?drivers/usb/host/ehci-q.c | ? 14 ++++++++++++++ ?1 files changed, 14 insertions(+), 0 deletions(-)

drivers/usb/host/ehci-q.c +0 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
 ? ? ? ? ? ? ? ? ? ? ? wmb ();
 ? ? ? ? ? ? ? ? ? ? ? dummy->hw_token = token;
 
Quoted 15 lines

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1097

Quoted 13 lines
drivers/usb/host/ehci-q.c +13 -0
+ ? ? ? ? ? ? ? ? ? ? /* The mb() below is added to make sure that
+ ? ? ? ? ? ? ? ? ? ? ?* 'token' can be writen into qtd, so that ehci
+ ? ? ? ? ? ? ? ? ? ? ?* HC can see the up-to-date qtd descriptor. On
+ ? ? ? ? ? ? ? ? ? ? ?* some archs(at least on ARM Cortex A9 dual core),
+ ? ? ? ? ? ? ? ? ? ? ?* writing into coherenet memory doesn't mean the
+ ? ? ? ? ? ? ? ? ? ? ?* value written can reach physical memory
+ ? ? ? ? ? ? ? ? ? ? ?* immediately, and the value may be buffered
+ ? ? ? ? ? ? ? ? ? ? ?* inside L2 cache. 'dummy->hw_token = token;'
+ ? ? ? ? ? ? ? ? ? ? ?* after mb() is added for obeying correct mb()
+ ? ? ? ? ? ? ? ? ? ? ?* usage.
+ ? ? ? ? ? ? ? ? ? ? ?* */
+ ? ? ? ? ? ? ? ? ? ? mb();
+ ? ? ? ? ? ? ? ? ? ? token = dummy->hw_token;

Your comment does not match the code, so something is wrong here.

Quoted 2 lines

If you mean "L2 cache flush", I confess to the mistaken description, and will update it later. If you mean others, could you help to point it out?

I mean others, please read the the last 3 lines of the comment and compare that to the code lines you added.

greg k-h

author
#7 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Thanks for your comment.

Quoted 4 lines · Greg KH
Quoted 2 lines

As Santosh pointed out, mb on ARM will flush L2 write buffer. The description here is wrong.

Then this can't be accepted as-is :)

Yes, I will update it in v1, :-)

Quoted 11 lines
Quoted 8 lines

I think the below should make the writing reach into memory on all ARCH after ' token = dummy->hw_token;' is executed.

? ? ? ? ? ? ? ? ? ? ? ?dummy->hw_token = token;
? ? ? ? ? ? ? ? ? ? ? ?mb()
? ? ? ? ? ? ? ? ? ? ? ?token = dummy->hw_token;

The above is the idea introduced to fix the problem.

Are you sure? ?Have you read the documentation about memory barriers to confirm this?

I read the doc again, :-), and it mentions few about mb/wmb/rmb, I think my above description is still not correct. Generally speaking, mb only means there is a order between two accesses.

Now I think only one mb() after 'dummy->hw_token = token;' is enough:
HC will read the up-to-date value of qtd->hw_token after mb() is executed because of the effect of the mb(), which should be guaranteed by mb.

Quoted 2 lines

I mean others, please read the the last 3 lines of the comment and compare that to the code lines you added.

I see now, the comment of the last 3 lines is wrong, should be

* inside L2 cache. 'token = dummy->hw_token'
* after mb() is added for obeying correct mb()
* usage.

But the 'token = dummy->hw_token' after mb() isn't needed any more as described above, is it?

thanks,
--
Ming Lei

author
#8 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 10 lines · Ming Lei
Quoted 2 lines

Are you sure? ?Have you read the documentation about memory barriers to confirm this?

I read the doc again, :-), and it mentions few about mb/wmb/rmb, I think my above description is still not correct. Generally speaking, mb only means there is a order between two accesses.

Now I think only one mb() after 'dummy->hw_token = token;' is enough:
HC will read the up-to-date value of qtd->hw_token after mb() is executed because of the effect of the mb(), which should be guaranteed by mb.

Looks like there is still another similar problem in qh_link_async():
the last wmb
should be changed into mb, because HC will read 'head->hw->hw_next' from qh descriptor and this pointer in qh is read only for HC. But this problem can't be observed on ARM, since wmb on ARM is same with mb.

thanks,
--
Ming Lei

maintainer
#9 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 5 lines · Ming Lei

Looks like there is still another similar problem in qh_link_async():
the last wmb
should be changed into mb, because HC will read 'head->hw->hw_next' from qh descriptor and this pointer in qh is read only for HC. But this problem can't be observed on ARM, since wmb on ARM is same with mb.

It doesn't matter what the HC does -- the wmb() instruction is executed by the CPU, not the HC. The point of that instruction is to make sure that the

	qh->hw->hw_next = head->hw->hw_next;

line (and all the preceding lines as well) is ordered before the

	head->hw->hw_next = dma;

3C
line. Since both of these lines are writes, not reads, it suffices to use wmb() rather than mb().

Alan Stern

author
#10 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 12 lines · Alan Stern
Quoted 5 lines · Ming Lei

Looks like there is still another similar problem in qh_link_async():
the last wmb
should be changed into mb, because HC will read 'head->hw->hw_next' from qh descriptor and this pointer in qh is read only for HC. But this problem can't be observed on ARM, since wmb on ARM is same with mb.

It doesn't matter what the HC does -- the wmb() instruction is executed by the CPU, not the HC. ?The point of that instruction is to make sure that the

Quoted 8 lines

? ? ? ?qh->hw->hw_next = head->hw->hw_next;

line (and all the preceding lines as well) is ordered before the

? ? ? ?head->hw->hw_next = dma;
3C
line. ?Since both of these lines are writes, not reads, it suffices to use wmb() rather than mb().

oops, I added a wmb() after the line of 'head->hw->hw_next = dma;', in my debug code, and not found that it is my local modification before sending out last mail. (I described it as 'the last wmb' in fact), sorry for the noise, please ignore it.

thanks,
--
Ming Lei

maintainer
#11 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

In reply to Ming Lei
Quoted 7 lines · Ming Lei

I read the doc again, :-), and it mentions few about mb/wmb/rmb, I think my above description is still not correct. Generally speaking, mb only means there is a order between two accesses.

Now I think only one mb() after 'dummy->hw_token = token;' is enough:
HC will read the up-to-date value of qtd->hw_token after mb() is executed because of the effect of the mb(), which should be guaranteed by mb.

I've been following this whole discussion. I didn't understand the idea behind the original patch, and I don't understand this. What reason is there for adding a memory barrier after the "dummy->hw_token = token" line? Such a barrier would not accomplish anything useful, because there are no later memory accesses which need to be ordered with respect to that line.

(By the way, Santosh appears to be right about the earlier wmb() in this routine. As far as I can see, it isn't needed. David Brownell probably wrote it just as a precaution.)

Quoted 11 lines
Quoted 2 lines

I mean others, please read the the last 3 lines of the comment and compare that to the code lines you added.

I see now, the comment of the last 3 lines is wrong, should be

* inside L2 cache. 'token = dummy->hw_token'
* after mb() is added for obeying correct mb()
* usage.

But the 'token = dummy->hw_token' after mb() isn't needed any more as described above, is it?

I don't see why it was ever needed at all. The compiler will realize that token is a dead variable at that point in the routine and will optimize the new line away.

Quoted 4 lines

The patch can make ehci HC see the up-to-date qtd, so make usb transaction executed correctly. If a qtd->token is not updated, maybe IOC is not set or set very late, so interrupt can't be triggered in time, also mistaken 'total bytes to transfer' can make HC work badly.

This doesn't make any sense. qtd becomes the new dummy; by the time the HC sees qtd the only important bit set in qtd->hw_token is the HALT bit.

Quoted 2 lines

In fact, I have traced the problem and found ehci irq is often delayed by ehci HC. also sometimes ehci irq is lost, so I start to trace ehci driver and find the problem here.

I don't think you have identified the underlying cause correctly. Something else must be responsible.

Alan Stern

author
#12 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Thanks for your comment.

Quoted 10 lines · Alan Stern
Quoted 7 lines · Ming Lei

I read the doc again, :-), and it mentions few about mb/wmb/rmb, I think my above description is still not correct. Generally speaking, mb only means there is a order between two accesses.

Now I think only one mb() after 'dummy->hw_token = token;' is enough:
HC will read the up-to-date value of qtd->hw_token after mb() is executed because of the effect of the mb(), which should be guaranteed by mb.

Quoted 6 lines

I've been following this whole discussion. ?I didn't understand the idea behind the original patch, and I don't understand this. ?What reason is there for adding a memory barrier after the "dummy->hw_token = token" line? ?Such a barrier would not accomplish anything useful, because there are no later memory accesses which need to be ordered with respect to that line.

Here, mb is used to synchronize between writing of CPU and reading of ehci HC, see below:

	CPU								EHCI
	dummy->hw_token = token;
	mb
									read  dummy->hw_token

The mb() introduced was supposed to be used to make sure that EHCI can see the correct value of dummy->hw_token. If EHCI can't get the correct hw_token in time, it will work badly, such as irq delay or irq lost which may be caused by mistaken 'IOC' or 'total bytes to transfer' in hw_token. This is just what the patch is to fix.

But I think the above is still not correct, and the correct way may be like below:

	CPU								EHCI
	dummy->hw_token = token;
	wmb
	qh->hw->hw_qtd_next = QTD_NEXT(ehci, dummy->qtd_dma);
									fetch next qtd from qh->hw->hw_qtd_next
									read  qtd->hw_token

The problem is that qh has already linked dummy into its queue before(as did in qh_update), so even after 'dummy->hw_token = token;' is executed on CPU, EHCI still may not fetch the up-to-date value of the qtd(dummy in here), and this should
be the root cause, IMO.

I will figure out a elegant way to handle this race.

Quoted 13 lines

(By the way, Santosh appears to be right about the earlier wmb() in this routine. ?As far as I can see, it isn't needed. ?David Brownell probably wrote it just as a precaution.)

Quoted 9 lines
Quoted 2 lines

I mean others, please read the the last 3 lines of the comment and compare that to the code lines you added.

I see now, the comment of the last 3 lines is wrong, should be

? ? ? ? ? ? ? ? ? ? ? ? * inside L2 cache. 'token = dummy->hw_token' ? ? ? ? ? ? ? ? ? ? ? ? ?* after mb() is added for obeying correct mb() ? ? ? ? ? ? ? ? ? ? ? ? ?* usage.

Quoted 6 lines
Quoted 2 lines

But the 'token = dummy->hw_token' after mb() isn't needed any more as described above, ?is it?

I don't see why it was ever needed at all. ?The compiler will realize that token is a dead variable at that point in the routine and will optimize the new line away.

Yes, it is wrong, as I said before.

Quoted 9 lines
Quoted 5 lines

The patch can make ehci HC see the up-to-date qtd, so make usb transaction executed correctly. If a qtd->token is not updated, maybe IOC is not set or set very late, so interrupt can't be triggered in time, also mistaken 'total bytes to transfer' can make HC work badly.

This doesn't make any sense. ?qtd becomes the new dummy; by the time the HC sees qtd the only important bit set in qtd->hw_token is the HALT bit.

Here, I mean the 'qtd' is the 'dummy' in code, and I described it in such way because 'dummy' has been scheduled into qh queue, so qtd should be its new identity, a little confusion about the description, :-)

Quoted 6 lines
Quoted 3 lines

In fact, I have traced the problem and found ehci irq is often delayed by ehci HC. also sometimes ehci irq is lost, so I start to trace ehci driver and find the problem here.

I don't think you have identified the underlying cause correctly. Something else must be responsible.

I have explained above, so please point it out if anything is still wrong.

thanks,
--
Ming Lei

maintainer
#13 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 9 lines · Ming Lei
Quoted 6 lines

I've been following this whole discussion. ?I didn't understand the idea behind the original patch, and I don't understand this. ?What reason is there for adding a memory barrier after the "dummy->hw_token = token" line? ?Such a barrier would not accomplish anything useful, because there are no later memory accesses which need to be ordered with respect to that line.

Here, mb is used to synchronize between writing of CPU and reading of ehci HC, see below:

A memory barrier never synchronizes events between two processors (for example, between the CPU and HC). It only affects ordering for the processor it executes on. That's why you always have to use memory barriers in pairs for inter-CPU synchronization: one memory barrier instruction for each CPU.

Quoted 7 lines
	CPU								EHCI
	dummy->hw_token = token;
	mb
									read  dummy->hw_token

The mb() introduced was supposed to be used to make sure that EHCI can see the correct value of dummy->hw_token.

It won't do that. All it will do is guarantee that the CPU writes out dumy->hw_token before it writes out or reads in any values executed after the mb.

Quoted 3 lines

If EHCI can't get the correct
hw_token in time, it will work badly, such as irq delay or irq lost which may be caused by mistaken 'IOC' or 'total bytes to transfer' in hw_token.

No. All that will happen is that the HC will execute the qTD a little later, when it does read the correct hw_token. No IRQs will be lost, and there will be no mistaken IOC or "total bytes to transfer" values.

Quoted 2 lines

This is just
what the patch is to fix.

The patch won't fix this. You're trying to force the CPU to write out dummy->hw_token more quickly. A memory barrier won't do that. And even if the CPU does write out the value more quickly, that doesn't guarantee it will be in time for the HC to see the new value right away.

Quoted 7 lines

But I think the above is still not correct, and the correct way may be like below:

	CPU								EHCI
	dummy->hw_token = token;
	wmb
	qh->hw->hw_qtd_next = QTD_NEXT(ehci, dummy->qtd_dma);

If you do this write, you will corrupt the hardware list. You _must_ _not_ modify the hardware fields of an active QH.

Besides, qh_link_async() calls qh_refresh() -> qh_update(), which does this already. And it does this correctly, with the appropriate checks to make sure the QH isn't active.

Quoted 8 lines
									fetch next qtd from qh->hw->hw_qtd_next
									read  qtd->hw_token

The problem is that qh has already linked dummy into its queue before(as did in qh_update), so even after 'dummy->hw_token = token;' is executed on CPU, EHCI still may not fetch the up-to-date value of the qtd(dummy in here), and this should
be the root cause, IMO.

There's no way to fix this. The CPU and the HC are independent. The HC is allowed to cache values whenever it likes (within certain limits). If it has already cached an old value from the qTD, there's no way to force it to use a new value. You just have to wait until it refreshes its cache.

Alan Stern

#14 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 3 lines · Alan Stern

It won't do that. All it will do is guarantee that the CPU writes out dumy->hw_token before it writes out or reads in any values executed after the mb.

You're right from the perspective of how things are defined today. However, that isn't how things work on ARM.

With ARMv6 and ARMv7, we have weak memory ordering. This includes so called "DMA coherent" memory. This means that the architecture does not guarantee the order of writes to DMA coherent memory (which is non- cacheable normal memory) without an intervening 'data synchronization barrier' (dsb). Even that may not be sufficient without also poking at the L2 cache controller.

We get around some of that by ensuring that our MMIO read/write macros contain the necessary barriers to ensure that DMA memory is up to date before the DMA agent is programmed. However, this doesn't cater for agents which continue to run in the background.

These agents will need some kind of barrier to ensure that the write becomes visible - there's no way to get around that. Maybe we need yet another new barrier macro...

maintainer
#15 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 15 lines · Russell King - ARM Linux
Quoted 3 lines · Alan Stern

It won't do that. All it will do is guarantee that the CPU writes out dumy->hw_token before it writes out or reads in any values executed after the mb.

You're right from the perspective of how things are defined today. However, that isn't how things work on ARM.

With ARMv6 and ARMv7, we have weak memory ordering. This includes so called "DMA coherent" memory. This means that the architecture does not guarantee the order of writes to DMA coherent memory (which is non- cacheable normal memory) without an intervening 'data synchronization barrier' (dsb). Even that may not be sufficient without also poking at the L2 cache controller.

Quoted 8 lines

We get around some of that by ensuring that our MMIO read/write macros contain the necessary barriers to ensure that DMA memory is up to date before the DMA agent is programmed. However, this doesn't cater for agents which continue to run in the background.

These agents will need some kind of barrier to ensure that the write becomes visible - there's no way to get around that. Maybe we need yet another new barrier macro...

Hmmm. Although the semantics of the various mb() macros were originally defined only for inter-CPU synchronization, I believe they are also supposed to work for guaranteeing the order of accesses to DMA-coherent memory. If that's not the case with ARM, something is seriously wrong. (Maybe I'm wrong about this, but if I am then there's currently _no_ way for the kernel to order DMA-coherent accesses on ARM.)

You know better than I do what is needed to resolve the ordering issue. However, contrary to what the original patch description said, this isn't entirely a matter of making the write visible to the host
controller: No doubt in time the write will eventually become visible anyway. It's a matter of making the write become visible reasonably quickly and in the correct order with respect to other writes.

Is this extra L2-cache "poke" needed for proper ordering, or is it needed merely to flush the write out to memory in a timely manner?

Alan Stern

#16 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 7 lines · Alan Stern

Hmmm. Although the semantics of the various mb() macros were originally defined only for inter-CPU synchronization, I believe they are also supposed to work for guaranteeing the order of accesses to DMA-coherent memory. If that's not the case with ARM, something is seriously wrong. (Maybe I'm wrong about this, but if I am then there's currently _no_ way for the kernel to order DMA-coherent accesses on ARM.)

That is the case with ARM - mb() and wmb() does everything that's required. rmb() is weaker than the other two.

Quoted 6 lines

You know better than I do what is needed to resolve the ordering issue. However, contrary to what the original patch description said, this isn't entirely a matter of making the write visible to the host
controller: No doubt in time the write will eventually become visible anyway. It's a matter of making the write become visible reasonably quickly and in the correct order with respect to other writes.

I'm not entirely sure what the problem is - I think its about a write by the CPU to dma coherent memory being delayed and not being visible to the HC in a timely manner. Either mb() or wmb() placed after the write on ARM will do that - and ARM has no requirement to do a read- back after the barrier.

Quoted 2 lines

Is this extra L2-cache "poke" needed for proper ordering, or is it needed merely to flush the write out to memory in a timely manner?

Both, though primerily it's about ensuring correct ordering. A side effect of it is that it will flush all pending writes in L2 before completing.

maintainer
#17 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 7 lines · Russell King - ARM Linux
Quoted 6 lines

You know better than I do what is needed to resolve the ordering issue. However, contrary to what the original patch description said, this isn't entirely a matter of making the write visible to the host
controller: No doubt in time the write will eventually become visible anyway. It's a matter of making the write become visible reasonably quickly and in the correct order with respect to other writes.

Quoted 5 lines

I'm not entirely sure what the problem is - I think its about a write by the CPU to dma coherent memory being delayed and not being visible to the HC in a timely manner. Either mb() or wmb() placed after the write on ARM will do that - and ARM has no requirement to do a read- back after the barrier.

Okay, then this needs to be done in a way that won't slow down other architectures with an unnecessary memory barrier. And there needs to be a comment in the code explaining that the new mb() instruction isn't being used as a memory barrier but rather to expedite writeback of the L2 cache.

This certainly is starting to sound like something that needs to be addressed in the arch-specific #include files...

Quoted 14 lines
Quoted 2 lines

Is this extra L2-cache "poke" needed for proper ordering, or is it needed merely to flush the write out to memory in a timely manner?

Both, though primerily it's about ensuring correct ordering. A side effect of it is that it will flush all pending writes in L2 before completing.

From the theoretical viewpoint, I think I'm right to say that mb() doesn't need to provide that level of ordering as its supposed to be an inter-CPU barrier - which probably means we need to invent a new barrier to deal with DMA memory ordering. However, given the difficulty of getting the existing barriers placed correctly, I don't think inventing new barriers is a very good idea.

Quoted 5 lines

What we can do is view devices which perform DMA as being strongly ordered with respect to their memory accesses - iow, they have an implicit memory barrier before and after their accesses to memory. This would make the CPUs use of mb() have a conceptual pairing with the DMA agents.

Yes, that's the model I have been using all along. After all, if a DMA master carries out its memory accesses in some random order then it's impossible for the CPU to make any guarantees.

Alan Stern

author
#18 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 15 lines · Alan Stern
Quoted 7 lines · Russell King - ARM Linux
Quoted 6 lines

You know better than I do what is needed to resolve the ordering issue. However, contrary to what the original patch description said, this isn't entirely a matter of making the write visible to the host
controller: No doubt in time the write will eventually become visible anyway. ?It's a matter of making the write become visible reasonably quickly and in the correct order with respect to other writes.

Quoted 5 lines

I'm not entirely sure what the problem is - I think its about a write by the CPU to dma coherent memory being delayed and not being visible to the HC in a timely manner. ?Either mb() or wmb() placed after the write on ARM will do that - and ARM has no requirement to do a read- back after the barrier.

Quoted 5 lines

Okay, then this needs to be done in a way that won't slow down other architectures with an unnecessary memory barrier. ?And there needs to be a comment in the code explaining that the new mb() instruction isn't being used as a memory barrier but rather to expedite writeback of the L2 cache.

If writing to coherent memory can't reach physical memory immediately on other ARCHs, the problem can still happen on these ARCHs. But I am not sure if there are these kind of ARCHs except for ARM.

Anyway, current memory barriers in qh_append_tds() can't prevent the problem from happening on ARM.

If no better solutions, maybe we have to use 'mb()' after 'dummy->hw_token = token'
to fix the problem:

Quoted 31 lines

This certainly is starting to sound like something that needs to be addressed in the arch-specific #include files...

Quoted 14 lines
Quoted 2 lines

Is this extra L2-cache "poke" needed for proper ordering, or is it needed merely to flush the write out to memory in a timely manner?

Both, though primerily it's about ensuring correct ordering. ?A side effect of it is that it will flush all pending writes in L2 before completing.

From the theoretical viewpoint, I think I'm right to say that mb() doesn't need to provide that level of ordering as its supposed to be an inter-CPU barrier - which probably means we need to invent a new barrier to deal with DMA memory ordering. ?However, given the difficulty of getting the existing barriers placed correctly, I don't think inventing new barriers is a very good idea.

Quoted 5 lines

What we can do is view devices which perform DMA as being strongly ordered with respect to their memory accesses - iow, they have an implicit memory barrier before and after their accesses to memory. This would make the CPUs use of mb() have a conceptual pairing with the DMA agents.

Yes, that's the model I have been using all along. ?After all, if a DMA master carries out its memory accesses in some random order then it's impossible for the CPU to make any guarantees.

Alan Stern

#19 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 3 lines · Ming Lei

If writing to coherent memory can't reach physical memory immediately on other ARCHs, the problem can still happen on these ARCHs. But I am not sure if there are these kind of ARCHs except for ARM.

If there is write buffering which prevents outside bus masters from seeing the data, can we really call it coherent memory?

--Mark

author
#20 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

In reply to Alan Stern

Hi,

Quoted 9 lines · Alan Stern
Quoted 7 lines · Ming Lei
Quoted 6 lines

I've been following this whole discussion. ?I didn't understand the idea behind the original patch, and I don't understand this. ?What reason is there for adding a memory barrier after the "dummy->hw_token = token" line? ?Such a barrier would not accomplish anything useful, because there are no later memory accesses which need to be ordered with respect to that line.

Quoted 8 lines
Quoted 2 lines

Here, mb is used to synchronize ?between writing of CPU and reading of ehci HC, see below:

A memory barrier never synchronizes events between two processors (for example, between the CPU and HC). ?It only affects ordering for the processor it executes on. ?That's why you always have to use memory barriers in pairs for inter-CPU synchronization: one memory barrier instruction for each CPU.

IMO, for SMP case, memory barrier pairs(smp_*) are required to synchronize events between two processors. mb/wmb/rmb are mainly used to synchronize events between CPU and device, and one barrier may be enough, for example:

	CPU							device	
	A=1;
	wmb
	B=2;
									read B
									read A

one wmb is used to order 'A=1' and 'B=2', which will make the two write operations reach to physical memory as the order: 'A=1' first, 'B=1' second. Then the device can observe the two write events as the order above, so if device has seen 'B==2', then device will surely see 'A==1'.

Quoted 13 lines
Quoted 8 lines

? ? ? CPU ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? EHCI
? ? ? dummy->hw_token = token;
? ? ? mb
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? read ?dummy->hw_token

The mb() introduced ?was supposed to be used to make sure that EHCI can see the correct value of ?dummy->hw_token.

It won't do that. ?All it will do is guarantee that the CPU writes out dumy->hw_token before it writes out or reads in any values executed after the mb.

Quoted 7 lines
Quoted 3 lines

?If EHCI can't get the correct
hw_token in time, it will work badly, such as irq delay or irq lost which may be caused by mistaken 'IOC' or 'total bytes to transfer' in hw_token.

No. ?All that will happen is that the HC will execute the qTD a little later, when it does read the correct hw_token. ?No IRQs will be lost, and there will be no mistaken IOC or "total bytes to transfer" values.

IMO, the dummy has been linked into queue pointed by qh->hw->hw_qtd_next, so EHCI will fetch dummy qtd and execute the transaction and will not have any delay on the transaction.

Let me explain the problem again: On ARM, the wmb() before 'dummy->hw_token = token;'
will flush l2 write buffer into memory and all parts of 'dummy' except for hw_token field
have reached into memory already, but dummy->hw_token will stay at l2 write buffer
and not reach into memory at this time, so ehci may fetch a inconsistent qtd and execute it,
then mistaken IOC or "total bytes to transfer" are read by EHCI and cause delayed irq
or lost irq.

It is not only a reasoning or guess, and I have traced this kind of fact certainly.

Quoted 13 lines
Quoted 3 lines

?This is just
what the patch is to fix.

The patch won't fix this. ?You're trying to force the CPU to write out dummy->hw_token more quickly. ?A memory barrier won't do that. ?And even if the CPU does write out the value more quickly, that doesn't guarantee it will be in time for the HC to see the new value right away.

Quoted 3 lines

But I think the above is still not correct, and the correct way may be like below:

Quoted 7 lines
Quoted 4 lines

? ? ? CPU ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? EHCI ? ? ? dummy->hw_token = token;
? ? ? wmb
? ? ? qh->hw->hw_qtd_next = QTD_NEXT(ehci, dummy->qtd_dma);

If you do this write, you will corrupt the hardware list. ?You _must_ _not_ modify the hardware fields of an active QH.

Yes, I agree on it. hw_qtd_next is write by HC and qh_refresh(), and it should be written only by cpu when the qh is in idle state.

Quoted 13 lines

Besides, qh_link_async() calls qh_refresh() -> qh_update(), which does this already. ?And it does this correctly, with the appropriate checks to make sure the QH isn't active.

Quoted 8 lines

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? fetch next qtd from qh->hw->hw_qtd_next ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? read ?qtd->hw_token

The problem is that qh has already linked dummy into its queue before(as did in qh_update), so even after 'dummy->hw_token = token;' is executed on CPU, EHCI still may not fetch the up-to-date value of the qtd(dummy in here), and this should
be the root cause, IMO.

Quoted 5 lines

There's no way to fix this. ?The CPU and the HC are independent. ?The HC is allowed to cache values whenever it likes (within certain limits). ?If it has already cached an old value from the qTD, there's no way to force it to use a new value. ?You just have to wait until it refreshes its cache.

I don't think ehci will cache something inside hardware, and it will always fetch qtd linked into qh from memory(not cache) to qh transfer overlay. We can find it in ehci spec 4.10.2.

As I explained above in the 'wmb' example, I hope we can use the current memory barrier rules to fix this kind of problem and avoid to invent new barrier. But because we can't write qh->hw->hw_qtd_next when the qh has been linked into ehci async queue, it is not doable.

thanks,
--
Ming Lei

maintainer
#21 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 4 lines · Ming Lei

IMO, the dummy has been linked into queue pointed by qh->hw->hw_qtd_next, so EHCI will fetch dummy qtd and execute the transaction and will not have any delay on the transaction.

Quoted 11 lines

Let me explain the problem again: On ARM, the wmb() before 'dummy->hw_token = token;'
will flush l2 write buffer into memory and all parts of 'dummy' except for hw_token field
have reached into memory already, but dummy->hw_token will stay at l2 write buffer
and not reach into memory at this time, so ehci may fetch a inconsistent qtd and execute it,
then mistaken IOC or "total bytes to transfer" are read by EHCI and cause delayed irq
or lost irq.

No. Even if the HC reads dummy before dummy->hw_token has been written out to memory from the L2 cache, it will not see any inconsistencies. It will see the old value in hw_token, which has the ACTIVE bit clear. Therefore it will not try to execute the qTD but will move on to the next QH. See what the fourth paragraph in section 4.10.2 of the EHCI spec says about the case where a qTD's ACTIVE bit is set to 0.

Some EHCI implementations have a quirk, in which they perform the overlay even when ACTIVE is clear. But even these implementations won't try to execute the qTD, because the old value of dummy->hw_token also has the HALT bit set.

Quoted 2 lines

It is not only a reasoning or guess, and I have traced this kind of fact certainly.

If your controller behaves as you suggest then it is buggy. And in that case, adding another memory barrier won't fix it. There is still the possibility that the HC will read dummy during the brief time after the existing wmb() and before the CPU has written dummy->hw_token.

Alan Stern

author
#22 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi,

Quoted 18 lines · Alan Stern
Quoted 4 lines · Ming Lei

IMO, the dummy has been linked into queue pointed by qh->hw->hw_qtd_next, so EHCI will fetch dummy qtd and execute the transaction and will not have any delay on the transaction.

Quoted 11 lines

Let me explain the problem again: On ARM, the wmb() before 'dummy->hw_token = token;'
will flush l2 write buffer into memory and all parts of 'dummy' except for hw_token field
have reached into memory already, but dummy->hw_token will stay at l2 write buffer
and not reach into memory at this time, so ehci may fetch a inconsistent qtd and execute it,
then mistaken IOC or "total bytes to transfer" ?are read by EHCI and cause delayed irq
or lost irq.

Quoted 6 lines

No. ?Even if the HC reads dummy before dummy->hw_token has been written out to memory from the L2 cache, it will not see any inconsistencies. It will see the old value in hw_token, which has the ACTIVE bit clear. Therefore it will not try to execute the qTD but will move on to the next QH. ?See what the fourth paragraph in section 4.10.2 of the EHCI spec says about the case where a qTD's ACTIVE bit is set to 0.

Suppose HC can see the old value in hw_token, which has the ACTIVE bit clear.

The qtd transaction will not be executed until the new token is flushed into memory.

maintainer
#23 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Quoted 8 lines · Ming Lei

Suppose HC can see the old value in hw_token, which has the ACTIVE bit clear.

The qtd transaction will not be executed until the new token is flushed into memory.
From view of CPU, the irq is still be delayed because ioc irq is generated after the qtd transaction is executed when new token is flushed into memory. The delay
depends on how long the token will be flushed into memory.

That's right.

Quoted 2 lines

From my observation, the delay is commonly over 5ms for CSW, sometimes the delay is more than 20ms, so caused the bad performance.

No wonder! Do you see the same sort of performance degradation when using shared memory to communicate between two CPUs?

Regardless, this is not quite the same as what you were talking about earlier. You specifically mentioned

... so ehci may fetch a inconsistent qtd and execute it, then mistaken IOC or "total bytes to transfer" are read by EHCI and cause delayed irq or lost irq.

Bad performance is different from inconsistencies and lost IRQs.

Quoted 2 lines

Also I am not sure if EHCI can read the old hw_token correctly in this kind of inconsistent memory state.

The memory state is NOT inconsistent! It is consistent at all times, both before and after the new hw_token value is stored. The memory state is just slow to update, that's all. This is a speed issue, not a correctness issue.

Nevertheless, it remains true that you want to add a memory barrier instruction simply in order to speed up a cache writeback, not to force any sort of ordering. This needs to be explained carefully in the code (not just in the patch description!) and it needs to be done in a way that won't affect other architectures.

Also, as you mentioned before, you may want to do the same thing in qh_link_async() just after the

	head->hw->hw_next = dma;

line. Delays in flushing that write would also slow down performance.

Alan Stern

author
#24 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

Hi Alan,

Thanks for your comment.

Quoted 5 lines · Alan Stern

Nevertheless, it remains true that you want to add a memory barrier instruction simply in order to speed up a cache writeback, not to force any sort of ordering. ?This needs to be explained carefully in the code (not just in the patch description!) and it needs to be done in a way that won't affect other architectures.

OK, will do it.

Quoted 6 lines

Also, as you mentioned before, you may want to do the same thing in qh_link_async() just after the

? ? ? ?head->hw->hw_next = dma;

line. ?Delays in flushing that write would also slow down performance.

Will do it too.

thanks,
--
Ming Lei

#25 lore raw

PATCH 1/1 usb: ehci: fix update qtd->token in qh_append_tds

In reply to ming.lei at canonical.com

Hello.

Quoted 25 lines · ming.lei at canonical.com

From: Ming Lei<[email protected]>

This patch fixs one performance bug on ARM Cortex A9 dual core platform, which has been reported on quite a few ARM machines(OMAP4, Tegra 2, snowball...), see details from link of https://bugs.launchpad.net/bugs/709245.

In fact, one mb() on ARM is enough to flush L2 cache, but 'dummy->hw_token = token;' after mb() is added just for obeying correct mb() usage.

The patch has been tested ok on OMAP4 panda A1 board, the performance of 'dd' over usb mass storage can be increased from 4~5MB/sec to 14~16MB/sec after applying this patch.

---
  drivers/usb/host/ehci-q.c |   14 ++++++++++++++
  1 files changed, 14 insertions(+), 0 deletions(-)
drivers/usb/host/ehci-q.c +0 -0
index 0917e3a..65b5021 100644
@@ -1082,6 +1082,20 @@ static struct ehci_qh *qh_append_tds (
  			wmb ();

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1092

Quoted 10 lines
drivers/usb/host/ehci-q.c +8 -0
  			dummy->hw_token = token;
 
+			/* The mb() below is added to make sure that
+			 * 'token' can be writen into qtd, so that ehci
+			 * HC can see the up-to-date qtd descriptor. On
+			 * some archs(at least on ARM Cortex A9 dual core),
+			 * writing into coherenet memory doesn't mean the
+			 * value written can reach physical memory
+			 * immediately, and the value may be buffered
+			 * inside L2 cache. 'dummy->hw_token = token;'
    You meant 'token = dummy->hw_token;'?

drivers/usb/host/ehci-q.c static struct ehci_qh *qh_append_tds ( · line 1101

Quoted 9 lines
drivers/usb/host/ehci-q.c +6 -0
+			 * after mb() is added for obeying correct mb()
+			 * usage.
+			 * */
+			mb();
+			token = dummy->hw_token;
+
  			urb->hcpriv = qh_get (qh);
  		}
  	}

WBR, Sergei