-
Notifications
You must be signed in to change notification settings - Fork 22
Emulated eXtensible Host Controller Interface (xHCI) host controller #906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lifning
wants to merge
1
commit into
oxidecomputer:master
Choose a base branch
from
lifning:xhci-ft-luqman
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Special thanks to @luqmana getting this started in early 2023: https://github.com/luqmana/propolis/commits/xhci/ The version of the standard referenced throughout the comments in this module is xHCI 1.2, but we do not implement the features required of a 1.1 or 1.2 compliant host controller - that is, we are only implementing a subset of what xHCI version 1.0 requires of an xHC, as described by version 1.2 of the *specification*. https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf At present, the only USB device supported is a USB 2.0 `NullUsbDevice` with no actual functionality, which exists as a proof-of-concept and as a means to show that USB `DeviceDescriptor`s are successfully communicated to the guest in phd-tests. ``` +---------+ | PciXhci | +---------+ | has-a +-----------------------------+ | XhciState | |-----------------------------| | PCI MMIO registers | | XhciInterrupter | | DeviceSlotTable | | Usb2Ports + Usb3Ports | | CommandRing | | newly attached USB devices | +-----------------------------+ | has-a | +-------------------+ | has-a | XhciInterrupter | +-----------------+ |-------------------| | DeviceSlotTable | | EventRing | |-----------------| | MSI-X/INTxPin | | DeviceSlot(s) |___+------------------+ +-------------------+ | DCBAAP | | DeviceSlot | | Active USB devs | |------------------| +-----------------+ | TransferRing(s) | +------------------+ ``` Conventions =========== Wherever possible, the framework represents `Trb` data through a further level of abstraction, such as enums constructed from the raw TRB bitfields before being passed to other parts of the system that use them, such that the behavior of identifying `TrbType` and accessing their fields properly according to the spec lives in a conversion function rather than strewn across implementation of other xHC functionality. The nomenclature used is generally trading the "Descriptor" suffix for "Info", e.g. the high-level enum-variant version of an `EventDescriptor` is `EventInfo` (which is passed to the `EventRing` to be converted into Event TRBs and written into guest memory). For 1-based indeces defined by the spec (slot ID, port ID), we put placeholder values at position 0 of any arrays in which the ID is used as an index, such that we aspire to categorically avoid off-by-one errors of omission (of `- 1`). Implementation ============== `DeviceSlotTable` ----------------- When a USB device is attached to the xHC, it is enqueued in a list within `XhciState` along with its `PortId`. The next time the xHC runs: - it will update the corresponding **PORTSC** register and inform the guest with a TRB on the `EventRing`, and if enabled, a hardware interrupt. - it moves the USB device to the `DeviceSlotTable` in preparation for being configured and assigned a slot. When the guest xHCD rings Doorbell 0 to run an `EnableSlot` Command, the `DeviceSlotTable` assigns the first unused slot ID to it. Hot-plugging devices live (i.e. not just attaching all devices defined by the instance spec at boot time as is done now) is not yet implemented. Device-slot-related Command TRBs are handled by the `DeviceSlotTable`. The command interface methods are written as translations of the behaviors defined in xHCI 1.2 section 4.6 to Rust, with liberties taken around redundant `TrbCompletionCode` writes; i.e. when the outlined behavior from the spec describes the xHC placing a `Success` into a new TRB on the `EventRing` immediately at the beginning of the command's execution, and then overwriting it with a failure code in the event of a failure, our implementation postpones the creation and enqueueing of the event until after the outcome of the command's execution (and thus the Event TRB's values) are all known. Ports ----- Root hub port state machines (xHCI 1.2 section 4.19.1) and port registers are managed by `Usb2Port`, which has separate methods for handling register writes by the guest and by the xHC itself. TRB Rings --------- **Consumer**: The `CommandRing` and each slot endpoint's `TransferRing` are implemented as `ConsumerRing<CommandInfo>` and `ConsumerRing<TransferInfo>`. Dequeued work items are converted from raw `CommandDescriptor`s and `TransferDescriptor`s, respectively). Starting at the dequeue pointer provided by the guest, the `ConsumerRing` will consume non-Link TRBs (and follow Link TRBs, as in xHCI 1.2 figure 4-15) into complete work items. In the case of the `CommandRing`, `CommandDescriptor`s are each only made up of one `Trb`, but for the `TransferRing` multi-TRB work items are possible, where all but the last item have the `chain_bit` set. **Producer**: The only type of producer ring is the `EventRing`. Events destined for it are fed through the `XhciInterrupter`, which handles enablement and rate-limiting of PCI-level machine interrupts being generated as a result of the events. Similarly (and inversely) to the consumer rings, the `EventRing` converts the `EventInfo`s enqueued in it into `EventDescriptor`s to be written to guest memory regions defined by the `EventRingSegment` Table. Doorbells --------- The guest writing to a `DoorbellRegister` makes the host controller process a consumer TRB ring (the `CommandRing` for doorbell 0, or the corresponding slot's `TransferRing` for nonzero doorbells). The ring consumption is performed by the doorbell register write handler, in `process_command_ring` and `process_transfer_ring`. Timer registers --------------- The value of registers defined as incrementing/decrementing per time interval, such as **MFINDEX** and the `XhciInterrupter`'s **IMODC**, are simulated with `Instant`s and `Duration`s rather than by repeated incrementation. DTrace support ============== To see a trace of all MMIO register reads/writes and TRB enqueue/dequeues: ```sh pfexec ./scripts/xhci-trace.d -p $(pgrep propolis-server) ``` The name of each register as used by DTrace is `&'static`ally defined in `registers::Registers::reg_name`.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Special thanks to @luqmana getting this started in early 2023: https://github.com/luqmana/propolis/commits/xhci/
(commit-message design outline that follows can also be found in
cargo doc
at/propolis/hw/usb/xhci/index.html
, with the named identifiers being hypertext)The version of the standard referenced throughout the comments in this module is xHCI 1.2, but we do not implement the features required of a 1.1 or 1.2 compliant host controller - that is, we are only implementing a subset of what xHCI version 1.0 requires of an xHC, as described by version 1.2 of the specification.
https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf
At present, the only USB device supported is a USB 2.0
NullUsbDevice
with no actual functionality, which exists as a proof-of-concept and as a means to show that USBDeviceDescriptor
s are successfully communicated to the guest in phd-tests.Conventions
Wherever possible, the framework represents
Trb
data through a further level of abstraction, such as enums constructed from the raw TRB bitfields before being passed to other parts of the system that use them, such that the behavior of identifyingTrbType
and accessing their fields properly according to the spec lives in a conversion function rather than strewn across implementation of other xHC functionality.The nomenclature used is generally trading the "Descriptor" suffix for "Info", e.g. the high-level enum-variant version of an
EventDescriptor
isEventInfo
(which is passed to theEventRing
to be converted into Event TRBs and written into guest memory).For 1-based indeces defined by the spec (slot ID, port ID), we put placeholder values at position 0 of any arrays in which the ID is used as an index, such that we aspire to categorically avoid off-by-one errors of omission (of
- 1
).Implementation
DeviceSlotTable
When a USB device is attached to the xHC, it is enqueued in a list within
XhciState
along with itsPortId
. The next time the xHC runs:EventRing
, and if enabled, a hardware interrupt.DeviceSlotTable
in preparation for being configured and assigned a slot. When the guest xHCD rings Doorbell 0 to run anEnableSlot
Command, theDeviceSlotTable
assigns the first unused slot ID to it.Hot-plugging devices live (i.e. not just attaching all devices defined by the instance spec at boot time as is done now) is not yet implemented.
Device-slot-related Command TRBs are handled by the
DeviceSlotTable
. The command interface methods are written as translations of the behaviors defined in xHCI 1.2 section 4.6 to Rust, with liberties taken around redundantTrbCompletionCode
writes; i.e. when the outlined behavior from the spec describes the xHC placing aSuccess
into a new TRB on theEventRing
immediately at the beginning of the command's execution, and then overwriting it with a failure code in the event of a failure, our implementation postpones the creation and enqueueing of the event until after the outcome of the command's execution (and thus the Event TRB's values) are all known.Ports
Root hub port state machines (xHCI 1.2 section 4.19.1) and port registers are managed by
Usb2Port
, which has separate methods for handling register writes by the guest and by the xHC itself.TRB Rings
Consumer:
The
CommandRing
and each slot endpoint'sTransferRing
are implemented asConsumerRing<CommandInfo>
andConsumerRing<TransferInfo>
. Dequeued work items are converted from rawCommandDescriptor
s andTransferDescriptor
s, respectively).Starting at the dequeue pointer provided by the guest, the
ConsumerRing
will consume non-Link TRBs (and follow Link TRBs, as in xHCI 1.2 figure 4-15) into complete work items. In the case of theCommandRing
,CommandDescriptor
s are each only made up of oneTrb
, but for theTransferRing
multi-TRB work items are possible, where all but the last item have thechain_bit
set.Producer:
The only type of producer ring is the
EventRing
. Events destined for it are fed through theXhciInterrupter
, which handles enablement and rate-limiting of PCI-level machine interrupts being generated as a result of the events.Similarly (and inversely) to the consumer rings, the
EventRing
converts theEventInfo
s enqueued in it intoEventDescriptor
s to be written to guest memory regions defined by theEventRingSegment
Table.Doorbells
The guest writing to a
DoorbellRegister
makes the host controller process a consumer TRB ring (theCommandRing
for doorbell 0, or the corresponding slot'sTransferRing
for nonzero doorbells). The ring consumption is performed by the doorbell register write handler, inprocess_command_ring
andprocess_transfer_ring
.Timer registers
The value of registers defined as incrementing/decrementing per time interval, such as MFINDEX and the
XhciInterrupter
's IMODC, are simulated withInstant
s andDuration
s rather than by repeated incrementation.DTrace support
To see a trace of all MMIO register reads/writes and TRB enqueue/dequeues:
pfexec ./scripts/xhci-trace.d -p $(pgrep propolis-server)
The name of each register as used by DTrace is
&'static
ally defined inregisters::Registers::reg_name
.