U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 0/1] USB xHCI wrong act_len calculation in case of using multipe TRB
@ 2025-03-21 16:58 bigunclemax
  2025-03-21 16:58 ` [PATCH v1 1/1] usb: xhci: fix calculation of act_len " bigunclemax
  2025-04-07 17:41 ` [PATCH v1 0/1] USB xHCI wrong act_len calculation " marcan
  0 siblings, 2 replies; 3+ messages in thread
From: bigunclemax @ 2025-03-21 16:58 UTC (permalink / raw)
  Cc: bigunclemax, Bin Meng, Marek Vasut, Tom Rini, Godfrey Mwangi,
	Janne Grunau, Mattijs Korpershoek, Hector Martin, u-boot

From: Maksim Kiselev <bigunclemax@gmail.com>

Hello everyone!

I've encountered an issue where the actual length of received data is
calculated incorrectly in the case of a multiple TRB request.

Below, I'll try to describe the essence of the problem:

A USB-ethernet adapter ASIX ax88179 is connected to my board Li4pi,
and I'm sending a DHCP request to the server via dhpc command.

The response from the DHCP server always has the same length (0x168).
However, in some cases, I noticed that the received response had
an incorrect length and network subsystem is completely ingnore
incoming packets.

The problem turned out to be in the xhci_bulk_tx() function.
I added some debugging[1] to better understand what's happening.

Here's the log from a working case:
```
    dev=000000057f786b90, pipe=c0010283, buffer=000000057f787540, length=20480
    PUSH. trb_len: 0x5000
    POP. trb_len: 0x4e98, comp_code: 0xd
    udev->act_len: 0x168
```

And here's the log from a non-working case:
```
    dev=000000057f78b610, pipe=c0010283, buffer=000000057f78bfc0, length=20480
    PUSH. trb_len: 0x4040
    PUSH. trb_len: 0xfc0
    POP. trb_len: 0x3ed8, comp_code: 0xd
    POP. trb_len: 0x0, comp_code: 0x1
    udev->act_len: 0x1128
```
As you can see, in the second case, the buffer spans a 64KB boundary
(buffer=000000057f78bfc0 + 0x5000).
Therefore, it is split into two TRBs with lengths 0x4040 and 0xfc0.

Then, act_len is calculated as the difference between length and
trans_event.transfer_len:
```
0x5000 - 0x3ed8 = 0x1128
```

However, it seems that this is not entirely correct,
and act_len should be calculated as:

```
trb_buff_len - trans_event.transfer_len
(0x4040 - 0x3ed8 = 0x168)
```

0x168 is the correct length, which is the same as the length
obtained in the first case where network rx works fine.

Also I looked at the Linux xhci-ring code where urb->actual_length
calculated as:
[2]
```
    requested = td->urb->transfer_buffer_length;
    remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
```
[3]
```
    td->urb->actual_length = requested - remaining;
```

Perhaps I missed something or misunderstood, so I would appreciate any help.

---

[1] Patch for debug output
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 89d2e54f20a..b1433a16a99 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -791,6 +791,8 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
                trb_fields[2] = length_field;
                trb_fields[3] = field | TRB_TYPE(TRB_NORMAL);
 
+               debug("PUSH. trb_len: 0x%x\n", trb_buff_len);
+
                last_transfer_trb_addr = queue_trb(ctrl, ring, (num_trbs > 1), trb_fields);
 
                --num_trbs;
@@ -816,6 +818,10 @@ again:
                return -ETIMEDOUT;
        }
 
