Re: [PATCH v3] xhci: fix reporting of 0-sized URBs in control endpoint

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



>>>> When a control transfer has a short data stage, the xHCI controller generates
>>>> two transfer events: a COMP_SHORT_TX event that specifies the untransferred
>>>> amount, and a COMP_SUCCESS event. But when the data stage is not short, only the
>>>> COMP_SUCCESS event occurs. Therefore, xhci-hcd must set urb->actual_length to
>>>> urb->transfer_buffer_length while processing the COMP_SUCCESS event, unless
>>>> urb->actual_length was set already by a previous COMP_SHORT_TX event.
>>>>
>>>
>>> I think that the only case you should see a COMP_SUCCESS after a COMP_SHORT_TX
>>> is if xhci hits a link TRB while automatically moving to the next TRB after the
>>> short packet.
>>>
>>> If Event Data TRBs are used, or a later TRB in that TD has the IOC flag set then xhci should just
>>> generate a second transfer event with the same COMP_SHORT_TX completion code.
>>> (xhci specs section 4.10.1.1)
>>>
>>
>> Well, I can only speak for this usecase I have here with the Option
>> HSO modem; in this case the COMP_SHORT_TX+COMP_SUCCESS pair happens
>> always, as the hso driver submits a URB with a 1024 byte buffer, and
>> the modem usually replies with AT responses character by character;
>> for each character I end up getting both events: first with
>> COMP_SHORT_TX and the event length set to 1023, second with
>> COMP_SUCESS and event length set to 0.
>>
>
> Looking at the code it seems that xhci controllers after 0.96 generate a
> spurious COMP_SUCCESS after short packet, code says:
>
>         /* In xhci controllers which follow xhci 1.0 spec gives a spurious
>          * success event after a short transfer. This quirk will ignore such
>          * spurious event.
>          */
>         if (xhci->hci_version > 0x96)
>                 xhci->quirks |= XHCI_SPURIOUS_SUCCESS;
>
>> I read the xhci specs as well and I also got the impression that there
>> wasn't anything explicitly stating that a COMP_SUCCESS event always
>> follows a COMP_SHORT_TX. But, the code already assumes that if you get
>> a COMP_SHORT_TX event for a TRB which isn't the last one, you should
>> still get an event for the last TRB (i.e. finish_td() isn't called for
>> a COMP_SHORT_TX event if the TRB isn't the first one (event_trb !=
>> ep_ring->dequeue) and if it isn't the last one (event_trb !=
>> td->last_trb).
>
> Thats right, we always set the IOC (interrupt on completion) in the last TRB of
> a control transfer, so it will always interrupt, generating a transfer event.
>
> I read through your previous mails where you were investigating this and also looked a bit
> in more detail at the xhci code. I now understand better the issue. Its clearly a bug in xhci driver.
>
> Can you still add some debugging and check the if the second event after the COMP_SHORT_TX really
> is a COMP_SUCCESS ? and also what it says about the transfer length.
>
> printing out these values for the second event should do it:
>   GET_COMP_CODE(le32_to_cpu(event->transfer_len))
>   EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))
>

The second event is always COMP_SUCCESS and the event->transfer_len is
always set to 0 in that one. The 3 cases I've seen are:

case 1: 1 event on last TRB
  COMP_SUCCESS, event->len=0

case 2:  short event but with data
  COMP_SHORT_TX, event->len < urb->transfer_buffer_len
  COMP_SUCCESS, event->len=0

case 3: short event with no data
  COMP_SHORT_TX, event->len = urb->transfer_buffer_len
  COMP_SUCCESS, event->len=0

> Right now process_ctrl_td() sets the urb->actual_length based on TRB position in TD (first, last, neither) and
> what value urb->actual_length contains. I think that by checking the actual event completion code, and the event
> reported  remaining length we could get this correct without adding any additional fields to struct xhci_td.
>

The problem with that logic is that just by checking the last event
completion code and event->length we cannot know whether there was a
previous COMP_SHORT_TX event. i.e. when processing that last event you
wouldn't know whether it was case 1, case 2 or case 3 from above.

> If I find the time I'll rewrite this part, if not then I'll add your patch, (v4)
>

