Hi Peter,

sorry about poking you -- I'm aware you've been on vacation, but in
<http://lists.nongnu.org/archive/html/qemu-devel/2015-03/msg00174.html>
you said you still inteded to pull stuff.

So, can you please pull this? :) I'd like to enable the XHCI driver in
both OVMF and AAVMF, and I'd like to reference the qemu commit in the
edk2 commit messages.

Thanks!
Laszlo

On 03/03/15 09:14, Gerd Hoffmann wrote:
> From: Laszlo Ersek <ler...@redhat.com>
> 
> At the moment, when the XHCI driver in edk2
> (MdeModulePkg/Bus/Pci/XhciDxe/XhciDxe.inf) runs on QEMU, with the options
> 
>   -device nec-usb-xhci -device usb-kbd
> 
> it crashes with:
> 
>   ASSERT MdeModulePkg/Bus/Pci/XhciDxe/XhciSched.c(1759):
>   TrsRing != ((void*) 0)
> 
> The crash hits in the following edk2 call sequence (all files under
> MdeModulePkg/Bus/):
> 
> UsbEnumerateNewDev()                         [Usb/UsbBusDxe/UsbEnumer.c]
>   UsbBuildDescTable()                        [Usb/UsbBusDxe/UsbDesc.c]
>     UsbGetDevDesc()                          [Usb/UsbBusDxe/UsbDesc.c]
>       UsbCtrlGetDesc(USB_REQ_GET_DESCRIPTOR) [Usb/UsbBusDxe/UsbDesc.c]
>         UsbCtrlRequest()                     [Usb/UsbBusDxe/UsbDesc.c]
>           UsbHcControlTransfer()             [Usb/UsbBusDxe/UsbUtility.c]
>             XhcControlTransfer()             [Pci/XhciDxe/Xhci.c]
>               XhcCreateUrb()                 [Pci/XhciDxe/XhciSched.c]
>                 XhcCreateTransferTrb()       [Pci/XhciDxe/XhciSched.c]
>               XhcExecTransfer()              [Pci/XhciDxe/XhciSched.c]
>                 XhcCheckUrbResult()          [Pci/XhciDxe/XhciSched.c]
>                   //
>                   // look for TRB_TYPE_DATA_STAGE event [1]
>                   //
>               //
>               // Store a copy of the device descriptor, as the hub device
>               // needs this info to configure endpoint. [2]
>               //
>   UsbSetConfig()                             [Usb/UsbBusDxe/UsbDesc.c]
>     UsbCtrlRequest(USB_REQ_SET_CONFIG)       [Usb/UsbBusDxe/UsbDesc.c]
>       UsbHcControlTransfer()                 [Usb/UsbBusDxe/UsbUtility.c]
>         XhcControlTransfer()                 [Pci/XhciDxe/Xhci.c]
>           XhcSetConfigCmd()                  [Pci/XhciDxe/XhciSched.c]
>             XhcInitializeEndpointContext()   [Pci/XhciDxe/XhciSched.c]
>               //
>               // allocate transfer ring for the endpoint [3]
>               //
> 
> USBKeyboardDriverBindingStart()              [Usb/UsbKbDxe/EfiKey.c]
>   UsbIoAsyncInterruptTransfer()              [Usb/UsbBusDxe/UsbBus.c]
>     UsbHcAsyncInterruptTransfer()            [Usb/UsbBusDxe/UsbUtility.c]
>       XhcAsyncInterruptTransfer()            [Pci/XhciDxe/Xhci.c]
>         XhcCreateUrb()                       [Pci/XhciDxe/Xhci.c]
>           XhcCreateTransferTrb()             [Pci/XhciDxe/XhciSched.c]
>             XhcSyncTrsRing()                 [Pci/XhciDxe/XhciSched.c]
>               ASSERT (TrsRing != NULL) [4]
> 
> UsbEnumerateNewDev() in the USB bus driver issues a GET_DESCRIPTOR
> request, in order to determine the number of configurations that the
> endpoint supports. The requests consists of three stages (three TRBs),
> setup, data, and status. The length of the response is determined in [1],
> namely from the transfer event that the host controller generates in
> response to the request's middle stage (ie. the data stage).
> 
> If the length of the answer is correct (a full GET_DESCRIPTOR request
> takes 18 bytes), then the XHCI driver that underlies the USB bus driver
> "snoops" (caches) the descriptor data for later [2].
> 
> Later, the USB bus driver sends a SET_CONFIG request. The underlying XHCI
> driver allocates a transfer ring for the endpoint, relying on the data
> snooped and cached in step [2].
> 
> Finally, the USB keyboard driver submits an asynchronous interrupt
> transfer to manage the keyboard. As part of this it asserts [4] that the
> ring has been allocated in step [3].
> 
> And this ASSERT() fires. The root cause can be found in the way QEMU
> handles the initial GET_DESCRIPTOR request.
> 
> Again, that request consists of three stages (TRBs, Transfer Request
> Blocks), "setup", "data", and "status". The XhcCreateTransferTrb()
> function sets the IOC ("Interrupt on Completion") flag in each of these
> TRBs.
> 
> According to the XHCI specification, the host controller shall generate a
> Transfer Event in response to *each* individual TRB of the request that
> had the IOC flag set. This means that QEMU should queue three events:
> setup, data, and status, for edk2's XHCI driver.
> 
> However, QEMU only generates two events:
> - one for the setup (ie. 1st) stage,
> - another for the status (ie. 3rd) stage.
> 
> No event is generated for the middle (ie. data) stage. The loop in QEMU's
> xhci_xfer_report() function runs three times, but due to the "reported"
> variable, only the first and the last TRBs elicit events, the middle (data
> stage) results in no event queued.
> 
> As a consequence:
> - When handling the GET_DESCRIPTOR request, XhcCheckUrbResult() in [1]
>   does not update the response length from zero.
> 
> - XhcControlTransfer() thinks that the response is invalid (it has zero
>   length payload instead of 18 bytes), hence [2] is not reached; the
>   device descriptor is not stashed for later, and the number of possible
>   configurations is left at zero.
> 
> - When handling the SET_CONFIG request, (NumConfigurations == 0) from
>   above prevents the allocation of the endpoint's transfer ring.
> 
> - When the keyboard driver tries to use the endpoint, the ASSERT() blows
>   up.
> 
> The solution is to correct the emulation in QEMU, and to generate a
> transfer event whenever IOC is set in a TRB.
> 
> The patch replaces
> 
>   !reported && (IOC || foo)    == !reported && IOC ||
>                                   !reported && foo
> 
> with
> 
>   IOC || (!reported && foo)    == IOC ||
>                                   !reported && foo
> 
> which only changes how
> 
>   reported && IOC
> 
> is handled. (Namely, it now generates an event.)
> 
> Tested with edk2 built for "qemu-system-aarch64 -M virt" (ie.
> "ArmVirtualizationQemu.dsc", aka "AAVMF"), and guest Linux.
> 
> Signed-off-by: Laszlo Ersek <ler...@redhat.com>
> Signed-off-by: Gerd Hoffmann <kra...@redhat.com>
> ---
>  hw/usb/hcd-xhci.c | 15 ++++++++++++---
>  1 file changed, 12 insertions(+), 3 deletions(-)
> 
> diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
> index 776699b..828c2a7 100644
> --- a/hw/usb/hcd-xhci.c
> +++ b/hw/usb/hcd-xhci.c
> @@ -1767,9 +1767,18 @@ static void xhci_xfer_report(XHCITransfer *xfer)
>              break;
>          }
>  
> -        if (!reported && ((trb->control & TRB_TR_IOC) ||
> -                          (shortpkt && (trb->control & TRB_TR_ISP)) ||
> -                          (xfer->status != CC_SUCCESS && left == 0))) {
> +        /*
> +         * XHCI 1.1, 4.11.3.1 Transfer Event TRB -- "each Transfer TRB
> +         * encountered with its IOC flag set to '1' shall generate a Transfer
> +         * Event."
> +         *
> +         * Otherwise, longer transfers can have multiple data TRBs (for 
> scatter
> +         * gather). Short transfers and errors should be reported once per
> +         * transfer only.
> +         */
> +        if ((trb->control & TRB_TR_IOC) ||
> +            (!reported && ((shortpkt && (trb->control & TRB_TR_ISP)) ||
> +                           (xfer->status != CC_SUCCESS && left == 0)))) {
>              event.slotid = xfer->slotid;
>              event.epid = xfer->epid;
>              event.length = (trb->status & 0x1ffff) - chunk;
> 


Reply via email to