+       debug("POP. trb_len: 0x%x, comp_code: 0x%x\n",
+             (int)EVENT_TRB_LEN(le32_to_cpu(event->trans_event.transfer_len)),
+             GET_COMP_CODE(le32_to_cpu(event->trans_event.transfer_len)));
+
        if ((uintptr_t)(le64_to_cpu(event->trans_event.buffer)) !=
            (uintptr_t)last_transfer_trb_addr) {
                available_length -=
@@ -831,6 +837,7 @@ again:
        available_length -= first_trb_trimmed_sz;
 
        record_transfer_result(udev, event, available_length);
+       debug("udev->act_len: 0x%x\n", udev->act_len);
        xhci_acknowledge_event(ctrl);
        xhci_inval_cache((uintptr_t)buffer, length);
        xhci_dma_unmap(ctrl, buf_64, length);


[2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/host/xhci-ring.c?h=v6.14-rc7#n2346
[3] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/host/xhci-ring.c?h=v6.14-rc7#n2413

Maksim Kiselev (1):
  usb: xhci: fix calculation of act_len in case of using multipe TRB

 drivers/usb/host/xhci-ring.c | 5 +++++
 1 file changed, 5 insertions(+)

-- 
2.45.2


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH v1 1/1] usb: xhci: fix calculation of act_len in case of using multipe TRB
  2025-03-21 16:58 [PATCH v1 0/1] USB xHCI wrong act_len calculation in case of using multipe TRB bigunclemax
@ 2025-03-21 16:58 ` bigunclemax
  2025-04-07 17:41 ` [PATCH v1 0/1] USB xHCI wrong act_len calculation " marcan
  1 sibling, 0 replies; 3+ messages in thread
From: bigunclemax @ 2025-03-21 16:58 UTC (permalink / raw)
  Cc: bigunclemax, Bin Meng, Marek Vasut, Tom Rini, Neal Gompa,
	Janne Grunau, Hector Martin, Godfrey Mwangi, u-boot

From: Maksim Kiselev <bigunclemax@gmail.com>

In the case when the transfer buffer spans 64KB boundary, one trb is
divided into several. In this case, the size of the first trb buffer
sets equal to the size of truncated to 64K boundary.

However, this is not taken into account when calculating the actual
length.

To fix this need to subtract the trimmed size of first TRB buff
from the available_length.

Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
 drivers/usb/host/xhci-ring.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 34eb4536f0e..89d2e54f20a 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -659,6 +659,7 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
 	u64 buf_64 = xhci_dma_map(ctrl, buffer, length);
 	dma_addr_t last_transfer_trb_addr;
 	int available_length;
+	int first_trb_trimmed_sz = 0;
 
 	debug("dev=%p, pipe=%lx, buffer=%p, length=%d\n",
 		udev, pipe, buffer, length);
@@ -740,6 +741,8 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
 
 	if (trb_buff_len > length)
 		trb_buff_len = length;
+	else
+		first_trb_trimmed_sz = length - trb_buff_len;
 
 	first_trb = true;
 
@@ -825,6 +828,8 @@ again:
 	BUG_ON(TRB_TO_SLOT_ID(field) != slot_id);
 	BUG_ON(TRB_TO_EP_INDEX(field) != ep_index);
 
+	available_length -= first_trb_trimmed_sz;
+
 	record_transfer_result(udev, event, available_length);
 	xhci_acknowledge_event(ctrl);
 	xhci_inval_cache((uintptr_t)buffer, length);
-- 
2.45.2


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH v1 0/1] USB xHCI wrong act_len calculation in case of using multipe TRB
  2025-03-21 16:58 [PATCH v1 0/1] USB xHCI wrong act_len calculation in case of using multipe TRB bigunclemax
  2025-03-21 16:58 ` [PATCH v1 1/1] usb: xhci: fix calculation of act_len " bigunclemax
@ 2025-04-07 17:41 ` marcan
  1 sibling, 0 replies; 3+ messages in thread
From: marcan @ 2025-04-07 17:41 UTC (permalink / raw)
  To: bigunclemax
  Cc: Bin Meng, Marek Vasut, Tom Rini, Godfrey Mwangi, Janne Grunau,
	Mattijs Korpershoek, u-boot

Hi,

On 2025/03/22 1:58, bigunclemax@gmail.com wrote:
> From: Maksim Kiselev <bigunclemax@gmail.com>
> 
> Hello everyone!
> 
> I've encountered an issue where the actual length of received data is
> calculated incorrectly in the case of a multiple TRB request.
> 
> Below, I'll try to describe the essence of the problem:
> 
> A USB-ethernet adapter ASIX ax88179 is connected to my board Li4pi,
> and I'm sending a DHCP request to the server via dhpc command.
> 
> The response from the DHCP server always has the same length (0x168).
> However, in some cases, I noticed that the received response had
> an incorrect length and network subsystem is completely ingnore
> incoming packets.
> 
> The problem turned out to be in the xhci_bulk_tx() function.
> I added some debugging[1] to better understand what's happening.
> 
> Here's the log from a working case:
> ```
>      dev=000000057f786b90, pipe=c0010283, buffer=000000057f787540, length=20480
>      PUSH. trb_len: 0x5000
>      POP. trb_len: 0x4e98, comp_code: 0xd
>      udev->act_len: 0x168
> ```
> 
> And here's the log from a non-working case:
> ```
>      dev=000000057f78b610, pipe=c0010283, buffer=000000057f78bfc0, length=20480
>      PUSH. trb_len: 0x4040
>      PUSH. trb_len: 0xfc0
>      POP. trb_len: 0x3ed8, comp_code: 0xd
>      POP. trb_len: 0x0, comp_code: 0x1
>      udev->act_len: 0x1128

This looks like a bug in your host controller. Completion code 0xd is 
"short packet", which is as expected. However, completion code 0x1 is 
"success", which is unexpected. According to xHCI 4.10.1.1.2:

> If the Short Packet occurred while processing a Transfer TRB with only an ISP
> flag set, then two events shall be generated for the transfer; one for the Transfer
> TRB that the Short Packet occurred on, and a second for the last TRB with the
> IOC flag set. Table 6-38 defines the Completion Code and TRB Transfer Length
> for the first event. In the second event, the Completion Code shall be set to
> Short Packet, and the TRB Transfer Length should be set to the same value that
> was reported by the initial Short Packet Event.

So the second event should have had completion code 0xd too.

What host controller is this? Though this is kind of irrelevant, because 
the correct fix would ignore this quirk anyway (see below)

> ```
> As you can see, in the second case, the buffer spans a 64KB boundary
> (buffer=000000057f78bfc0 + 0x5000).
> Therefore, it is split into two TRBs with lengths 0x4040 and 0xfc0.
> 
> Then, act_len is calculated as the difference between length and
> trans_event.transfer_len:
> ```
> 0x5000 - 0x3ed8 = 0x1128
> ```
> 
> However, it seems that this is not entirely correct,
> and act_len should be calculated as:
> 
> ```
> trb_buff_len - trans_event.transfer_len
> (0x4040 - 0x3ed8 = 0x168)
> ```
> 
> 0x168 is the correct length, which is the same as the length
> obtained in the first case where network rx works fine.
> 
> Also I looked at the Linux xhci-ring code where urb->actual_length
> calculated as:
> [2]
> ```
>      requested = td->urb->transfer_buffer_length;
>      remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
> ```
> [3]
> ```
>      td->urb->actual_length = requested - remaining;
> ```
> 
> Perhaps I missed something or misunderstood, so I would appreciate any help.