If you want to suggest any other approach, let me know and I'll spend
time with it.

>
>>
>>>
>>>> The driver checks this by seeing whether urb->actual_length == 0, but this alone
>>>> is the wrong test, as it is entirely possible for a short transfer to have an
>>>> urb->actual_length = 0.
>>>
>>> This should be fixed, handling short packets look a bit messy in general right now
>>>
>>>>
>>>> This patch changes the xhci driver to rely not only on the urb->actual_length,
>>>> but also on the ep_ring->last_td_was_short flag, which is set to true when a
>>>> COMP_SHORT_TX event is received.
>>>>
>>>> This fixes a bug which affected the HSO plugin, which relies on URBs with
>>>> urb->actual_length == 0 to halt re-submitting the RX URB in the control
>>>> endpoint.
>>>>
>>>> Signed-off-by: Aleksander Morgado <aleksander@xxxxxxxxxxxxx>
>>>> ---
>>>>
>>>> Hey,
>>>>
>>>> This is the third update of the patch:
>>>>
>>>>  * v2 modified the commit message to make it shorter and clearer.
>>>>
>>>>  * v3 updated the format of the commented lines in the patch.
>>>>
>>>> Cheers!
>>>>
>>>> ---
>>>>  drivers/usb/host/xhci-ring.c | 16 +++++++++++-----
>>>>  1 file changed, 11 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
>>>> index 88da8d6..eda3276 100644
>>>> --- a/drivers/usb/host/xhci-ring.c
>>>> +++ b/drivers/usb/host/xhci-ring.c
>>>> @@ -1955,7 +1955,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
>>>>                                       /* Did we already see a short data
>>>>                                        * stage? */
>>>>                                       *status = -EREMOTEIO;
>>>> -                     } else {
>>>> +                     } else if (!ep_ring->last_td_was_short) {
>>>>                               td->urb->actual_length =
>>>>                                       td->urb->transfer_buffer_length;
>>>>                       }
>>>> @@ -2447,10 +2447,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
>>>>                       ret = skip_isoc_td(xhci, td, event, ep, &status);
>>>>                       goto cleanup;
>>>>               }
>>>> -             if (trb_comp_code == COMP_SHORT_TX)
>>>> -                     ep_ring->last_td_was_short = true;
>>>> -             else
>>>> -                     ep_ring->last_td_was_short = false;
>>>>
>>>>               if (ep->skip) {
>>>>                       xhci_dbg(xhci, "Found td. Clear skip flag.\n");
>>>> @@ -2484,6 +2480,16 @@ static int handle_tx_event(struct xhci_hcd *xhci,
>>>>                       ret = process_bulk_intr_td(xhci, td, event_trb, event,
>>>>                                                ep, &status);
>>>>
>>>> +             /*
>>>> +              * Flag whether the just processed TRB was short. Do it after
>>>> +              * processing, so that the processor methods can also use this
>>>> +              * flag.
>>>> +              */
>>>> +             if (trb_comp_code == COMP_SHORT_TX)
>>>> +                     ep_ring->last_td_was_short = true;
>>>> +             else
>>>> +                     ep_ring->last_td_was_short = false;
>>>> +
>>>
>>> How about the case where we only get one COMP_SHORT_TX event for that control transfer,
>>> xhci then advances to the next TD, which completes successfully? That successful TD won't get
>>> its td->urb->actual length set because the last_td_was_short flag it still set?
>>>
>>
>> If that is something that can happen, i.e. if COMP_SHORT_TX isn't
>> always followed by a COMP_SUCCESS, then we should definitely not use
>> the flag in the ep_ring. Maybe some flag in the URB itself?
>
> I think this is possible for xhci host v0.96 and older, we probably get a
> second COMP_SHORT_TX for the status stage TRB
>
>>
>> The other thing I thought of was to somehow always initialize the URB
>> actual length to the transfer buffer length from the very beginning,
>> and only update it if a COMP_SHORT_TX event is received. Not sure if
>> that would be much more complex to handle, though.
>>
>
> This could be an option, need to look into it.




-- 
Aleksander
https://aleksander.es
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux