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]

 



On 23.02.2015 19:02, Aleksander Morgado wrote:
> On Mon, Feb 23, 2015 at 4:23 PM, Mathias Nyman
> <mathias.nyman@xxxxxxxxxxxxxxx> wrote:
>> Hi
>>
>> On 23.02.2015 13:52, Aleksander Morgado wrote:
>>> 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))

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.

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

 
> 
>>
>>> 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.

-Mathias
--
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