You're looking at the control transfer path. This is the bulk path:

https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/host/xhci-ring.c?h=v6.14-rc7#n2604

There you can see there are two branches. If the event happened on the 
last TRB, just subtract the remaining bytes from the total transfer 
size. If the event happened earlier (short packet with completely 
untransferred TRBs remaining), then you need to add up the TRB lengths 
up to and including the current TRB, and subtract the event remaining 
bytes from *that* (excluding any subsequent TRBs). This both fixes your 
issue and ignores the apparent bug in your host controller (since you're 
supposed to do all this processing when the short TRB is processed and 
completely ignore the second event on the last TRB).

So your patch is incorrect, it fixes the specific case you are hitting 
but breaks other split TRB cases. A more complex patch is needed that 
does this:

- In the if branch (short packet in non final TRB case, first event):
-- Add up the TRB lengths up to and including the event's associated TRB
-- Call record_transfer_result() passing that partial sum as length 
(instead of available_length) and the current event (first of the two)
-- Set a flag
- Later, before returning from the function, check the flag and do NOT 
call record_transfer_result() again on the final event, since the length 
was already calculated earlier and the length field of the final event 
is not useful (it is either wrong as in your case, or per spec a repeat 
of the short TRB's length field, but this information is useless without 
the TRB index that was short, which is not the final TRB).

> 
> ---
> 
> [1] Patch for debug output
> diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
> index 89d2e54f20a..b1433a16a99 100644
> --- a/drivers/usb/host/xhci-ring.c
> +++ b/drivers/usb/host/xhci-ring.c
> @@ -791,6 +791,8 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
>                  trb_fields[2] = length_field;
>                  trb_fields[3] = field | TRB_TYPE(TRB_NORMAL);
>   
> +               debug("PUSH. trb_len: 0x%x\n", trb_buff_len);
> +
>                  last_transfer_trb_addr = queue_trb(ctrl, ring, (num_trbs > 1), trb_fields);
>   
>                  --num_trbs;
> @@ -816,6 +818,10 @@ again:
>                  return -ETIMEDOUT;
>          }
>   
> +       debug("POP. trb_len: 0x%x, comp_code: 0x%x\n",
> +             (int)EVENT_TRB_LEN(le32_to_cpu(event->trans_event.transfer_len)),
> +             GET_COMP_CODE(le32_to_cpu(event->trans_event.transfer_len)));
> +
>          if ((uintptr_t)(le64_to_cpu(event->trans_event.buffer)) !=
>              (uintptr_t)last_transfer_trb_addr) {
>                  available_length -=
> @@ -831,6 +837,7 @@ again:
>          available_length -= first_trb_trimmed_sz;
>   
>          record_transfer_result(udev, event, available_length);
> +       debug("udev->act_len: 0x%x\n", udev->act_len);
>          xhci_acknowledge_event(ctrl);
>          xhci_inval_cache((uintptr_t)buffer, length);
>          xhci_dma_unmap(ctrl, buf_64, length);
> 
> 
> [2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/host/xhci-ring.c?h=v6.14-rc7#n2346
> [3] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/host/xhci-ring.c?h=v6.14-rc7#n2413
> 
> Maksim Kiselev (1):
>    usb: xhci: fix calculation of act_len in case of using multipe TRB
> 
>   drivers/usb/host/xhci-ring.c | 5 +++++
>   1 file changed, 5 insertions(+)
> 


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2025-04-07 18:16 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-21 16:58 [PATCH v1 0/1] USB xHCI wrong act_len calculation in case of using multipe TRB bigunclemax
2025-03-21 16:58 ` [PATCH v1 1/1] usb: xhci: fix calculation of act_len " bigunclemax
2025-04-07 17:41 ` [PATCH v1 0/1] USB xHCI wrong act_len calculation " marcan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox