USB hubs are devices that attach multiple USB devices to one port - be it a port on the host controller, or a port on another hub.
USB hubs are intimidating to start with - however, as devices, they are in fact quite simple. USB hubs are hubs in the same sense as Ethernet hubs are - they are neither switches nor routers. They operate at the physical layer by connecting the data lines together.
In practice, things are slightly more complex than this, as there are certain tasks that USB hubs must perform. But it's a good conceptual model to begin with.
USB 3 hubs are best treated conceptually as two different hubs in one box - a USB 2 hub, and a SuperSpeed hub. Similarly to xHCI, a USB device is connected to the appropriate logical port for its speed. Note that the hub then appears on both the USB 2 protocol port on its host, and the USB 3 protocol port!
However, if a USB 3 hub is connected to a USB 2 port at any point in the chain back to the host, it will then only operate as USB 2.
The USB 3 hub device is very similar to USB 2, so this device is best described by explaining its differences.
USB 2 hub
A USB hub is a device with two endpoints - the default control endpoint, and the Status change endpoint.
The main responsibility of the hub device is managing the status of its downstream ports.
The first thing you need to do when you've detected a hub is to configure it. Once you have the configuration descriptor as with a normal USB device, you need to set the configuration. xHCI can get a little interesting, so I'll describe that.
To set the device configuration on xHCI, you first need to issue an Configure Endpoint TRB. This needs to have the add context flags set to 1 | (EndpointFlags). EndpointFlags will likely be (1<<4), representing Endpoint Context 1 IN. However, this should be set according to the endpoint descriptor from the device.
Bit zero is set because we need to update the slot context. The Context Entries field needs to be set to the index of the last endpoint (not necessarily the number of entries). And, as this is a hub, the Hub fields need to be initialised. This means setting the Hub bit to 1 and setting the Number of Ports.
This of course raises the question - how do we know the Number of Ports!?
Well, handily, that's contained in the hub descriptor. Less handily, per the USB 2 spec, "If the hub is not configured, the hub’s response to this request is undefined."
In practice, USB 2 hubs tend to give you the hub descriptor if you request it. But the correct way of dealing with this is to initialise the Number of Ports to a sensible default (typically 4), then reconfigure the endpoint when you get the descriptor. This resembles the procedure with MaxPacketSize on full speed devices.
To get the descriptor (per spec once the device is configured), issue a GET_DESCRIPTOR request, bmRequestType=10100000b, as this is a class descriptor.
wValue is set with a descriptor type of 0x29.
|0||bLength||1||Number||Size of this descriptor in bytes|
|1||bDescriptorType||1||Constant||HUB Descriptor Type 0x29|
|2||bNbrPorts||1||Number||Number of ports|
|5||bPowerOnGood||1||Number||Time (*2 ms) from port power on to power good|
|6||bHubContrCurrent||1||Number||Maximum current used by hub controller (mA).|
|7||DeviceRemovable||bNbrPorts bits||Bitmap||Is port device removable|
|After DeviceRemovable||PortPwrCtrlMask||bNbrPorts bits||Bitmap||Should be all ones for anything above USB 1.0|
Hub Command Set
All hub commands have a type of 01b = Class. The recipient is normally 00000b = Device, but Port or TT commands target 00011b = Other.
CLEAR_FEATURE, GET_STATUS, and SET_FEATURE can target either the hub or a port.