xemu/hw/usb
Laszlo Ersek aa6857891d xhci: generate a Transfer Event for each Transfer TRB with the IOC bit set
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 <lersek@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2015-03-03 08:36:58 +01:00
..
bus.c hmp: Normalize HMP command handler names 2015-02-25 13:14:37 +00:00
ccid-card-emulated.c ccid-card-emulated: use EventNotifier 2014-07-01 15:49:51 +02:00
ccid-card-passthru.c devices: Associate devices to their logical category 2013-07-29 10:37:09 -05:00
ccid.h hw: move private headers to hw/ subdirectories. 2013-04-08 18:13:16 +02:00
combined-packet.c usb: Fix iovec memleak on combined-packet free 2013-09-19 11:28:40 +02:00
core.c usb: add usb_pick_speed 2014-06-02 16:29:00 +02:00
desc-msos.c usb: Pair g_malloc() with g_free(), not free() 2015-02-10 09:27:20 +03:00
desc.c usb: add usb_pick_speed 2014-06-02 16:29:00 +02:00
desc.h usb: add CompatibleID support to msos 2014-04-22 12:40:57 +02:00
dev-audio.c usb-audio: convert init to realize 2014-09-23 12:51:07 +02:00
dev-bluetooth.c usb: Improve -usbdevice error reporting a bit 2015-02-18 10:53:10 +01:00
dev-hid.c usb-hid: Add high speed keyboard configuration 2014-10-15 13:39:22 +02:00
dev-hub.c usb-hub: convert init to realize 2014-09-23 12:51:07 +02:00
dev-mtp.c usb-mtp: convert init to realize 2014-09-23 12:51:07 +02:00
dev-network.c Clean up around error_get_pretty(), qerror_report_err() 2015-02-26 07:01:08 +00:00
dev-serial.c usb: Improve -usbdevice error reporting a bit 2015-02-18 10:53:10 +01:00
dev-smartcard-reader.c usb: Convert usb-ccid to hotplug handler API 2014-10-15 05:03:14 +02:00
dev-storage.c usb: Improve -usbdevice error reporting a bit 2015-02-18 10:53:10 +01:00
dev-uas.c dev-uas: using error_report instead of fprintf 2014-09-23 12:51:07 +02:00
dev-wacom.c dev-wacom: convert init to realize 2014-09-23 12:51:07 +02:00
hcd-ehci-pci.c usb: tag standalone ehci as hotpluggable 2014-09-23 12:51:08 +02:00
hcd-ehci-sysbus.c savevm: Remove all the unneeded version_minimum_id_old (usb) 2014-05-14 15:24:51 +02:00
hcd-ehci.c vmstate: accept QEMUTimer in VMSTATE_TIMER*, add VMSTATE_TIMER_PTR* 2015-01-26 12:22:44 +01:00
hcd-ehci.h usb: tag standalone ehci as hotpluggable 2014-09-23 12:51:08 +02:00
hcd-musb.c hcd-musb: fix dereference null return value 2014-11-17 18:02:31 +01:00
hcd-ohci.c vmstate: accept QEMUTimer in VMSTATE_TIMER*, add VMSTATE_TIMER_PTR* 2015-01-26 12:22:44 +01:00
hcd-uhci.c vmstate: accept QEMUTimer in VMSTATE_TIMER*, add VMSTATE_TIMER_PTR* 2015-01-26 12:22:44 +01:00
hcd-xhci.c xhci: generate a Transfer Event for each Transfer TRB with the IOC bit set 2015-03-03 08:36:58 +01:00
host-legacy.c usb: Improve -usbdevice error reporting a bit 2015-02-18 10:53:10 +01:00
host-libusb.c Clean up around error_get_pretty(), qerror_report_err() 2015-02-26 07:01:08 +00:00
host-stub.c hmp: Name HMP info handler functions hmp_info_SUBCOMMAND() 2015-02-18 11:58:50 +01:00
host.h usb-host: move legacy cmd line bits 2013-02-19 12:30:05 +01:00
libhw.c dma: eliminate DMAContext 2013-06-20 16:39:52 +02:00
Makefile.objs Make sysbus EHCI devices ARM only by default 2015-02-18 10:53:10 +01:00
quirks-ftdi-ids.h usbredir: Add support for buffered bulk input (v2) 2013-01-08 10:56:58 +01:00
quirks-pl2303-ids.h usbredir: Add support for buffered bulk input (v2) 2013-01-08 10:56:58 +01:00
quirks.c usbredir: Add support for buffered bulk input (v2) 2013-01-08 10:56:58 +01:00
quirks.h usbredir: Add support for buffered bulk input (v2) 2013-01-08 10:56:58 +01:00
redirect.c error: Use error_report_err() where appropriate 2015-02-18 10:51:09 +01:00