diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/USB/DevXHCI.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/USB/DevXHCI.cpp')
-rw-r--r-- | src/VBox/Devices/USB/DevXHCI.cpp | 8295 |
1 files changed, 8295 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevXHCI.cpp b/src/VBox/Devices/USB/DevXHCI.cpp new file mode 100644 index 00000000..ede6413c --- /dev/null +++ b/src/VBox/Devices/USB/DevXHCI.cpp @@ -0,0 +1,8295 @@ +/* $Id: DevXHCI.cpp $ */ +/** @file + * DevXHCI - eXtensible Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_xhci xHCI - eXtensible Host Controller Interface Emulation. + * + * This component implements an xHCI USB controller. + * + * The xHCI device is significantly different from the EHCI and OHCI + * controllers in that it is not timer driven. A worker thread is responsible + * for transferring data between xHCI and VUSB. + * + * Since there can be dozens or even hundreds of USB devices, and because USB + * transfers must share the same bus, only one worker thread is created (per + * host controller). + * + * + * The xHCI operational model is heavily based around a producer/consumer + * model utilizing rings -- Command, Event, and Transfer rings. The Event ring + * is only written by the xHC and is read-only for the HCD (Host Controller + * Driver). The Command/Transfer rings are only written by the HCD and are + * read-only for the xHC. + * + * The rings contain TRBs (Transfer Request Blocks). The TRBs represent not + * only data transfers but also commands and status information. Each type of + * ring only produces/consumes specific TRB types. + * + * When processing a ring, the xHC simply keeps advancing an internal pointer. + * For the Command/Transfer rings, the HCD uses Link TRBs to manage the ring + * storage in a fairly arbitrary manner. Since the HCD cannot write to the + * Event ring, the Event Ring Segment Table (ERST) is used to manage the ring + * storage instead. + * + * The Cycle bit is used to manage the ring buffer full/empty condition. The + * Producer and Consumer both have their own Cycle State (PCS/CCS). The Cycle + * bit of each TRB determines who owns it. The consumer only processes TRBs + * whose Cycle bit matches the CCS. HCD software typically toggles the Cycle + * bit on each pass through the ring. The Link TRB can be used to toggle the + * CCS accordingly. + * + * Multiple Transfer TRBs can be chained together (via the Chain bit) into a + * single Transfer Descriptor (TD). This provides a convenient capability for + * the HCD to turn a URB into a single TD regardless of how the URB is laid + * out in physical memory. If a transfer encounters an error or is terminated + * by a short packet, the entire TD (i.e. chain of TRBs) is retired. + * + * Note that the xHC detects and handles short packets on its own. Backends + * are always asked not to consider a short packet to be an error condition. + * + * Command and Event TRBs cannot be chained, thus an ED (Event Descriptor) + * or a Command Descriptor (CD) always consists of a single TRB. + * + * There is one Command ring per xHC, one Event ring per interrupter (one or + * more), and a potentially very large number of Transfer rings. There is a + * 1:1 mapping between Transfer Rings and USB pipes, hence each USB device + * uses 1-31 Transfer rings (at least one for the default control endpoint, + * up to 31 if all IN/OUT endpoints are used). USB 3.0 devices may also use + * up to 64K streams per endpoint, each with its Transfer ring, massively + * increasing the potential number of Transfer rings in use. + * + * When building a Transfer ring, it's possible to queue up a large number + * of TDs and as soon as the oldest ones are retired, queue up new TDs. The + * Transfer ring might thus never be empty. + * + * For tracking ring buffer position, the TRDP and TREP fields in an endpoint + * context are used. The TRDP is the 'TR Dequeue Pointer', i.e. the position + * of the next TRB to be completed. This field is visible by the HCD when the + * endpoint isn't running. It reflects TRBs completely processed by the xHC + * and hence no longer owned by the xHC. + * + * The TREP field is the 'TR Enqueue Pointer' and tracks the position of the + * next TRB to start processing (submit). This is purely internal to the + * xHC. The TREP can potentially get far ahead of the TRDP, but only in the + * part of the ring owned by the xHC (i.e. with matching DCS bit). + * + * Unlike most other xHCI data structures, transfer TRBs may describe memory + * buffers with no alignment restrictions (both starting position and size). + * In addition, there is no relationship between TRB boundaries and USB + * packet boundaries. + * + * + * Typically an event would be generated via the IOC bit (Interrupt On + * Completion) when the last TRB of a TD is completed. However, multiple IOC + * bits may be set per TD. This may be required when a TD equal or larger + * than 16MB is used, since transfer events utilize a 24-bit length field. + * + * There is also the option of using Transfer Event TRBs to report TRB + * completion. Transfer Event TRBs may be freely intermixed with transfer + * TRBs. Note that an event TRB will produce an event reporting the size of + * data transferred since the last event TRB or since the beginning of a TD. + * The xHC submits URBs such that they either comprise the entire TD or end + * at a Transfer Event TRB, thus there is no need to track the EDTLA + * separately. + * + * Transfer errors always generate events, irrespective of IOC settings. The + * xHC has always the option to generate events at implementation-specific + * points so that the HCD does not fall too far behind. + * + * Control transfers use special TDs. A Setup Stage TD consists of only a + * single Setup Stage TRB (there's no Chain bit). The optional Data Stage + * TD consists of a Data Stage TRB chained to zero or more Normal TRBs + * and/or Event Data TRBs. The Status Stage TD then consists of a Status + * Stage TRB optionally chained to an Event Data TRB. The HCD is responsible + * for building the TDs correctly. + * + * For isochronous transfers, only the first TRB of a TD is actually an + * isochronous TRB. If the TD is chained, it will contain Normal TRBs (and + * possibly Event Data TRBs). + * + * + * Isochronous transfers require multiple TDs/URBs to be in flight at a + * time. This complicates dealing with non-data TRBs (such as link or event + * data TRBs). These TRBs cannot be completed while a previous TRB is still + * in flight. They are completed either: a) when submitting URBs and there + * are no in-flight URBs, or b) just prior to completing an URB. + * + * This approach works because URBs must be completed strictly in-order. The + * TRDP and TREP determine whether there are in-flight TRBs (TREP equals + * TRDP if and only if there are no in-flight TRBs). + * + * When submitting TRBs and there is in-flight traffic, non-data TRBs must + * be examined and skipped over. Link TRBs need to be taken into account. + * + * Unfortunately, certain HCDs (looking at you, Microsoft!) violate the xHCI + * specification and make assumptions about how far ahead of the TRDP the + * xHC can get. We have to artificially limit the number of in-flight TDs + * for this reason. + * + * Non-isochronous TRBs do not require this treatment for correct function + * but are likely to benefit performance-wise from the pipelining. + * + * With high-speed and faster transfers, there is an added complication for + * endpoints with more than one transfer per frame, i.e. short intervals. At + * least some host USB stacks require URBs to cover an entire frame, which + * means we may have to glue together several TDs into a single URB. + * + * + * A buggy or malicious guest can create a transfer or command ring that + * loops in on itself (in the simplest case using a sequence of one or more + * link TRBs where the last TRB points to the beginning of the sequence). + * Such a loop would effectively hang the processing thread. Since we cannot + * easily detect a generic loop, and because even non-looped TRB/command + * rings might contain extremely large number of items, we limit the number + * of entries that we are willing to process at once. If the limit is + * crossed, the xHC reports a host controller error and shuts itself down + * until it's reset. + * + * Note that for TRB lists, both URB submission and completion must protect + * against loops because the lists in guest memory are not guaranteed to stay + * unchanged between submitting and completing URBs. + * + * The event ring is not susceptible to loops because the xHC is the producer, + * not consumer. The event ring can run out of space but that is not a fatal + * problem. + * + * + * The interrupt logic uses an internal IPE (Interrupt Pending Enable) bit + * which controls whether the register-visible IP (Interrupt Pending) bit + * can be set. The IPE bit is set when a non-blocking event (BEI bit clear) + * is enqueued. The IPE bit is cleared when the event ring is initialized or + * transitions to empty (i.e. ERDP == EREP). When IPE transtitions to set, + * it will set IP unless the EHB (Event Handler Busy) bit is set or IMODC + * (Interrupt Moderation Counter) is non-zero. When IMODC counts down to + * zero, it sets the IP bit if IPE is set and EHB is not. Setting the IP bit + * triggers interrupt delivery. Note that clearing the IPE bit does not + * change the IP bit state. + * + * Interrupt delivery depends on whether MSI/MSI-X is in use or not. With MSI, + * an interrupter's IP (Interrupt Pending) bit is cleared as soon as the MSI + * message is written; with classic PCI interrupt delivery, the HCD must clear + * the IP bit. However, the EHB (Event Handler Busy) bit is always set, which + * causes further interrupts to be blocked on the interrupter until the HCD + * processes pending events and clears the EHB bit. + * + * Note that clearing the EHB bit may immediately trigger an interrupt if + * additional event TRBs were queued up while the HCD was processing previous + * ones. + * + * + * Each enabled USB device has a corresponding slot ID, a doorbell, as well as + * a device context which can be accessed through the DCBAA (Device Context + * Base Address Array). Valid slot IDs are in the 1-255 range; the first entry + * (i.e. index 0) in the DCBAA may optionally point to the Scratchpad Buffer + * Array, while doorbell 0 is associated with the Command Ring. + * + * While 255 valid slot IDs is an xHCI architectural limit, existing xHC + * implementations usually set a considerably lower limit, such as 32. See + * the XHCI_NDS constant. + * + * It would be tempting to use the DCBAA to determine which slots are free. + * Unfortunately the xHC is not allowed to access DCBAA entries which map to + * disabled slots (see section 6.1). A parallel aSlotState array is hence used + * to internally track the slot state and find available slots. Once a slot + * is enabled, the slot context entry in the DCBAA is used to track the + * slot state. + * + * + * Unlike OHCI/UHCI/EHCI, the xHC much more closely tracks USB device state. + * HCDs are not allowed to issue SET_ADDRESS requests at all and must use + * the Address Device xHCI command instead. + * + * HCDs can use SET_CONFIGURATION and SET_INTERFACE requests normally, but + * must inform the xHC of the changes via Configure Endpoint and Evaluate + * Context commands. Similarly there are Reset Endpoint and Stop Endpoint + * commands to manage endpoint state. + * + * A corollary of the above is that unlike OHCI/UHCI/EHCI, with xHCI there + * are very clear rules and a straightforward protocol for managing + * ownership of structures in physical memory. During normal operation, the + * xHC owns all device context memory and the HCD must explicitly ask the xHC + * to relinquish the ownership. + * + * The xHCI architecture offers an interesting feature in that it reserves + * opaque fields for xHCI use in certain data structures (slot and endpoint + * contexts) and gives the xHC an option to request scratchpad buffers that + * a HCD must provide. The xHC may use the opaque storage and/or scratchpad + * buffers for saving internal state. + * + * For implementation reasons, the xHCI device creates two root hubs on the + * VUSB level; one for USB2 devices (USB 1.x and 2.0), one for USB3. The + * behavior of USB2 vs. USB3 ports is different, and a device can only be + * attached to either one or the other hub. However, there is a single array + * of ports to avoid overly complicating the code, given that port numbering + * is linear and encompasses both USB2 and USB3 ports. + * + * + * The default emulated device is an Intel 7-Series xHC aka Panther Point. + * This was Intel's first xHC and is widely supported. It is also possible + * to select an Intel 8-Series xHC aka Lynx Point; this is only useful for + * debugging and requires the 'other' set of Windows 7 drivers. + * + * For Windows XP guest support, it is possible to emulate a Renesas + * (formerly NEC) uPD720201 xHC. It would be possible to emulate the earlier + * NEC chips but those a) only support xHCI 0.96, and b) their drivers + * require a reboot during installation. Renesas' drivers also support + * Windows Vista and 7. + * + * + * NB: Endpoints are addressed differently in xHCI and USB. In USB, + * endpoint addresses are 8-bit values with the low four bits identifying + * the endpoint number and the high bit indicating the direction (0=OUT, + * 1=IN); see e.g. 9.3.4 in USB 2.0 spec. In xHCI, endpoint addresses are + * used as DCIs (Device Context Index) and for that reason, they're + * compressed into 5 bits where the lowest bit(!) indicates direction (again + * 1=IN) and bits 1-4 designate the endpoint number. Endpoint 0 is somewhat + * special and uses DCI 1. See 4.8.1 in xHCI spec. + * + * + * NB: A variable named iPort is a zero-based index into the port array. + * On the other hand, a variable named uPort is a one-based port number! + * The implementation (obviously) uses zero-based indexing, but USB ports + * are numbered starting with 1. The same is true of xHCI slot numbering. + * The macros IDX_TO_ID() and ID_TO_IDX(a) should be used to convert between + * the two numbering conventions to make the intent clear. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_XHCI +#include <VBox/pci.h> +#include <VBox/msi.h> +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#ifdef IN_RING3 +# include <iprt/uuid.h> +# include <iprt/critsect.h> +#endif +#include <VBox/vusb.h> +#ifdef VBOX_IN_EXTPACK_R3 +# include <VBox/version.h> +#endif +#ifndef VBOX_IN_EXTPACK +# include "VBoxDD.h" +#endif + + +/********************************************************************************************************************************* +* (Most of the) Defined Constants, Macros and Structures * +*********************************************************************************************************************************/ + +/* Optional error injection support via DBGF. */ +//#define XHCI_ERROR_INJECTION + +/** The saved state version. */ +#define XHCI_SAVED_STATE_VERSION 1 + + +/** Convert a zero-based index to a 1-based ID. */ +#define IDX_TO_ID(a) (a + 1) +/** Convert a 1-based ID to a zero-based index. */ +#define ID_TO_IDX(a) (a - 1) + +/** PCI device related constants. */ +#define XHCI_PCI_MSI_CAP_OFS 0x80 + +/** Number of LUNs/root hubs. One each for USB2/USB3. */ +#define XHCI_NUM_LUNS 2 + +/** @name The following two constants were determined experimentally. + * They determine the maximum number of TDs allowed to be in flight. + * NB: For isochronous TDs, the number *must* be limited because + * Windows 8+ violates the xHCI specification and does not keep + * the transfer rings consistent. + * @{ + */ +//#define XHCI_MAX_ISOC_IN_FLIGHT 3 /* Scarlett needs 3; was 12 */ +#define XHCI_MAX_ISOC_IN_FLIGHT 12 +#define XHCI_MAX_BULK_IN_FLIGHT 8 +/** @} */ + +/** @name Implementation limit on the number of TRBs and commands + * the xHC is willing to process at once. A larger number is taken + * to indicate a broken or malicious guest, and causes a HC error. + * @{ + */ +#define XHCI_MAX_NUM_CMDS 128 +#define XHCI_MAX_NUM_TRBS 1024 +/** @} */ + +/** Implementation TD size limit. Prevents EDTLA wrap-around. */ +#define XHCI_MAX_TD_SIZE (16 * _1M - 1) + +/** Special value to prevent further queuing. */ +#define XHCI_NO_QUEUING_IN_FLIGHT (XHCI_MAX_BULK_IN_FLIGHT * 2) + +/* Structural Parameters #1 (HCSPARAMS1) values. */ + +/** Maximum allowed Number of Downstream Ports on the root hub. Careful + * when changing -- other structures may need adjusting! + */ +#define XHCI_NDP_MAX 32 + +/** Default number of USB 2.0 ports. + * + * @note AppleUSBXHCI does not handle more than 15 ports. At least OS X + * 10.8.2 crashes if we report more than 15 ports! Hence the default + * is 8 USB2 + 6 USB3 ports for a total of 14 so that OS X is happy. + */ +#define XHCI_NDP_20_DEFAULT 8 + +/** Default number of USB 3.0 ports. */ +#define XHCI_NDP_30_DEFAULT 6 + +/** Number of interrupters. */ +#define XHCI_NINTR 8 + +/** Mask for interrupter indexing. */ +#define XHCI_INTR_MASK (XHCI_NINTR - 1) + +/* The following is only true if XHCI_NINTR is a (non-zero) power of two. */ +AssertCompile((XHCI_NINTR & XHCI_INTR_MASK) == 0); + +/** Number of Device Slots. Determines the number of doorbell + * registers and device slots, among other things. */ +#define XHCI_NDS 32 + +/* Enforce xHCI architectural limits on HCSPARAMS1. */ +AssertCompile(XHCI_NDP_MAX < 255 && XHCI_NINTR < 1024 && XHCI_NDS < 255); +AssertCompile(XHCI_NDP_20_DEFAULT + XHCI_NDP_30_DEFAULT <= XHCI_NDP_MAX); +AssertCompile(XHCI_NDP_MAX <= XHCI_NDS); + +/* Structural Parameters #2 (HCSPARAMS2) values. */ + +/** Isochronous Scheduling Threshold. */ +#define XHCI_IST (RT_BIT(3) | 1) /* One frame. */ + +/** Max number of Event Ring Segment Table entries as a power of two. */ +#define XHCI_ERSTMAX_LOG2 5 +/** Max number of Event Ring Segment Table entries. */ +#define XHCI_ERSTMAX RT_BIT(XHCI_ERSTMAX_LOG2) + +/* Enforce xHCI architectural limits on HCSPARAMS2. */ +AssertCompile(XHCI_ERSTMAX_LOG2 < 16); + + +/** Size of the xHCI memory-mapped I/O region. */ +#define XHCI_MMIO_SIZE _64K + +/** Size of the capability part of the MMIO region. */ +#define XHCI_CAPS_REG_SIZE 0x80 + +/** Offset of the port registers in operational register space. */ +#define XHCI_PORT_REG_OFFSET 0x400 + +/** Offset of xHCI extended capabilities in MMIO region. */ +#define XHCI_XECP_OFFSET 0x1000 + +/** Offset of the run-time registers in MMIO region. */ +#define XHCI_RTREG_OFFSET 0x2000 + +/** Offset of the doorbell registers in MMIO region. */ +#define XHCI_DOORBELL_OFFSET 0x3000 + +/** Size of the extended capability area. */ +#define XHCI_EXT_CAP_SIZE 1024 + +/* Make sure we can identify MMIO register accesses properly. */ +AssertCompile(XHCI_DOORBELL_OFFSET > XHCI_RTREG_OFFSET); +AssertCompile(XHCI_XECP_OFFSET > XHCI_PORT_REG_OFFSET + XHCI_CAPS_REG_SIZE); +AssertCompile(XHCI_RTREG_OFFSET > XHCI_XECP_OFFSET + XHCI_EXT_CAP_SIZE); + + +/** Maximum size of a single extended capability. */ +#define MAX_XCAP_SIZE 256 + +/** @name xHCI Extended capability types. + * @{ */ +#define XHCI_XCP_USB_LEGACY 1 /**< USB legacy support. */ +#define XHCI_XCP_PROTOCOL 2 /**< Protocols supported by ports. */ +#define XHCI_XCP_EXT_PM 3 /**< Extended power management (non-PCI). */ +#define XHCI_XCP_IOVIRT 4 /**< Hardware xHCI virtualization support. */ +#define XHCI_XCP_MSI 5 /**< Message interrupts (non-PCI). */ +#define XHCI_XCP_LOCAL_MEM 6 /**< Local memory (for debug support). */ +#define XHCI_XCP_USB_DEBUG 10 /**< USB debug capability. */ +#define XHCI_XCP_EXT_MSI 17 /**< MSI-X (non-PCI). */ +/** @} */ + + +/* xHCI Register Bits. */ + + +/** @name Capability Parameters (HCCPARAMS) bits + * @{ */ +#define XHCI_HCC_AC64 RT_BIT(0) /**< RO */ +#define XHCI_HCC_BNC RT_BIT(1) /**< RO */ +#define XHCI_HCC_CSZ RT_BIT(2) /**< RO */ +#define XHCI_HCC_PPC RT_BIT(3) /**< RO */ +#define XHCI_HCC_PIND RT_BIT(4) /**< RO */ +#define XHCI_HCC_LHRC RT_BIT(5) /**< RO */ +#define XHCI_HCC_LTC RT_BIT(6) /**< RO */ +#define XHCI_HCC_NSS RT_BIT(7) /**< RO */ +#define XHCI_HCC_MAXPSA_MASK (RT_BIT(12)|RT_BIT(13)|RT_BIT(14)| RT_BIT(15)) /**< RO */ +#define XHCI_HCC_MAXPSA_SHIFT 12 +#define XHCI_HCC_XECP_MASK 0xFFFF0000 /**< RO */ +#define XHCI_HCC_XECP_SHIFT 16 +/** @} */ + + +/** @name Command Register (USBCMD) bits + * @{ */ +#define XHCI_CMD_RS RT_BIT(0) /**< RW - Run/Stop */ +#define XHCI_CMD_HCRST RT_BIT(1) /**< RW - Host Controller Reset */ +#define XHCI_CMD_INTE RT_BIT(2) /**< RW - Interrupter Enable */ +#define XHCI_CMD_HSEE RT_BIT(3) /**< RW - Host System Error Enable */ +#define XHCI_CMD_LCRST RT_BIT(7) /**< RW - Light HC Reset */ +#define XHCI_CMD_CSS RT_BIT(8) /**< RW - Controller Save State */ +#define XHCI_CMD_CRS RT_BIT(9) /**< RW - Controller Restore State */ +#define XHCI_CMD_EWE RT_BIT(10) /**< RW - Enable Wrap Event */ +#define XHCI_CMD_EU3S RT_BIT(11) /**< RW - Enable U3 MFINDEX Stop */ + +#define XHCI_CMD_MASK ( XHCI_CMD_RS | XHCI_CMD_HCRST | XHCI_CMD_INTE | XHCI_CMD_HSEE | XHCI_CMD_LCRST \ + | XHCI_CMD_CSS | XHCI_CMD_CRS | XHCI_CMD_EWE | XHCI_CMD_EU3S) +/** @} */ + + +/** @name Status Register (USBSTS) bits + * @{ */ +#define XHCI_STATUS_HCH RT_BIT(0) /**< RO - HC Halted */ +#define XHCI_STATUS_HSE RT_BIT(2) /**< RW1C - Host System Error */ +#define XHCI_STATUS_EINT RT_BIT(3) /**< RW1C - Event Interrupt */ +#define XHCI_STATUS_PCD RT_BIT(4) /**< RW1C - Port Change Detect */ +#define XHCI_STATUS_SSS RT_BIT(8) /**< RO - Save State Status */ +#define XHCI_STATUS_RSS RT_BIT(9) /**< RO - Resture State Status */ +#define XHCI_STATUS_SRE RT_BIT(10) /**< RW1C - Save/Restore Error */ +#define XHCI_STATUS_CNR RT_BIT(11) /**< RO - Controller Not Ready */ +#define XHCI_STATUS_HCE RT_BIT(12) /**< RO - Host Controller Error */ + +#define XHCI_STATUS_WRMASK (XHCI_STATUS_HSE | XHCI_STATUS_EINT | XHCI_STATUS_PCD | XHCI_STATUS_SRE) +/** @} */ + + +/** @name Default xHCI speed definitions (7.2.2.1.1) + * @{ */ +#define XHCI_SPD_FULL 1 +#define XHCI_SPD_LOW 2 +#define XHCI_SPD_HIGH 3 +#define XHCI_SPD_SUPER 4 +/** @} */ + +/** @name Port Status and Control Register bits (PORTSCUSB2/PORTSCUSB3) + * @{ */ +#define XHCI_PORT_CCS RT_BIT(0) /**< ROS - Current Connection Status */ +#define XHCI_PORT_PED RT_BIT(1) /**< RW1S - Port Enabled/Disabled */ +#define XHCI_PORT_OCA RT_BIT(3) /**< RO - Over-current Active */ +#define XHCI_PORT_PR RT_BIT(4) /**< RW1S - Port Reset */ +#define XHCI_PORT_PLS_MASK (RT_BIT(5) | RT_BIT(6) | RT_BIT(7) | RT_BIT(8)) /**< RWS */ +#define XHCI_PORT_PLS_SHIFT 5 +#define XHCI_PORT_PP RT_BIT(9) /**< RWS - Port Power */ +#define XHCI_PORT_SPD_MASK (RT_BIT(10) | RT_BIT(11) | RT_BIT(12) | RT_BIT(13)) /**< ROS */ +#define XHCI_PORT_SPD_SHIFT 10 +#define XHCI_PORT_LWS RT_BIT(16) /**< RW - Link State Write Strobe */ +#define XHCI_PORT_CSC RT_BIT(17) /**< RW1CS - Connect Status Change */ +#define XHCI_PORT_PEC RT_BIT(18) /**< RW1CS - Port Enabled/Disabled Change */ +#define XHCI_PORT_WRC RT_BIT(19) /**< RW1CS - Warm Port Reset Change */ +#define XHCI_PORT_OCC RT_BIT(20) /**< RW1CS - Over-current Change */ +#define XHCI_PORT_PRC RT_BIT(21) /**< RW1CS - Port Reset Change */ +#define XHCI_PORT_PLC RT_BIT(22) /**< RW1CS - Port Link State Change */ +#define XHCI_PORT_CEC RT_BIT(23) /**< RW1CS - Port Config Error Change */ +#define XHCI_PORT_CAS RT_BIT(24) /**< RO - Cold Attach Status */ +#define XHCI_PORT_WCE RT_BIT(25) /**< RWS - Wake on Connect Enable */ +#define XHCI_PORT_WDE RT_BIT(26) /**< RWS - Wake on Disconnect Enable */ +#define XHCI_PORT_WOE RT_BIT(27) /**< RWS - Wake on Over-current Enable */ +#define XHCI_PORT_DR RT_BIT(30) /**< RO - Device (Not) Removable */ +#define XHCI_PORT_WPR RT_BIT(31) /**< RW1S - Warm Port Reset */ + +#define XHCI_PORT_RESERVED (RT_BIT(2) | RT_BIT(14) | RT_BIT(15) | RT_BIT(28) | RT_BIT(29)) + +#define XHCI_PORT_WAKE_MASK (XHCI_PORT_WCE|XHCI_PORT_WDE|XHCI_PORT_WOE) +#define XHCI_PORT_CHANGE_MASK (XHCI_PORT_CSC|XHCI_PORT_PEC|XHCI_PORT_WRC|XHCI_PORT_OCC|XHCI_PORT_PRC|XHCI_PORT_PLC|XHCI_PORT_CEC) +#define XHCI_PORT_CTL_RW_MASK (XHCI_PORT_PP|XHCI_PORT_LWS) +#define XHCI_PORT_CTL_W1_MASK (XHCI_PORT_PED|XHCI_PORT_PR|XHCI_PORT_WPR) +#define XHCI_PORT_RO_MASK (XHCI_PORT_CCS|XHCI_PORT_OCA|XHCI_PORT_SPD_MASK|XHCI_PORT_CAS|XHCI_PORT_DR) +/** @} */ + +/** @name Port Link State values + * @{ */ +#define XHCI_PLS_U0 0 /**< U0 State. */ +#define XHCI_PLS_U1 1 /**< U1 State. */ +#define XHCI_PLS_U2 2 /**< U2 State. */ +#define XHCI_PLS_U3 3 /**< U3 State (Suspended). */ +#define XHCI_PLS_DISABLED 4 /**< Disabled. */ +#define XHCI_PLS_RXDETECT 5 /**< RxDetect. */ +#define XHCI_PLS_INACTIVE 6 /**< Inactive. */ +#define XHCI_PLS_POLLING 7 /**< Polling. */ +#define XHCI_PLS_RECOVERY 8 /**< Recovery. */ +#define XHCI_PLS_HOTRST 9 /**< Hot Reset. */ +#define XHCI_PLS_CMPLMODE 10 /**< Compliance Mode. */ +#define XHCI_PLS_TSTMODE 11 /**< Test Mode. */ +/* Values 12-14 are reserved. */ +#define XHCI_PLS_RESUME 15 /**< Resume. */ +/** @} */ + + +/** @name Command Ring Control Register (CRCR) bits + * @{ */ +#define XHCI_CRCR_RCS RT_BIT(0) /**< RW - Ring Cycle State */ +#define XHCI_CRCR_CS RT_BIT(1) /**< RW1S - Command Stop */ +#define XHCI_CRCR_CA RT_BIT(2) /**< RW1S - Command Abort */ +#define XHCI_CRCR_CRR RT_BIT(3) /**< RO - Command Ring Running */ + +#define XHCI_CRCR_RD_MASK UINT64_C(0xFFFFFFFFFFFFFFF8) /* Mask off bits always read as zero. */ +#define XHCI_CRCR_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) +#define XHCI_CRCR_UPD_MASK (XHCI_CRCR_ADDR_MASK | XHCI_CRCR_RCS) +/** @} */ + + +/** @name Interrupter Management Register (IMAN) bits + * @{ */ +#define XHCI_IMAN_IP RT_BIT(0) /**< RW1C - Interrupt Pending */ +#define XHCI_IMAN_IE RT_BIT(1) /**< RW - Interrupt Enable */ + +#define XHCI_IMAN_VALID_MASK (XHCI_IMAN_IP | XHCI_IMAN_IE) +/** @} */ + + +/** @name Interrupter Moderation Register (IMOD) bits + * @{ */ +#define XHCI_IMOD_IMODC_MASK 0xFFFF0000 /**< RW */ +#define XHCI_IMOD_IMODC_SHIFT 16 +#define XHCI_IMOD_IMODI_MASK 0x0000FFFF /**< RW */ +/** @} */ + + +/** @name Event Ring Segment Table Size Register (ERSTSZ) bits + * @{ */ +#define XHCI_ERSTSZ_MASK 0x0000FFFF /**< RW */ +/** @} */ + +/** @name Event Ring Segment Table Base Address Register (ERSTBA) bits + * @{ */ +#define XHCI_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) +/** @} */ + +/** For reasons that are not obvious, NEC/Renesas xHCs only require 16-bit + * alignment for the ERST base. This is not in line with the xHCI spec + * (which requires 64-bit alignment) but is clearly documented by NEC. + */ +#define NEC_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) + +/** Firmware revision reported in NEC/Renesas mode. Value chosen based on + * OS X driver check (OS X supports these chips since they're commonly + * found in ExpressCards). + */ +#define NEC_FW_REV 0x3028 + +/** @name Event Ring Deqeue Pointer Register (ERDP) bits + * @{ */ +#define XHCI_ERDP_DESI_MASK 0x00000007 /**< RW - Dequeue ERST Segment Index */ +#define XHCI_ERDP_EHB RT_BIT(3) /**< RW1C - Event Handler Busy */ +#define XHCI_ERDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) /**< RW - ERDP address mask */ +/** @} */ + +/** @name Device Context Base Address Array (DCBAA) definitions + * @{ */ +#define XHCI_DCBAA_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) /**< Applies to DCBAAP and its entries. */ +/** @} */ + +/** @name Doorbell Register bits + * @{ */ +#define XHCI_DB_TGT_MASK 0x000000FF /**< DB Target mask. */ +#define XHCI_DB_STRMID_SHIFT 16 /**< DB Stream ID shift. */ +#define XHCI_DB_STRMID_MASK 0xFFFF0000 /**< DB Stream ID mask. */ +/** @} */ + +/** Address mask for device/endpoint/input contexts. */ +#define XHCI_CTX_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) + +/** @name TRB Completion Codes + * @{ */ +#define XHCI_TCC_INVALID 0 /**< CC field not updated. */ +#define XHCI_TCC_SUCCESS 1 /**< Successful TRB completion. */ +#define XHCI_TCC_DATA_BUF_ERR 2 /**< Overrun/underrun. */ +#define XHCI_TCC_BABBLE 3 /**< Babble detected. */ +#define XHCI_TCC_USB_XACT_ERR 4 /**< USB transaction error. */ +#define XHCI_TCC_TRB_ERR 5 /**< TRB error detected. */ +#define XHCI_TCC_STALL 6 /**< USB Stall detected. */ +#define XHCI_TCC_RSRC_ERR 7 /**< Inadequate xHC resources. */ +#define XHCI_TCC_BWIDTH_ERR 8 /**< Unable to allocate bandwidth. */ +#define XHCI_TCC_NO_SLOTS 9 /**< MaxSlots (NDS) exceeded. */ +#define XHCI_TCC_INV_STRM_TYP 10 /**< Invalid stream context type. */ +#define XHCI_TCC_SLOT_NOT_ENB 11 /**< Slot not enabled. */ +#define XHCI_TCC_EP_NOT_ENB 12 /**< Endpoint not enabled. */ +#define XHCI_TCC_SHORT_PKT 13 /**< Short packet detected. */ +#define XHCI_TCC_RING_UNDERRUN 14 /**< Transfer ring underrun. */ +#define XHCI_TCC_RING_OVERRUN 15 /**< Transfer ring overrun. */ +#define XHCI_TCC_VF_RING_FULL 16 /**< VF event ring full. */ +#define XHCI_TCC_PARM_ERR 17 /**< Invalid context parameter. */ +#define XHCI_TCC_BWIDTH_OVER 18 /**< Isoc bandwidth overrun. */ +#define XHCI_TCC_CTX_STATE_ERR 19 /**< Transition from illegal context state. */ +#define XHCI_TCC_NO_PING 20 /**< No ping response in time. */ +#define XHCI_TCC_EVT_RING_FULL 21 /**< Event Ring full. */ +#define XHCI_TCC_DEVICE_COMPAT 22 /**< Incompatible device detected. */ +#define XHCI_TCC_MISS_SVC 23 /**< Missed isoc service. */ +#define XHCI_TCC_CMDR_STOPPED 24 /**< Command ring stopped. */ +#define XHCI_TCC_CMD_ABORTED 25 /**< Command aborted. */ +#define XHCI_TCC_STOPPED 26 /**< Endpoint stopped. */ +#define XHCI_TCC_STP_INV_LEN 27 /**< EP stopped, invalid transfer length. */ + /* 28 Reserved. */ +#define XHCI_TCC_MAX_EXIT_LAT 29 /**< Max exit latency too large. */ + /* 30 Reserved. */ +#define XHCI_TCC_ISOC_OVERRUN 31 /**< Isochronous buffer overrun. */ +#define XHCI_TCC_EVT_LOST 32 /**< Event lost due to overrun. */ +#define XHCI_TCC_ERR_OTHER 33 /**< Implementation specific error. */ +#define XHCI_TCC_INV_STRM_ID 34 /**< Invalid stream ID. */ +#define XHCI_TCC_SEC_BWIDTH_ERR 35 /**< Secondary bandwidth error. */ +#define XHCI_TCC_SPLIT_ERR 36 /**< Split transaction error. */ +/** @} */ + +#if defined(IN_RING3) && defined(LOG_ENABLED) +/** Human-readable completion code descriptions for debugging. */ +static const char * const g_apszCmplCodes[] = { + "CC field not updated", "Successful TRB completion", "Overrun/underrun", "Babble detected", /* 0-3 */ + "USB transaction error", "TRB error detected", "USB Stall detected", "Inadequate xHC resources", /* 4-7 */ + "Unable to allocate bandwidth", "MaxSlots (NDS) exceeded", "Invalid stream context type", "Slot not enabled", /* 8-11 */ + "Endpoint not enabled", "Short packet detected", "Transfer ring underrun", "Transfer ring overrun", /* 12-15 */ + "VF event ring full", "Invalid context param", "Isoc bandwidth overrun", "Transition from illegal ctx state", /* 16-19 */ + "No ping response in time", "Event Ring full", "Incompatible device detected", "Missed isoc service", /* 20-23 */ + "Command ring stopped", "Command aborted", "Endpoint stopped", "EP stopped, invalid transfer length", /* 24-27 */ + "Reserved", "Max exit latency too large", "Reserved", "Isochronous buffer overrun", /* 28-31 */ + "Event lost due to overrun", "Implementation specific error", "Invalid stream ID", "Secondary bandwidth error", /* 32-35 */ + "Split transaction error" /* 36 */ +}; +#endif + + +/* TRBs marked as 'TRB' are only valid in the transfer ring. TRBs marked + * as 'Command' are only valid in the command ring. TRBs marked as 'Event' + * are the only ones generated in the event ring. The Link TRB is valid + * in both the transfer and command rings. + */ + +/** @name TRB Types + * @{ */ +#define XHCI_TRB_INVALID 0 /**< Reserved/unused TRB type. */ +#define XHCI_TRB_NORMAL 1 /**< Normal TRB. */ +#define XHCI_TRB_SETUP_STG 2 /**< Setup Stage TRB. */ +#define XHCI_TRB_DATA_STG 3 /**< Data Stage TRB. */ +#define XHCI_TRB_STATUS_STG 4 /**< Status Stage TRB. */ +#define XHCI_TRB_ISOCH 5 /**< Isochronous TRB. */ +#define XHCI_TRB_LINK 6 /**< Link. */ +#define XHCI_TRB_EVT_DATA 7 /**< Event Data TRB. */ +#define XHCI_TRB_NOOP_XFER 8 /**< No-op transfer TRB. */ +#define XHCI_TRB_ENB_SLOT 9 /**< Enable Slot Command. */ +#define XHCI_TRB_DIS_SLOT 10 /**< Disable Slot Command. */ +#define XHCI_TRB_ADDR_DEV 11 /**< Address Device Command. */ +#define XHCI_TRB_CFG_EP 12 /**< Configure Endpoint Command. */ +#define XHCI_TRB_EVAL_CTX 13 /**< Evaluate Context Command. */ +#define XHCI_TRB_RESET_EP 14 /**< Reset Endpoint Command. */ +#define XHCI_TRB_STOP_EP 15 /**< Stop Endpoint Command. */ +#define XHCI_TRB_SET_DEQ_PTR 16 /**< Set TR Dequeue Pointer Command. */ +#define XHCI_TRB_RESET_DEV 17 /**< Reset Device Command. */ +#define XHCI_TRB_FORCE_EVT 18 /**< Force Event Command. */ +#define XHCI_TRB_NEG_BWIDTH 19 /**< Negotiate Bandwidth Command. */ +#define XHCI_TRB_SET_LTV 20 /**< Set Latency Tolerate Value Command. */ +#define XHCI_TRB_GET_PORT_BW 21 /**< Get Port Bandwidth Command. */ +#define XHCI_TRB_FORCE_HDR 22 /**< Force Header Command. */ +#define XHCI_TRB_NOOP_CMD 23 /**< No-op Command. */ + /* 24-31 Reserved. */ +#define XHCI_TRB_XFER 32 /**< Transfer Event. */ +#define XHCI_TRB_CMD_CMPL 33 /**< Command Completion Event. */ +#define XHCI_TRB_PORT_SC 34 /**< Port Status Change Event. */ +#define XHCI_TRB_BW_REQ 35 /**< Bandwidth Request Event. */ +#define XHCI_TRB_DBELL 36 /**< Doorbell Event. */ +#define XHCI_TRB_HC_EVT 37 /**< Host Controller Event. */ +#define XHCI_TRB_DEV_NOTIFY 38 /**< Device Notification Event. */ +#define XHCI_TRB_MFIDX_WRAP 39 /**< MFINDEX Wrap Event. */ + /* 40-47 Reserved. */ +#define NEC_TRB_CMD_CMPL 48 /**< Command Completion Event, NEC specific. */ +#define NEC_TRB_GET_FW_VER 49 /**< Get Firmware Version Command, NEC specific. */ +#define NEC_TRB_AUTHENTICATE 50 /**< Authenticate Command, NEC specific. */ +/** @} */ + +#if defined(IN_RING3) && defined(LOG_ENABLED) +/** Human-readable TRB names for debugging. */ +static const char * const g_apszTrbNames[] = { + "Reserved/unused TRB!!", "Normal TRB", "Setup Stage TRB", "Data Stage TRB", /* 0-3 */ + "Status Stage TRB", "Isochronous TRB", "Link", "Event Data TRB", /* 4-7 */ + "No-op transfer TRB", "Enable Slot", "Disable Slot", "Address Device", /* 8-11 */ + "Configure Endpoint", "Evaluate Context", "Reset Endpoint", "Stop Endpoint", /* 12-15 */ + "Set TR Dequeue Pointer", "Reset Device", "Force Event", "Negotiate Bandwidth", /* 16-19 */ + "Set Latency Tolerate Value", "Get Port Bandwidth", "Force Header", "No-op", /* 20-23 */ + "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 24-31 */ + "Transfer", "Command Completion", "Port Status Change", "BW Request", /* 32-35 */ + "Doorbell", "Host Controller", "Device Notification", "MFINDEX Wrap", /* 36-39 */ + "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 40-47 */ + "NEC FW Version Completion", "NEC Get FW Version", "NEC Authenticate" /* 48-50 */ +}; +#endif + +/** Generic TRB template. */ +typedef struct sXHCI_TRB_G { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_G; +AssertCompile(sizeof(XHCI_TRB_G) == 0x10); + +/** Generic transfer TRB template. */ +typedef struct sXHCI_TRB_GX { + uint32_t resvd0; + uint32_t resvd1; + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t resvd2 : 5; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd3 : 3; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_GX; +AssertCompile(sizeof(XHCI_TRB_GX) == 0x10); + + +/* -= Transfer TRB types =- */ + + +/** Normal Transfer TRB. */ +typedef struct sXHCI_TRB_NORM { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd0 : 2; + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd1 : 16; +} XHCI_TRB_NORM; +AssertCompile(sizeof(XHCI_TRB_NORM) == 0x10); + +/** Control Transfer - Setup Stage TRB. */ +typedef struct sXHCI_TRB_CTSP { + uint8_t bmRequestType; /**< See the USB spec. */ + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint32_t xfr_len : 17; /**< Transfer length (8). */ + uint32_t resvd0 : 5; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 4; + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd2 : 2; + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t trt : 2; /**< Transfer Type. */ + uint32_t resvd3 : 14; +} XHCI_TRB_CTSP; +AssertCompile(sizeof(XHCI_TRB_CTSP) == 0x10); + +/** Control Transfer - Data Stage TRB. */ +typedef struct sXHCI_TRB_CTDT { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd0 : 3; + uint32_t type : 6; /**< TRB Type. */ + uint32_t dir : 1; /**< Direction (1=IN). */ + uint32_t resvd1 : 15; +} XHCI_TRB_CTDT; +AssertCompile(sizeof(XHCI_TRB_CTDT) == 0x10); + +/** Control Transfer - Status Stage TRB. */ +typedef struct sXHCI_TRB_CTSS { + uint64_t resvd0; + uint32_t resvd1 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t resvd2 : 2; + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t resvd3 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t dir : 1; /**< Direction (1=IN). */ + uint32_t resvd4 : 15; +} XHCI_TRB_CTSS; +AssertCompile(sizeof(XHCI_TRB_CTSS) == 0x10); + +/** Isochronous Transfer TRB. */ +typedef struct sXHCI_TRB_ISOC { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t tbc : 2; /**< Transfer Burst Count. */ + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t tlbpc : 4; /**< Transfer Last Burst Packet Count. */ + uint32_t frm_id : 11; /**< Frame ID. */ + uint32_t sia : 1; /**< Start Isoch ASAP. */ +} XHCI_TRB_ISOC; +AssertCompile(sizeof(XHCI_TRB_ISOC) == 0x10); + +/* Number of bits in the frame ID. */ +#define XHCI_FRAME_ID_BITS 11 + +/** No Op Transfer TRB. */ +typedef struct sXHCI_TRB_NOPT { + uint64_t resvd0; + uint32_t resvd1 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t resvd2 : 2; + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t resvd3 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_NOPT; +AssertCompile(sizeof(XHCI_TRB_NOPT) == 0x10); + + +/* -= Event TRB types =- */ + + +/** Transfer Event TRB. */ +typedef struct sXHCI_TRB_TE { + uint64_t trb_ptr; /**< TRB pointer. */ + uint32_t xfr_len : 24; /**< Transfer length. */ + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd0 : 1; + uint32_t ed : 1; /**< Event Data flag. */ + uint32_t resvd1 : 7; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd2 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_TE; +AssertCompile(sizeof(XHCI_TRB_TE) == 0x10); + +/** Command Completion Event TRB. */ +typedef struct sXHCI_TRB_CCE { + uint64_t trb_ptr; /**< Command TRB pointer. */ + uint32_t resvd0 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t vf_id : 8; /**< Virtual Function ID. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_CCE; +AssertCompile(sizeof(XHCI_TRB_CCE) == 0x10); + +/** Port Staus Change Event TRB. */ +typedef struct sXHCI_TRB_PSCE { + uint32_t resvd0 : 24; + uint32_t port_id : 8; /**< Port ID. */ + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_PSCE; +AssertCompile(sizeof(XHCI_TRB_PSCE) == 0x10); + +/** Bandwidth Request Event TRB. */ +typedef struct sXHCI_TRB_BRE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_BRE; +AssertCompile(sizeof(XHCI_TRB_BRE) == 0x10); + +/** Doorbell Event TRB. */ +typedef struct sXHCI_TRB_DBE { + uint32_t reason : 5; /**< DB Reason/target. */ + uint32_t resvd0 : 27; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t vf_id : 8; /**< Virtual Function ID. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DBE; +AssertCompile(sizeof(XHCI_TRB_DBE) == 0x10); + +/** Host Controller Event TRB. */ +typedef struct sXHCI_TRB_HCE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_HCE; +AssertCompile(sizeof(XHCI_TRB_HCE) == 0x10); + +/** Device Notification Event TRB. */ +typedef struct sXHCI_TRB_DNE { + uint32_t resvd0 : 4; + uint32_t dn_type : 4; /**< Device Notification Type. */ + uint32_t dnd_lo : 5; /**< Device Notification Data Lo. */ + uint32_t dnd_hi; /**< Device Notification Data Hi. */ + uint32_t resvd1 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd2 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DNE; +AssertCompile(sizeof(XHCI_TRB_DNE) == 0x10); + +/** MFINDEX Wrap Event TRB. */ +typedef struct sXHCI_TRB_MWE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_MWE; +AssertCompile(sizeof(XHCI_TRB_MWE) == 0x10); + +/** NEC Specific Command Completion Event TRB. */ +typedef struct sXHCI_TRB_NCE { + uint64_t trb_ptr; /**< Command TRB pointer. */ + uint32_t word1 : 16; /**< First result word. */ + uint32_t resvd0 : 8; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t word2 : 16; /**< Second result word. */ +} XHCI_TRB_NCE; +AssertCompile(sizeof(XHCI_TRB_NCE) == 0x10); + + + +/* -= Command TRB types =- */ + + +/** No Op Command TRB. */ +typedef struct sXHCI_TRB_NOPC { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_NOPC; +AssertCompile(sizeof(XHCI_TRB_NOPC) == 0x10); + +/** Enable Slot Command TRB. */ +typedef struct sXHCI_TRB_ESL { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_ESL; +AssertCompile(sizeof(XHCI_TRB_ESL) == 0x10); + +/** Disable Slot Command TRB. */ +typedef struct sXHCI_TRB_DSL { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DSL; +AssertCompile(sizeof(XHCI_TRB_DSL) == 0x10); + +/** Address Device Command TRB. */ +typedef struct sXHCI_TRB_ADR { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 8; + uint32_t bsr : 1; /**< Block Set Address Request. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_ADR; +AssertCompile(sizeof(XHCI_TRB_ADR) == 0x10); + +/** Configure Endpoint Command TRB. */ +typedef struct sXHCI_TRB_CFG { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 8; + uint32_t dc : 1; /**< Deconfigure. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_CFG; +AssertCompile(sizeof(XHCI_TRB_CFG) == 0x10); + +/** Evaluate Context Command TRB. */ +typedef struct sXHCI_TRB_EVC { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_EVC; +AssertCompile(sizeof(XHCI_TRB_EVC) == 0x10); + +/** Reset Endpoint Command TRB. */ +typedef struct sXHCI_TRB_RSE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 8; + uint32_t tsp : 1; /**< Transfer State Preserve. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd4 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_RSE; +AssertCompile(sizeof(XHCI_TRB_RSE) == 0x10); + +/** Stop Endpoint Command TRB. */ +typedef struct sXHCI_TRB_STP { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd4 : 2; + uint32_t sp : 1; /**< Suspend. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_STP; +AssertCompile(sizeof(XHCI_TRB_STP) == 0x10); + +/** Set TR Dequeue Pointer Command TRB. */ +typedef struct sXHCI_TRB_STDP { +#if 0 + uint64_t dcs : 1; /**< Dequeue Cycle State. */ + uint64_t sct : 3; /**< Stream Context Type. */ + uint64_t tr_dqp : 60; /**< New TR Dequeue Pointer (63:4). */ +#else + uint64_t tr_dqp; +#endif + uint16_t resvd0; + uint16_t strm_id; /**< Stream ID. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd2 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_STDP; +AssertCompile(sizeof(XHCI_TRB_STDP) == 0x10); + +/** Reset Device Command TRB. */ +typedef struct sXHCI_TRB_RSD { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_RSD; +AssertCompile(sizeof(XHCI_TRB_RSD) == 0x10); + +/** Get Port Bandwidth Command TRB. */ +typedef struct sXHCI_TRB_GPBW { + uint64_t pbctx_ptr; /**< Port Bandwidth Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t spd : 4; /**< Dev Speed. */ + uint32_t resvd2 : 4; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_GPBW; +AssertCompile(sizeof(XHCI_TRB_GPBW) == 0x10); + +/** Force Header Command TRB. */ +typedef struct sXHCI_TRB_FHD { + uint32_t pkt_typ : 5; /**< Packet Type. */ + uint32_t hdr_lo : 27; /**< Header Info Lo. */ + uint32_t hdr_mid; /**< Header Info Mid. */ + uint32_t hdr_hi; /**< Header Info Hi. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd0 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd1 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_FHD; +AssertCompile(sizeof(XHCI_TRB_FHD) == 0x10); + +/** NEC Specific Authenticate Command TRB. */ +typedef struct sXHCI_TRB_NAC { + uint64_t cookie; /**< Cookie to munge. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_NAC; +AssertCompile(sizeof(XHCI_TRB_NAC) == 0x10); + + +/* -= Other TRB types =- */ + + +/** Link TRB. */ +typedef struct sXHCI_TRB_LNK { + uint64_t rseg_ptr; /**< Ring Segment Pointer. */ + uint32_t resvd0 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t toggle : 1; /**< Toggle Cycle flag. */ + uint32_t resvd1 : 2; + uint32_t chain : 1; /**< Chain flag. */ + uint32_t ioc : 1; /**< Interrupt On Completion flag. */ + uint32_t resvd2 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 16; +} XHCI_TRB_LNK; +AssertCompile(sizeof(XHCI_TRB_LNK) == 0x10); + +/** Event Data TRB. */ +typedef struct sXHCI_TRB_EVTD { + uint64_t evt_data; /**< Event Data. */ + uint32_t resvd0 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next Target flag. */ + uint32_t resvd1 : 2; + uint32_t chain : 1; /**< Chain flag. */ + uint32_t ioc : 1; /**< Interrupt On Completion flag. */ + uint32_t resvd2 : 3; + uint32_t bei : 1; /**< Block Event Interrupt flag. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 16; +} XHCI_TRB_EVTD; +AssertCompile(sizeof(XHCI_TRB_EVTD) == 0x10); + + +/* -= Union TRB types for the three rings =- */ + + +typedef union sXHCI_XFER_TRB { + XHCI_TRB_NORM norm; + XHCI_TRB_CTSP setup; + XHCI_TRB_CTDT data; + XHCI_TRB_CTSS status; + XHCI_TRB_ISOC isoc; + XHCI_TRB_EVTD evtd; + XHCI_TRB_NOPT nop; + XHCI_TRB_LNK link; + XHCI_TRB_GX gen; +} XHCI_XFER_TRB; +AssertCompile(sizeof(XHCI_XFER_TRB) == 0x10); + +typedef union sXHCI_COMMAND_TRB { + XHCI_TRB_ESL esl; + XHCI_TRB_DSL dsl; + XHCI_TRB_ADR adr; + XHCI_TRB_CFG cfg; + XHCI_TRB_EVC evc; + XHCI_TRB_RSE rse; + XHCI_TRB_STP stp; + XHCI_TRB_STDP stdp; + XHCI_TRB_RSD rsd; + XHCI_TRB_GPBW gpbw; + XHCI_TRB_FHD fhd; + XHCI_TRB_NAC nac; + XHCI_TRB_NOPC nopc; + XHCI_TRB_LNK link; + XHCI_TRB_G gen; +} XHCI_COMMAND_TRB; +AssertCompile(sizeof(XHCI_COMMAND_TRB) == 0x10); + +typedef union sXHCI_EVENT_TRB { + XHCI_TRB_TE te; + XHCI_TRB_CCE cce; + XHCI_TRB_PSCE psce; + XHCI_TRB_BRE bre; + XHCI_TRB_DBE dbe; + XHCI_TRB_HCE hce; + XHCI_TRB_DNE dne; + XHCI_TRB_MWE mwe; + XHCI_TRB_NCE nce; + XHCI_TRB_G gen; +} XHCI_EVENT_TRB; +AssertCompile(sizeof(XHCI_EVENT_TRB) == 0x10); + + + +/* -=-=-= Contexts =-=-=- */ + +/** Slot Context. */ +typedef struct sXHCI_SLOT_CTX { + uint32_t route_str : 20; /**< Route String. */ + uint32_t speed : 4; /**< Device speed. */ + uint32_t resvd0 : 1; + uint32_t mtt : 1; /**< Multi-TT flag. */ + uint32_t hub : 1; /**< Hub flag. */ + uint32_t ctx_ent : 5; /**< Context entries. */ + uint32_t max_lat : 16; /**< Max exit latency in usec. */ + uint32_t rh_port : 8; /**< Root hub port number (1-based). */ + uint32_t n_ports : 8; /**< No. of ports for hubs. */ + uint32_t tt_slot : 8; /**< TT hub slot ID. */ + uint32_t tt_port : 8; /**< TT port number. */ + uint32_t ttt : 2; /**< TT Think Time. */ + uint32_t resvd1 : 4; + uint32_t intr_tgt : 10; /**< Interrupter Target. */ + uint32_t dev_addr : 8; /**< Device Address. */ + uint32_t resvd2 : 19; + uint32_t slot_state : 5; /**< Slot State. */ + uint32_t opaque[4]; /**< For xHC (i.e. our own) use. */ +} XHCI_SLOT_CTX; +AssertCompile(sizeof(XHCI_SLOT_CTX) == 0x20); + +/** @name Slot Context states + * @{ */ +#define XHCI_SLTST_ENDIS 0 /**< Enabled/Disabled. */ +#define XHCI_SLTST_DEFAULT 1 /**< Default. */ +#define XHCI_SLTST_ADDRESSED 2 /**< Addressed. */ +#define XHCI_SLTST_CONFIGURED 3 /**< Configured. */ +/** @} */ + +#ifdef IN_RING3 +/** Human-readable slot state descriptions for debugging. */ +static const char * const g_apszSltStates[] = { + "Enabled/Disabled", "Default", "Addressed", "Configured" /* 0-3 */ +}; +#endif + +/** Endpoint Context. */ +typedef struct sXHCI_EP_CTX { + uint32_t ep_state : 3; /**< Endpoint state. */ + uint32_t resvd0 : 5; + uint32_t mult : 2; /**< SS isoc burst count. */ + uint32_t maxps : 5; /**< Max Primary Streams. */ + uint32_t lsa : 1; /**< Linear Stream Array. */ + uint32_t interval : 8; /**< USB request interval. */ + uint32_t resvd1 : 8; + uint32_t resvd2 : 1; + uint32_t c_err : 2; /**< Error count. */ + uint32_t ep_type : 3; /**< Endpoint type. */ + uint32_t resvd3 : 1; + uint32_t hid : 1; /**< Host Initiate Disable. */ + uint32_t max_brs_sz : 8; /**< Max Burst Size. */ + uint32_t max_pkt_sz : 16; /**< Max Packet Size. */ + uint64_t trdp; /**< TR Dequeue Pointer. */ + uint32_t avg_trb_len : 16; /**< Average TRB Length. */ + uint32_t max_esit : 16; /**< Max EP Service Interval Time Payload. */ + /**< The rest for xHC (i.e. our own) use. */ + uint32_t last_frm : 16; /**< Last isochronous frame used (opaque). */ + uint32_t ifc : 8; /**< isoch in-flight TD count (opaque). */ + uint32_t last_cc : 8; /**< Last TRB completion code (opaque). */ + uint64_t trep; /**< TR Enqueue Pointer (opaque). */ +} XHCI_EP_CTX; +AssertCompile(sizeof(XHCI_EP_CTX) == 0x20); + +/** @name Endpoint Context states + * @{ */ +#define XHCI_EPST_DISABLED 0 /**< Disabled. */ +#define XHCI_EPST_RUNNING 1 /**< Running. */ +#define XHCI_EPST_HALTED 2 /**< Halted. */ +#define XHCI_EPST_STOPPED 3 /**< Not running/stopped. */ +#define XHCI_EPST_ERROR 4 /**< Not running/error. */ +/** @} */ + +/** @name Endpoint Type values + * @{ */ +#define XHCI_EPTYPE_INVALID 0 /**< Not valid. */ +#define XHCI_EPTYPE_ISOCH_OUT 1 /**< Isochronous Out. */ +#define XHCI_EPTYPE_BULK_OUT 2 /**< Bulk Out. */ +#define XHCI_EPTYPE_INTR_OUT 3 /**< Interrupt Out. */ +#define XHCI_EPTYPE_CONTROL 4 /**< Control Bidi. */ +#define XHCI_EPTYPE_ISOCH_IN 5 /**< Isochronous In. */ +#define XHCI_EPTYPE_BULK_IN 6 /**< Bulk In. */ +#define XHCI_EPTYPE_INTR_IN 7 /**< Interrupt In. */ +/** @} */ + +/* Pick out transfer type from endpoint. */ +#define XHCI_EP_XTYPE(a) (a & 3) + +/* Endpoint transfer types. */ +#define XHCI_XFTYPE_CONTROL 0 +#define XHCI_XFTYPE_ISOCH XHCI_EPTYPE_ISOCH_OUT +#define XHCI_XFTYPE_BULK XHCI_EPTYPE_BULK_OUT +#define XHCI_XFTYPE_INTR XHCI_EPTYPE_INTR_OUT + +/* Transfer Ring Dequeue Pointer address mask. */ +#define XHCI_TRDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) +#define XHCI_TRDP_DCS_MASK RT_BIT(0) /* Dequeue Cycle State bit. */ + + +#ifdef IN_RING3 + +/* Human-readable endpoint state descriptions for debugging. */ +static const char * const g_apszEpStates[] = { + "Disabled", "Running", "Halted", "Stopped", "Error" /* 0-4 */ +}; + +/* Human-readable endpoint type descriptions for debugging. */ +static const char * const g_apszEpTypes[] = { + "Not Valid", "Isoch Out", "Bulk Out", "Interrupt Out", /* 0-3 */ + "Control", "Isoch In", "Bulk In", "Interrupt In" /* 4-7 */ +}; + +#endif /* IN_RING3 */ + +/* Input Control Context. */ +typedef struct sXHCI_INPC_CTX { + uint32_t drop_flags; /* Drop Context flags (2-31). */ + uint32_t add_flags; /* Add Context flags (0-31). */ + uint32_t resvd[6]; +} XHCI_INPC_CTX; +AssertCompile(sizeof(XHCI_INPC_CTX) == 0x20); + +/* Make sure all contexts are the same size. */ +AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_SLOT_CTX)); +AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_INPC_CTX)); + +/* -= Event Ring Segment Table =- */ + +/** Event Ring Segment Table Entry. */ +typedef struct sXHCI_ERSTE { + uint64_t addr; + uint16_t size; + uint16_t resvd0; + uint32_t resvd1; +} XHCI_ERSTE; +AssertCompile(sizeof(XHCI_ERSTE) == 0x10); + + +/* -=-= Internal data structures not defined by xHCI =-=- */ + + +/** Device slot entry -- either slot context or endpoint context. */ +typedef union sXHCI_DS_ENTRY { + XHCI_SLOT_CTX sc; /**< Slot context. */ + XHCI_EP_CTX ep; /**< Endpoint context. */ +} XHCI_DS_ENTRY; + +/** Full device context (slot context + 31 endpoint contexts). */ +typedef struct sXHCI_DEV_CTX { + XHCI_DS_ENTRY entry[32]; +} XHCI_DEV_CTX; +AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_EP_CTX)); +AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_SLOT_CTX)); + +/** Pointer to the xHCI device state. */ +typedef struct XHCI *PXHCI; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * The xHCI controller data associated with each URB. + */ +typedef struct VUSBURBHCIINT +{ + /** The slot index. */ + uint8_t uSlotID; + /** Number of Tds in the array. */ + uint32_t cTRB; +} VUSBURBHCIINT; +#endif + +/** + * An xHCI root hub port, shared. + */ +typedef struct XHCIHUBPORT +{ + /** PORTSC: Port status/control register (R/W). */ + uint32_t portsc; + /** PORTPM: Power management status/control register (R/W). */ + uint32_t portpm; + /** PORTLI: USB3 port link information (R/O). */ + uint32_t portli; +} XHCIHUBPORT; +/** Pointer to a shared xHCI root hub port. */ +typedef XHCIHUBPORT *PXHCIHUBPORT; + +/** + * An xHCI root hub port, ring-3. + */ +typedef struct XHCIHUBPORTR3 +{ + /** Flag whether there is a device attached to the port. */ + bool fAttached; +} XHCIHUBPORTR3; +/** Pointer to a ring-3 xHCI root hub port. */ +typedef XHCIHUBPORTR3 *PXHCIHUBPORTR3; + +/** + * The xHCI root hub, ring-3 only. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + */ +typedef struct XHCIROOTHUBR3 +{ + /** Pointer to the parent xHC. */ + R3PTRTYPE(struct XHCIR3 *) pXhciR3; + /** Pointer to the base interface of the VUSB RootHub. */ + R3PTRTYPE(PPDMIBASE) pIBase; + /** Pointer to the connector interface of the VUSB RootHub. */ + R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn; + /** The base interface exposed to the roothub driver. */ + PDMIBASE IBase; + /** The roothub port interface exposed to the roothub driver. */ + VUSBIROOTHUBPORT IRhPort; + + /** The LED for this hub. */ + PDMLED Led; + + /** Number of actually implemented ports. */ + uint8_t cPortsImpl; + /** Index of first port for this hub. */ + uint8_t uPortBase; + + uint16_t Alignment0; /**< Force alignment. */ +#if HC_ARCH_BITS == 64 + uint32_t Alignment1; +#endif +} XHCIROOTHUBR3; +/** Pointer to a xHCI root hub (ring-3 only). */ +typedef XHCIROOTHUBR3 *PXHCIROOTHUBR3; + +/** + * An xHCI interrupter. + */ +typedef struct sXHCIINTRPTR +{ + /* Registers defined by xHCI. */ + /** IMAN: Interrupt Management Register (R/W). */ + uint32_t iman; + /** IMOD: Interrupt Moderation Register (R/W). */ + uint32_t imod; + /** ERSTSZ: Event Ring Segment Table Size (R/W). */ + uint32_t erstsz; + /* Reserved/padding. */ + uint32_t reserved; + /** ERSTBA: Event Ring Segment Table Base Address (R/W). */ + uint64_t erstba; + /** ERDP: Event Ring Dequeue Pointer (R/W). */ + uint64_t erdp; + /* Interrupter lock. */ + PDMCRITSECT lock; + /* Internal xHCI non-register state. */ + /** Internal Event Ring enqueue pointer. */ + uint64_t erep; + /** Internal ERDP re-write counter. */ + uint32_t erdp_rewrites; + /** This interrupter's index (for logging). */ + uint32_t index; + /** Internal index into Event Ring Segment Table. */ + uint16_t erst_idx; + /** Internal index into Event Ring Segment. */ + uint16_t trb_count; + /** Internal Event Ring Producer Cycle State. */ + bool evtr_pcs; + /** Internal Interrupt Pending Enable flag. */ + bool ipe; +} XHCIINTRPTR, *PXHCIINTRPTR; + +/** + * xHCI device state. + * @implements PDMILEDPORTS + */ +typedef struct XHCI +{ + /** MFINDEX wraparound timer. */ + TMTIMERHANDLE hWrapTimer; + +#ifdef XHCI_ERROR_INJECTION + bool fDropIntrHw; + bool fDropIntrIpe; + bool fDropUrb; + uint8_t Alignment00[1]; +#else + uint32_t Alignment00; /**< Force alignment. */ +#endif + + /** Flag indicating a sleeping worker thread. */ + volatile bool fWrkThreadSleeping; + volatile bool afPadding[3]; + + /** The event semaphore the worker thread waits on. */ + SUPSEMEVENT hEvtProcess; + + /** Bitmap for finished tasks (R3 -> Guest). */ + volatile uint32_t u32TasksFinished; + /** Bitmap for finished queued tasks (R3 -> Guest). */ + volatile uint32_t u32QueuedTasksFinished; + /** Bitmap for new queued tasks (Guest -> R3). */ + volatile uint32_t u32TasksNew; + + /** Copy of XHCIR3::RootHub2::cPortsImpl. */ + uint8_t cUsb2Ports; + /** Copy of XHCIR3::RootHub3::cPortsImpl. */ + uint8_t cUsb3Ports; + /** Sum of cUsb2Ports and cUsb3Ports. */ + uint8_t cTotalPorts; + /** Explicit padding. */ + uint8_t bPadding; + + /** Start of current frame. */ + uint64_t SofTime; + /** State of the individual ports. */ + XHCIHUBPORT aPorts[XHCI_NDP_MAX]; + /** Interrupters array. */ + XHCIINTRPTR aInterrupters[XHCI_NINTR]; + + /** @name Host Controller Capability Registers + * @{ */ + /** CAPLENGTH: base + CAPLENGTH = operational register start (R/O). */ + uint32_t cap_length; + /** HCIVERSION: host controller interface version (R/O). */ + uint32_t hci_version; + /** HCSPARAMS: Structural parameters 1 (R/O). */ + uint32_t hcs_params1; + /** HCSPARAMS: Structural parameters 2 (R/O). */ + uint32_t hcs_params2; + /** HCSPARAMS: Structural parameters 3 (R/O). */ + uint32_t hcs_params3; + /** HCCPARAMS: Capability parameters (R/O). */ + uint32_t hcc_params; + /** DBOFF: Doorbell offset (R/O). */ + uint32_t dbell_off; + /** RTSOFF: Run-time register space offset (R/O). */ + uint32_t rts_off; + /** @} */ + + /** @name Host Controller Operational Registers + * @{ */ + /** USB command register - USBCMD (R/W). */ + uint32_t cmd; + /** USB status register - USBSTS (R/W).*/ + uint32_t status; + /** Device Control Notification register - DNCTRL (R/W). */ + uint32_t dnctrl; + /** Configure Register (R/W). */ + uint32_t config; + /** Command Ring Control Register - CRCR (R/W). */ + uint64_t crcr; + /** Device Context Base Address Array Pointer (R/W). */ + uint64_t dcbaap; + /** @} */ + + /** Extended Capabilities storage. */ + uint8_t abExtCap[XHCI_EXT_CAP_SIZE]; + /** Size of valid extended capabilities. */ + uint32_t cbExtCap; + + uint32_t Alignment1; /**< Align cmdr_dqp. */ + + /** @name Internal xHCI non-register state + * @{ */ + /** Internal Command Ring dequeue pointer. */ + uint64_t cmdr_dqp; + /** Internal Command Ring Consumer Cycle State. */ + bool cmdr_ccs; + uint8_t aAlignment2[7]; /**< Force alignment. */ + /** Internal Device Slot states. */ + uint8_t aSlotState[XHCI_NDS]; + /** Internal doorbell states. Each bit corresponds to an endpoint. */ + uint32_t aBellsRung[XHCI_NDS]; + /** @} */ + + /** @name Model specific configuration + * @{ */ + /** ERST address mask. */ + uint64_t erst_addr_mask; + /** @} */ + + /** The MMIO region. */ + IOMMMIOHANDLE hMmio; + + /** Detected isochronous URBs completed with error. */ + STAMCOUNTER StatErrorIsocUrbs; + /** Detected isochronous packets (not URBs!) with error. */ + STAMCOUNTER StatErrorIsocPkts; + + /** Event TRBs written to event ring(s). */ + STAMCOUNTER StatEventsWritten; + /** Event TRBs not written to event ring(s) due to HC being stopped. */ + STAMCOUNTER StatEventsDropped; + /** Requests to set the IP bit. */ + STAMCOUNTER StatIntrsPending; + /** Actual interrupt deliveries. */ + STAMCOUNTER StatIntrsSet; + /** Interrupts not raised because they were disabled. */ + STAMCOUNTER StatIntrsNotSet; + /** A pending interrupt was cleared. */ + STAMCOUNTER StatIntrsCleared; + /** Number of TRBs that formed a single control URB. */ + STAMCOUNTER StatTRBsPerCtlUrb; + /** Number of TRBs that formed a single data (bulk/interrupt) URB. */ + STAMCOUNTER StatTRBsPerDtaUrb; + /** Number of TRBs that formed a single isochronous URB. */ + STAMCOUNTER StatTRBsPerIsoUrb; + /** Size of a control URB in bytes. */ + STAMCOUNTER StatUrbSizeCtrl; + /** Size of a data URB in bytes. */ + STAMCOUNTER StatUrbSizeData; + /** Size of an isochronous URB in bytes. */ + STAMCOUNTER StatUrbSizeIsoc; + +#ifdef VBOX_WITH_STATISTICS + /** @name Register access counters. + * @{ */ + STAMCOUNTER StatRdCaps; + STAMCOUNTER StatRdCmdRingCtlHi; + STAMCOUNTER StatRdCmdRingCtlLo; + STAMCOUNTER StatRdConfig; + STAMCOUNTER StatRdDevCtxBaapHi; + STAMCOUNTER StatRdDevCtxBaapLo; + STAMCOUNTER StatRdDevNotifyCtrl; + STAMCOUNTER StatRdDoorBell; + STAMCOUNTER StatRdEvtRingDeqPtrHi; + STAMCOUNTER StatRdEvtRingDeqPtrLo; + STAMCOUNTER StatRdEvtRsTblBaseHi; + STAMCOUNTER StatRdEvtRsTblBaseLo; + STAMCOUNTER StatRdEvtRstblSize; + STAMCOUNTER StatRdEvtRsvd; + STAMCOUNTER StatRdIntrMgmt; + STAMCOUNTER StatRdIntrMod; + STAMCOUNTER StatRdMfIndex; + STAMCOUNTER StatRdPageSize; + STAMCOUNTER StatRdPortLinkInfo; + STAMCOUNTER StatRdPortPowerMgmt; + STAMCOUNTER StatRdPortRsvd; + STAMCOUNTER StatRdPortStatusCtrl; + STAMCOUNTER StatRdUsbCmd; + STAMCOUNTER StatRdUsbSts; + STAMCOUNTER StatRdUnknown; + + STAMCOUNTER StatWrCmdRingCtlHi; + STAMCOUNTER StatWrCmdRingCtlLo; + STAMCOUNTER StatWrConfig; + STAMCOUNTER StatWrDevCtxBaapHi; + STAMCOUNTER StatWrDevCtxBaapLo; + STAMCOUNTER StatWrDevNotifyCtrl; + STAMCOUNTER StatWrDoorBell0; + STAMCOUNTER StatWrDoorBellN; + STAMCOUNTER StatWrEvtRingDeqPtrHi; + STAMCOUNTER StatWrEvtRingDeqPtrLo; + STAMCOUNTER StatWrEvtRsTblBaseHi; + STAMCOUNTER StatWrEvtRsTblBaseLo; + STAMCOUNTER StatWrEvtRstblSize; + STAMCOUNTER StatWrIntrMgmt; + STAMCOUNTER StatWrIntrMod; + STAMCOUNTER StatWrPortPowerMgmt; + STAMCOUNTER StatWrPortStatusCtrl; + STAMCOUNTER StatWrUsbCmd; + STAMCOUNTER StatWrUsbSts; + STAMCOUNTER StatWrUnknown; + /** @} */ +#endif +} XHCI; + +/** + * xHCI device state, ring-3 edition. + * @implements PDMILEDPORTS + */ +typedef struct XHCIR3 +{ + /** The async worker thread. */ + R3PTRTYPE(PPDMTHREAD) pWorkerThread; + /** The device instance. + * @note This is only so interface functions can get their bearings. */ + PPDMDEVINSR3 pDevIns; + + /** Status LUN: The base interface. */ + PDMIBASE IBase; + /** Status LUN: Leds interface. */ + PDMILEDPORTS ILeds; + /** Status LUN: Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + /** USB 2.0 Root hub device. */ + XHCIROOTHUBR3 RootHub2; + /** USB 3.0 Root hub device. */ + XHCIROOTHUBR3 RootHub3; + + /** State of the individual ports. */ + XHCIHUBPORTR3 aPorts[XHCI_NDP_MAX]; + + /** Critsect to synchronize worker and I/O completion threads. */ + RTCRITSECT CritSectThrd; +} XHCIR3; +/** Pointer to ring-3 xHCI device state. */ +typedef XHCIR3 *PXHCIR3; + +/** + * xHCI device data, ring-0 edition. + */ +typedef struct XHCIR0 +{ + uint32_t uUnused; +} XHCIR0; +/** Pointer to ring-0 xHCI device data. */ +typedef struct XHCIR0 *PXHCIR0; + + +/** + * xHCI device data, raw-mode edition. + */ +typedef struct XHCIRC +{ + uint32_t uUnused; +} XHCIRC; +/** Pointer to raw-mode xHCI device data. */ +typedef struct XHCIRC *PXHCIRC; + + +/** @typedef XHCICC + * The xHCI device data for the current context. */ +typedef CTX_SUFF(XHCI) XHCICC; +/** @typedef PXHCICC + * Pointer to the xHCI device for the current context. */ +typedef CTX_SUFF(PXHCI) PXHCICC; + + +/* -=-= Local implementation details =-=- */ + +typedef enum sXHCI_JOB { + XHCI_JOB_PROCESS_CMDRING, /**< Process the command ring. */ + XHCI_JOB_DOORBELL, /**< A doorbell (other than DB0) was rung. */ + XHCI_JOB_XFER_DONE, /**< Transfer completed, look for more work. */ + XHCI_JOB_MAX +} XHCI_JOB; + +/* -=-=- Local xHCI definitions -=-=- */ + +/** @name USB states. + * @{ */ +#define XHCI_USB_RESET 0x00 +#define XHCI_USB_RESUME 0x40 +#define XHCI_USB_OPERATIONAL 0x80 +#define XHCI_USB_SUSPEND 0xc0 +/** @} */ + +/* Primary interrupter (for readability). */ +#define XHCI_PRIMARY_INTERRUPTER 0 + +/** @name Device Slot states. + * @{ */ +#define XHCI_DEVSLOT_EMPTY 0 +#define XHCI_DEVSLOT_ENABLED 1 +#define XHCI_DEVSLOT_DEFAULT 2 +#define XHCI_DEVSLOT_ADDRESSED 3 +#define XHCI_DEVSLOT_CONFIGURED 4 +/** @} */ + +/** Get the pointer to a root hub corresponding to given port index. */ +#define GET_PORT_PRH(a_pThisCC, a_uPort) \ + ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl ? &(a_pThisCC)->RootHub3 : &(a_pThisCC)->RootHub2) +#define GET_VUSB_PORT_FROM_XHCI_PORT(a_pRh, a_iPort) \ + (((a_iPort) - (a_pRh)->uPortBase) + 1) +#define GET_XHCI_PORT_FROM_VUSB_PORT(a_pRh, a_uPort) \ + ((a_pRh)->uPortBase + (a_uPort) - 1) + +/** Check if port corresponding to index is USB3, using shared data. */ +#define IS_USB3_PORT_IDX_SHR(a_pThis, a_uPort) ((a_uPort) >= (a_pThis)->cUsb2Ports) + +/** Check if port corresponding to index is USB3, using ring-3 data. */ +#define IS_USB3_PORT_IDX_R3(a_pThisCC, a_uPort) ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl) + +/** Query the number of configured USB2 ports. */ +#define XHCI_NDP_USB2(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub2.cPortsImpl) + +/** Query the number of configured USB3 ports. */ +#define XHCI_NDP_USB3(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub3.cPortsImpl) + +/** Query the total number of configured ports. */ +#define XHCI_NDP_CFG(a_pThis) ((unsigned)RT_MIN((a_pThis)->cTotalPorts, XHCI_NDP_MAX)) + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +#ifdef IN_RING3 + +/** Build a Protocol extended capability. */ +static uint32_t xhciR3BuildProtocolCaps(uint8_t *pbCap, uint32_t cbMax, int cPorts, int nPortOfs, int ver) +{ + uint32_t *pu32Cap = (uint32_t *)pbCap; + unsigned cPsi; + + Assert(nPortOfs + cPorts < 255); + Assert(ver == 2 || ver == 3); + + cPsi = 0; /* Currently only implied port speed IDs. */ + + /* Make sure there's enough room. */ + if (cPsi * 4 + 16 > cbMax) + return 0; + + /* Header - includes (USB) specification version. */ + *pu32Cap++ = (ver << 24) | (0 << 16) | XHCI_XCP_PROTOCOL; + /* Specification - 'USB ' */ + *pu32Cap++ = 0x20425355; + /* Port offsets and counts. 1-based! */ + *pu32Cap++ = (cPsi << 28) | (cPorts << 8) | (nPortOfs + 1); + /* Reserved dword. */ + *pu32Cap++ = 0; + + return (uint8_t *)pu32Cap - pbCap; +} + + +/** Add an extended capability and link it into the chain. */ +static int xhciR3AddExtCap(PXHCI pThis, const uint8_t *pCap, uint32_t cbCap, uint32_t *puPrevOfs) +{ + Assert(*puPrevOfs <= pThis->cbExtCap); + Assert(!(cbCap & 3)); + + /* Check that the extended capability is sane. */ + if (cbCap == 0) + return VERR_BUFFER_UNDERFLOW; + if (pThis->cbExtCap + cbCap > XHCI_EXT_CAP_SIZE) + return VERR_BUFFER_OVERFLOW; + if (cbCap > 255 * 4) /* Size must fit into 8-bit dword count. */ + return VERR_BUFFER_OVERFLOW; + + /* Copy over the capability data and update offsets. */ + memcpy(pThis->abExtCap + pThis->cbExtCap, pCap, cbCap); + pThis->abExtCap[*puPrevOfs + 1] = cbCap >> 2; + pThis->abExtCap[pThis->cbExtCap + 1] = 0; + *puPrevOfs = pThis->cbExtCap; + pThis->cbExtCap += cbCap; + return VINF_SUCCESS; +} + +/** Build the xHCI Extended Capabilities region. */ +static int xhciR3BuildExtCaps(PXHCI pThis, PXHCICC pThisCC) +{ + int rc; + uint8_t abXcp[MAX_XCAP_SIZE]; + uint32_t cbXcp; + uint32_t uPrevOfs = 0; + + Assert(XHCI_NDP_USB2(pThisCC)); + Assert(XHCI_NDP_USB3(pThisCC)); + + /* Most of the extended capabilities are optional or not relevant for PCI + * implementations. However, the Supported Protocol caps are required. + */ + cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB2(pThisCC), 0, 2); + rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs); + AssertReturn(RT_SUCCESS(rc), rc); + + cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB3(pThisCC), XHCI_NDP_USB2(pThisCC), 3); + rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs); + AssertReturn(RT_SUCCESS(rc), rc); + + return VINF_SUCCESS; +} + + +/** + * Select an unused device address. Note that this may fail in the unlikely + * case where all possible addresses are exhausted. + */ +static uint8_t xhciR3SelectNewAddress(PXHCI pThis, uint8_t uSlotID) +{ + RT_NOREF(pThis, uSlotID); + + /* + * Since there is a 1:1 mapping between USB devices and device slots, we + * should be able to assign a USB address which equals slot ID to any USB + * device. However, the address selection algorithm could be completely + * different (it is not defined by the xHCI spec). + */ + return uSlotID; +} + + +/** + * Read the address of a device context for a slot from the DCBAA. + * + * @returns Given slot's device context base address. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to get the context address of. + */ +static uint64_t xhciR3FetchDevCtxAddr(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID) +{ + uint64_t uCtxAddr; + RTGCPHYS GCPhysDCBAAE; + + Assert(uSlotID > 0); + Assert(uSlotID < XHCI_NDS); + + /* Fetch the address of the output slot context from the DCBAA. */ + GCPhysDCBAAE = pThis->dcbaap + uSlotID * sizeof(uint64_t); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysDCBAAE, &uCtxAddr, sizeof(uCtxAddr)); + LogFlowFunc(("Slot ID %u, device context @ %RGp\n", uSlotID, uCtxAddr)); + Assert(uCtxAddr); + + return uCtxAddr & XHCI_CTX_ADDR_MASK; +} + + +/** + * Fetch a device's slot or endpoint context from memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Device Context Index. + * @param pCtx Pointer to storage for the context. + */ +static int xhciR3FetchDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, void *pCtx) +{ + RTGCPHYS GCPhysCtx; + + GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + LogFlowFunc(("Reading device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI)); + GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCtx, pCtx, sizeof(XHCI_SLOT_CTX)); + return VINF_SUCCESS; +} + + +/** + * Fetch a device's slot and endpoint contexts from guest memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Endpoint Device Context Index. + * @param pSlot Pointer to storage for the slot context. + * @param pEp Pointer to storage for the endpoint context. + */ +static int xhciR3FetchCtxAndEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_SLOT_CTX *pSlot, XHCI_EP_CTX *pEp) +{ + AssertPtr(pSlot); + AssertPtr(pEp); + Assert(uDCI); /* Can't be 0 -- that's the device context. */ + + /* Load the slot context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, pSlot); + /// @todo sanity check the slot context here? + Assert(pSlot->ctx_ent >= uDCI); + + /* Load the endpoint context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, uDCI, pEp); + /// @todo sanity check the endpoint context here? + + return VINF_SUCCESS; +} + + +/** + * Update an endpoint context in guest memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Endpoint Device Context Index. + * @param pEp Pointer to storage of the endpoint context. + */ +static int xhciR3WriteBackEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_EP_CTX *pEp) +{ + RTGCPHYS GCPhysCtx; + + AssertPtr(pEp); + Assert(uDCI); /* Can't be 0 -- that's the device context. */ + + /// @todo sanity check the endpoint context here? + /* Find the physical address. */ + GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + LogFlowFunc(("Writing device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI)); + GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX); + /* Write the updated context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysCtx, pEp, sizeof(XHCI_EP_CTX)); + + return VINF_SUCCESS; +} + + +/** + * Modify an endpoint context such that it enters the running state. + * + * @param pEpCtx Pointer to the endpoint context. + */ +static void xhciR3EnableEP(XHCI_EP_CTX *pEpCtx) +{ + LogFlow(("Enabling EP, TRDP @ %RGp, DCS=%u\n", pEpCtx->trdp & XHCI_TRDP_ADDR_MASK, pEpCtx->trdp & XHCI_TRDP_DCS_MASK)); + pEpCtx->ep_state = XHCI_EPST_RUNNING; + pEpCtx->trep = pEpCtx->trdp; +} + +#endif /* IN_RING3 */ + +#define MFIND_PERIOD_NS (UINT64_C(2048) * 1000000) + +/** + * Set up the MFINDEX wrap timer. + */ +static void xhciSetWrapTimer(PPDMDEVINS pDevIns, PXHCI pThis) +{ + uint64_t u64Now; + uint64_t u64LastWrap; + uint64_t u64Expire; + int rc; + + /* Try to avoid drift. */ + u64Now = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer); +// u64LastWrap = u64Now - (u64Now % (0x3FFF * 125000)); + u64LastWrap = u64Now / MFIND_PERIOD_NS * MFIND_PERIOD_NS; + /* The MFINDEX counter wraps around every 2048 milliseconds. */ + u64Expire = u64LastWrap + (uint64_t)2048 * 1000000; + rc = PDMDevHlpTimerSet(pDevIns, pThis->hWrapTimer, u64Expire); + AssertRC(rc); +} + +/** + * Determine whether MSI/MSI-X is enabled for this PCI device. + * + * This influences interrupt handling in xHCI. NB: There should be a PCIDevXxx + * function for this. + */ +static bool xhciIsMSIEnabled(PPDMPCIDEV pDevIns) +{ + uint16_t uMsgCtl; + + uMsgCtl = PDMPciDevGetWord(pDevIns, XHCI_PCI_MSI_CAP_OFS + VBOX_MSI_CAP_MESSAGE_CONTROL); + return !!(uMsgCtl & VBOX_PCI_MSI_FLAGS_ENABLE); +} + +/** + * Get the worker thread going -- there's something to do. + */ +static void xhciKickWorker(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_JOB enmJob, uint32_t uWorkDesc) +{ + RT_NOREF(enmJob, uWorkDesc); + + /* Tell the worker thread there's something to do. */ + if (ASMAtomicReadBool(&pThis->fWrkThreadSleeping)) + { + LogFlowFunc(("Signal event semaphore\n")); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess); + AssertRC(rc); + } +} + +/** + * Fetch the current ERST entry from guest memory. + */ +static void xhciFetchErstEntry(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + RTGCPHYS GCPhysErste; + XHCI_ERSTE entry; + + Assert(ip->erst_idx < ip->erstsz); + GCPhysErste = ip->erstba + ip->erst_idx * sizeof(XHCI_ERSTE); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry)); + + /* + * 6.5 claims values in 16-4096 range are valid, but does not say what + * happens for values outside of that range... + */ + Assert((pThis->status & XHCI_STATUS_HCH) || (entry.size >= 16 && entry.size <= 4096)); + + /* Cache the entry data internally. */ + ip->erep = entry.addr & pThis->erst_addr_mask; + ip->trb_count = entry.size; + Log(("Fetched ERST Entry at %RGp: %u entries at %RGp\n", GCPhysErste, ip->trb_count, ip->erep)); +} + +/** + * Set the interrupter's IP and EHB bits and trigger an interrupt if required. + * + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param ip Pointer to the interrupter structure. + * + */ +static void xhciSetIntr(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + Assert(pThis && ip); + LogFlowFunc(("old IP: %u\n", !!(ip->iman & XHCI_IMAN_IP))); + + if (!(ip->iman & XHCI_IMAN_IP)) + { + /// @todo assert that we own the interrupter lock + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_EINT); + ASMAtomicOrU64(&ip->erdp, XHCI_ERDP_EHB); + ASMAtomicOrU32(&ip->iman, XHCI_IMAN_IP); + if ((ip->iman & XHCI_IMAN_IE) && (pThis->cmd & XHCI_CMD_INTE)) + { +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropIntrHw) + { + pThis->fDropIntrHw = false; + ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP); + } + else +#endif + { + Log2(("Triggering interrupt on interrupter %u\n", ip->index)); + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + STAM_COUNTER_INC(&pThis->StatIntrsSet); + } + } + else + { + Log2(("Not triggering interrupt on interrupter %u (interrupts disabled)\n", ip->index)); + STAM_COUNTER_INC(&pThis->StatIntrsNotSet); + } + + /* If MSI/MSI-X is in use, the IP bit is immediately cleared again. */ + if (xhciIsMSIEnabled(pDevIns->apPciDevs[0])) + ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP); + } +} + +#ifdef IN_RING3 + +/** + * Set the interrupter's IPE bit. If this causes a 0->1 transition, an + * interrupt may be triggered. + * + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param ip Pointer to the interrupter structure. + */ +static void xhciR3SetIntrPending(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + uint16_t imodc = (ip->imod >> XHCI_IMOD_IMODC_SHIFT) & XHCI_IMOD_IMODC_MASK; + + Assert(pThis && ip); + LogFlowFunc(("old IPE: %u, IMODC: %u, EREP: %RGp, EHB: %u\n", ip->ipe, imodc, (RTGCPHYS)ip->erep, !!(ip->erdp & XHCI_ERDP_EHB))); + STAM_COUNTER_INC(&pThis->StatIntrsPending); + + if (!ip->ipe) + { +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropIntrIpe) + { + pThis->fDropIntrIpe = false; + } + else +#endif + { + ip->ipe = true; + if (!(ip->erdp & XHCI_ERDP_EHB) && (imodc == 0)) + xhciSetIntr(pDevIns, pThis, ip); + } + } +} + + +/** + * Check if there is space available for writing at least two events on the + * event ring. See 4.9.4 for the state machine (right hand side of diagram). + * If there's only room for one event, the Event Ring Full TRB will need to + * be written out, hence the ring is considered full. + * + * @returns True if space is available, false otherwise. + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param pIntr Pointer to the interrupter structure. + */ +static bool xhciR3IsEvtRingFull(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr) +{ + uint64_t next_ptr; + uint64_t erdp = pIntr->erdp & XHCI_ERDP_ADDR_MASK; + + if (pIntr->trb_count > 1) + { + /* Check the current segment. */ + next_ptr = pIntr->erep + sizeof(XHCI_EVENT_TRB); + } + else + { + uint16_t erst_idx; + XHCI_ERSTE entry; + RTGCPHYS GCPhysErste; + + /* Need to check the next segment. */ + erst_idx = pIntr->erst_idx + 1; + if (erst_idx == pIntr->erstsz) + erst_idx = 0; + GCPhysErste = pIntr->erstba + erst_idx * sizeof(XHCI_ERSTE); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry)); + next_ptr = entry.addr & pThis->erst_addr_mask; + } + + /// @todo We'll have to remember somewhere that the ring is full + return erdp == next_ptr; +} + +/** + * Write an event to the given Event Ring. This implements a good chunk of + * the event ring state machine in section 4.9.4 of the xHCI spec. + * + * @returns VBox status code. Error if event could not be enqueued. + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param pEvent Pointer to the Event TRB to be enqueued. + * @param iIntr Index of the interrupter to write to. + * @param fBlockInt Set if interrupt should be blocked (BEI bit). + */ +static int xhciR3WriteEvent(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_EVENT_TRB *pEvent, unsigned iIntr, bool fBlockInt) +{ + PXHCIINTRPTR pIntr; + int rc = VINF_SUCCESS; + + LogFlowFunc(("Interrupter: %u\n", iIntr)); + + /* If the HC isn't running, events can not be generated. However, + * especially port change events can be triggered at any time. We just + * drop them here -- it's often not an error condition. + */ + if (pThis->cmd & XHCI_CMD_RS) + { + STAM_COUNTER_INC(&pThis->StatEventsWritten); + Assert(iIntr < XHCI_NINTR); /* Supplied by guest, potentially invalid. */ + pIntr = &pThis->aInterrupters[iIntr & XHCI_INTR_MASK]; + + /* + * If the interrupter/event ring isn't in a sane state, just + * give up and report Host Controller Error (HCE). + */ + // pIntr->erst_idx + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pIntr->lock, VERR_IGNORED); /* R3 only, no rcBusy. */ + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pIntr->lock, rcLock); /* eventually, most call chains ignore the status. */ + + if (xhciR3IsEvtRingFull(pDevIns, pThis, pIntr)) + { + LogRel(("xHCI: Event ring full!\n")); + } + + /* Set the TRB's Cycle bit as appropriate. */ + pEvent->gen.cycle = pIntr->evtr_pcs; + + /* Write out the TRB and advance the EREP. */ + /// @todo This either has to be atomic from the guest's POV or the cycle bit needs to be toggled last!! + PDMDevHlpPCIPhysWriteMeta(pDevIns, pIntr->erep, pEvent, sizeof(*pEvent)); + pIntr->erep += sizeof(*pEvent); + --pIntr->trb_count; + + /* Advance to the next ERST entry if necessary. */ + if (pIntr->trb_count == 0) + { + ++pIntr->erst_idx; + /* If necessary, roll over back to the beginning. */ + if (pIntr->erst_idx == pIntr->erstsz) + { + pIntr->erst_idx = 0; + pIntr->evtr_pcs = !pIntr->evtr_pcs; + } + xhciFetchErstEntry(pDevIns, pThis, pIntr); + } + + /* Set the IPE bit unless interrupts are blocked. */ + if (!fBlockInt) + xhciR3SetIntrPending(pDevIns, pThis, pIntr); + + PDMDevHlpCritSectLeave(pDevIns, &pIntr->lock); + } + else + { + STAM_COUNTER_INC(&pThis->StatEventsDropped); + Log(("Event dropped because HC is not running.\n")); + } + + return rc; +} + + +/** + * Post a port change TRB to an Event Ring. + */ +static int xhciR3GenPortChgEvent(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uPort) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Port ID: %u\n", uPort)); + + /* + * Devices can be "physically" attached/detached regardless of whether + * the HC is running or not, but the port status change events can only + * be generated when R/S is set; xhciR3WriteEvent() takes care of that. + */ + RT_ZERO(ed); + ed.psce.cc = XHCI_TCC_SUCCESS; + ed.psce.port_id = uPort; + ed.psce.type = XHCI_TRB_PORT_SC; + return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); +} + + +/** + * Post a command completion TRB to an Event Ring. + */ +static int xhciR3PostCmdCompletion(PPDMDEVINS pDevIns, PXHCI pThis, unsigned cc, unsigned uSlotID) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Cmd @ %RGp, Completion Code: %u (%s), Slot ID: %u\n", (RTGCPHYS)pThis->cmdr_dqp, cc, + cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!", uSlotID)); + + /* The Command Ring dequeue pointer still holds the address of the current + * command TRB. It is written to the completion event TRB as the command + * TRB pointer. + */ + RT_ZERO(ed); + ed.cce.trb_ptr = pThis->cmdr_dqp; + ed.cce.cc = cc; + ed.cce.type = XHCI_TRB_CMD_CMPL; + ed.cce.slot_id = uSlotID; + return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); +} + + +/** + * Post a transfer event TRB to an Event Ring. + */ +static int xhciR3PostXferEvent(PPDMDEVINS pDevIns, PXHCI pThis, unsigned uIntTgt, unsigned uXferLen, unsigned cc, + unsigned uSlotID, unsigned uEpDCI, uint64_t uEvtData, bool fBlockInt, bool fEvent) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Xfer @ %RGp, Completion Code: %u (%s), Slot ID=%u DCI=%u Target=%u EvtData=%RX64 XfrLen=%u BEI=%u ED=%u\n", + (RTGCPHYS)pThis->cmdr_dqp, cc, cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!", + uSlotID, uEpDCI, uIntTgt, uEvtData, uXferLen, fBlockInt, fEvent)); + + /* A transfer event may be either generated by TRB completion (in case + * fEvent=false) or by a special transfer event TRB (fEvent=true). In + * either case, interrupts may be suppressed. + */ + RT_ZERO(ed); + ed.te.trb_ptr = uEvtData; + ed.te.xfr_len = uXferLen; + ed.te.cc = cc; + ed.te.ed = fEvent; + ed.te.type = XHCI_TRB_XFER; + ed.te.ep_id = uEpDCI; + ed.te.slot_id = uSlotID; + return xhciR3WriteEvent(pDevIns, pThis, &ed, uIntTgt, fBlockInt); /* Sets the cycle bit, too. */ +} + + +static int xhciR3FindRhDevBySlot(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, PXHCIROOTHUBR3 *ppRh, uint32_t *puPort) +{ + XHCI_SLOT_CTX slot_ctx; + PXHCIROOTHUBR3 pRh; + unsigned iPort; + int rc; + + /// @todo Do any of these need to be release assertions? + Assert(uSlotID <= RT_ELEMENTS(pThis->aSlotState)); + Assert(pThis->aSlotState[ID_TO_IDX(uSlotID)] > XHCI_DEVSLOT_EMPTY); + + /* Load the slot context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, &slot_ctx); + + /* The port ID is stored in the slot context. */ + iPort = ID_TO_IDX(slot_ctx.rh_port); + if (iPort < XHCI_NDP_CFG(pThis)) + { + /* Find the corresponding root hub. */ + pRh = GET_PORT_PRH(pThisCC, iPort); + Assert(pRh); + + /* And the device; if the device was ripped out fAttached will be false. */ + if (pThisCC->aPorts[iPort].fAttached) + { + /* Provide the information the caller asked for. */ + if (ppRh) + *ppRh = pRh; + if (puPort) + *puPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort); + rc = VINF_SUCCESS; + } + else + { + LogFunc(("No device attached (port index %u)!\n", iPort)); + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + } + } + else + { + LogFunc(("Port out of range (index %u)!\n", iPort)); + rc = VERR_INVALID_PARAMETER; + } + return rc; +} + + +static void xhciR3EndlessTrbError(PPDMDEVINS pDevIns, PXHCI pThis) +{ + /* Clear the R/S bit and indicate controller error. */ + ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE); + + /* Ensure that XHCI_STATUS_HCH gets set by the worker thread. */ + xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0); + + LogRelMax(10, ("xHCI: Attempted to process too many TRBs, stopping xHC!\n")); +} + +/** + * TRB walker callback prototype. + * + * @returns true if walking should continue. + * @returns false if walking should be terminated. + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param pXferTRB Pointer to the transfer TRB to handle. + * @param GCPhysXfrTRB Physical address of the TRB. + * @param pvContext User-defined walk context. + * @remarks We don't need to use DECLCALLBACKPTR here, since all users are in + * the same source file, but having the functions marked with + * DECLCALLBACK helps readability. + */ +typedef DECLCALLBACKPTR(bool, PFNTRBWALKCB,(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, + RTGCPHYS GCPhysXfrTRB, void *pvContext)); + + +/** + * Walk a chain of TRBs which comprise a single TD. + * + * This is something we need to do potentially more than once when submitting a + * URB and then often again when completing the URB. Note that the walker does + * not update the endpoint state (TRDP/TREP/DCS) so that it can be re-run + * multiple times. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uTRP Initial TR pointer and DCS. + * @param pfnCbk Callback routine. + * @param pvContext User-defined walk context. + * @param pTREP Pointer to storage for final TR Enqueue Pointer/DCS. + */ +static int xhciR3WalkXferTrbChain(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uTRP, + PFNTRBWALKCB pfnCbk, void *pvContext, uint64_t *pTREP) +{ + RTGCPHYS GCPhysXfrTRB; + uint64_t uTREP; + XHCI_XFER_TRB XferTRB; + bool fContinue = true; + bool dcs; + int rc = VINF_SUCCESS; + unsigned cTrbs = 0; + + AssertPtr(pvContext); + AssertPtr(pTREP); + Assert(uTRP); + + /* Find the transfer TRB address and the DCS. */ + GCPhysXfrTRB = uTRP & XHCI_TRDP_ADDR_MASK; + dcs = !!(uTRP & XHCI_TRDP_DCS_MASK); /* MSC upgrades bool to signed something when comparing with a uint8_t:1. */ + LogFlowFunc(("Walking Transfer Ring, TREP:%RGp DCS=%u\n", GCPhysXfrTRB, dcs)); + + do { + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB)); + + if ((bool)XferTRB.gen.cycle == dcs) + { + Log2(("Walking TRB@%RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, XferTRB.gen.type, + XferTRB.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[XferTRB.gen.type] : "WHAT?!!", + XferTRB.gen.xfr_len, XferTRB.gen.ent, XferTRB.gen.isp, XferTRB.gen.ns, XferTRB.gen.ch, XferTRB.gen.ioc, XferTRB.gen.idt)); + + /* DCS matches, the TRB is ours to process. */ + switch (XferTRB.gen.type) { + case XHCI_TRB_LINK: + Log2(("Link intra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", XferTRB.link.rseg_ptr, XferTRB.link.ioc, XferTRB.link.toggle, XferTRB.link.chain)); + Assert(XferTRB.link.chain); + /* Do not update the actual TRDP/TREP and DCS yet, just the temporary images. */ + GCPhysXfrTRB = XferTRB.link.rseg_ptr & XHCI_TRDP_ADDR_MASK; + if (XferTRB.link.toggle) + dcs = !dcs; + Assert(!XferTRB.link.ioc); /// @todo Needs to be reported. + break; + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + case XHCI_TRB_EVT_DATA: + fContinue = pfnCbk(pDevIns, pThis, &XferTRB, GCPhysXfrTRB, pvContext); + GCPhysXfrTRB += sizeof(XferTRB); + break; + default: + /* NB: No-op TRBs are not allowed within TDs (4.11.7). */ + Log(("Bad TRB type %u found within TD!!\n", XferTRB.gen.type)); + fContinue = false; + /// @todo Stop EP etc.? + } + } + else + { + /* We don't have a complete TD. Interesting times. */ + Log2(("DCS mismatch, no more TRBs available.\n")); + fContinue = false; + rc = VERR_TRY_AGAIN; + } + + /* Kill the xHC if the TRB list has no end in sight. */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* Inform caller of the new TR Enqueue Pointer/DCS (not necessarily changed). */ + Assert(!(GCPhysXfrTRB & ~XHCI_TRDP_ADDR_MASK)); + uTREP = GCPhysXfrTRB | (unsigned)dcs; + Log2(("Final TRP after walk: %RGp\n", uTREP)); + *pTREP = uTREP; + + return rc; +} + + +/** Context for probing TD size. */ +typedef struct { + uint32_t uXferLen; + uint32_t cTRB; + uint32_t uXfrLenLastED; + uint32_t cTRBLastED; +} XHCI_CTX_XFER_PROBE; + + +/** Context for submitting 'out' TDs. */ +typedef struct { + PVUSBURB pUrb; + uint32_t uXferPos; + unsigned cTRB; +} XHCI_CTX_XFER_SUBMIT; + + +/** Context for completing TDs. */ +typedef struct { + PVUSBURB pUrb; + uint32_t uXferPos; + uint32_t uXferLeft; + unsigned cTRB; + uint32_t uEDTLA : 24; + uint32_t uLastCC : 8; + uint8_t uSlotID; + uint8_t uEpDCI; + bool fMaxCount; +} XHCI_CTX_XFER_COMPLETE; + + +/** Context for building isochronous URBs. */ +typedef struct { + PVUSBURB pUrb; + unsigned iPkt; + uint32_t offCur; + uint64_t uInitTREP; + bool fSubmitFailed; +} XHCI_CTX_ISOCH; + + +/** + * @callback_method_impl{PFNTRBWALKCB, + * Probe a TD and figure out how big it is so that a URB can be allocated to back it.} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsProbe(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + RT_NOREF(pDevIns, pThis, GCPhysXfrTRB); + XHCI_CTX_XFER_PROBE *pCtx = (XHCI_CTX_XFER_PROBE *)pvContext; + + pCtx->cTRB++; + + /* Only consider TRBs which transfer data. */ + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + pCtx->uXferLen += pXferTRB->norm.xfr_len; + if (RT_UNLIKELY(pCtx->uXferLen > XHCI_MAX_TD_SIZE)) + { + /* NB: We let the TD size get a bit past the max so that we don't lose anything, + * but the EDTLA will wrap around. + */ + LogRelMax(10, ("xHCI: TD size (%u) too big, not continuing!\n", pCtx->uXferLen)); + return false; + } + break; + case XHCI_TRB_EVT_DATA: + /* Remember where the last seen Event Data TRB was. */ + pCtx->cTRBLastED = pCtx->cTRB; + pCtx->uXfrLenLastED = pCtx->uXferLen; + break; + default: /* Could be a link TRB, too. */ + break; + } + + return pXferTRB->gen.ch; +} + + +/** + * @callback_method_impl{PFNTRBWALKCB, + * Copy data from a TD (TRB chain) into the corresponding TD. OUT direction only.} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsSubmit(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + RT_NOREF(pThis, GCPhysXfrTRB); + XHCI_CTX_XFER_SUBMIT *pCtx = (XHCI_CTX_XFER_SUBMIT *)pvContext; + uint32_t uXferLen = pXferTRB->norm.xfr_len; + + + /* Only consider TRBs which transfer data. */ + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + /* NB: Transfer length may be zero! */ + /// @todo explain/verify abuse of various TRB types here (data stage mapped to normal etc.). + if (uXferLen) + { + /* Sanity check for broken guests (TRBs may have changed since probing). */ + if (pCtx->uXferPos + uXferLen <= pCtx->pUrb->cbData) + { + /* Data might be immediate or elsewhere in memory. */ + if (pXferTRB->norm.idt) + { + /* If an immediate data TRB claims there's more than 8 bytes, we have a problem. */ + if (uXferLen > 8) + { + LogRelMax(10, ("xHCI: Immediate data TRB length %u bytes, ignoring!\n", uXferLen)); + return false; /* Stop walking the chain immediately. */ + } + + Assert(uXferLen >= 1 && uXferLen <= 8); + Log2(("Copying %u bytes to URB offset %u (immediate data)\n", uXferLen, pCtx->uXferPos)); + memcpy(pCtx->pUrb->abData + pCtx->uXferPos, pXferTRB, uXferLen); + } + else + { + PDMDevHlpPCIPhysReadUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen); + Log2(("Copying %u bytes to URB offset %u (from %RGp)\n", uXferLen, pCtx->uXferPos, pXferTRB->norm.data_ptr)); + } + pCtx->uXferPos += uXferLen; + } + else + { + LogRelMax(10, ("xHCI: Attempted to submit too much data, ignoring!\n")); + return false; /* Stop walking the chain immediately. */ + } + + } + break; + default: /* Could be an event or status stage TRB, too. */ + break; + } + pCtx->cTRB++; + + /// @todo Maybe have to make certain that the number of probed TRBs matches? Potentially + /// by the time TRBs get submitted, there might be more of them available if the TD was + /// initially not fully written by HCD. + + return pXferTRB->gen.ch; +} + + +/** + * Perform URB completion processing. + * + * Figure out how much data was really transferred, post events if required, and + * for IN transfers, copy data from the URB. + * + * @callback_method_impl{PFNTRBWALKCB} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsComplete(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + XHCI_CTX_XFER_COMPLETE *pCtx = (XHCI_CTX_XFER_COMPLETE *)pvContext; + int rc; + unsigned uXferLen; + unsigned uResidue; + uint8_t cc; + bool fKeepGoing = true; + + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: /// @todo document abuse; esp. check BEI bit + case XHCI_TRB_STATUS_STG: + /* Assume successful transfer. */ + uXferLen = pXferTRB->norm.xfr_len; + cc = XHCI_TCC_SUCCESS; + + /* If there was a short packet, handle it accordingly. */ + if (pCtx->uXferLeft < uXferLen) + { + /* The completion code is set regardless of IOC/ISP. It may be + * reported later via an Event Data TRB (4.10.1.1) + */ + uXferLen = pCtx->uXferLeft; + cc = XHCI_TCC_SHORT_PKT; + } + + if (pCtx->pUrb->enmDir == VUSBDIRECTION_IN) + { + Assert(!pXferTRB->norm.idt); + + /* NB: Transfer length may be zero! */ + if (uXferLen) + { + if (uXferLen <= pCtx->uXferLeft) + { + Log2(("Writing %u bytes to %RGp from URB offset %u (TRB@%RGp)\n", uXferLen, pXferTRB->norm.data_ptr, pCtx->uXferPos, GCPhysXfrTRB)); + PDMDevHlpPCIPhysWriteUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen); + } + else + { + LogRelMax(10, ("xHCI: Attempted to read too much data, ignoring!\n")); + } + } + } + + /* Update position within TD. */ + pCtx->uXferLeft -= uXferLen; + pCtx->uXferPos += uXferLen; + Log2(("Current uXferLeft=%u, uXferPos=%u (length was %u)\n", pCtx->uXferLeft, pCtx->uXferPos, uXferLen)); + + /* Keep track of the EDTLA and last completion status. */ + pCtx->uEDTLA += uXferLen; /* May wrap around! */ + pCtx->uLastCC = cc; + + /* Report events as required. */ + uResidue = pXferTRB->norm.xfr_len - uXferLen; + if (pXferTRB->norm.ioc || (pXferTRB->norm.isp && uResidue)) + { + rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->norm.int_tgt, uResidue, cc, + pCtx->uSlotID, pCtx->uEpDCI, GCPhysXfrTRB, pXferTRB->norm.bei, false); + } + break; + case XHCI_TRB_EVT_DATA: + if (pXferTRB->evtd.ioc) + { + rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->evtd.int_tgt, pCtx->uEDTLA, pCtx->uLastCC, + pCtx->uSlotID, pCtx->uEpDCI, pXferTRB->evtd.evt_data, pXferTRB->evtd.bei, true); + } + /* Clear the EDTLA. */ + pCtx->uEDTLA = 0; + break; + default: + AssertMsgFailed(("%#x\n", pXferTRB->gen.type)); + break; + } + + pCtx->cTRB--; + /* For TD fragments, enforce the maximum count, but only as long as the transfer + * is successful. In case of error we have to complete the entire TD! */ + if (!pCtx->cTRB && pCtx->fMaxCount && pCtx->uLastCC == XHCI_TCC_SUCCESS) + { + Log2(("Stopping at the end of TD Fragment.\n")); + fKeepGoing = false; + } + + /* NB: We currently do not enforce that the number of TRBs can't change between + * submission and completion. If we do, we'll have to store it somewhere for + * isochronous URBs. + */ + return pXferTRB->gen.ch && fKeepGoing; +} + +/** + * Process (consume) non-data TRBs on a transfer ring. This function + * completes TRBs which do not have any URB associated with them. Only + * used with running endpoints. Usable regardless of whether there are + * in-flight TRBs or not. Returns the next TRB and its address to the + * caller. May modify the endpoint context! + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID The slot corresponding to this USB device. + * @param uEpDCI The DCI of this endpoint. + * @param pEpCtx Endpoint context. May be modified. + * @param pXfer Storage for returning the next TRB to caller. + * @param pGCPhys Storage for returning the physical address of TRB. + */ +static int xhciR3ConsumeNonXferTRBs(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uEpDCI, + XHCI_EP_CTX *pEpCtx, XHCI_XFER_TRB *pXfer, RTGCPHYS *pGCPhys) +{ + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB = 0; + bool dcs; + bool fInFlight; + bool fContinue = true; + int rc; + unsigned cTrbs = 0; + + LogFlowFunc(("Slot ID: %u, EP DCI %u\n", uSlotID, uEpDCI)); + Assert(uSlotID > 0); + Assert(uSlotID <= XHCI_NDS); + + Assert(pEpCtx->ep_state == XHCI_EPST_RUNNING); + do + { + /* Find the transfer TRB address. */ + GCPhysXfrTRB = pEpCtx->trdp & XHCI_TRDP_ADDR_MASK; + dcs = !!(pEpCtx->trdp & XHCI_TRDP_DCS_MASK); + + /* Determine whether there are any in-flight TRBs or not. This affects TREP + * processing -- when nothing is in flight, we have to move both TREP and TRDP; + * otherwise only the TRDP must be updated. + */ + fInFlight = pEpCtx->trep != pEpCtx->trdp; + LogFlowFunc(("Skipping non-data TRBs, TREP:%RGp, TRDP:%RGp, in-flight: %RTbool\n", pEpCtx->trep, pEpCtx->trdp, fInFlight)); + + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer)); + + /* Make sure the Cycle State matches. */ + if ((bool)xfer.gen.cycle == dcs) + { + Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type, + xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!", + xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt)); + + switch (xfer.gen.type) { + case XHCI_TRB_LINK: + Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain)); + Assert(!xfer.link.chain); + /* Set new TRDP but leave DCS bit alone... */ + pEpCtx->trdp = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (pEpCtx->trdp & XHCI_TRDP_DCS_MASK); + /* ...and flip the DCS bit if required. Then update the TREP. */ + if (xfer.link.toggle) + pEpCtx->trdp = (pEpCtx->trdp & ~XHCI_TRDP_DCS_MASK) | (pEpCtx->trdp ^ XHCI_TRDP_DCS_MASK); + if (!fInFlight) + pEpCtx->trep = pEpCtx->trdp; + if (xfer.link.ioc) + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.link.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI, + GCPhysXfrTRB, false, false); + break; + case XHCI_TRB_NOOP_XFER: + Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent)); + /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */ + Assert(!xfer.link.chain); + /* Update enqueue/dequeue pointers. */ + pEpCtx->trdp += sizeof(XHCI_XFER_TRB); + if (!fInFlight) + pEpCtx->trep += sizeof(XHCI_XFER_TRB); + if (xfer.nop.ioc) + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.nop.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI, + GCPhysXfrTRB, false, false); + break; + default: + fContinue = false; + break; + } + } + else + { + LogFunc(("Transfer Ring empty\n")); + fContinue = false; + } + + /* Kill the xHC if the TRB list has no end in sight. */ + /* NB: The limit here could perhaps be much lower because a sequence of Link + * and No-op TRBs with no real work to be done would be highly suspect. + */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* The caller will need the next TRB. Hand it over. */ + Assert(GCPhysXfrTRB); + *pGCPhys = GCPhysXfrTRB; + *pXfer = xfer; + LogFlowFunc(("Final TREP:%RGp, TRDP:%RGp GCPhysXfrTRB:%RGp\n", pEpCtx->trep, pEpCtx->trdp, GCPhysXfrTRB)); + + return VINF_SUCCESS; +} + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) xhciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX ep_ctx; + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB; + int rc; + unsigned uResidue = 0; + uint8_t uSlotID = pUrb->pHci->uSlotID; + uint8_t cc = XHCI_TCC_SUCCESS; + uint8_t uEpDCI; + + /* Check for URBs completed synchronously as part of xHCI command execution. + * The URB will have zero cTRB as it's not associated with a TD. + */ + if (!pUrb->pHci->cTRB) + { + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus)); + LogFlow(("%s: xhciR3RhXferCompletion: Completing xHCI-generated request\n", pUrb->pszDesc)); + return; + } + + /* If the xHC isn't running, just drop the URB right here. */ + if (pThis->status & XHCI_STATUS_HCH) + { + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus)); + LogFlow(("%s: xhciR3RhXferCompletion: xHC halted, skipping URB completion\n", pUrb->pszDesc)); + return; + } + +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropUrb) + { + LogFlow(("%s: xhciR3RhXferCompletion: Error injection, dropping URB!\n", pUrb->pszDesc)); + pThis->fDropUrb = false; + return; + } +#endif + + RTCritSectEnter(&pThisCC->CritSectThrd); + + /* Convert USB endpoint address to xHCI format. */ + if (pUrb->EndPt) + uEpDCI = pUrb->EndPt * 2 + (pUrb->enmDir == VUSBDIRECTION_IN ? 1 : 0); + else + uEpDCI = 1; /* EP 0 */ + + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB)); + LogFlow(("%s: xhciR3RhXferCompletion: EP DCI=%u, cbData=%u status=%u\n", pUrb->pszDesc, uEpDCI, pUrb->cbData, pUrb->enmStatus)); + + /* Load the slot/endpoint contexts from guest memory. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uEpDCI, &slot_ctx, &ep_ctx); + + /* If the EP is disabled, we don't own it so we can't complete the URB. + * Leave this EP alone and drop the URB. + */ + if (ep_ctx.ep_state != XHCI_EPST_RUNNING) + { + Log(("EP DCI %u not running (state %u), skipping URB completion\n", uEpDCI, ep_ctx.ep_state)); + RTCritSectLeave(&pThisCC->CritSectThrd); + return; + } + + /* Now complete any non-transfer TRBs that might be on the transfer ring before + * the TRB(s) corresponding to this URB. Preloads the TRB as a side effect. + * Endpoint state now must be written back in case it was modified! + */ + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB); + + /* Deal with failures which halt the EP first. */ + if (RT_UNLIKELY(pUrb->enmStatus != VUSBSTATUS_OK)) + { + switch(pUrb->enmStatus) + { + case VUSBSTATUS_STALL: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_STALL; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + case VUSBSTATUS_DNR: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_USB_XACT_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + case VUSBSTATUS_CRC: /// @todo Separate status for canceling?! + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_USB_XACT_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + + /* NB: The TRDP is *not* advanced and TREP is reset. */ + ep_ctx.trep = ep_ctx.trdp; + break; + case VUSBSTATUS_DATA_OVERRUN: + case VUSBSTATUS_DATA_UNDERRUN: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_DATA_BUF_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + default: + AssertMsgFailed(("Unexpected URB status %u\n", pUrb->enmStatus)); + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + STAM_COUNTER_INC(&pThis->StatErrorIsocUrbs); + } + else if (xfer.gen.type == XHCI_TRB_NORMAL) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + + ctxComplete.pUrb = pUrb; + ctxComplete.uXferPos = 0; + ctxComplete.uXferLeft = pUrb->cbData; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT; + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + + if (ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT) + ep_ctx.ifc -= XHCI_NO_QUEUING_IN_FLIGHT; /* TD fragment done, allow further queuing. */ + else + ep_ctx.ifc--; /* TD done, decrement in-flight counter. */ + } + else if (xfer.gen.type == XHCI_TRB_ISOCH) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + unsigned iPkt; + + ctxComplete.pUrb = pUrb; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + + for (iPkt = 0; iPkt < pUrb->cIsocPkts; ++iPkt) { + ctxComplete.uXferPos = pUrb->aIsocPkts[iPkt].off; + ctxComplete.uXferLeft = pUrb->aIsocPkts[iPkt].cb; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uEDTLA = 0; // Zero at TD start. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = false; + if (pUrb->aIsocPkts[iPkt].enmStatus != VUSBSTATUS_OK) + STAM_COUNTER_INC(&pThis->StatErrorIsocPkts); + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB); + } + ep_ctx.ifc--; /* TD done, decrement in-flight counter. */ + } + else if (xfer.gen.type == XHCI_TRB_SETUP_STG || xfer.gen.type == XHCI_TRB_DATA_STG || xfer.gen.type == XHCI_TRB_STATUS_STG) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + + ctxComplete.pUrb = pUrb; + ctxComplete.uXferPos = 0; + ctxComplete.uXferLeft = pUrb->cbData; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT; + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + } + else + { + AssertMsgFailed(("Unexpected TRB type %u\n", xfer.gen.type)); + Log2(("TRB @ %RGp, type %u unexpected!\n", GCPhysXfrTRB, xfer.gen.type)); + /* Advance the TRDP anyway so that the endpoint isn't completely stuck. */ + ep_ctx.trdp += sizeof(XHCI_XFER_TRB); + } + + /* Update the endpoint state. */ + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx); + + RTCritSectLeave(&pThisCC->CritSectThrd); + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + /* Completion callback usually runs on a separate thread. Let the worker do more. */ + Log2(("Ring bell for slot %u, DCI %u\n", uSlotID, uEpDCI)); + ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], 1 << uEpDCI); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0); + } +} + + +/** + * Handle transfer errors. + * + * VUSB calls this when a transfer attempt failed. This function will respond + * indicating whether to retry or complete the URB with failure. + * + * @returns true if the URB should be retired. + * @returns false if the URB should be re-tried. + * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) xhciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PXHCI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PXHCI); + bool fRetire = true; + + /* If the xHC isn't running, get out of here immediately. */ + if (pThis->status & XHCI_STATUS_HCH) + { + Log(("xHC halted, skipping URB error handling\n")); + return fRetire; + } + + RTCritSectEnter(&pThisCC->CritSectThrd); + + Assert(pUrb->pHci->cTRB); /* xHCI-generated URBs should not fail! */ + if (!pUrb->pHci->cTRB) + { + Log(("%s: Failing xHCI-generated request!\n", pUrb->pszDesc)); + } + else if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + /* Don't retry on stall. */ + Log2(("%s: xhciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_CRC) { + /* Don't retry on CRC errors either. These indicate canceled URBs, among others. */ + Log2(("%s: xhciR3RhXferError: CRC, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DNR) { + /* Don't retry on DNR errors. These indicate the device vanished. */ + Log2(("%s: xhciR3RhXferError: DNR, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) { + /* Don't retry on OVERRUN errors. These indicate a fatal error. */ + Log2(("%s: xhciR3RhXferError: OVERRUN, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) { + /* Don't retry on UNDERRUN errors. These indicate a fatal error. */ + Log2(("%s: xhciR3RhXferError: UNDERRUN, giving up.\n", pUrb->pszDesc)); + } else { + /// @todo + AssertMsgFailed(("%#x\n", pUrb->enmStatus)); + } + + RTCritSectLeave(&pThisCC->CritSectThrd); + return fRetire; +} + + +/** + * Queue a TD composed of normal TRBs, event data TRBs, and suchlike. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical gues address of the TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + */ +static int xhciR3QueueDataTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI) +{ + RT_NOREF(GCPhysTRB); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + bool fFragOnly = false; + int rc; + VUSBXFERTYPE enmType; + VUSBDIRECTION enmDir; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + if (RT_SUCCESS(rc)) + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + else + { + LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP)); + if (rc == VERR_TRY_AGAIN && pTrb->gen.type == XHCI_TRB_NORMAL && ctxProbe.cTRBLastED) + { + /* The TD is incomplete, but we have at least one TD fragment. We can create a URB for + * what we have but we can't safely queue any more because if any error occurs, the + * TD needs to fail as a whole. + * OS X Mavericks and Yosemite tend to trigger this case when reading from USB 3.0 + * MSDs (transfers up to 1MB). + */ + fFragOnly = true; + + /* Because we currently do not maintain the EDTLA across URBs, we have to only submit + * TD fragments up to where we last saw an Event Data TRB. If there was no Event Data + * TRB, we'll just try waiting a bit longer for the TD to be complete or an Event Data + * TRB to show up. The guest is extremely likely to do one or the other, since otherwise + * it has no way to tell when the transfer completed. + */ + ctxProbe.cTRB = ctxProbe.cTRBLastED; + ctxProbe.uXferLen = ctxProbe.uXfrLenLastED; + } + else + return rc; + } + + /* Determine the transfer kind based on endpoint type. */ + switch (pEpCtx->ep_type) + { + case XHCI_EPTYPE_BULK_IN: + case XHCI_EPTYPE_BULK_OUT: + enmType = VUSBXFERTYPE_BULK; + break; + case XHCI_EPTYPE_INTR_IN: + case XHCI_EPTYPE_INTR_OUT: + enmType = VUSBXFERTYPE_INTR; + break; + case XHCI_EPTYPE_CONTROL: + enmType = VUSBXFERTYPE_CTRL; + break; + case XHCI_EPTYPE_ISOCH_IN: + case XHCI_EPTYPE_ISOCH_OUT: + default: + enmType = VUSBXFERTYPE_INVALID; + AssertMsgFailed(("%#x\n", pEpCtx->ep_type)); + } + + /* Determine the direction based on endpoint type. */ + switch (pEpCtx->ep_type) + { + case XHCI_EPTYPE_BULK_IN: + case XHCI_EPTYPE_INTR_IN: + enmDir = VUSBDIRECTION_IN; + break; + case XHCI_EPTYPE_BULK_OUT: + case XHCI_EPTYPE_INTR_OUT: + enmDir = VUSBDIRECTION_OUT; + break; + default: + enmDir = VUSBDIRECTION_INVALID; + AssertMsgFailed(("%#x\n", pEpCtx->ep_type)); + } + + /* Allocate and initialize a URB. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, enmType, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB, NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerDtaUrb, ctxProbe.cTRB); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + + /// @todo Cross check that the EP type corresponds to direction. Probably + //should check when configuring device? + pUrb->pHci->uSlotID = uSlotID; + + /* For OUT transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = 0; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + ctxProbe.cTRB = ctxSubmit.cTRB; + } + + /* If only completing a fragment, remember the TRB count and increase + * the in-flight count past the limit so we won't queue any more. + */ + pUrb->pHci->cTRB = ctxProbe.cTRB; + if (fFragOnly) + /* Bit of a hack -- prevent further queuing. */ + pEpCtx->ifc += XHCI_NO_QUEUING_IN_FLIGHT; + else + /* Increment the in-flight counter before queuing more. */ + pEpCtx->ifc++; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* + * Submit the URB. + */ + STAM_COUNTER_ADD(&pThis->StatUrbSizeData, pUrb->cbData); + Log(("%s: xhciR3QueueDataTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + /// @todo Mark the EP as halted and inactive and write back the changes. + + return VERR_OUT_OF_RESOURCES; +} + + +/** + * Queue an isochronous TD composed of isochronous and normal TRBs, event + * data TRBs, and suchlike. This TD may either correspond to a single URB or + * form one packet of an isochronous URB. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical guest address of the TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + * @param pCtxIso Additional isochronous URB context. + */ +static int xhciR3QueueIsochTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI, + XHCI_CTX_ISOCH *pCtxIso) +{ + RT_NOREF(GCPhysTRB, pTrb); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + PVUSBURB pUrb; + unsigned cIsoPackets; + uint32_t cbPktMax; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + /* For isochronous transfers, there's a bit of extra work to do. The interval + * is key and determines whether the TD will directly correspond to a URB or + * if it will only form part of a larger URB. In any case, one TD equals one + * 'packet' of an isochronous URB. + */ + switch (pEpCtx->interval) + { + case 0: /* Every 2^0 * 125us, i.e. 8 per frame. */ + cIsoPackets = 8; + break; + case 1: /* Every 2^1 * 125us, i.e. 4 per frame. */ + cIsoPackets = 4; + break; + case 2: /* Every 2^2 * 125us, i.e. 2 per frame. */ + cIsoPackets = 2; + break; + case 3: /* Every 2^3 * 125us, i.e. 1 per frame. */ + default:/* Or any larger interval (every n frames).*/ + cIsoPackets = 1; + break; + } + + /* We do not know exactly how much data might be transferred until we + * look at all TDs/packets that constitute the URB. However, we do know + * the maximum possible size even without probing any TDs at all. + * The actual size is expected to be the same or at most slightly smaller, + * hence it makes sense to allocate the URB right away and copy data into + * it as we go, rather than doing complicated probing first. + * The Max Endpoint Service Interval Time (ESIT) Payload defines the + * maximum number of bytes that can be transferred per interval (4.14.2). + * Unfortunately Apple was lazy and their driver leaves the Max ESIT + * Payload as zero, so we have to do the math ourselves. + */ + + /* Calculate the maximum transfer size per (micro)frame. */ + /// @todo This ought to be stored within the URB somewhere. + cbPktMax = pEpCtx->max_pkt_sz * (pEpCtx->max_brs_sz + 1) * (pEpCtx->mult + 1); + if (!pCtxIso->pUrb) + { + uint32_t cbUrbMax = cIsoPackets * cbPktMax; + + /* Validate endpoint type. */ + AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN || pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_OUT, + ("%#x\n", pEpCtx->ep_type)); + + /* Allocate and initialize a new URB. */ + pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_ISOC, + (pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT, + cbUrbMax, ctxProbe.cTRB, NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerIsoUrb, ctxProbe.cTRB); + + LogFlowFunc(("Allocated URB with %u packets, %u bytes total (ESIT payload %u)\n", cIsoPackets, cbUrbMax, cbPktMax)); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->cIsocPkts = cIsoPackets; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = ctxProbe.cTRB; + + /* If TRB says so or if there are multiple packets per interval, don't even + * bother with frame counting and schedule everything ASAP. + */ + if (pTrb->isoc.sia || cIsoPackets != 1) + pUrb->uStartFrameDelta = 0; + else + { + uint16_t uFrameDelta; + uint32_t uPort; + + /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after + * writing back the EP state so that the completion callback can operate. + */ + if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, NULL, &uPort))) + { + + uFrameDelta = pRh->pIRhConn->pfnUpdateIsocFrameDelta(pRh->pIRhConn, uPort, uEpDCI / 2, + uEpDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT, + pTrb->isoc.frm_id, XHCI_FRAME_ID_BITS); + pUrb->uStartFrameDelta = uFrameDelta; + Log(("%s: Isoch frame delta set to %u\n", pUrb->pszDesc, uFrameDelta)); + } + else + { + Log(("%s: Failed to find device for slot! Setting frame delta to zero.\n", pUrb->pszDesc)); + pUrb->uStartFrameDelta = 0; + } + } + + Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u FrmID=%u Isoch URB created\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData, pTrb->isoc.frm_id)); + + /* Set up the context for later use. */ + pCtxIso->pUrb = pUrb; + /* Save the current TREP in case we need to rewind. */ + pCtxIso->uInitTREP = pEpCtx->trep; + } + else + { + Assert(cIsoPackets > 1); + /* Grab the URB we initialized earlier. */ + pUrb = pCtxIso->pUrb; + } + + /* Set up the packet corresponding to this TD. */ + pUrb->aIsocPkts[pCtxIso->iPkt].cb = RT_MIN(ctxProbe.uXferLen, cbPktMax); + pUrb->aIsocPkts[pCtxIso->iPkt].off = pCtxIso->offCur; + pUrb->aIsocPkts[pCtxIso->iPkt].enmStatus = VUSBSTATUS_NOT_ACCESSED; + + /* For OUT transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = pCtxIso->offCur; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + } + + /* Done preparing this packet. */ + Assert(pCtxIso->iPkt < 8); + pCtxIso->iPkt++; + pCtxIso->offCur += ctxProbe.uXferLen; + Assert(pCtxIso->offCur <= pUrb->cbData); + + /* Increment the in-flight counter before queuing more. */ + if (pCtxIso->iPkt == pUrb->cIsocPkts) + pEpCtx->ifc++; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* If the URB is complete, submit it. */ + if (pCtxIso->iPkt == pUrb->cIsocPkts) + { + /* Change cbData to reflect how much data should be transferred. This can differ + * from how much data was allocated for the URB. + */ + pUrb->cbData = pCtxIso->offCur; + STAM_COUNTER_ADD(&pThis->StatUrbSizeIsoc, pUrb->cbData); + Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u Isoch URB being submitted\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + pCtxIso->fSubmitFailed = true; + /// @todo Mark the EP as halted and inactive and write back the changes. + return VERR_OUT_OF_RESOURCES; + } + /* Clear the isochronous URB context. */ + RT_ZERO(*pCtxIso); + } + + return VINF_SUCCESS; +} + + +/** + * Queue a control TD composed of setup/data/status stage TRBs, event data + * TRBs, and suchlike. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical guest address of th TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + */ +static int xhciR3QueueControlTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI) +{ + RT_NOREF(GCPhysTRB); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + int rc; + VUSBDIRECTION enmDir; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + if (RT_SUCCESS(rc)) + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + else + { + LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP)); + return rc; + } + + /* Determine the transfer direction. */ + switch (pTrb->gen.type) + { + case XHCI_TRB_SETUP_STG: + enmDir = VUSBDIRECTION_SETUP; + /* For setup TRBs, there is always 8 bytes of immediate data. */ + Assert(sizeof(VUSBSETUP) == 8); + Assert(ctxProbe.uXferLen == 8); + Log2(("bmRequestType:%02X bRequest:%02X wValue:%04X wIndex:%04X wLength:%04X\n", pTrb->setup.bmRequestType, + pTrb->setup.bRequest, pTrb->setup.wValue, pTrb->setup.wIndex, pTrb->setup.wLength)); + break; + case XHCI_TRB_STATUS_STG: + enmDir = pTrb->status.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + break; + case XHCI_TRB_DATA_STG: + enmDir = pTrb->data.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + break; + default: + AssertMsgFailed(("%#x\n", pTrb->gen.type)); /* Can't happen unless caller messed up. */ + return VERR_INTERNAL_ERROR; + } + + /* Allocate and initialize a URB. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_CTRL, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB, + NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerCtlUrb, ctxProbe.cTRB); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + /* This had better be a control endpoint. */ + AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_CONTROL, ("%#x\n", pEpCtx->ep_type)); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + + /* For OUT/SETUP transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT || pUrb->enmDir == VUSBDIRECTION_SETUP) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = 0; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + ctxProbe.cTRB = ctxSubmit.cTRB; + } + + pUrb->pHci->cTRB = ctxProbe.cTRB; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* + * Submit the URB. + */ + STAM_COUNTER_ADD(&pThis->StatUrbSizeCtrl, pUrb->cbData); + Log(("%s: xhciR3QueueControlTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + /// @todo Mark the EP as halted and inactive and write back the changes. + + return VERR_OUT_OF_RESOURCES; +} + + +/** + * Process a device context (transfer data). + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot/doorbell which had been rung. + * @param uDBVal Value written to the doorbell. + */ +static int xhciR3ProcessDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint32_t uDBVal) +{ + uint8_t uDBTarget = uDBVal & XHCI_DB_TGT_MASK; + XHCI_CTX_ISOCH ctxIsoch = {0}; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX ep_ctx; + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB; + PXHCIROOTHUBR3 pRh; + bool dcs; + bool fContinue = true; + int rc; + unsigned cTrbs = 0; + + LogFlowFunc(("Slot ID: %u, DB target %u, DB stream ID %u\n", uSlotID, uDBTarget, (uDBVal & XHCI_DB_STRMID_MASK) >> XHCI_DB_STRMID_SHIFT)); + Assert(uSlotID > 0); + Assert(uSlotID <= XHCI_NDS); + /// @todo report errors for bogus DB targets + Assert(uDBTarget > 0); + Assert(uDBTarget < 32); + + /// @todo Check for aborts and the like? + + /* Load the slot and endpoint contexts. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx); + /// @todo sanity check the context in here? + + /* Select the root hub corresponding to the port. */ + pRh = GET_PORT_PRH(pThisCC, ID_TO_IDX(slot_ctx.rh_port)); + + /* Stopped endpoints automatically transition to running state. */ + if (RT_UNLIKELY(ep_ctx.ep_state == XHCI_EPST_STOPPED)) + { + Log(("EP DCI %u stopped -> running\n", uDBTarget)); + ep_ctx.ep_state = XHCI_EPST_RUNNING; + /* Update EP right here. Theoretically could be postponed, but we + * must ensure that the EP does get written back even if there is + * no other work to do. + */ + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + } + + /* If the EP isn't running, get outta here. */ + if (RT_UNLIKELY(ep_ctx.ep_state != XHCI_EPST_RUNNING)) + { + Log2(("EP DCI %u not running (state %u), bail!\n", uDBTarget, ep_ctx.ep_state)); + return VINF_SUCCESS; + } + + /* Get any non-transfer TRBs out of the way. */ + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx, &xfer, &GCPhysXfrTRB); + /// @todo This is inefficient. + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + + do + { + /* Fetch the contexts again and find the TRB address at enqueue point. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx); + GCPhysXfrTRB = ep_ctx.trep & XHCI_TRDP_ADDR_MASK; + dcs = !!(ep_ctx.trep & XHCI_TRDP_DCS_MASK); + LogFlowFunc(("Processing Transfer Ring, TREP: %RGp\n", GCPhysXfrTRB)); + + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer)); + + /* Make sure the Cycle State matches. */ + if ((bool)xfer.gen.cycle == dcs) + { + Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type, + xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!", + xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt)); + + /* If there is an "in-flight" TRDP, check if we need to wait until the transfer completes. */ + if ((ep_ctx.trdp & XHCI_TRDP_ADDR_MASK) != GCPhysXfrTRB) + { + switch (xfer.gen.type) { + case XHCI_TRB_ISOCH: + if (ep_ctx.ifc >= XHCI_MAX_ISOC_IN_FLIGHT) + { + Log(("%u isoch URBs in flight, backing off\n", ep_ctx.ifc)); + fContinue = false; + break; + } + RT_FALL_THRU(); + case XHCI_TRB_LINK: + Log2(("TRB OK, continuing @ %RX64\n", GCPhysXfrTRB)); + break; + case XHCI_TRB_NORMAL: + if (XHCI_EP_XTYPE(ep_ctx.ep_type) != XHCI_XFTYPE_BULK) + { + Log2(("Normal TRB not bulk, not continuing @ %RX64\n", GCPhysXfrTRB)); + fContinue = false; + break; + } + if (ep_ctx.ifc >= XHCI_MAX_BULK_IN_FLIGHT) + { + Log(("%u normal URBs in flight, backing off\n", ep_ctx.ifc)); + fContinue = false; + break; + } + Log2(("Bulk TRB OK, continuing @ %RX64\n", GCPhysXfrTRB)); + break; + case XHCI_TRB_EVT_DATA: + case XHCI_TRB_NOOP_XFER: + Log2(("TRB not OK, not continuing @ %RX64\n", GCPhysXfrTRB)); + fContinue = false; + break; + default: + Log2(("Some other TRB (type %u), not continuing @ %RX64\n", xfer.gen.type, GCPhysXfrTRB)); + fContinue = false; + break; + } + } + if (!fContinue) + break; + + switch (xfer.gen.type) { + case XHCI_TRB_NORMAL: + Log(("Normal TRB: Ptr=%RGp IOC=%u CH=%u\n", xfer.norm.data_ptr, xfer.norm.ioc, xfer.norm.ch)); + rc = xhciR3QueueDataTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_SETUP_STG: + Log(("Setup stage TRB: IOC=%u IDT=%u\n", xfer.setup.ioc, xfer.setup.idt)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_DATA_STG: + Log(("Data stage TRB: Ptr=%RGp IOC=%u CH=%u DIR=%u\n", xfer.data.data_ptr, xfer.data.ioc, xfer.data.ch, xfer.data.dir)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_STATUS_STG: + Log(("Status stage TRB: IOC=%u CH=%u DIR=%u\n", xfer.status.ioc, xfer.status.ch, xfer.status.dir)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_ISOCH: + Log(("Isoch TRB: Ptr=%RGp IOC=%u CH=%u TLBPC=%u TBC=%u SIA=%u FrmID=%u\n", xfer.isoc.data_ptr, xfer.isoc.ioc, xfer.isoc.ch, xfer.isoc.tlbpc, xfer.isoc.tbc, xfer.isoc.sia, xfer.isoc.frm_id)); + rc = xhciR3QueueIsochTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget, &ctxIsoch); + break; + case XHCI_TRB_LINK: + Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain)); + Assert(!xfer.link.chain); + /* Set new TREP but leave DCS bit alone... */ + ep_ctx.trep = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (ep_ctx.trep & XHCI_TRDP_DCS_MASK); + /* ...and flip the DCS bit if required. Then update the TREP. */ + if (xfer.link.toggle) + ep_ctx.trep = (ep_ctx.trep & ~XHCI_TRDP_DCS_MASK) | (ep_ctx.trep ^ XHCI_TRDP_DCS_MASK); + rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + break; + case XHCI_TRB_NOOP_XFER: + Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent)); + /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */ + Assert(!xfer.link.chain); + /* Update enqueue pointer (TRB was not yet completed). */ + ep_ctx.trep += sizeof(XHCI_XFER_TRB); + rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + break; + default: + Log(("Unsupported TRB!!\n")); + rc = VERR_NOT_SUPPORTED; + break; + } + /* If queuing failed, stop right here. */ + if (RT_FAILURE(rc)) + fContinue = false; + } + else + { + LogFunc(("Transfer Ring empty\n")); + fContinue = false; + + /* If an isochronous ring is empty, this is an overrun/underrun. At this point + * the ring will no longer be scheduled (until the doorbell is rung again) + * but it remains in the Running state. This error is only reported if someone + * rang the doorbell and there are no TDs available or in-flight. + */ + if ( (ep_ctx.trep == ep_ctx.trdp) /* Nothing in-flight? */ + && (ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN || ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_OUT)) + { + /* There is no TRB associated with this error; the slot context + * determines the interrupter. + */ + Log(("Isochronous ring %s, TRDP:%RGp\n", ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? "overrun" : "underrun", ep_ctx.trdp & XHCI_TRDP_ADDR_MASK)); + rc = xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0, + ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? XHCI_TCC_RING_OVERRUN : XHCI_TCC_RING_UNDERRUN, + uSlotID, uDBTarget, 0, false, false); + } + + } + + /* Kill the xHC if the TRB list has no end in sight. */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* It can unfortunately happen that for endpoints with more than one + * transfer per USB frame, there won't be a complete multi-packet URB ready + * when we go looking for it. If that happens, we'll "rewind" the TREP and + * try again later. Since the URB construction is done under a lock, this + * is safe as we won't be accessing the endpoint concurrently. + */ + if (ctxIsoch.pUrb) + { + Log(("Unfinished ISOC URB (%u packets out of %u)!\n", ctxIsoch.iPkt, ctxIsoch.pUrb->cIsocPkts)); + /* If submitting failed, the URB is already freed. */ + if (!ctxIsoch.fSubmitFailed) + VUSBIRhFreeUrb(pRh->pIRhConn, ctxIsoch.pUrb); + ep_ctx.trep = ctxIsoch.uInitTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + } + return VINF_SUCCESS; +} + + +/** + * A worker routine for Address Device command. Builds a URB containing + * a SET_ADDRESS requests and (synchronously) submits it to VUSB, then + * follows up with a status stage URB. + * + * @returns true on success. + * @returns false on failure to submit. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot ID to assign address to. + * @param uDevAddr New device address. + * @param iPort The xHCI root hub port index. + */ +static bool xhciR3IssueSetAddress(PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDevAddr, unsigned iPort) +{ + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + Assert(uSlotID); + LogFlowFunc(("Slot %u port idx %u: new address is %u\n", uSlotID, iPort, uDevAddr)); + + /* For USB3 devices, force the port number. This simulates the fact that USB3 uses directed (unicast) traffic. */ + if (!IS_USB3_PORT_IDX_R3(pThisCC, iPort)) + iPort = VUSB_DEVICE_PORT_INVALID; + else + iPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort); + + /* Allocate and initialize a URB. NB: Zero cTds indicates a URB not submitted by guest. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_SETUP, + sizeof(VUSBSETUP), 0 /* cTds */, NULL); + if (!pUrb) + return false; + + pUrb->EndPt = 0; + pUrb->fShortNotOk = true; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = 0; + + /* Build the request. */ + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + pSetup->bmRequestType = VUSB_DIR_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_TO_DEVICE; + pSetup->bRequest = VUSB_REQ_SET_ADDRESS; + pSetup->wValue = uDevAddr; + pSetup->wIndex = 0; + pSetup->wLength = 0; + + /* NB: We assume the address assignment is a synchronous operation. */ + + /* Submit the setup URB. */ + Log(("%s: xhciSetAddress setup: cbData=%u\n", pUrb->pszDesc, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + Log(("xhciSetAddress: setup stage failed pUrb=%p!!\n", pUrb)); + return false; + } + + /* To complete the SET_ADDRESS request, the status stage must succeed. */ + pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_IN, 0 /* cbData */, 0 /* cTds */, + NULL); + if (!pUrb) + return false; + + pUrb->EndPt = 0; + pUrb->fShortNotOk = true; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = 0; + + /* Submit the setup URB. */ + Log(("%s: xhciSetAddress status: cbData=%u\n", pUrb->pszDesc, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + Log(("xhciSetAddress: status stage failed pUrb=%p!!\n", pUrb)); + return false; + } + + Log(("xhciSetAddress: set address succeeded\n")); + return true; +} + + +/** + * Address a device. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID to assign address to. + * @param fBSR Block Set address Request flag. + */ +static unsigned xhciR3AddressDevice(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint64_t uInpCtxAddr, + uint8_t uSlotID, bool fBSR) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX inp_slot_ctx; /* Input Slot Context (ICI=1). */ + XHCI_EP_CTX ep_ctx; /* Endpoint Context (ICI=2+). */ + XHCI_SLOT_CTX out_slot_ctx; /* Output Slot Context. */ + uint8_t dev_addr; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(GCPhysInpCtx); + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /// @todo Check for valid context (6.2.2.1, 6.2.3.1) + + /* See 4.6.5 */ + do { + /* Parameter validation depends on whether the BSR flag is set or not. */ + if (fBSR) + { + /* Check that the output slot context state is in Enabled state. */ + if (out_slot_ctx.slot_state >= XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state (%u) wrong (BSR)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + dev_addr = 0; + } + else + { + /* Check that the output slot context state is in Enabled or Default state. */ + if (out_slot_ctx.slot_state > XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state (%u) wrong (no-BSR)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + dev_addr = xhciR3SelectNewAddress(pThis, uSlotID); + } + + /* Fetch the input control context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + Assert(icc.add_flags == (RT_BIT(0) | RT_BIT(1))); /* Should have been already checked. */ + Assert(!icc.drop_flags); + + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the input slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &inp_slot_ctx, sizeof(inp_slot_ctx)); + + /* If BSR isn't set, issue the actual SET_ADDRESS request. */ + if (!fBSR) { + unsigned iPort; + + /* We have to dig out the port number/index to determine which virtual root hub to use. */ + iPort = ID_TO_IDX(inp_slot_ctx.rh_port); + if (iPort >= XHCI_NDP_CFG(pThis)) + { + Log(("Port out of range (index %u)!\n", iPort)); + cc = XHCI_TCC_USB_XACT_ERR; + break; + } + if (!xhciR3IssueSetAddress(pThisCC, uSlotID, dev_addr, iPort)) + { + Log(("SET_ADDRESS failed!\n")); + cc = XHCI_TCC_USB_XACT_ERR; + break; + } + } + + /* Copy the slot context with appropriate modifications. */ + out_slot_ctx = inp_slot_ctx; + if (fBSR) + out_slot_ctx.slot_state = XHCI_SLTST_DEFAULT; + else + out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED; + out_slot_ctx.dev_addr = dev_addr; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* Point at the EP0 contexts. */ + GCPhysInpSlot += sizeof(inp_slot_ctx); + GCPhysOutSlot += sizeof(out_slot_ctx); + + /* Copy EP0 context with appropriate modifications. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &ep_ctx, sizeof(ep_ctx)); + xhciR3EnableEP(&ep_ctx); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &ep_ctx, sizeof(ep_ctx)); + } while (0); + + return cc; +} + + +/** + * Reset a halted endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to reset. + * @param fTSP The Transfer State Preserve flag. + */ +static unsigned xhciR3ResetEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, bool fTSP) +{ + RT_NOREF(fTSP); + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.8 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Halted. */ + if (endp_ctx.ep_state != XHCI_EPST_HALTED) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Transition EP state. */ + endp_ctx.ep_state = XHCI_EPST_STOPPED; + + /// @todo What can we do with the TSP flag? + /// @todo Anything to do WRT enabling the corresponding doorbell register? + + /* Write back the updated endpoint context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + } while (0); + + return cc; +} + + +/** + * Stop a running endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to stop. + * @param fTSP The Suspend flag. + */ +static unsigned xhciR3StopEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDCI, bool fTSP) +{ + RT_NOREF(fTSP); + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.9 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* The doorbell could be ringing; stop it if so. */ + if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << uDCI)) + { + Log(("Unring bell for slot ID %u, DCI %u\n", uSlotID, uDCI)); + ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDCI)); + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Running. */ + if (endp_ctx.ep_state != XHCI_EPST_RUNNING) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Transition EP state. */ + endp_ctx.ep_state = XHCI_EPST_STOPPED; + + /* Write back the updated endpoint context *now*, before actually canceling anyhing. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /// @todo What can we do with the SP flag? + + PXHCIROOTHUBR3 pRh; + uint32_t uPort; + + /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after + * writing back the EP state so that the completion callback can operate. + */ + if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, &pRh, &uPort))) + { + /* Temporarily give up the lock so that the completion callbacks can run. */ + RTCritSectLeave(&pThisCC->CritSectThrd); + Log(("Aborting DCI %u -> ep=%u d=%u\n", uDCI, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT)); + pRh->pIRhConn->pfnAbortEp(pRh->pIRhConn, uPort, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT); + RTCritSectEnter(&pThisCC->CritSectThrd); + } + + /// @todo The completion callbacks should do more work for canceled URBs. + /* Once the completion callbacks had a chance to run, we have to adjust + * the endpoint state. + * NB: The guest may just ring the doorbell to continue and not execute + * 'Set TRDP' after stopping the endpoint. + */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + bool fXferWasInProgress = endp_ctx.trep != endp_ctx.trdp; + + /* Reset the TREP, but the EDTLA should be left alone. */ + endp_ctx.trep = endp_ctx.trdp; + + if (fXferWasInProgress) + { + /* Fetch the transfer TRB to see the length. */ + RTGCPHYS GCPhysXfrTRB = endp_ctx.trdp & XHCI_TRDP_ADDR_MASK; + XHCI_XFER_TRB XferTRB; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB)); + + xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, XferTRB.gen.xfr_len, XHCI_TCC_STOPPED, uSlotID, uDCI, + GCPhysXfrTRB, false, false); + } + else + { + /* We need to generate a Force Stopped Event or FSE. Note that FSEs were optional + * in xHCI 0.96 but aren't in 1.0. + */ + xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0, XHCI_TCC_STP_INV_LEN, uSlotID, uDCI, + endp_ctx.trdp & XHCI_TRDP_ADDR_MASK, false, false); + } + + /* Write back the updated endpoint context again. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + } while (0); + + return cc; +} + + +/** + * Set a new TR Dequeue Pointer for an endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to reset. + * @param uTRDP The TRDP including DCS/ flag. + */ +static unsigned xhciR3SetTRDP(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, uint64_t uTRDP) +{ + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.10 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Stopped or Error. */ + if (endp_ctx.ep_state != XHCI_EPST_STOPPED && endp_ctx.ep_state != XHCI_EPST_ERROR) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Update the TRDP/TREP and DCS. */ + endp_ctx.trdp = uTRDP; + endp_ctx.trep = uTRDP; + + /* Also clear the in-flight counter! */ + endp_ctx.ifc = 0; + + /// @todo Handle streams + + /* Write back the updated endpoint context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + } while (0); + + return cc; +} + + +/** + * Prepare for a device reset. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + */ +static unsigned xhciR3ResetDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID) +{ + RTGCPHYS GCPhysSlot; + XHCI_SLOT_CTX slot_ctx; + XHCI_DEV_CTX dc; + unsigned num_ctx; + unsigned i; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the address of the slot/device context. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.11. */ + do { + /* Check that the slot context state is Addressed or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_ADDRESSED) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Read the entire Device Context. */ + num_ctx = slot_ctx.ctx_ent + 1; /* Slot context plus EPs. */ + Assert(num_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX)); + + /// @todo Abort any outstanding transfers! + + /* Set slot state to Default and reset the USB device address. */ + dc.entry[0].sc.slot_state = XHCI_SLTST_DEFAULT; + dc.entry[0].sc.dev_addr = 0; + + /* Disable all endpoints except for EP 0 (aka DCI 1). */ + for (i = 2; i < num_ctx; ++i) + dc.entry[i].ep.ep_state = XHCI_EPST_DISABLED; + + /* Write back the updated device context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX)); + } while (0); + + return cc; +} + + +/** + * Configure a device (even though the relevant command is called 'Configure + * Endpoint'. This includes adding/dropping endpoint contexts as directed by + * the input control context bits. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID associated with the context. + * @param fDC Deconfigure flag set (input context unused). + */ +static unsigned xhciR3ConfigureDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID, bool fDC) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + RTGCPHYS GCPhysOutEndp; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */ + XHCI_EP_CTX out_endp_ctx; /* Endpoint Context (DCI=1). */ + unsigned cc = XHCI_TCC_SUCCESS; + uint32_t uAddFlags; + uint32_t uDropFlags; + unsigned num_inp_ctx; + unsigned num_out_ctx; + XHCI_DEV_CTX dc_inp; + XHCI_DEV_CTX dc_out; + unsigned uDCI; + + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + Assert(GCPhysOutSlot); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* See 4.6.6 */ + do { + /* Check that the output slot context state is Addressed, or Configured. */ + if (out_slot_ctx.slot_state < XHCI_SLTST_ADDRESSED) + { + Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Check for deconfiguration request. */ + if (fDC) { + if (out_slot_ctx.slot_state == XHCI_SLTST_CONFIGURED) { + /* Disable all enabled endpoints. */ + uDropFlags = 0xFFFFFFFC; /** @todo r=bird: Why do you set uDropFlags and uAddFlags in a code path that doesn't use + * them? This is a _very_ difficult function to get the hang of the way it's written. + * Stuff like this looks like there's a control flow flaw (as to the do-break-while-false + * loop which doesn't do any clean up or logging at the end and seems only sever the very + * dubious purpose of making sure ther's only one return statement). The insistance on + * C-style variable declarations (top of function), makes checking state harder, which is + * why it's discouraged. */ + uAddFlags = 0; + + /* Start with EP1. */ + GCPhysOutEndp = GCPhysOutSlot + sizeof(XHCI_SLOT_CTX) + sizeof(XHCI_EP_CTX); + + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx)); + out_endp_ctx.ep_state = XHCI_EPST_DISABLED; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx)); + GCPhysOutEndp += sizeof(XHCI_EP_CTX); /* Point to the next EP context. */ + + /* Finally update the output slot context. */ + out_slot_ctx.ctx_ent = 1; /* Only EP0 left. */ + out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + LogFlow(("Setting Output Slot State to Addressed, Context Entries = %u\n", out_slot_ctx.ctx_ent)); + } + else + /* NB: Attempts to deconfigure a slot in Addressed state are ignored. */ + Log(("Ignoring attempt to deconfigure slot in Addressed state!\n")); + break; + } + + /* Fetch the input control context. */ + Assert(GCPhysInpCtx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + Assert(icc.add_flags || icc.drop_flags); /* Make sure there's something to do. */ + + uAddFlags = icc.add_flags; + uDropFlags = icc.drop_flags; + LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags)); + + /* If and only if any 'add context' flag is set, fetch the corresponding + * input device context. + */ + if (uAddFlags) { + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the input Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_inp_ctx = ASMBitLastSetU32(uAddFlags); + Assert(num_inp_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY)); + + /// @todo Check that the highest set add flag isn't beyond input slot Context Entries + + /// @todo Check input slot context according to 6.2.2.2 + /// @todo Check input EP contexts according to 6.2.3.2 + } +/** @todo r=bird: Looks like MSC is right that dc_inp can be used uninitalized. + * + * However, this function is so hard to read I'm leaving the exorcism of it to + * the author and just zeroing it in the mean time. + * + */ + else + RT_ZERO(dc_inp); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' or 'drop' bit set. + */ + num_out_ctx = ASMBitLastSetU32(uAddFlags | uDropFlags); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + + /* Drop contexts as directed by flags. */ + for (uDCI = 2; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uDropFlags)) + continue; + + Log2(("Dropping EP DCI %u\n", uDCI)); + dc_out.entry[uDCI].ep.ep_state = XHCI_EPST_DISABLED; + /// @todo Do we need to bother tracking resources/bandwidth? + } + + /* Now add contexts as directed by flags. */ + for (uDCI = 2; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uAddFlags)) + continue; + + Assert(!fDC); + /* Copy over EP context, set to running. */ + Log2(("Adding EP DCI %u\n", uDCI)); + dc_out.entry[uDCI].ep = dc_inp.entry[uDCI].ep; + xhciR3EnableEP(&dc_out.entry[uDCI].ep); + /// @todo Do we need to bother tracking resources/bandwidth? + } + + /* Finally update the device context. */ + if (fDC || dc_inp.entry[0].sc.ctx_ent == 1) + { + dc_out.entry[0].sc.slot_state = XHCI_SLTST_ADDRESSED; + dc_out.entry[0].sc.ctx_ent = 1; + LogFlow(("Setting Output Slot State to Addressed\n")); + } + else + { + uint32_t uKillFlags = uDropFlags & ~uAddFlags; /* Endpoints going away. */ + + /* At least one EP enabled. Update Context Entries and state. */ + Assert(dc_inp.entry[0].sc.ctx_ent); + dc_out.entry[0].sc.slot_state = XHCI_SLTST_CONFIGURED; + if (ID_TO_IDX(ASMBitLastSetU32(uAddFlags)) > dc_out.entry[0].sc.ctx_ent) + { + /* Adding new endpoints. */ + dc_out.entry[0].sc.ctx_ent = ID_TO_IDX(ASMBitLastSetU32(uAddFlags)); + } + else if (ID_TO_IDX(ASMBitLastSetU32(uKillFlags)) == dc_out.entry[0].sc.ctx_ent) + { + /* Removing the last endpoint, find the last non-disabled one. */ + unsigned num_ctx_ent; + + Assert(dc_out.entry[0].sc.ctx_ent + 1u == num_out_ctx); + for (num_ctx_ent = dc_out.entry[0].sc.ctx_ent; num_ctx_ent > 1; --num_ctx_ent) + if (dc_out.entry[num_ctx_ent].ep.ep_state != XHCI_EPST_DISABLED) + break; + dc_out.entry[0].sc.ctx_ent = num_ctx_ent; /* Last valid index to be precise. */ + } + LogFlow(("Setting Output Slot State to Configured, Context Entries = %u\n", dc_out.entry[0].sc.ctx_ent)); + } + + /* If there were no errors, write back the updated output context. */ + LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot)); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + } while (0); + + return cc; +} + + +/** + * Evaluate an input context. This involves modifying device and endpoint + * contexts as directed by the input control context add bits. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID associated with the context. + */ +static unsigned xhciR3EvalContext(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */ + unsigned cc = XHCI_TCC_SUCCESS; + uint32_t uAddFlags; + uint32_t uDropFlags; + unsigned num_inp_ctx; + unsigned num_out_ctx; + XHCI_DEV_CTX dc_inp; + XHCI_DEV_CTX dc_out; + unsigned uDCI; + + Assert(GCPhysInpCtx); + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + Assert(GCPhysOutSlot); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* See 4.6.7 */ + do { + /* Check that the output slot context state is Default, Addressed, or Configured. */ + if (out_slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the input control context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + uAddFlags = icc.add_flags; + uDropFlags = icc.drop_flags; + LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags)); + + /* Drop flags "shall be cleared to 0" but also "do not apply" (4.6.7). Log & ignore. */ + if (uDropFlags) + Log(("Drop flags set (%X) for evaluating context!\n", uDropFlags)); + + /* If no add flags are set, nothing will be done but an error is not reported + * according to the logic flow in 4.6.7. + */ + if (!uAddFlags) + { + Log(("Warning: no add flags set for evaluating context!\n")); + break; + } + + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_inp_ctx = ASMBitLastSetU32(uAddFlags); + Assert(num_inp_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY)); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_out_ctx = ASMBitLastSetU32(uAddFlags); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + + /// @todo Check input slot context according to 6.2.2.3 + /// @todo Check input EP contexts according to 6.2.3.3 + /// @todo Check that the highest set add flag isn't beyond input slot Context Entries + + /* Evaluate endpoint contexts as directed by add flags. */ + /// @todo 6.2.3.3 suggests only the A1 bit matters? Anything besides A0/A1 is ignored?? + for (uDCI = 1; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uAddFlags)) + continue; + + /* Evaluate Max Packet Size. */ + LogFunc(("DCI %u: Max Packet Size: %u -> %u\n", uDCI, dc_out.entry[uDCI].ep.max_pkt_sz, dc_inp.entry[uDCI].ep.max_pkt_sz)); + dc_out.entry[uDCI].ep.max_pkt_sz = dc_inp.entry[uDCI].ep.max_pkt_sz; + } + + /* Finally update the device context if directed to do so (A0 flag set). */ + if (uAddFlags & RT_BIT(0)) + { + /* 6.2.2.3 - evaluate Interrupter Target and Max Exit Latency. */ + Log(("Interrupter Target: %u -> %u\n", dc_out.entry[0].sc.intr_tgt, dc_inp.entry[0].sc.intr_tgt)); + Log(("Max Exit Latency : %u -> %u\n", dc_out.entry[0].sc.max_lat, dc_inp.entry[0].sc.max_lat)); + + /// @todo Non-zero Max Exit Latency (see 4.6.7) + dc_out.entry[0].sc.intr_tgt = dc_inp.entry[0].sc.intr_tgt; + dc_out.entry[0].sc.max_lat = dc_inp.entry[0].sc.max_lat; + } + + /* If there were no errors, write back the updated output context. */ + LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot)); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + } while (0); + + return cc; +} + + +/** + * Query available port bandwidth. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uDevSpd Speed of not yet attached devices. + * @param uHubSlotID Hub Slot ID to query (unsupported). + * @param uBwCtx Bandwidth context physical address. + */ +static unsigned xhciR3GetPortBandwidth(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uDevSpd, uint8_t uHubSlotID, uint64_t uBwCtx) +{ + RT_NOREF(uHubSlotID); + RTGCPHYS GCPhysBwCtx; + unsigned cc = XHCI_TCC_SUCCESS; + unsigned ctx_size; + unsigned iPort; + uint8_t bw_ctx[RT_ALIGN_32(XHCI_NDP_MAX + 1, 4)] = {0}; + uint8_t dev_spd; + uint8_t avail_bw; + + Assert(!uHubSlotID); + Assert(uBwCtx); + + /* See 4.6.15. */ + + /* Hubs are not supported because guests will never see them. The + * reported values are more or less dummy because we have no real + * information about the bandwidth available on the host. The reported + * values are optimistic, as if each port had its own separate Bus + * Instance aka BI. + */ + + GCPhysBwCtx = uBwCtx & XHCI_CTX_ADDR_MASK; + + /* Number of ports + 1, rounded up to DWORDs. */ + ctx_size = RT_ALIGN_32(XHCI_NDP_CFG(pThis) + 1, 4); + LogFlowFunc(("BW Context at %RGp, size %u\n", GCPhysBwCtx, ctx_size)); + Assert(ctx_size <= sizeof(bw_ctx)); + + /* Go over all the ports. */ + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + /* Get the device speed from the port... */ + dev_spd = (pThis->aPorts[iPort].portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + /* ...and if nothing is attached, use the provided default. */ + if (!dev_spd) + dev_spd = uDevSpd; + + /* For USB3 ports, report 90% available for SS devices (see 6.2.6). */ + if (IS_USB3_PORT_IDX_SHR(pThis, iPort)) + avail_bw = dev_spd == XHCI_SPD_SUPER ? 90 : 0; + else + /* For USB2 ports, report 80% available for HS and 90% for FS/LS. */ + switch (dev_spd) + { + case XHCI_SPD_HIGH: + avail_bw = 80; + break; + case XHCI_SPD_FULL: + case XHCI_SPD_LOW: + avail_bw = 90; + break; + default: + avail_bw = 0; + } + + /* The first entry in the context is reserved. */ + bw_ctx[iPort + 1] = avail_bw; + } + + /* Write back the bandwidth context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysBwCtx, &bw_ctx, ctx_size); + + return cc; +} + +#define NEC_MAGIC ('x' | ('H' << 8) | ('C' << 16) | ('I' << 24)) + +/** + * Take a 64-bit input, shake well, produce 32-bit token. This mechanism + * prevents NEC/Renesas drivers from running on 3rd party hardware. Mirrors + * code found in vendor's drivers. + */ +static uint32_t xhciR3NecAuthenticate(uint64_t cookie) +{ + uint32_t cookie_lo = RT_LODWORD(cookie); + uint32_t cookie_hi = RT_HIDWORD(cookie); + uint32_t shift_cnt; + uint32_t token; + + shift_cnt = (cookie_hi >> 8) & 31; + token = ASMRotateRightU32(cookie_lo - NEC_MAGIC, shift_cnt); + shift_cnt = cookie_hi & 31; + token += ASMRotateLeftU32(cookie_lo + NEC_MAGIC, shift_cnt); + shift_cnt = (cookie_lo >> 16) & 31; + token -= ASMRotateLeftU32(cookie_hi ^ NEC_MAGIC, shift_cnt); + + return ~token; +} + +/** + * Process a single command TRB and post completion information. + */ +static int xhciR3ExecuteCommand(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, XHCI_COMMAND_TRB *pCmd) +{ + XHCI_EVENT_TRB ed; + uint32_t token; + unsigned slot; + unsigned cc; + int rc = VINF_SUCCESS; + LogFlowFunc(("Executing command %u (%s) @ %RGp\n", pCmd->gen.type, + pCmd->gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[pCmd->gen.type] : "WHAT?!!", + (RTGCPHYS)pThis->cmdr_dqp)); + + switch (pCmd->gen.type) + { + case XHCI_TRB_NOOP_CMD: + /* No-op, slot ID is always zero. */ + rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_LINK: + /* Link; set the dequeue pointer. CH bit is ignored. */ + Log(("Link: Ptr=%RGp IOC=%u TC=%u\n", pCmd->link.rseg_ptr, pCmd->link.ioc, pCmd->link.toggle)); + if (pCmd->link.ioc) /* Command completion event is optional! */ + rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0); + /* Update the dequeue pointer and flip DCS if required. */ + pThis->cmdr_dqp = pCmd->link.rseg_ptr & XHCI_TRDP_ADDR_MASK; + pThis->cmdr_ccs = pThis->cmdr_ccs ^ pCmd->link.toggle; + break; + + case XHCI_TRB_ENB_SLOT: + /* Look for an empty device slot. */ + for (slot = 0; slot < RT_ELEMENTS(pThis->aSlotState); ++slot) + { + if (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY) + { + /* Found a slot - transition to enabled state. */ + pThis->aSlotState[slot] = XHCI_DEVSLOT_ENABLED; + break; + } + } + Log(("Enable Slot: found slot ID %u\n", IDX_TO_ID(slot))); + + /* Post command completion event. */ + if (slot == RT_ELEMENTS(pThis->aSlotState)) + xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_NO_SLOTS, 0); + else + xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, IDX_TO_ID(slot)); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_DIS_SLOT: + /* Disable the given device slot. */ + Log(("Disable Slot: slot ID %u\n", pCmd->dsl.slot_id)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->dsl.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + { + /// @todo set slot state of assoc. context to disabled + pThis->aSlotState[slot] = XHCI_DEVSLOT_EMPTY; + } + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->dsl.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_ADDR_DEV: + /* Address a device. */ + Log(("Address Device: slot ID %u, BSR=%u\n", pCmd->adr.slot_id, pCmd->adr.bsr)); + slot = ID_TO_IDX(pCmd->cfg.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3AddressDevice(pDevIns, pThis, pThisCC, pCmd->adr.ctx_ptr, pCmd->adr.slot_id, pCmd->adr.bsr); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->adr.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_CFG_EP: + /* Configure endpoint. */ + Log(("Configure endpoint: slot ID %u, DC=%u, Ctx @ %RGp\n", pCmd->cfg.slot_id, pCmd->cfg.dc, pCmd->cfg.ctx_ptr)); + slot = ID_TO_IDX(pCmd->cfg.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ConfigureDevice(pDevIns, pThis, pCmd->cfg.ctx_ptr, pCmd->cfg.slot_id, pCmd->cfg.dc); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->cfg.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_EVAL_CTX: + /* Evaluate context. */ + Log(("Evaluate context: slot ID %u, Ctx @ %RGp\n", pCmd->evc.slot_id, pCmd->evc.ctx_ptr)); + slot = ID_TO_IDX(pCmd->evc.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3EvalContext(pDevIns, pThis, pCmd->evc.ctx_ptr, pCmd->evc.slot_id); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->evc.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_RESET_EP: + /* Reset the given endpoint. */ + Log(("Reset Endpoint: slot ID %u, EP ID %u, TSP=%u\n", pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->rse.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ResetEndpoint(pDevIns, pThis, pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_STOP_EP: + /* Stop the given endpoint. */ + Log(("Stop Endpoint: slot ID %u, EP ID %u, SP=%u\n", pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->stp.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3StopEndpoint(pDevIns, pThis, pThisCC, pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_SET_DEQ_PTR: + /* Set TR Dequeue Pointer. */ + Log(("Set TRDP: slot ID %u, EP ID %u, TRDP=%RX64\n", pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->stdp.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3SetTRDP(pDevIns, pThis, pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stdp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_RESET_DEV: + /* Reset a device. */ + Log(("Reset Device: slot ID %u\n", pCmd->rsd.slot_id)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->rsd.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ResetDevice(pDevIns, pThis, pCmd->rsd.slot_id); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->rsd.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_GET_PORT_BW: + /* Get port bandwidth. */ + Log(("Get Port Bandwidth: Dev Speed %u, Hub Slot ID %u, Context=%RX64\n", pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr)); + cc = XHCI_TCC_SUCCESS; + if (pCmd->gpbw.slot_id) + cc = XHCI_TCC_PARM_ERR; /* Potential undefined behavior, see 4.6.15. */ + else + cc = xhciR3GetPortBandwidth(pDevIns, pThis, pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, 0); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case NEC_TRB_GET_FW_VER: + /* Get NEC firmware version. */ + Log(("Get NEC firmware version\n")); + cc = XHCI_TCC_SUCCESS; + + RT_ZERO(ed); + ed.nce.word1 = NEC_FW_REV; + ed.nce.trb_ptr = pThis->cmdr_dqp; + ed.nce.cc = cc; + ed.nce.type = NEC_TRB_CMD_CMPL; + + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case NEC_TRB_AUTHENTICATE: + /* NEC authentication. */ + Log(("NEC authentication, cookie %RX64\n", pCmd->nac.cookie)); + cc = XHCI_TCC_SUCCESS; + + token = xhciR3NecAuthenticate(pCmd->nac.cookie); + RT_ZERO(ed); + ed.nce.word1 = RT_LOWORD(token); + ed.nce.word2 = RT_HIWORD(token); + ed.nce.trb_ptr = pThis->cmdr_dqp; + ed.nce.cc = cc; + ed.nce.type = NEC_TRB_CMD_CMPL; + + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + default: + Log(("Unsupported command!\n")); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + } + + return rc; +} + + +/** + * Stop the Command Ring. + */ +static int xhciR3StopCommandRing(PPDMDEVINS pDevIns, PXHCI pThis) +{ + LogFlowFunc(("Command Ring stopping\n")); + + Assert(pThis->crcr & (XHCI_CRCR_CA | XHCI_CRCR_CS)); + Assert(pThis->crcr & XHCI_CRCR_CRR); + ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS)); + return xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_CMDR_STOPPED, 0); +} + + +/** + * Process the xHCI command ring. + */ +static int xhciR3ProcessCommandRing(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + RTGCPHYS GCPhysCmdTRB; + XHCI_COMMAND_TRB cmd; /* Command Descriptor */ + unsigned cCmds; + + Assert(pThis->crcr & XHCI_CRCR_CRR); + LogFlowFunc(("Processing commands...\n")); + + for (cCmds = 0;; cCmds++) + { + /* First check if the xHC is running at all. */ + if (!(pThis->cmd & XHCI_CMD_RS)) + { + /* Note that this will call xhciR3PostCmdCompletion() which will + * end up doing nothing because R/S is clear. + */ + xhciR3StopCommandRing(pDevIns, pThis); + break; + } + + /* Check if Command Ring was stopped in the meantime. */ + if (pThis->crcr & (XHCI_CRCR_CS | XHCI_CRCR_CA)) + { + /* NB: We currently do not abort commands. If we did, we would + * abort the currently running command and complete it with + * the XHCI_TCC_CMD_ABORTED status. + */ + xhciR3StopCommandRing(pDevIns, pThis); + break; + } + + /* Fetch the command TRB. */ + GCPhysCmdTRB = pThis->cmdr_dqp; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCmdTRB, &cmd, sizeof(cmd)); + + /* Make sure the Cycle State matches. */ + if ((bool)cmd.gen.cycle == pThis->cmdr_ccs) + xhciR3ExecuteCommand(pDevIns, pThis, pThisCC, &cmd); + else + { + Log(("Command Ring empty\n")); + break; + } + + /* Check if we're being fed suspiciously many commands. */ + if (cCmds > XHCI_MAX_NUM_CMDS) + { + /* Clear the R/S bit and any command ring running bits. + * Note that the caller (xhciR3WorkerLoop) will set XHCI_STATUS_HCH. + */ + ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS); + ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS)); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE); + LogRelMax(10, ("xHCI: Attempted to execute too many commands, stopping xHC!\n")); + break; + } + } + return VINF_SUCCESS; +} + + +/** + * The xHCI asynchronous worker thread. + * + * @returns VBox status code. + * @param pDevIns The xHCI device instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) xhciR3WorkerLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + int rc; + + LogFlow(("xHCI entering worker thread loop.\n")); + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + uint32_t u32Tasks = 0; + uint8_t uSlotID; + + ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, true); + u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0); + if (!u32Tasks) + { + Assert(ASMAtomicReadBool(&pThis->fWrkThreadSleeping)); + rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hEvtProcess, RT_INDEFINITE_WAIT); + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + LogFlowFunc(("Woken up with rc=%Rrc\n", rc)); + u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0); + } + + RTCritSectEnter(&pThisCC->CritSectThrd); + + if (pThis->crcr & XHCI_CRCR_CRR) + xhciR3ProcessCommandRing(pDevIns, pThis, pThisCC); + + /* Run down the list of doorbells that are ringing. */ + for (uSlotID = 1; uSlotID < XHCI_NDS; ++uSlotID) + { + if (pThis->aSlotState[ID_TO_IDX(uSlotID)] >= XHCI_DEVSLOT_ENABLED) + { + while (pThis->aBellsRung[ID_TO_IDX(uSlotID)]) + { + uint8_t bit; + uint32_t uDBVal = 0; + + for (bit = 0; bit < 32; ++bit) + if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << bit)) + { + uDBVal = bit; + break; + } + + Log2(("Stop ringing bell for slot %u, DCI %u\n", uSlotID, uDBVal)); + ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDBVal)); + xhciR3ProcessDevCtx(pDevIns, pThis, pThisCC, uSlotID, uDBVal); + } + } + } + + /* If the R/S bit is no longer set, halt the xHC. */ + if (!(pThis->cmd & XHCI_CMD_RS)) + { + Log(("R/S clear, halting the xHC.\n")); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH); + } + + RTCritSectLeave(&pThisCC->CritSectThrd); + + ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, false); + } /* While running */ + + LogFlow(("xHCI worker thread exiting.\n")); + return VINF_SUCCESS; +} + + +/** + * Unblock the worker thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDevIns The xHCI device instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) xhciR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + NOREF(pThread); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess); +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) xhciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pRh->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pRh->IRhPort); + return NULL; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) xhciR3QueryStatusInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PXHCIR3 pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds); + return NULL; +} + +/** + * Gets the pointer to the status LED of a unit. + * + * @returns VBox status code. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param iLUN The unit which status LED we desire. + * @param ppLed Where to store the LED pointer. + */ +static DECLCALLBACK(int) xhciR3QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PXHCICC pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, ILeds); + + if (iLUN < XHCI_NUM_LUNS) + { + *ppLed = iLUN ? &pThisCC->RootHub3.Led : &pThisCC->RootHub2.Led; + Assert((*ppLed)->u32Magic == PDMLED_MAGIC); + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** + * Get the number of ports available in the hub. + * + * @returns The number of ports available. + * @param pInterface Pointer to this structure. + * @param pAvailable Bitmap indicating the available ports. Set bit == available port. + */ +static DECLCALLBACK(unsigned) xhciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + unsigned iPort; + unsigned cPorts = 0; + LogFlow(("xhciR3RhGetAvailablePorts\n")); + + memset(pAvailable, 0, sizeof(*pAvailable)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + for (iPort = pRh->uPortBase; iPort < (unsigned)pRh->uPortBase + pRh->cPortsImpl; iPort++) + { + Assert(iPort < XHCI_NDP_CFG(PDMDEVINS_2_DATA(pDevIns, PXHCI))); + if (!pThisCC->aPorts[iPort].fAttached) + { + cPorts++; + ASMBitSet(pAvailable, IDX_TO_ID(iPort - pRh->uPortBase)); + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return cPorts; +} + + +/** + * Get the supported USB versions for USB2 hubs. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions2(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_11 | VUSB_STDVER_20; +} + + +/** + * Get the supported USB versions for USB2 hubs. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions3(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_30; +} + + +/** + * Start sending SOF tokens across the USB bus, lists are processed in the + * next frame. + */ +static void xhciR3BusStart(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + unsigned iPort; + + pThisCC->RootHub2.pIRhConn->pfnPowerOn(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnPowerOn(pThisCC->RootHub3.pIRhConn); +// xhciR3BumpFrameNumber(pThis); + + Log(("xHCI: Bus started\n")); + + Assert(pThis->status & XHCI_STATUS_HCH); + ASMAtomicAndU32(&pThis->status, ~XHCI_STATUS_HCH); + + /* HCH gates PSCEG (4.19.2). When clearing HCH, re-evaluate port changes. */ + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + if (pThis->aPorts[iPort].portsc & XHCI_PORT_CHANGE_MASK) + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + } + + /// @todo record the starting time? +// pThis->SofTime = TMTimerGet(pThis->CTX_SUFF(pEndOfFrameTimer)) - pThis->cTicksPerFrame; +} + +/** + * Stop sending SOF tokens on the bus and processing the data. + */ +static void xhciR3BusStop(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + LogFlow(("xhciR3BusStop\n")); + + /* Stop the controller and Command Ring. */ + pThis->cmd &= ~XHCI_CMD_RS; + pThis->crcr |= XHCI_CRCR_CS; + + /* Power off the root hubs. */ + pThisCC->RootHub2.pIRhConn->pfnPowerOff(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnPowerOff(pThisCC->RootHub3.pIRhConn); + + /* The worker thread will halt the HC (set HCH) when done. */ + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); +} + + +/** + * Power a port up or down + */ +static void xhciR3PortPower(PXHCI pThis, PXHCICC pThisCC, unsigned iPort, bool fPowerUp) +{ + PXHCIHUBPORT pPort = &pThis->aPorts[iPort]; + PXHCIHUBPORTR3 pPortR3 = &pThisCC->aPorts[iPort]; + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + bool fOldPPS = !!(pPort->portsc & XHCI_PORT_PP); + LogFlow(("xhciR3PortPower (port %u) %s\n", IDX_TO_ID(iPort), fPowerUp ? "UP" : "DOWN")); + + if (fPowerUp) + { + /* Power up a port. */ + if (pPortR3->fAttached) + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS); + if (pPort->portsc & XHCI_PORT_CCS) + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_PP); + if (pPortR3->fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort)); + } + else + { + /* Power down. */ + ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_PP | XHCI_PORT_CCS)); + if (pPortR3->fAttached && fOldPPS) + VUSBIRhDevPowerOff(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort)); + } +} + + +/** + * Port reset done callback. + * + * @returns nothing. + * @param pDevIns The device instance data. + * @param iPort The XHCI port index of the port being resetted. + */ +static void xhciR3PortResetDone(PPDMDEVINS pDevIns, unsigned iPort) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + Log2(("xhciR3PortResetDone\n")); + + AssertReturnVoid(iPort < XHCI_NDP_CFG(pThis)); + + /* + * Successful reset. + */ + Log2(("xhciR3PortResetDone: Reset completed.\n")); + + uint32_t fChangeMask = XHCI_PORT_PED | XHCI_PORT_PRC; + /* For USB2 ports, transition the link state. */ + if (!IS_USB3_PORT_IDX_SHR(pThis, iPort)) + { + pThis->aPorts[iPort].portsc &= ~XHCI_PORT_PLS_MASK; + pThis->aPorts[iPort].portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT; + } + else + { + if (pThis->aPorts[iPort].portsc & XHCI_PORT_WPR) + fChangeMask |= XHCI_PORT_WRC; + } + + ASMAtomicAndU32(&pThis->aPorts[iPort].portsc, ~(XHCI_PORT_PR | XHCI_PORT_WPR)); + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fChangeMask); + /// @todo Set USBSTS.PCD and manage PSCEG correctly! + /// @todo just guessing?! +// ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CSC | XHCI_PORT_PLC); + + /// @todo Is this the right place? + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); +} + + +/** + * Sets a flag in a port status register, but only if a device is connected; + * if not, set ConnectStatusChange flag to force HCD to reevaluate connect status. + * + * @returns true if device was connected and the flag was cleared. + */ +static bool xhciR3RhPortSetIfConnected(PXHCI pThis, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * The port might be still/already disconnected. + */ + if (!(pThis->aPorts[iPort].portsc & XHCI_PORT_CCS)) + return false; + + bool fRc = !(pThis->aPorts[iPort].portsc & fValue); + + /* Set the bit. */ + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fValue); + + return fRc; +} + + +/** Translate VUSB speed enum to xHCI definition. */ +static unsigned xhciR3UsbSpd2XhciSpd(VUSBSPEED enmSpeed) +{ + unsigned uSpd; + + switch (enmSpeed) + { + default: AssertMsgFailed(("%d\n", enmSpeed)); + RT_FALL_THRU(); + case VUSB_SPEED_LOW: uSpd = XHCI_SPD_LOW; break; + case VUSB_SPEED_FULL: uSpd = XHCI_SPD_FULL; break; + case VUSB_SPEED_HIGH: uSpd = XHCI_SPD_HIGH; break; + case VUSB_SPEED_SUPER: uSpd = XHCI_SPD_SUPER; break; + } + return uSpd; +} + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) xhciR3RhAttach(PVUSBIROOTHUBPORT pInterface, unsigned uPort, VUSBSPEED enmSpeed) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCIHUBPORT pPort; + unsigned iPort; + LogFlow(("xhciR3RhAttach: uPort=%u (iPort=%u)\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + AssertRCReturn(rcLock, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= pRh->cPortsImpl); + iPort = ID_TO_IDX(uPort) + pRh->uPortBase; + Assert(iPort < XHCI_NDP_CFG(pThis)); + pPort = &pThis->aPorts[iPort]; + Assert(!pThisCC->aPorts[iPort].fAttached); + Assert(enmSpeed != VUSB_SPEED_UNKNOWN); + + /* + * Attach it. + */ + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS | XHCI_PORT_CSC); + pThisCC->aPorts[iPort].fAttached = true; + xhciR3PortPower(pThis, pThisCC, iPort, 1 /* power on */); + + /* USB3 ports automatically transition to Enabled state. */ + if (IS_USB3_PORT_IDX_R3(pThisCC, iPort)) + { + Assert(enmSpeed == VUSB_SPEED_SUPER); + pPort->portsc |= XHCI_PORT_PED; + pPort->portsc &= ~XHCI_PORT_PLS_MASK; + pPort->portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT; + pPort->portsc &= ~XHCI_PORT_SPD_MASK; + pPort->portsc |= XHCI_SPD_SUPER << XHCI_PORT_SPD_SHIFT; + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), + false, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + } + else + { + Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL || enmSpeed == VUSB_SPEED_HIGH); + pPort->portsc &= ~XHCI_PORT_SPD_MASK; + pPort->portsc |= xhciR3UsbSpd2XhciSpd(enmSpeed) << XHCI_PORT_SPD_SHIFT; + } + + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * A device is being detached from a port in the root hub. + * + * @param pInterface Pointer to this structure. + * @param uPort The 1-based port number assigned to the device. + */ +static DECLCALLBACK(void) xhciR3RhDetach(PVUSBIROOTHUBPORT pInterface, unsigned uPort) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCIHUBPORT pPort; + unsigned iPort; + LogFlow(("xhciR3RhDetach: uPort=%u iPort=%u\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= pRh->cPortsImpl); + iPort = ID_TO_IDX(uPort) + pRh->uPortBase; + Assert(iPort < XHCI_NDP_CFG(pThis)); + pPort = &pThis->aPorts[iPort]; + Assert(pThisCC->aPorts[iPort].fAttached); + + /* + * Detach it. + */ + pThisCC->aPorts[iPort].fAttached = false; + ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_CCS | XHCI_PORT_SPD_MASK | XHCI_PORT_PLS_MASK)); + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CSC); + /* Link state goes to RxDetect. */ + ASMAtomicOrU32(&pPort->portsc, XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT); + /* Disconnect clears the port enable bit. */ + if (pPort->portsc & XHCI_PORT_PED) + ASMAtomicAndU32(&pPort->portsc, ~XHCI_PORT_PED); + + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); +} + + +/** + * One of the root hub devices has completed its reset + * operation. + * + * Currently, we don't think anything is required to be done here + * so it's just a stub for forcing async resetting of the devices + * during a root hub reset. + * + * @param pDev The root hub device. + * @param rc The result of the operation. + * @param uPort The port number of the device on the roothub being resetted. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) xhciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + LogRel(("xHCI: Root hub-attached device reset completed with %Rrc\n", rc)); + RT_NOREF(pDev, uPort, rc, pvUser); +} + + +/** + * Does a software or hardware reset of the controller. + * + * This is called in response to setting HcCommandStatus.HCR, hardware reset, + * and device construction. + * + * @param pThis The shared XHCI instance data + * @param pThisCC The ring-3 XHCI instance data + * @param fNewMode The new mode of operation. This is UsbSuspend if + * it's a software reset, and UsbReset if it's a + * hardware reset / cold boot. + * @param fTrueReset Set if we can do a real reset of the devices + * attached to the root hub. This is really a just a + * hack for the non-working linux device reset. Linux + * has this feature called 'logical disconnect' if + * device reset fails which prevents us from doing + * resets when the guest asks for it - the guest will + * get confused when the device seems to be + * reconnected everytime it tries to reset it. But if + * we're at hardware reset time, we can allow a device + * to be 'reconnected' without upsetting the guest. + * + * @remark This has nothing to do with software setting the + * mode to UsbReset. + */ +static void xhciR3DoReset(PXHCI pThis, PXHCICC pThisCC, uint32_t fNewMode, bool fTrueReset) +{ + LogFunc(("%s reset%s\n", fNewMode == XHCI_USB_RESET ? "Hardware" : "Software", + fTrueReset ? " (really reset devices)" : "")); + + /* + * Cancel all outstanding URBs. + * + * We can't, and won't, deal with URBs until we're moved out of the + * suspend/reset state. Also, a real HC isn't going to send anything + * any more when a reset has been signaled. + */ + pThisCC->RootHub2.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub3.pIRhConn); + + /* + * Reset the hardware registers. + */ + /** @todo other differences between hardware reset and VM reset? */ + + pThis->cmd = 0; + pThis->status = XHCI_STATUS_HCH; + pThis->dnctrl = 0; + pThis->crcr = 0; + pThis->dcbaap = 0; + pThis->config = 0; + + /* + * Reset the internal state. + */ + pThis->cmdr_dqp = 0; + pThis->cmdr_ccs = 0; + + RT_ZERO(pThis->aSlotState); + RT_ZERO(pThis->aBellsRung); + + /* Zap everything but the lock. */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + pThis->aInterrupters[i].iman = 0; + pThis->aInterrupters[i].imod = 0; + pThis->aInterrupters[i].erstsz = 0; + pThis->aInterrupters[i].erstba = 0; + pThis->aInterrupters[i].erdp = 0; + pThis->aInterrupters[i].erep = 0; + pThis->aInterrupters[i].erst_idx = 0; + pThis->aInterrupters[i].trb_count = 0; + pThis->aInterrupters[i].evtr_pcs = false; + pThis->aInterrupters[i].ipe = false; + } + + if (fNewMode == XHCI_USB_RESET) + { + /* Only a hardware reset reinits the port registers. */ + for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++) + { + /* Need to preserve the speed of attached devices. */ + pThis->aPorts[i].portsc &= XHCI_PORT_SPD_MASK; + pThis->aPorts[i].portsc |= XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT; + /* If Port Power Control is not supported, ports are always powered on. */ + if (!(pThis->hcc_params & XHCI_HCC_PPC)) + pThis->aPorts[i].portsc |= XHCI_PORT_PP; + } + } + + /* + * If this is a hardware reset, we will initialize the root hub too. + * Software resets doesn't do this according to the specs. + * (It's not possible to have a device connected at the time of the + * device construction, so nothing to worry about there.) + */ + if (fNewMode == XHCI_USB_RESET) + { + pThisCC->RootHub2.pIRhConn->pfnReset(pThisCC->RootHub2.pIRhConn, fTrueReset); + pThisCC->RootHub3.pIRhConn->pfnReset(pThisCC->RootHub3.pIRhConn, fTrueReset); + + /* + * Reattach the devices. + */ + for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++) + { + bool fAttached = pThisCC->aPorts[i].fAttached; + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, i); + pThisCC->aPorts[i].fAttached = false; + + if (fAttached) + { + VUSBSPEED enmSpeed = VUSBIRhDevGetSpeed(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i)); + xhciR3RhAttach(&pRh->IRhPort, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i), enmSpeed); + } + } + } +} + +/** + * Reset the root hub. + * + * @returns VBox status code. + * @param pInterface Pointer to this structure. + * @param fTrueReset This is used to indicate whether we're at VM reset + * time and can do real resets or if we're at any other + * time where that isn't such a good idea. + * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion! + * @thread EMT + */ +static DECLCALLBACK(int) xhciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fTrueReset) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + Log(("xhciR3RhReset fTrueReset=%d\n", fTrueReset)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + AssertRCReturn(rcLock, rcLock); + + /* Soft reset first */ + xhciR3DoReset(pThis, pThisCC, XHCI_USB_SUSPEND, false /* N/A */); + + /* + * We're pretending to _reattach_ the devices without resetting them. + * Except, during VM reset where we use the opportunity to do a proper + * reset before the guest comes along and expects things. + * + * However, it's very very likely that we're not doing the right thing + * here when end up here on request from the guest (USB Reset state). + * The docs talk about root hub resetting, however what exact behaviour + * in terms of root hub status and changed bits, and HC interrupts aren't + * stated clearly. IF we get trouble and see the guest doing "USB Resets" + * we will have to look into this. For the time being we stick with simple. + */ + for (unsigned iPort = pRh->uPortBase; iPort < XHCI_NDP_CFG(pThis); iPort++) + { + if (pThisCC->aPorts[iPort].fAttached) + { + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CCS | XHCI_PORT_CSC); + if (fTrueReset) + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), fTrueReset, + xhciR3RhResetDoneOneDev, pDevIns, PDMDevHlpGetVM(pDevIns)); + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + +#endif /* IN_RING3 */ + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Operational Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcUsbcmd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdUsbCmd); + *pu32Value = pThis->cmd; + return VINF_SUCCESS; +} + +/** + * Write to the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcUsbcmd_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); +#endif + RT_NOREF(iReg); + STAM_COUNTER_INC(&pThis->StatWrUsbCmd); +#ifdef LOG_ENABLED + Log(("HcUsbcmd_w old=%x new=%x\n", pThis->cmd, val)); + if (val & XHCI_CMD_RS) + Log((" XHCI_CMD_RS\n")); + if (val & XHCI_CMD_HCRST) + Log((" XHCI_CMD_HCRST\n")); + if (val & XHCI_CMD_INTE ) + Log((" XHCI_CMD_INTE\n")); + if (val & XHCI_CMD_HSEE) + Log((" XHCI_CMD_HSEE\n")); + if (val & XHCI_CMD_LCRST) + Log((" XHCI_CMD_LCRST\n")); + if (val & XHCI_CMD_CSS) + Log((" XHCI_CMD_CSS\n")); + if (val & XHCI_CMD_CRS) + Log((" XHCI_CMD_CRS\n")); + if (val & XHCI_CMD_EWE) + Log((" XHCI_CMD_EWE\n")); + if (val & XHCI_CMD_EU3S) + Log((" XHCI_CMD_EU3S\n")); +#endif + + if (val & ~XHCI_CMD_MASK) + Log(("Unknown USBCMD bits %#x are set!\n", val & ~XHCI_CMD_MASK)); + + uint32_t old_cmd = pThis->cmd; +#ifdef IN_RING3 + pThis->cmd = val; +#endif + + if (val & XHCI_CMD_HCRST) + { +#ifdef IN_RING3 + LogRel(("xHCI: Hardware reset\n")); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else if (val & XHCI_CMD_LCRST) + { +#ifdef IN_RING3 + LogRel(("xHCI: Software reset\n")); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_SUSPEND, false /* N/A */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else if (pThis->status & XHCI_STATUS_HCE) + { + /* If HCE is set, don't restart the controller. Only a reset + * will clear the HCE bit. + */ + Log(("xHCI: HCE bit set, ignoring USBCMD register changes!\n")); + pThis->cmd = old_cmd; + return VINF_SUCCESS; + } + else + { + /* See what changed and take action on that. First the R/S bit. */ + uint32_t old_state = old_cmd & XHCI_CMD_RS; + uint32_t new_state = val & XHCI_CMD_RS; + + if (old_state != new_state) + { +#ifdef IN_RING3 + switch (new_state) + { + case XHCI_CMD_RS: + LogRel(("xHCI: USB Operational\n")); + xhciR3BusStart(pDevIns, pThis, pThisCC); + break; + case 0: + xhciR3BusStop(pDevIns, pThis, pThisCC); + LogRel(("xHCI: USB Suspended\n")); + break; + } +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + + /* Check EWE (Enable MFINDEX Wraparound Event) changes. */ + old_state = old_cmd & XHCI_CMD_EWE; + new_state = val & XHCI_CMD_EWE; + + if (old_state != new_state) + { + switch (new_state) + { + case XHCI_CMD_EWE: + Log(("xHCI: MFINDEX Wrap timer started\n")); + xhciSetWrapTimer(pDevIns, pThis); + break; + case 0: + PDMDevHlpTimerStop(pDevIns, pThis->hWrapTimer); + Log(("xHCI: MFINDEX Wrap timer stopped\n")); + break; + } + } + + /* INTE transitions need to twiddle interrupts. */ + old_state = old_cmd & XHCI_CMD_INTE; + new_state = val & XHCI_CMD_INTE; + if (old_state != new_state) + { + switch (new_state) + { + case XHCI_CMD_INTE: + /* Check whether the event interrupt bit is set and trigger an interrupt. */ + if (pThis->status & XHCI_STATUS_EINT) + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + break; + case 0: + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW); + break; + } + } + + /* We currently do nothing for state save/restore. If we did, the CSS/CRS command bits + * would set the SSS/RSS status bits until the operation is done. The CSS/CRS bits are + * never read as one. + */ + /// @todo 4.9.4 describes internal state that needs to be saved/restored: + /// ERSTE, ERST Count, EREP, and TRB Count + /// Command Ring Dequeue Pointer? + if (val & XHCI_CMD_CSS) + { + Log(("xHCI: Save State requested\n")); + val &= ~XHCI_CMD_CSS; + } + + if (val & XHCI_CMD_CRS) + { + Log(("xHCI: Restore State requested\n")); + val &= ~XHCI_CMD_CRS; + } + } +#ifndef IN_RING3 + pThis->cmd = val; +#endif + return VINF_SUCCESS; +} + +#ifdef LOG_ENABLED +static void HcUsbstsLogBits(uint32_t val) +{ + if (val & XHCI_STATUS_HCH) + Log((" XHCI_STATUS_HCH (HC Halted)\n")); + if (val & XHCI_STATUS_HSE) + Log((" XHCI_STATUS_HSE (Host System Error)\n")); + if (val & XHCI_STATUS_EINT) + Log((" XHCI_STATUS_EINT (Event Interrupt)\n")); + if (val & XHCI_STATUS_PCD) + Log((" XHCI_STATUS_PCD (Port Change Detect)\n")); + if (val & XHCI_STATUS_SSS) + Log((" XHCI_STATUS_SSS (Save State Status)\n")); + if (val & XHCI_STATUS_RSS) + Log((" XHCI_STATUS_RSS (Restore State Status)\n")); + if (val & XHCI_STATUS_SRE) + Log((" XHCI_STATUS_SRE (Save/Restore Error)\n")); + if (val & XHCI_STATUS_CNR) + Log((" XHCI_STATUS_CNR (Controller Not Ready)\n")); + if (val & XHCI_STATUS_HCE) + Log((" XHCI_STATUS_HCE (Host Controller Error)\n")); +} +#endif + +/** + * Read the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcUsbsts_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#ifdef LOG_ENABLED + Log(("HcUsbsts_r current value %x\n", pThis->status)); + HcUsbstsLogBits(pThis->status); +#endif + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdUsbSts); + + *pu32Value = pThis->status; + return VINF_SUCCESS; +} + +/** + * Write to the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcUsbsts_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef LOG_ENABLED + Log(("HcUsbsts_w current value %x; new %x\n", pThis->status, val)); + HcUsbstsLogBits(val); +#endif + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrUsbSts); + + if ( (val & ~XHCI_STATUS_WRMASK) + && val != 0xffffffff /* Ignore clear-all-like requests. */) + Log(("Unknown USBSTS bits %#x are set!\n", val & ~XHCI_STATUS_WRMASK)); + + /* Most bits are read-only. */ + val &= XHCI_STATUS_WRMASK; + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + ASMAtomicAndU32(&pThis->status, ~val); + + return VINF_SUCCESS; +} + +/** + * Read the PAGESIZE register of the host controller. + */ +static VBOXSTRICTRC HcPagesize_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + STAM_COUNTER_INC(&pThis->StatRdPageSize); + *pu32Value = 1; /* 2^(bit n + 12) -> 4K page size only. */ + return VINF_SUCCESS; +} + +/** + * Read the DNCTRL (Device Notification Control) register. + */ +static VBOXSTRICTRC HcDevNotifyCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevNotifyCtrl); + *pu32Value = pThis->dnctrl; + return VINF_SUCCESS; +} + +/** + * Write the DNCTRL (Device Notification Control) register. + */ +static VBOXSTRICTRC HcDevNotifyCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevNotifyCtrl); + pThis->dnctrl = val; + return VINF_SUCCESS; +} + +/** + * Read the low dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlLo); + *pu32Value = (uint32_t)(pThis->crcr & XHCI_CRCR_RD_MASK); + return VINF_SUCCESS; +} + +/** + * Write the low dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlLo); + /* NB: A dword write to the low half clears the high half. */ + + /* Sticky Abort/Stop bits - update register and kick the worker thread. */ + if (val & (XHCI_CRCR_CA | XHCI_CRCR_CS)) + { + pThis->crcr |= val & (XHCI_CRCR_CA | XHCI_CRCR_CS); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); + } + + /* + * If the command ring is not running, the internal dequeue pointer + * and the cycle state is updated. Otherwise the update is ignored. + */ + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + pThis->crcr = (pThis->crcr & ~XHCI_CRCR_UPD_MASK) | (val & XHCI_CRCR_UPD_MASK); + /// @todo cmdr_dqp: atomic? volatile? + pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK; + pThis->cmdr_ccs = pThis->crcr & XHCI_CRCR_RCS; + } + + return VINF_SUCCESS; +} + +/** + * Read the high dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlHi); + *pu32Value = pThis->crcr >> 32; + return VINF_SUCCESS; +} + +/** + * Write the high dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlHi); + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + pThis->crcr = ((uint64_t)val << 32) | (uint32_t)pThis->crcr; + pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK; + } + return VINF_SUCCESS; +} + +/** + * Read the low dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapLo); + *pu32Value = (uint32_t)pThis->dcbaap; + return VINF_SUCCESS; +} + +/** + * Write the low dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapLo); + /* NB: A dword write to the low half clears the high half. */ + /// @todo Should this mask off the reserved bits? + pThis->dcbaap = val; + return VINF_SUCCESS; +} + +/** + * Read the high dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapHi); + *pu32Value = pThis->dcbaap >> 32; + return VINF_SUCCESS; +} + +/** + * Write the high dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapHi); + pThis->dcbaap = ((uint64_t)val << 32) | (uint32_t)pThis->dcbaap; + return VINF_SUCCESS; +} + +/** + * Read the CONFIG register. + */ +static VBOXSTRICTRC HcConfig_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdConfig); + *pu32Value = pThis->config; + return VINF_SUCCESS; +} + +/** + * Write the CONFIG register. + */ +static VBOXSTRICTRC HcConfig_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrConfig); + /// @todo side effects? + pThis->config = val; + return VINF_SUCCESS; +} + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Port Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the PORTSC register. + */ +static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortStatusCtrl); + + Assert(!(pThis->hcc_params & XHCI_HCC_PPC)); + + if (p->portsc & XHCI_PORT_PR) + { +/// @todo Probably not needed? +#ifdef IN_RING3 + Log2(("HcPortStatusCtrl_r(): port %u: Impatient guest!\n", IDX_TO_ID(iPort))); + RTThreadYield(); +#else + Log2(("HcPortStatusCtrl_r: yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + + /* The WPR bit is always read as zero. */ + *pu32Value = p->portsc & ~XHCI_PORT_WPR; + return VINF_SUCCESS; +} + +/** + * Write the PORTSC register. + */ +static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; +#ifdef IN_RING3 + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); +#endif + STAM_COUNTER_INC(&pThis->StatWrPortStatusCtrl); + + /* If no register change results, we're done. */ + if ( p->portsc == val + && !(val & XHCI_PORT_CHANGE_MASK)) + return VINF_SUCCESS; + + /* If port state is not changing (status bits are being cleared etc.), we can do it in any context. + * This case occurs when the R/W control bits are not changing and the W1C bits are not being set. + */ + if ( (p->portsc & XHCI_PORT_CTL_RW_MASK) == (val & XHCI_PORT_CTL_RW_MASK) + && !(val & XHCI_PORT_CTL_W1_MASK)) + { + Log(("HcPortStatusCtrl_w port %u (status only): old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val)); + + if (val & XHCI_PORT_RESERVED) + Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED)); + + /* A write to clear any of the change notification bits. */ + if (val & XHCI_PORT_CHANGE_MASK) + p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK); + + /* Update the wake mask. */ + p->portsc &= ~XHCI_PORT_WAKE_MASK; + p->portsc |= val & XHCI_PORT_WAKE_MASK; + + /* There may still be differences between 'portsc' and 'val' in + * the R/O bits; that does not count as a register change and is fine. + * The RW1x control bits are not considered either since those only matter + * if set in 'val'. Since the LWS bit was not set, the PLS bits should not + * be compared. The port change bits may differ as well since the guest + * could be clearing only some or none of them. + */ + AssertMsg(!(val & XHCI_PORT_CTL_W1_MASK), ("val=%X\n", val)); + AssertMsg(!(val & XHCI_PORT_LWS), ("val=%X\n", val)); + AssertMsg((val & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)) == (p->portsc & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)), ("val=%X vs. portsc=%X\n", val, p->portsc)); + return VINF_SUCCESS; + } + + /* Actual USB port state changes need to be done in R3. */ +#ifdef IN_RING3 + Log(("HcPortStatusCtrl_w port %u: old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val)); + Assert(!(pThis->hcc_params & XHCI_HCC_PPC)); + Assert(p->portsc & XHCI_PORT_PP); + + if (val & XHCI_PORT_RESERVED) + Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED)); + + /* A write to clear any of the change notification bits. */ + if (val & XHCI_PORT_CHANGE_MASK) + p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK); + + /* Writing the Port Enable/Disable bit as 1 disables a port; it cannot be + * enabled that way. Writing the bit as zero does does nothing. + */ + if ((val & XHCI_PORT_PED) && (p->portsc & XHCI_PORT_PED)) + { + p->portsc &= ~XHCI_PORT_PED; + Log(("HcPortStatusCtrl_w(): port %u: DISABLE\n", IDX_TO_ID(iPort))); + } + + if (!(val & XHCI_PORT_PP) && (p->portsc & XHCI_PORT_PP)) + { + p->portsc &= ~XHCI_PORT_PP; + Log(("HcPortStatusCtrl_w(): port %u: POWER OFF\n", IDX_TO_ID(iPort))); + } + + /* Warm Port Reset - USB3 only; see 4.19.5.1. */ + if ((val & XHCI_PORT_WPR) && IS_USB3_PORT_IDX_SHR(pThis, iPort)) + { + Log(("HcPortStatusCtrl_w(): port %u: WARM RESET\n", IDX_TO_ID(iPort))); + if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR | XHCI_PORT_WPR)) + { + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + xhciR3PortResetDone(pDevIns, iPort); + } + } + + if (val & XHCI_PORT_PR) + { + Log(("HcPortStatusCtrl_w(): port %u: RESET\n", IDX_TO_ID(iPort))); + if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR)) + { + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + xhciR3PortResetDone(pDevIns, iPort); + } + else if (p->portsc & XHCI_PORT_PR) + { + /* the guest is getting impatient. */ + Log2(("HcPortStatusCtrl_w(): port %u: Impatient guest!\n", IDX_TO_ID(iPort))); + RTThreadYield(); + } + } + + /// @todo Do some sanity checking on the new link state? + /* Update the link state if requested. */ + if (val & XHCI_PORT_LWS) + { + unsigned old_pls; + unsigned new_pls; + + old_pls = (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + new_pls = (val & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + + p->portsc &= ~XHCI_PORT_PLS_MASK; + p->portsc |= new_pls << XHCI_PORT_PLS_SHIFT; + Log2(("HcPortStatusCtrl_w(): port %u: Updating link state from %u to %u\n", IDX_TO_ID(iPort), old_pls, new_pls)); + /* U3->U0 (USB3) and Resume->U0 transitions set the PLC flag. See 4.15.2.2 */ + if (new_pls == XHCI_PLS_U0) + if (old_pls == XHCI_PLS_U3 || old_pls == XHCI_PLS_RESUME) + { + p->portsc |= XHCI_PORT_PLC; + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + } + } + + /// @todo which other bits can we safely ignore? + + /* Update the wake mask. */ + p->portsc &= ~XHCI_PORT_WAKE_MASK; + p->portsc |= val & XHCI_PORT_WAKE_MASK; + + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + + +/** + * Read the PORTPMSC register. + */ +static VBOXSTRICTRC HcPortPowerMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortPowerMgmt); + + *pu32Value = p->portpm; + return VINF_SUCCESS; +} + + +/** + * Write the PORTPMSC register. + */ +static VBOXSTRICTRC HcPortPowerMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatWrPortPowerMgmt); + + /// @todo anything to do here? + p->portpm = val; + return VINF_SUCCESS; +} + + +/** + * Read the PORTLI register. + */ +static VBOXSTRICTRC HcPortLinkInfo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortLinkInfo); + + /* The link information is R/O; we probably can't get it at all. If we + * do maintain it for USB3 ports, we also have to reset it (5.4.10). + */ + *pu32Value = p->portli; + return VINF_SUCCESS; +} + +/** + * Read the reserved register. Linux likes to do this. + */ +static VBOXSTRICTRC HcPortRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iPort); + STAM_COUNTER_INC(&pThis->StatRdPortRsvd); + *pu32Value = 0; + return VINF_SUCCESS; +} + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Interrupter Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the IMAN register. + */ +static VBOXSTRICTRC HcIntrMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdIntrMgmt); + + *pu32Value = ip->iman; + return VINF_SUCCESS; +} + +/** + * Write the IMAN register. + */ +static VBOXSTRICTRC HcIntrMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + uint32_t uNew = val & XHCI_IMAN_VALID_MASK; + STAM_COUNTER_INC(&pThis->StatWrIntrMgmt); + + if (val & ~XHCI_IMAN_VALID_MASK) + Log(("Reserved bits set %x!\n", val & ~XHCI_IMAN_VALID_MASK)); + + /* If the Interrupt Pending (IP) bit is set, writing one clears it. + * Note that when MSIs are enabled, the bit auto-clears almost immediately. + */ + if (val & ip->iman & XHCI_IMAN_IP) + { + Log2(("clearing interrupt on interrupter %u\n", ip->index)); + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW); + STAM_COUNTER_INC(&pThis->StatIntrsCleared); + uNew &= ~XHCI_IMAN_IP; + } + else + { + /* Preserve the current IP bit. */ + uNew = (uNew & ~XHCI_IMAN_IP) | (ip->iman & XHCI_IMAN_IP); + } + + /* Trigger an interrupt if the IP bit is set and IE transitions from 0 to 1. */ + if ( (uNew & XHCI_IMAN_IE) + && !(ip->iman & XHCI_IMAN_IE) + && (ip->iman & XHCI_IMAN_IP) + && (pThis->cmd & XHCI_CMD_INTE)) + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + + ip->iman = uNew; + return VINF_SUCCESS; +} + +/** + * Read the IMOD register. + */ +static VBOXSTRICTRC HcIntrMod_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdIntrMod); + + *pu32Value = ip->imod; + return VINF_SUCCESS; +} + +/** + * Write the IMOD register. + */ +static VBOXSTRICTRC HcIntrMod_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatWrIntrMod); + + /// @todo Does writing a zero to IMODC/IMODI potentially trigger + /// an interrupt? + ip->imod = val; + return VINF_SUCCESS; +} + +/** + * Read the ERSTSZ register. + */ +static VBOXSTRICTRC HcEvtRSTblSize_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRstblSize); + + *pu32Value = ip->erstsz; + return VINF_SUCCESS; +} + +/** + * Write the ERSTSZ register. + */ +static VBOXSTRICTRC HcEvtRSTblSize_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatWrEvtRstblSize); + + if (val & ~XHCI_ERSTSZ_MASK) + Log(("Reserved bits set %x!\n", val & ~XHCI_ERSTSZ_MASK)); + if (val > XHCI_ERSTMAX) + Log(("ERSTSZ (%u) > ERSTMAX (%u)!\n", val, XHCI_ERSTMAX)); + + /* Enforce the maximum size. */ + ip->erstsz = RT_MIN(val, XHCI_ERSTMAX); + + if (!ip->index && !ip->erstsz) /* Windows 8 does this temporarily. Thanks guys... */ + Log(("ERSTSZ is zero for primary interrupter: undefined behavior!\n")); + + return VINF_SUCCESS; +} + +/** + * Read the reserved register. Linux likes to do this. + */ +static VBOXSTRICTRC HcEvtRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, ip); + STAM_COUNTER_INC(&pThis->StatRdEvtRsvd); + *pu32Value = 0; + return VINF_SUCCESS; +} + +/** + * Read the low dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseLo); + + *pu32Value = (uint32_t)ip->erstba; + return VINF_SUCCESS; +} + + +/** + * Write the low dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseLo); + + if (val & ~pThis->erst_addr_mask) + Log(("Reserved bits set %x!\n", val & ~pThis->erst_addr_mask)); + + /* NB: A dword write to the low half clears the high half. */ + ip->erstba = val & pThis->erst_addr_mask; + + /* Initialize the internal event ring state. */ + ip->evtr_pcs = 1; + ip->erst_idx = 0; + ip->ipe = false; + + /* Fetch the first ERST entry now. Not later! That "sets the Event Ring + * State Machine:EREP Advancement to the Start state" + */ + xhciFetchErstEntry(pDevIns, pThis, ip); + + return VINF_SUCCESS; +} + +/** + * Read the high dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseHi); + + *pu32Value = (uint32_t)(ip->erstba >> 32); + return VINF_SUCCESS; +} + +/** + * Write the high dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseHi); + + /* Update the high dword while preserving the low one. */ + ip->erstba = ((uint64_t)val << 32) | (uint32_t)ip->erstba; + + /* We shouldn't be doing this when AC64 is set. But High Sierra + * ignores that because it "knows" the xHC handles 64-bit addressing, + * so we're going to assume that OSes are not going to write junk into + * ERSTBAH when they don't see AC64 set. + */ + xhciFetchErstEntry(pDevIns, pThis, ip); + + return VINF_SUCCESS; +} + + +/** + * Read the low dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrLo); + + /* Lock to avoid incomplete update being seen. */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_READ); + if (rc != VINF_SUCCESS) + return rc; + + *pu32Value = (uint32_t)ip->erdp; + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + +/** + * Write the low dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + uint64_t old_erdp; + uint64_t new_erdp; + STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrLo); + + /* NB: A dword write to the low half clears the high half. + * The high dword should be ignored when AC64=0, but High Sierra + * does not care what we report. Therefore a write to the low dword + * handles all the control bits and a write to the high dword still + * updates the ERDP address. On a 64-bit host, there must be a + * back-to-back low dword + high dword access. We are going to boldly + * assume that the guest will not place the event ring across the 4G + * boundary (i.e. storing the bottom part in the firmware ROM). + */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + old_erdp = ip->erdp & XHCI_ERDP_ADDR_MASK; /* Remember old ERDP address. */ + new_erdp = ip->erdp & XHCI_ERDP_EHB; /* Preserve EHB */ + + /* If the Event Handler Busy (EHB) bit is set, writing a one clears it. */ + if (val & ip->erdp & XHCI_ERDP_EHB) + { + Log2(("clearing EHB on interrupter %p\n", ip)); + new_erdp &= ~XHCI_ERDP_EHB; + } + /// @todo Check if this might inadvertently set EHB! + + new_erdp |= val & ~XHCI_ERDP_EHB; + ip->erdp = new_erdp; + + /* Check if the ERDP changed. See workaround below. */ + if (old_erdp != (new_erdp & XHCI_ERDP_ADDR_MASK)) + ip->erdp_rewrites = 0; + else + ++ip->erdp_rewrites; + + LogFlowFunc(("ERDP: %RGp, EREP: %RGp\n", (RTGCPHYS)(ip->erdp & XHCI_ERDP_ADDR_MASK), (RTGCPHYS)ip->erep)); + + if ((ip->erdp & XHCI_ERDP_ADDR_MASK) == ip->erep) + { + Log2(("Event Ring empty, clearing IPE\n")); + ip->ipe = false; + } + else if (ip->ipe && (val & XHCI_ERDP_EHB)) + { + /* EHB is being cleared but the ring isn't empty and IPE is still set. */ + if (RT_UNLIKELY(old_erdp == (new_erdp & XHCI_ERDP_ADDR_MASK) && ip->erdp_rewrites > 2)) + { + /* If guest does not advance the ERDP, do not trigger an interrupt + * again. Workaround for buggy xHCI initialization in Linux 4.6 which + * enables interrupts before setting up internal driver state. That + * leads to the guest IRQ handler not actually handling events and + * infinitely re-triggering interrupts. However, only do this if the + * guest has already written the same ERDP value a few times. The Intel + * xHCI driver always writes the same ERDP twice and we must still + * re-trigger interrupts in that case. + * See @bugref{8546}. + */ + Log2(("Event Ring not empty, ERDP not advanced, not re-triggering interrupt!\n")); + ip->ipe = false; + } + else + { + Log2(("Event Ring not empty, re-triggering interrupt\n")); + xhciSetIntr(pDevIns, pThis, ip); + } + } + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + +/** + * Read the high dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrHi); + + *pu32Value = (uint32_t)(ip->erdp >> 32); + return VINF_SUCCESS; +} + +/** + * Write the high dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pThis); + STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrHi); + + /* See HcEvtRingDeqPtrLo_w for semantics. */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + /* Update the high dword while preserving the low one. */ + ip->erdp = ((uint64_t)val << 32) | (uint32_t)ip->erdp; + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + + +/** + * xHCI register access routines. + */ +typedef struct +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t u32Value); +} XHCIREGACC; + +/** + * xHCI interrupter register access routines. + */ +typedef struct +{ + const char *pszName; + VBOXSTRICTRC (*pfnIntrRead )(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnIntrWrite)(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t u32Value); +} XHCIINTRREGACC; + +/** + * Operational registers descriptor table. + */ +static const XHCIREGACC g_aOpRegs[] = +{ + {"USBCMD" , HcUsbcmd_r, HcUsbcmd_w }, + {"USBSTS", HcUsbsts_r, HcUsbsts_w }, + {"PAGESIZE", HcPagesize_r, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"DNCTRL", HcDevNotifyCtrl_r, HcDevNotifyCtrl_w }, + {"CRCRL", HcCmdRingCtlLo_r, HcCmdRingCtlLo_w }, + {"CRCRH", HcCmdRingCtlHi_r, HcCmdRingCtlHi_w }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"DCBAAPL", HcDevCtxBAAPLo_r, HcDevCtxBAAPLo_w }, + {"DCBAAPH", HcDevCtxBAAPHi_r, HcDevCtxBAAPHi_w }, + {"CONFIG", HcConfig_r, HcConfig_w } +}; + + +/** + * Port registers descriptor table (for a single port). The number of ports + * and their associated registers depends on the NDP value. + */ +static const XHCIREGACC g_aPortRegs[] = +{ + /* + */ + {"PORTSC", HcPortStatusCtrl_r, HcPortStatusCtrl_w }, + {"PORTPMSC", HcPortPowerMgmt_r, HcPortPowerMgmt_w }, + {"PORTLI", HcPortLinkInfo_r, NULL }, + {"Reserved", HcPortRsvd_r, NULL } +}; +AssertCompile(RT_ELEMENTS(g_aPortRegs) * sizeof(uint32_t) == 0x10); + + +/** + * Interrupter runtime registers descriptor table (for a single interrupter). + * The number of interrupters depends on the XHCI_NINTR value. + */ +static const XHCIINTRREGACC g_aIntrRegs[] = +{ + {"IMAN", HcIntrMgmt_r, HcIntrMgmt_w }, + {"IMOD", HcIntrMod_r, HcIntrMod_w }, + {"ERSTSZ", HcEvtRSTblSize_r, HcEvtRSTblSize_w }, + {"Reserved", HcEvtRsvd_r, NULL }, + {"ERSTBAL", HcEvtRSTblBaseLo_r, HcEvtRSTblBaseLo_w }, + {"ERSTBAH", HcEvtRSTblBaseHi_r, HcEvtRSTblBaseHi_w }, + {"ERDPL", HcEvtRingDeqPtrLo_r, HcEvtRingDeqPtrLo_w }, + {"ERDPH", HcEvtRingDeqPtrHi_r, HcEvtRingDeqPtrHi_w } +}; +AssertCompile(RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t) == 0x20); + + +/** + * Read the MFINDEX register. + */ +static int HcMfIndex_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t *pu32Value) +{ + uint64_t uNanoTime; + uint64_t uMfTime; + STAM_COUNTER_INC(&pThis->StatRdMfIndex); + + /* MFINDEX increments once per micro-frame, i.e. 8 times per millisecond + * or every 125us. The resolution is only 14 bits, meaning that MFINDEX + * wraps around after it reaches 0x3FFF (16383) or every 2048 milliseconds. + */ + /// @todo MFINDEX should only be running when R/S is set. May not matter. + uNanoTime = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer); + uMfTime = uNanoTime / 125000; + + *pu32Value = uMfTime & 0x3FFF; + Log2(("MFINDEX read: %u\n", *pu32Value)); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNIOMMMIONEWREAD, Read a MMIO register.} + * + * @note We only accept 32-bit writes that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) xhciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + const uint32_t offReg = (uint32_t)off; + uint32_t * const pu32 = (uint32_t *)pv; + uint32_t iReg; + RT_NOREF(pvUser); + + Log2(("xhciRead %RGp (offset %04X) size=%d\n", off, offReg, cb)); + + if (offReg < XHCI_CAPS_REG_SIZE) + { + switch (offReg) + { + case 0x0: /* CAPLENGTH + HCIVERSION */ + *pu32 = (pThis->hci_version << 16) | pThis->cap_length; + break; + + case 0x4: /* HCSPARAMS1 (structural) */ + Log2(("HCSPARAMS1 read\n")); + *pu32 = pThis->hcs_params1; + break; + + case 0x8: /* HCSPARAMS2 (structural) */ + Log2(("HCSPARAMS2 read\n")); + *pu32 = pThis->hcs_params2; + break; + + case 0xC: /* HCSPARAMS3 (structural) */ + Log2(("HCSPARAMS3 read\n")); + *pu32 = pThis->hcs_params3; + break; + + case 0x10: /* HCCPARAMS1 (caps) */ + Log2(("HCCPARAMS1 read\n")); + *pu32 = pThis->hcc_params; + break; + + case 0x14: /* DBOFF (doorbell offset) */ + Log2(("DBOFF read\n")); + *pu32 = pThis->dbell_off; + break; + + case 0x18: /* RTSOFF (run-time register offset) */ + Log2(("RTSOFF read\n")); + *pu32 = pThis->rts_off; + break; + + case 0x1C: /* HCCPARAMS2 (caps) */ + Log2(("HCCPARAMS2 read\n")); + *pu32 = 0; /* xHCI 1.1 only */ + break; + + default: + Log(("xHCI: Trying to read unknown capability register %u!\n", offReg)); + STAM_COUNTER_INC(&pThis->StatRdUnknown); + return VINF_IOM_MMIO_UNUSED_FF; + } + STAM_COUNTER_INC(&pThis->StatRdCaps); + Log2(("xhciRead %RGp size=%d -> val=%x\n", off, cb, *pu32)); + return VINF_SUCCESS; + } + + /* + * Validate the access (in case of IOM bugs or incorrect MMIO registration). + */ + AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb), + VINF_IOM_MMIO_UNUSED_FF /* No idea what really would happen... */); + /** r=bird: If you don't have an idea what would happen for non-dword reads, + * then the flags passed to IOM when creating the MMIO region are doubtful, right? */ + AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_IOM_MMIO_UNUSED_FF); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF; + if (offReg >= XHCI_DOORBELL_OFFSET) + { + /* The doorbell registers are effectively write-only and return 0 when read. */ + iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2; + if (iReg < XHCI_NDS) + { + STAM_COUNTER_INC(&pThis->StatRdDoorBell); + *pu32 = 0; + rcStrict = VINF_SUCCESS; + Log2(("xhciRead: DBellReg (DB %u) %RGp size=%d -> val=%x (rc=%d)\n", iReg, off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (offReg >= XHCI_RTREG_OFFSET) + { + /* Run-time registers. */ + Assert(offReg < XHCI_DOORBELL_OFFSET); + /* The MFINDEX register would be interrupter -1... */ + if (offReg < XHCI_RTREG_OFFSET + RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) + { + if (offReg == XHCI_RTREG_OFFSET) + rcStrict = HcMfIndex_r(pDevIns, pThis, pu32); + else + { + /* The silly Linux xHCI driver reads the reserved registers. */ + STAM_COUNTER_INC(&pThis->StatRdUnknown); + *pu32 = 0; + rcStrict = VINF_SUCCESS; + } + } + else + { + Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0); + const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1; + + if (iIntr < XHCI_NINTR) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1); + const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg]; + if (pReg->pfnIntrRead) + { + PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr]; + rcStrict = pReg->pfnIntrRead(pDevIns, pThis, pIntr, pu32); + Log2(("xhciRead: IntrReg (intr %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + else if (offReg >= XHCI_XECP_OFFSET) + { + /* Extended Capability registers. */ + Assert(offReg < XHCI_RTREG_OFFSET); + uint32_t offXcp = offReg - XHCI_XECP_OFFSET; + + if (offXcp + cb <= RT_MIN(pThis->cbExtCap, sizeof(pThis->abExtCap))) /* can't trust cbExtCap in ring-0. */ + { + *pu32 = *(uint32_t *)&pThis->abExtCap[offXcp]; + rcStrict = VINF_SUCCESS; + } + Log2(("xhciRead: ExtCapReg %RGp size=%d -> val=%x (rc=%d)\n", off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + else + { + /* Operational registers (incl. port registers). */ + Assert(offReg < XHCI_XECP_OFFSET); + iReg = (offReg - XHCI_CAPS_REG_SIZE) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const XHCIREGACC *pReg = &g_aOpRegs[iReg]; + if (pReg->pfnRead) + { + rcStrict = pReg->pfnRead(pDevIns, pThis, iReg, pu32); + Log2(("xhciRead: OpReg %RGp (%s) size=%d -> val=%x (rc=%d)\n", off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2)) + { + iReg -= (XHCI_PORT_REG_OFFSET >> 2); + const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs); + if (iPort < XHCI_NDP_CFG(pThis)) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1); + Assert(iReg < RT_ELEMENTS(g_aPortRegs)); + const XHCIREGACC *pReg = &g_aPortRegs[iReg]; + if (pReg->pfnRead) + { + rcStrict = pReg->pfnRead(pDevIns, pThis, iPort, pu32); + Log2(("xhciRead: PortReg (port %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + + if (rcStrict != VINF_IOM_MMIO_UNUSED_FF) + { /* likely */ } + else + { + STAM_COUNTER_INC(&pThis->StatRdUnknown); + Log(("xHCI: Trying to read unimplemented register at offset %04X!\n", offReg)); + } + + return rcStrict; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register.} + * + * @note We only accept 32-bit writes that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + const uint32_t offReg = (uint32_t)off; + uint32_t * const pu32 = (uint32_t *)pv; + uint32_t iReg; + RT_NOREF(pvUser); + + Log2(("xhciWrite %RGp (offset %04X) %x size=%d\n", off, offReg, *(uint32_t *)pv, cb)); + + if (offReg < XHCI_CAPS_REG_SIZE) + { + /* These are read-only */ + Log(("xHCI: Trying to write to register %u!\n", offReg)); + STAM_COUNTER_INC(&pThis->StatWrUnknown); + return VINF_SUCCESS; + } + + /* + * Validate the access (in case of IOM bug or incorrect MMIO registration). + */ + AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS); + AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS); + + /* + * Validate the register and call the write operator. + */ + VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF; + if (offReg >= XHCI_DOORBELL_OFFSET) + { + /* Let's spring into action... as long as the xHC is running. */ + iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2; + if ((pThis->cmd & XHCI_CMD_RS) && iReg < XHCI_NDS) + { + if (iReg == 0) + { + /* DB0 aka Command Ring. */ + STAM_COUNTER_INC(&pThis->StatWrDoorBell0); + if (*pu32 == 0) + { + /* Set the Command Ring state to Running if not already set. */ + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + Log(("Command ring entered Running state\n")); + ASMAtomicOrU64(&pThis->crcr, XHCI_CRCR_CRR); + } + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); + } + else + Log2(("Ignoring DB0 write with value %X!\n", *pu32)); + } + else + { + /* Device context doorbell. Do basic parameter checking to avoid + * waking up the worker thread needlessly. + */ + STAM_COUNTER_INC(&pThis->StatWrDoorBellN); + uint8_t uDBTarget = *pu32 & XHCI_DB_TGT_MASK; + Assert(uDBTarget < 32); /// @todo Report an error? Or just ignore? + if (uDBTarget < 32) + { + Log2(("Ring bell for slot %u, DCI %u\n", iReg, uDBTarget)); + ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(iReg)], 1 << uDBTarget); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_DOORBELL, *pu32); + } + else + Log2(("Ignoring DB%u write with bad target %u!\n", iReg, uDBTarget)); + } + rcStrict = VINF_SUCCESS; + Log2(("xhciWrite: DBellReg (DB %u) %RGp size=%d <- val=%x (rc=%d)\n", iReg, off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (offReg >= XHCI_RTREG_OFFSET) + { + /* Run-time registers. */ + Assert(offReg < XHCI_DOORBELL_OFFSET); + /* NB: The MFINDEX register is R/O. */ + if (offReg >= XHCI_RTREG_OFFSET + (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t))) + { + Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0); + const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1; + + if (iIntr < XHCI_NINTR) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1); + const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg]; + if (pReg->pfnIntrWrite) + { + PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr]; + rcStrict = pReg->pfnIntrWrite(pDevIns, pThis, pIntr, *pu32); + Log2(("xhciWrite: IntrReg (intr %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + else + { + /* Operational registers (incl. port registers). */ + Assert(offReg < XHCI_RTREG_OFFSET); + iReg = (offReg - pThis->cap_length) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const XHCIREGACC *pReg = &g_aOpRegs[iReg]; + if (pReg->pfnWrite) + { + rcStrict = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv); + Log2(("xhciWrite: OpReg %RGp (%s) size=%d <- val=%x (rc=%d)\n", off, pReg->pszName, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2)) + { + iReg -= (XHCI_PORT_REG_OFFSET >> 2); + const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs); + if (iPort < XHCI_NDP_CFG(pThis)) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1); + Assert(iReg < RT_ELEMENTS(g_aPortRegs)); + const XHCIREGACC *pReg = &g_aPortRegs[iReg]; + if (pReg->pfnWrite) + { + rcStrict = pReg->pfnWrite(pDevIns, pThis, iPort, *pu32); + Log2(("xhciWrite: PortReg (port %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + + if (rcStrict != VINF_IOM_MMIO_UNUSED_FF) + { /* likely */ } + else + { + /* Ignore writes to unimplemented or read-only registers. */ + STAM_COUNTER_INC(&pThis->StatWrUnknown); + Log(("xHCI: Trying to write unimplemented or R/O register at offset %04X!\n", offReg)); + rcStrict = VINF_SUCCESS; + } + + return rcStrict; +} + + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNTMTIMERDEV, + * Provides periodic MFINDEX wrap events. See 4.14.2.} + */ +static DECLCALLBACK(void) xhciR3WrapTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PXHCI pThis = (PXHCI)pvUser; + XHCI_EVENT_TRB ed; + LogFlow(("xhciR3WrapTimer:\n")); + RT_NOREF(hTimer); + + /* + * Post the MFINDEX Wrap event and rearm the timer. Only called + * when the EWE bit is set in command register. + */ + RT_ZERO(ed); + ed.mwe.cc = XHCI_TCC_SUCCESS; + ed.mwe.type = XHCI_TRB_MFIDX_WRAP; + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + xhciSetWrapTimer(pDevIns, pThis); +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) xhciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t iPort; + uint32_t iSlot; + uint32_t iIntr; + + LogFlow(("xhciR3SaveExec: \n")); + + /* Save HC operational registers. */ + pHlp->pfnSSMPutU32(pSSM, pThis->cmd); + pHlp->pfnSSMPutU32(pSSM, pThis->status); + pHlp->pfnSSMPutU32(pSSM, pThis->dnctrl); + pHlp->pfnSSMPutU64(pSSM, pThis->crcr); + pHlp->pfnSSMPutU64(pSSM, pThis->dcbaap); + pHlp->pfnSSMPutU32(pSSM, pThis->config); + + /* Save HC non-register state. */ + pHlp->pfnSSMPutU64(pSSM, pThis->cmdr_dqp); + pHlp->pfnSSMPutBool(pSSM, pThis->cmdr_ccs); + + /* Save per-slot state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NDS); + for (iSlot = 0; iSlot < XHCI_NDS; ++iSlot) + { + pHlp->pfnSSMPutU8 (pSSM, pThis->aSlotState[iSlot]); + pHlp->pfnSSMPutU32(pSSM, pThis->aBellsRung[iSlot]); + } + + /* Save root hub (port) state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NDP_CFG(pThis)); + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portsc); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portpm); + } + + /* Save interrupter state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NINTR); + for (iIntr = 0; iIntr < XHCI_NINTR; ++iIntr) + { + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].iman); + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].imod); + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].erstsz); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erstba); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erdp); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erep); + pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].erst_idx); + pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].trb_count); + pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].evtr_pcs); + pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].ipe); + } + + /* Terminator marker. */ + pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); + + /* If not continuing after save, force HC into non-running state to avoid trouble later. */ + if (pHlp->pfnSSMHandleGetAfter(pSSM) != SSMAFTER_CONTINUE) + pThis->cmd &= ~XHCI_CMD_RS; + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) xhciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + uint32_t cPorts; + uint32_t iPort; + uint32_t cSlots; + uint32_t iSlot; + uint32_t cIntrs; + uint32_t iIntr; + uint64_t u64Dummy; + uint32_t u32Dummy; + uint16_t u16Dummy; + uint8_t u8Dummy; + bool fDummy; + + LogFlow(("xhciR3LoadExec:\n")); + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + if (uVersion != XHCI_SAVED_STATE_VERSION) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + /* Load HC operational registers. */ + pHlp->pfnSSMGetU32(pSSM, &pThis->cmd); + pHlp->pfnSSMGetU32(pSSM, &pThis->status); + pHlp->pfnSSMGetU32(pSSM, &pThis->dnctrl); + pHlp->pfnSSMGetU64(pSSM, &pThis->crcr); + pHlp->pfnSSMGetU64(pSSM, &pThis->dcbaap); + pHlp->pfnSSMGetU32(pSSM, &pThis->config); + + /* Load HC non-register state. */ + pHlp->pfnSSMGetU64(pSSM, &pThis->cmdr_dqp); + pHlp->pfnSSMGetBool(pSSM, &pThis->cmdr_ccs); + + /* Load per-slot state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cSlots); + AssertRCReturn(rc, rc); + if (cSlots > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + for (iSlot = 0; iSlot < cSlots; ++iSlot) + { + /* Load only as many slots as we have; discard any extras. */ + if (iSlot < XHCI_NDS) + { + pHlp->pfnSSMGetU8 (pSSM, &pThis->aSlotState[iSlot]); + pHlp->pfnSSMGetU32(pSSM, &pThis->aBellsRung[iSlot]); + } + else + { + pHlp->pfnSSMGetU8 (pSSM, &u8Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + } + } + + /* Load root hub (port) state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cPorts); + AssertRCReturn(rc, rc); + if (cPorts > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + + for (iPort = 0; iPort < cPorts; ++iPort) + { + /* Load only as many ports as we have; discard any extras. */ + if (iPort < XHCI_NDP_CFG(pThis)) + { + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portsc); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portpm); + } + else + { + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + } + } + + /* Load interrupter state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cIntrs); + AssertRCReturn(rc, rc); + if (cIntrs > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + for (iIntr = 0; iIntr < cIntrs; ++iIntr) + { + /* Load only as many interrupters as we have; discard any extras. */ + if (iIntr < XHCI_NINTR) + { + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].iman); + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].imod); + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].erstsz); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erstba); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erdp); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erep); + pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].erst_idx); + pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].trb_count); + pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].evtr_pcs); + pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].ipe); + } + else + { + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU16(pSSM, &u16Dummy); + pHlp->pfnSSMGetU16(pSSM, &u16Dummy); + pHlp->pfnSSMGetBool(pSSM, &fDummy); + pHlp->pfnSSMGetBool(pSSM, &fDummy); + } + } + + /* Terminator marker. */ + rc = pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + AssertRCReturn(rc, rc); + AssertReturn(u32Dummy == UINT32_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + return rc; +} + + +/* -=-=-=-=- DBGF -=-=-=-=- */ + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, Dumps xHCI state.} + */ +static DECLCALLBACK(void) xhciR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + RTGCPHYS GPAddr; + bool fVerbose = false; + unsigned i, j; + uint64_t u64Val; + + /* Parse arguments. */ + if (pszArgs) + fVerbose = strstr(pszArgs, "verbose") != NULL; + +#ifdef XHCI_ERROR_INJECTION + if (pszArgs && strstr(pszArgs, "dropintrhw")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (external)!\n"); + pThis->fDropIntrHw = true; + return; + } + + if (pszArgs && strstr(pszArgs, "dropintrint")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (internal)!\n"); + pThis->fDropIntrIpe = true; + return; + } + + if (pszArgs && strstr(pszArgs, "dropurb")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next URB!\n"); + pThis->fDropUrb = true; + return; + } +#endif + + /* Show basic information. */ + pHlp->pfnPrintf(pHlp, + "%s#%d: PCI MMIO=%RGp IRQ=%u MSI=%s R0=%RTbool RC=%RTbool\n", + pDevIns->pReg->szName, + pDevIns->iInstance, + PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio), + PCIDevGetInterruptLine(pDevIns->apPciDevs[0]), +#ifdef VBOX_WITH_MSI_DEVICES + xhciIsMSIEnabled(pDevIns->apPciDevs[0]) ? "on" : "off", +#else + "none", +#endif + pDevIns->fR0Enabled, pDevIns->fRCEnabled); + + /* Command register. */ + pHlp->pfnPrintf(pHlp, "USBCMD: %X:", pThis->cmd); + if (pThis->cmd & XHCI_CMD_EU3S) pHlp->pfnPrintf(pHlp, " EU3S" ); + if (pThis->cmd & XHCI_CMD_EWE) pHlp->pfnPrintf(pHlp, " EWE" ); + if (pThis->cmd & XHCI_CMD_CRS) pHlp->pfnPrintf(pHlp, " CRS" ); + if (pThis->cmd & XHCI_CMD_CSS) pHlp->pfnPrintf(pHlp, " CSS" ); + if (pThis->cmd & XHCI_CMD_LCRST) pHlp->pfnPrintf(pHlp, " LCRST" ); + if (pThis->cmd & XHCI_CMD_HSEE) pHlp->pfnPrintf(pHlp, " HSEE" ); + if (pThis->cmd & XHCI_CMD_INTE) pHlp->pfnPrintf(pHlp, " INTE" ); + if (pThis->cmd & XHCI_CMD_HCRST) pHlp->pfnPrintf(pHlp, " HCRST" ); + if (pThis->cmd & XHCI_CMD_RS) pHlp->pfnPrintf(pHlp, " RS" ); + pHlp->pfnPrintf(pHlp, "\n"); + + /* Status register. */ + pHlp->pfnPrintf(pHlp, "USBSTS: %X:", pThis->status); + if (pThis->status & XHCI_STATUS_HCH) pHlp->pfnPrintf(pHlp, " HCH" ); + if (pThis->status & XHCI_STATUS_HSE) pHlp->pfnPrintf(pHlp, " HSE" ); + if (pThis->status & XHCI_STATUS_EINT) pHlp->pfnPrintf(pHlp, " EINT" ); + if (pThis->status & XHCI_STATUS_PCD) pHlp->pfnPrintf(pHlp, " PCD" ); + if (pThis->status & XHCI_STATUS_SSS) pHlp->pfnPrintf(pHlp, " SSS" ); + if (pThis->status & XHCI_STATUS_RSS) pHlp->pfnPrintf(pHlp, " RSS" ); + if (pThis->status & XHCI_STATUS_SRE) pHlp->pfnPrintf(pHlp, " SRE" ); + if (pThis->status & XHCI_STATUS_CNR) pHlp->pfnPrintf(pHlp, " CNR" ); + if (pThis->status & XHCI_STATUS_HCE) pHlp->pfnPrintf(pHlp, " HCE" ); + pHlp->pfnPrintf(pHlp, "\n"); + + /* Device Notification Control and Configure registers. */ + pHlp->pfnPrintf(pHlp, "DNCTRL: %X CONFIG: %X (%u slots)\n", pThis->dnctrl, pThis->config, pThis->config); + + /* Device Context Base Address Array. */ + GPAddr = pThis->dcbaap & XHCI_DCBAA_ADDR_MASK; + pHlp->pfnPrintf(pHlp, "DCBAA ptr: %RGp\n", GPAddr); + /* The DCBAA must be valid in 'run' state. */ + if (fVerbose && (pThis->cmd & XHCI_CMD_RS)) + { + PDMDevHlpPCIPhysRead(pDevIns, GPAddr, &u64Val, sizeof(u64Val)); + pHlp->pfnPrintf(pHlp, " Scratchpad buffer: %RX64\n", u64Val); + } + + /* Command Ring Control Register. */ + pHlp->pfnPrintf(pHlp, "CRCR: %X:", pThis->crcr & ~XHCI_CRCR_ADDR_MASK); + if (pThis->crcr & XHCI_CRCR_RCS) pHlp->pfnPrintf(pHlp, " RCS"); + if (pThis->crcr & XHCI_CRCR_CS) pHlp->pfnPrintf(pHlp, " CS" ); + if (pThis->crcr & XHCI_CRCR_CA) pHlp->pfnPrintf(pHlp, " CA" ); + if (pThis->crcr & XHCI_CRCR_CRR) pHlp->pfnPrintf(pHlp, " CRR"); + pHlp->pfnPrintf(pHlp, "\n"); + GPAddr = pThis->crcr & XHCI_CRCR_ADDR_MASK; + pHlp->pfnPrintf(pHlp, "CRCR ptr : %RGp\n", GPAddr); + + /* Interrupters. */ + if (fVerbose) + { + for (i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + if (pThis->aInterrupters[i].erstsz) + { + XHCIINTRPTR *ir = &pThis->aInterrupters[i]; + + pHlp->pfnPrintf(pHlp, "Interrupter %d (IPE=%u)\n", i, ir->ipe); + + /* The Interrupt Management Register. */ + pHlp->pfnPrintf(pHlp, " IMAN : %X:", ir->iman); + if (ir->iman & XHCI_IMAN_IP) pHlp->pfnPrintf(pHlp, " IP"); + if (ir->iman & XHCI_IMAN_IE) pHlp->pfnPrintf(pHlp, " IE"); + pHlp->pfnPrintf(pHlp, "\n"); + + /* The Interrupt Moderation Register. */ + pHlp->pfnPrintf(pHlp, " IMOD : %X:", ir->imod); + pHlp->pfnPrintf(pHlp, " IMODI=%u", ir->imod & XHCI_IMOD_IMODI_MASK); + pHlp->pfnPrintf(pHlp, " IMODC=%u", (ir->imod & XHCI_IMOD_IMODC_MASK) >> XHCI_IMOD_IMODC_SHIFT); + pHlp->pfnPrintf(pHlp, "\n"); + + pHlp->pfnPrintf(pHlp, " ERSTSZ: %X\n", ir->erstsz); + pHlp->pfnPrintf(pHlp, " ERSTBA: %RGp\n", (RTGCPHYS)ir->erstba); + + pHlp->pfnPrintf(pHlp, " ERDP : %RGp:", (RTGCPHYS)ir->erdp); + pHlp->pfnPrintf(pHlp, " EHB=%u", !!(ir->erdp & XHCI_ERDP_EHB)); + pHlp->pfnPrintf(pHlp, " DESI=%u", ir->erdp & XHCI_ERDP_DESI_MASK); + pHlp->pfnPrintf(pHlp, " ptr=%RGp", ir->erdp & XHCI_ERDP_ADDR_MASK); + pHlp->pfnPrintf(pHlp, "\n"); + + pHlp->pfnPrintf(pHlp, " EREP : %RGp", ir->erep); + pHlp->pfnPrintf(pHlp, " Free TRBs in seg=%u", ir->trb_count); + pHlp->pfnPrintf(pHlp, "\n"); + } + } + } + + /* Port control/status. */ + for (i = 0; i < XHCI_NDP_CFG(pThis); ++i) + { + PXHCIHUBPORT p = &pThis->aPorts[i]; + + pHlp->pfnPrintf(pHlp, "Port %02u (USB%c): ", IDX_TO_ID(i), IS_USB3_PORT_IDX_SHR(pThis, i) ? '3' : '2'); + + /* Port Status register. */ + pHlp->pfnPrintf(pHlp, "PORTSC: %8X:", p->portsc); + if (p->portsc & XHCI_PORT_CCS) pHlp->pfnPrintf(pHlp, " CCS" ); + if (p->portsc & XHCI_PORT_PED) pHlp->pfnPrintf(pHlp, " PED" ); + if (p->portsc & XHCI_PORT_OCA) pHlp->pfnPrintf(pHlp, " OCA" ); + if (p->portsc & XHCI_PORT_PR ) pHlp->pfnPrintf(pHlp, " PR" ); + pHlp->pfnPrintf(pHlp, " PLS=%u", (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT); + if (p->portsc & XHCI_PORT_PP ) pHlp->pfnPrintf(pHlp, " PP" ); + pHlp->pfnPrintf(pHlp, " SPD=%u", (p->portsc & XHCI_PORT_SPD_MASK) >> XHCI_PORT_SPD_SHIFT); + if (p->portsc & XHCI_PORT_LWS) pHlp->pfnPrintf(pHlp, " LWS" ); + if (p->portsc & XHCI_PORT_CSC) pHlp->pfnPrintf(pHlp, " CSC" ); + if (p->portsc & XHCI_PORT_PEC) pHlp->pfnPrintf(pHlp, " PEC" ); + if (p->portsc & XHCI_PORT_WRC) pHlp->pfnPrintf(pHlp, " WRC" ); + if (p->portsc & XHCI_PORT_OCC) pHlp->pfnPrintf(pHlp, " OCC" ); + if (p->portsc & XHCI_PORT_PRC) pHlp->pfnPrintf(pHlp, " PRC" ); + if (p->portsc & XHCI_PORT_PLC) pHlp->pfnPrintf(pHlp, " PLC" ); + if (p->portsc & XHCI_PORT_CEC) pHlp->pfnPrintf(pHlp, " CEC" ); + if (p->portsc & XHCI_PORT_CAS) pHlp->pfnPrintf(pHlp, " CAS" ); + if (p->portsc & XHCI_PORT_WCE) pHlp->pfnPrintf(pHlp, " WCE" ); + if (p->portsc & XHCI_PORT_WDE) pHlp->pfnPrintf(pHlp, " WDE" ); + if (p->portsc & XHCI_PORT_WOE) pHlp->pfnPrintf(pHlp, " WOE" ); + if (p->portsc & XHCI_PORT_DR ) pHlp->pfnPrintf(pHlp, " DR" ); + if (p->portsc & XHCI_PORT_WPR) pHlp->pfnPrintf(pHlp, " WPR" ); + pHlp->pfnPrintf(pHlp, "\n"); + } + + /* Device contexts. */ + if (fVerbose && (pThis->cmd & XHCI_CMD_RS)) + { + for (i = 0; i < XHCI_NDS; ++i) + { + if (pThis->aSlotState[i] > XHCI_DEVSLOT_EMPTY) + { + RTGCPHYS GCPhysSlot; + XHCI_DEV_CTX ctxDevice; + XHCI_SLOT_CTX ctxSlot; + const char *pcszDesc; + uint8_t uSlotID = IDX_TO_ID(i); + + /* Find the slot address/ */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + pHlp->pfnPrintf(pHlp, "Slot %d (device context @ %RGp)\n", uSlotID, GCPhysSlot); + if (!GCPhysSlot) + { + pHlp->pfnPrintf(pHlp, "Bad context address, skipping!\n"); + continue; + } + + /* Just read in the whole lot and sort in which contexts are valid later. */ + PDMDevHlpPCIPhysRead(pDevIns, GCPhysSlot, &ctxDevice, sizeof(ctxDevice)); + + ctxSlot = ctxDevice.entry[0].sc; + pcszDesc = ctxSlot.slot_state < RT_ELEMENTS(g_apszSltStates) ? g_apszSltStates[ctxSlot.slot_state] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Speed:%u Entries:%u RhPort:%u", ctxSlot.speed, ctxSlot.ctx_ent, ctxSlot.rh_port); + pHlp->pfnPrintf(pHlp, " Address:%u State:%s \n", ctxSlot.dev_addr, pcszDesc); + + /* Endpoint contexts. */ + for (j = 1; j <= ctxSlot.ctx_ent; ++j) + { + XHCI_EP_CTX ctxEP = ctxDevice.entry[j].ep; + + /* Skip disabled endpoints -- they may be unused and do not + * contain valid data in any case. + */ + if (ctxEP.ep_state == XHCI_EPST_DISABLED) + continue; + + pcszDesc = ctxEP.ep_state < RT_ELEMENTS(g_apszEpStates) ? g_apszEpStates[ctxEP.ep_state] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Endpoint DCI %u State:%s", j, pcszDesc); + pcszDesc = ctxEP.ep_type < RT_ELEMENTS(g_apszEpTypes) ? g_apszEpTypes[ctxEP.ep_type] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Type:%s\n",pcszDesc); + + pHlp->pfnPrintf(pHlp, " Mult:%u MaxPStreams:%u LSA:%u Interval:%u\n", + ctxEP.mult, ctxEP.maxps, ctxEP.lsa, ctxEP.interval); + pHlp->pfnPrintf(pHlp, " CErr:%u HID:%u MaxPS:%u MaxBS:%u", + ctxEP.c_err, ctxEP.hid, ctxEP.max_pkt_sz, ctxEP.max_brs_sz); + pHlp->pfnPrintf(pHlp, " AvgTRBLen:%u MaxESIT:%u", + ctxEP.avg_trb_len, ctxEP.max_esit); + pHlp->pfnPrintf(pHlp, " LastFrm:%u IFC:%u LastCC:%u\n", + ctxEP.last_frm, ctxEP.ifc, ctxEP.last_cc); + pHlp->pfnPrintf(pHlp, " TRDP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trdp & XHCI_TRDP_ADDR_MASK), + ctxEP.trdp & XHCI_TRDP_DCS_MASK); + pHlp->pfnPrintf(pHlp, " TREP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trep & XHCI_TRDP_ADDR_MASK), + ctxEP.trep & XHCI_TRDP_DCS_MASK); + } + } + } + } +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) xhciR3Reset(PPDMDEVINS pDevIns) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + LogFlow(("xhciR3Reset:\n")); + + /* + * There is no distinction between cold boot, warm reboot and software reboots, + * all of these are treated as cold boots. We are also doing the initialization + * job of a BIOS or SMM driver. + * + * Important: Don't confuse UsbReset with hardware reset. Hardware reset is + * just one way of getting into the UsbReset state. + */ + + /* Set the HC Halted bit now to prevent completion callbacks from running + *(there is really no point when resetting). + */ + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH); + + xhciR3BusStop(pDevIns, pThis, pThisCC); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) xhciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + LogFlow(("xhciR3Destruct:\n")); + + /* + * Destroy interrupter locks. + */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->aInterrupters[i].lock)) + PDMDevHlpCritSectDelete(pDevIns, &pThis->aInterrupters[i].lock); + } + + /* + * Clean up the worker thread and associated machinery. + */ + if (pThis->hEvtProcess != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEvtProcess); + pThis->hEvtProcess = NIL_SUPSEMEVENT; + } + if (RTCritSectIsInitialized(&pThisCC->CritSectThrd)) + RTCritSectDelete(&pThisCC->CritSectThrd); + + return VINF_SUCCESS; +} + + +/** + * Worker for xhciR3Construct that registers a LUN (USB root hub). + */ +static int xhciR3RegisterHub(PPDMDEVINS pDevIns, PXHCIROOTHUBR3 pRh, int iLun, const char *pszDesc) +{ + int rc = PDMDevHlpDriverAttach(pDevIns, iLun, &pRh->IBase, &pRh->pIBase, pszDesc); + AssertMsgRCReturn(rc, ("Configuration error: Failed to attach root hub driver to LUN #%d! (%Rrc)\n", iLun, rc), rc); + + pRh->pIRhConn = PDMIBASE_QUERY_INTERFACE(pRh->pIBase, VUSBIROOTHUBCONNECTOR); + AssertMsgReturn(pRh->pIRhConn, + ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), + VERR_PDM_MISSING_INTERFACE); + + /* Set URB parameters. */ + rc = VUSBIRhSetUrbParams(pRh->pIRhConn, sizeof(VUSBURBHCIINT), 0); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to set URB parameters")); + + return rc; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct,XHCI + * constructor} + */ +static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t cUsb2Ports; + uint32_t cUsb3Ports; + int rc; + LogFlow(("xhciR3Construct:\n")); + RT_NOREF(iInstance); + + /* + * Initialize data so the destructor runs smoothly. + */ + pThis->hEvtProcess = NIL_SUPSEMEVENT; + + /* + * Validate and read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "USB2Ports|USB3Ports|ChipType", ""); + + /* Number of USB2 ports option. */ + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB2Ports", &cUsb2Ports, XHCI_NDP_20_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("xHCI configuration error: failed to read USB2Ports as integer")); + + if (cUsb2Ports == 0 || cUsb2Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB2Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Number of USB3 ports option. */ + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB3Ports", &cUsb3Ports, XHCI_NDP_30_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("xHCI configuration error: failed to read USB3Ports as integer")); + + if (cUsb3Ports == 0 || cUsb3Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB3Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Check that the total number of ports is within limits.*/ + if (cUsb2Ports + cUsb3Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB2Ports + USB3Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Determine the model. */ + char szChipType[16]; + rc = pHlp->pfnCFGMQueryStringDef(pCfg, "ChipType", &szChipType[0], sizeof(szChipType), "PantherPoint"); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("xHCI configuration error: Querying \"ChipType\" as string failed")); + + /* + * The default model is Panther Point (8086:1E31), Intel's first and most widely + * supported xHCI implementation. For debugging, the Lynx Point (8086:8C31) model + * can be selected. These two models work with the 7 Series and 8 Series Intel xHCI + * drivers for Windows 7, respectively. There is no functional difference. + * For Windows XP support, it's also possible to present a Renesas uPD720201 xHC; + * this is an evolution of the original NEC xHCI chip. + */ + bool fChipLynxPoint = false; + bool fChipRenesas = false; + if (!strcmp(szChipType, "PantherPoint")) + fChipLynxPoint = false; + else if (!strcmp(szChipType, "LynxPoint")) + fChipLynxPoint = true; + else if (!strcmp(szChipType, "uPD720201")) + fChipRenesas = true; + else + return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS, + N_("xHCI configuration error: The \"ChipType\" value \"%s\" is unsupported"), szChipType); + + LogFunc(("cUsb2Ports=%u cUsb3Ports=%u szChipType=%s (%d,%d) fR0Enabled=%d fRCEnabled=%d\n", cUsb2Ports, cUsb3Ports, + szChipType, fChipLynxPoint, fChipRenesas, pDevIns->fR0Enabled, pDevIns->fRCEnabled)); + + /* Set up interrupter locks. */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aInterrupters[i].lock, RT_SRC_POS, "xHCIIntr#%u", i); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("xHCI: Failed to create critical section for interrupter %u"), i); + pThis->aInterrupters[i].index = i; /* Stash away index, mostly for logging/debugging. */ + } + + + /* + * Init instance data. + */ + pThisCC->pDevIns = pDevIns; + + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + if (fChipRenesas) + { + pThis->erst_addr_mask = NEC_ERST_ADDR_MASK; + PCIDevSetVendorId(pPciDev, 0x1912); + PCIDevSetDeviceId(pPciDev, 0x0014); + PCIDevSetByte(pPciDev, VBOX_PCI_REVISION_ID, 0x02); + } + else + { + pThis->erst_addr_mask = XHCI_ERST_ADDR_MASK; + PCIDevSetVendorId(pPciDev, 0x8086); + if (fChipLynxPoint) + PCIDevSetDeviceId(pPciDev, 0x8C31); /* Lynx Point / 8 Series */ + else + PCIDevSetDeviceId(pPciDev, 0x1E31); /* Panther Point / 7 Series */ + } + + PCIDevSetClassProg(pPciDev, 0x30); /* xHCI */ + PCIDevSetClassSub(pPciDev, 0x03); /* USB 3.0 */ + PCIDevSetClassBase(pPciDev, 0x0C); + PCIDevSetInterruptPin(pPciDev, 0x01); +#ifdef VBOX_WITH_MSI_DEVICES + PCIDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST); + PCIDevSetCapabilityList(pPciDev, 0x80); +#endif + PDMPciDevSetByte(pPciDev, 0x60, 0x20); /* serial bus release number register; 0x20 = USB 2.0 */ + /** @todo USBLEGSUP & USBLEGCTLSTS? Legacy interface for the BIOS (0xEECP+0 & 0xEECP+4) */ + + pThis->cTotalPorts = (uint8_t)(cUsb2Ports + cUsb3Ports); + + /* Set up the USB2 root hub interface. */ + pThis->cUsb2Ports = (uint8_t)cUsb2Ports; + pThisCC->RootHub2.pXhciR3 = pThisCC; + pThisCC->RootHub2.cPortsImpl = cUsb2Ports; + pThisCC->RootHub2.uPortBase = 0; + pThisCC->RootHub2.IBase.pfnQueryInterface = xhciR3RhQueryInterface; + pThisCC->RootHub2.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts; + pThisCC->RootHub2.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions2; + pThisCC->RootHub2.IRhPort.pfnAttach = xhciR3RhAttach; + pThisCC->RootHub2.IRhPort.pfnDetach = xhciR3RhDetach; + pThisCC->RootHub2.IRhPort.pfnReset = xhciR3RhReset; + pThisCC->RootHub2.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion; + pThisCC->RootHub2.IRhPort.pfnXferError = xhciR3RhXferError; + + /* Now the USB3 root hub interface. */ + pThis->cUsb3Ports = (uint8_t)cUsb3Ports; + pThisCC->RootHub3.pXhciR3 = pThisCC; + pThisCC->RootHub3.cPortsImpl = cUsb3Ports; + pThisCC->RootHub3.uPortBase = XHCI_NDP_USB2(pThisCC); + pThisCC->RootHub3.IBase.pfnQueryInterface = xhciR3RhQueryInterface; + pThisCC->RootHub3.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts; + pThisCC->RootHub3.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions3; + pThisCC->RootHub3.IRhPort.pfnAttach = xhciR3RhAttach; + pThisCC->RootHub3.IRhPort.pfnDetach = xhciR3RhDetach; + pThisCC->RootHub3.IRhPort.pfnReset = xhciR3RhReset; + pThisCC->RootHub3.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion; + pThisCC->RootHub3.IRhPort.pfnXferError = xhciR3RhXferError; + + /* USB LED */ + pThisCC->RootHub2.Led.u32Magic = PDMLED_MAGIC; + pThisCC->RootHub3.Led.u32Magic = PDMLED_MAGIC; + pThisCC->IBase.pfnQueryInterface = xhciR3QueryStatusInterface; + pThisCC->ILeds.pfnQueryStatusLed = xhciR3QueryStatusLed; + + /* Initialize the capability registers */ + pThis->cap_length = XHCI_CAPS_REG_SIZE; + pThis->hci_version = 0x100; /* Version 1.0 */ + pThis->hcs_params1 = (XHCI_NDP_CFG(pThis) << 24) | (XHCI_NINTR << 8) | XHCI_NDS; + pThis->hcs_params2 = (XHCI_ERSTMAX_LOG2 << 4) | XHCI_IST; + pThis->hcs_params3 = (4 << 16) | 1; /* Matches Intel 7 Series xHCI. */ + /* Note: The Intel 7 Series xHCI does not have port power control (XHCI_HCC_PPC). */ + pThis->hcc_params = ((XHCI_XECP_OFFSET >> 2) << XHCI_HCC_XECP_SHIFT); /// @todo other fields + pThis->dbell_off = XHCI_DOORBELL_OFFSET; + pThis->rts_off = XHCI_RTREG_OFFSET; + + /* + * Set up extended capabilities. + */ + rc = xhciR3BuildExtCaps(pThis, pThisCC); + AssertRCReturn(rc, rc); + + /* + * Register PCI device and I/O region. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + AssertRCReturn(rc, rc); + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = XHCI_PCI_MSI_CAP_OFS; + MsiReg.iMsiNextOffset = 0x00; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE (rc)) + { + PCIDevSetCapabilityList(pPciDev, 0x0); + /* That's OK, we can work without MSI */ + } +#endif + + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, XHCI_MMIO_SIZE, PCI_ADDRESS_SPACE_MEM, + xhciMmioWrite, xhciMmioRead, NULL, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + /*| IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE*/, + "USB xHCI", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, XHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + NULL, xhciR3SaveExec, NULL, + NULL, xhciR3LoadExec, NULL); + AssertRCReturn(rc, rc); + + /* + * Attach to the VBox USB RootHub Driver on LUN #0 (USB3 root hub). + * NB: USB3 must come first so that emulated devices which support both USB2 + * and USB3 are attached to the USB3 hub. + */ + rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub3, 0, "RootHubUSB3"); + AssertRCReturn(rc, rc); + + /* + * Attach to the VBox USB RootHub Driver on LUN #1 (USB2 root hub). + */ + rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub2, 1, "RootHubUSB2"); + AssertRCReturn(rc, rc); + + /* + * Attach the status LED (optional). + */ + PPDMIBASE pBase; + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else if (rc != VERR_PDM_NO_ATTACHED_DRIVER) + { + AssertMsgFailed(("xHCI: Failed to attach to status driver. rc=%Rrc\n", rc)); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("xHCI cannot attach to status driver")); + } + + /* + * Create the MFINDEX wrap event timer. + */ + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, xhciR3WrapTimer, pThis, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "xHCI MFINDEX Wrap", &pThis->hWrapTimer); + AssertRCReturn(rc, rc); + + /* + * Set up the worker thread. + */ + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hEvtProcess); + AssertLogRelRCReturn(rc, rc); + + rc = RTCritSectInit(&pThisCC->CritSectThrd); + AssertLogRelRCReturn(rc, rc); + + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->pWorkerThread, pThis, xhciR3WorkerLoop, xhciR3WorkerWakeUp, + 0, RTTHREADTYPE_IO, "xHCI"); + AssertLogRelRCReturn(rc, rc); + + /* + * Do a hardware reset. + */ + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, false /* don't reset devices */); + +# ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocUrbs, STAMTYPE_COUNTER, "IsocUrbsErr", STAMUNIT_OCCURENCES, "Isoch URBs completed w/error."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocPkts, STAMTYPE_COUNTER, "IsocPktsErr", STAMUNIT_OCCURENCES, "Isoch packets completed w/error."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsWritten, STAMTYPE_COUNTER, "EventsWritten", STAMUNIT_OCCURENCES, "Event TRBs delivered."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsDropped, STAMTYPE_COUNTER, "EventsDropped", STAMUNIT_OCCURENCES, "Event TRBs dropped (HC stopped)."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsPending, STAMTYPE_COUNTER, "IntrsPending", STAMUNIT_OCCURENCES, "Requests to set the IP bit."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsSet, STAMTYPE_COUNTER, "IntrsSet", STAMUNIT_OCCURENCES, "Actual interrupts delivered."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsNotSet, STAMTYPE_COUNTER, "IntrsNotSet", STAMUNIT_OCCURENCES, "Interrupts not delivered/disabled."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsCleared, STAMTYPE_COUNTER, "IntrsCleared", STAMUNIT_OCCURENCES, "Interrupts cleared by guest."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerCtlUrb, STAMTYPE_COUNTER, "UrbTrbsCtl", STAMUNIT_COUNT, "TRBs per one control URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerDtaUrb, STAMTYPE_COUNTER, "UrbTrbsDta", STAMUNIT_COUNT, "TRBs per one data (bulk/intr) URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerIsoUrb, STAMTYPE_COUNTER, "UrbTrbsIso", STAMUNIT_COUNT, "TRBs per one isochronous URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeCtrl, STAMTYPE_COUNTER, "UrbSizeCtl", STAMUNIT_COUNT, "Size of a control URB in bytes."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeData, STAMTYPE_COUNTER, "UrbSizeDta", STAMUNIT_COUNT, "Size of a data (bulk/intr) URB in bytes."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeIsoc, STAMTYPE_COUNTER, "UrbSizeIso", STAMUNIT_COUNT, "Size of an isochronous URB in bytes."); + + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCaps, STAMTYPE_COUNTER, "Regs/RdCaps", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdConfig, STAMTYPE_COUNTER, "Regs/RdConfig", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/RdDevNotifyCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDoorBell, STAMTYPE_COUNTER, "Regs/RdDoorBell", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRstblSize, STAMTYPE_COUNTER, "Regs/RdEvtRstblSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsvd, STAMTYPE_COUNTER, "Regs/RdEvtRsvd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMgmt, STAMTYPE_COUNTER, "Regs/RdIntrMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMod, STAMTYPE_COUNTER, "Regs/RdIntrMod", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdMfIndex, STAMTYPE_COUNTER, "Regs/RdMfIndex", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPageSize, STAMTYPE_COUNTER, "Regs/RdPageSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortLinkInfo, STAMTYPE_COUNTER, "Regs/RdPortLinkInfo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortPowerMgmt, STAMTYPE_COUNTER, "Regs/RdPortPowerMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortRsvd, STAMTYPE_COUNTER, "Regs/RdPortRsvd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortStatusCtrl, STAMTYPE_COUNTER, "Regs/RdPortStatusCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbCmd, STAMTYPE_COUNTER, "Regs/RdUsbCmd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbSts, STAMTYPE_COUNTER, "Regs/RdUsbSts", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUnknown, STAMTYPE_COUNTER, "Regs/RdUnknown", STAMUNIT_COUNT, ""); + + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrConfig, STAMTYPE_COUNTER, "Regs/WrConfig", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/WrDevNotifyCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBell0, STAMTYPE_COUNTER, "Regs/WrDoorBell0", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBellN, STAMTYPE_COUNTER, "Regs/WrDoorBellN", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRstblSize, STAMTYPE_COUNTER, "Regs/WrEvtRstblSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMgmt, STAMTYPE_COUNTER, "Regs/WrIntrMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMod, STAMTYPE_COUNTER, "Regs/WrIntrMod", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortPowerMgmt, STAMTYPE_COUNTER, "Regs/WrPortPowerMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortStatusCtrl, STAMTYPE_COUNTER, "Regs/WrPortStatusCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbCmd, STAMTYPE_COUNTER, "Regs/WrUsbCmd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbSts, STAMTYPE_COUNTER, "Regs/WrUsbSts", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUnknown, STAMTYPE_COUNTER, "Regs/WrUnknown", STAMUNIT_COUNT, ""); +# endif /* VBOX_WITH_STATISTICS */ + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "xhci", "xHCI registers.", xhciR3Info); + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) xhciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, xhciMmioWrite, xhciMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +/* Without this, g_DeviceXHCI won't be visible outside this module! */ +extern "C" const PDMDEVREG g_DeviceXHCI; + +const PDMDEVREG g_DeviceXHCI = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "usb-xhci", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(XHCI), + /* .cbInstanceCC = */ sizeof(XHCICC), + /* .cbInstanceRC = */ sizeof(XHCIRC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "xHCI USB controller.\n", +#if defined(IN_RING3) +# ifdef VBOX_IN_EXTPACK + /* .pszRCMod = */ "VBoxEhciRC.rc", + /* .pszR0Mod = */ "VBoxEhciR0.r0", +# else + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", +# endif + /* .pfnConstruct = */ xhciR3Construct, + /* .pfnDestruct = */ xhciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ xhciR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ NULL, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ xhciRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ xhciRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ |