diff mbox series

[v2,7/8] usb: xhci: Rewrite xhci_create_usb3_bos_desc()

Message ID 2d9a3de7f6cfeada9a15ec0ec4683b49c62543c2.1612410491.git.Thinh.Nguyen@synopsys.com
State Superseded
Headers show
Series usb: Check for genXxY on host | expand

Commit Message

Thinh Nguyen Feb. 4, 2021, 4:11 a.m. UTC
The current xhci_create_usb3_bos_desc() uses a static bos u8 array and
various magic numbers and offsets making it difficult to extend support
for USB 3.2. Let's rewrite this entire function to support dual-lane in
USB 3.2.

The hub driver matches the port speed ID from the extended port status
to the SSID of the sublink speed attributes to detect if the device
supports SuperSpeed Plus. Currently we don't provide the default gen1x2
and gen2x2 sublink speed capability descriptor for USB 3.2 roothub. The
USB stack depends on this to detect and match the correct speed.
In addition, if the xHCI host provides Protocol Speed ID (PSI)
capability, then make sure to convert Protocol Speed ID Mantissa and
Exponent (PSIM & PSIE) to lane speed for gen1x2 and gen2x2.

Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
---
Changes in v2:
- Use static array for SSA per Mathias suggestion
- Check bcdUSB >= 0x320 instead of bcdUSB == 0x320 per Mathias suggestion
- When host uses custom PSI, SSIC is the psi_uid_count and not SSAC. I missed
  this when transferring the previous logic over. Previous implementation
  was incorrect. Let's fix it here.

 drivers/usb/host/xhci-hub.c | 237 +++++++++++++++++++++++++++++++++++-
 1 file changed, 235 insertions(+), 2 deletions(-)

Comments

Mathias Nyman March 9, 2021, 11:47 a.m. UTC | #1
On 4.2.2021 6.11, Thinh Nguyen wrote:
> The current xhci_create_usb3_bos_desc() uses a static bos u8 array and

> various magic numbers and offsets making it difficult to extend support

> for USB 3.2. Let's rewrite this entire function to support dual-lane in

> USB 3.2.

> 

> The hub driver matches the port speed ID from the extended port status

> to the SSID of the sublink speed attributes to detect if the device

> supports SuperSpeed Plus. Currently we don't provide the default gen1x2

> and gen2x2 sublink speed capability descriptor for USB 3.2 roothub. The

> USB stack depends on this to detect and match the correct speed.

> In addition, if the xHCI host provides Protocol Speed ID (PSI)

> capability, then make sure to convert Protocol Speed ID Mantissa and

> Exponent (PSIM & PSIE) to lane speed for gen1x2 and gen2x2.

> 

> Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>

> ---

> Changes in v2:

> - Use static array for SSA per Mathias suggestion

> - Check bcdUSB >= 0x320 instead of bcdUSB == 0x320 per Mathias suggestion

> - When host uses custom PSI, SSIC is the psi_uid_count and not SSAC. I missed

>   this when transferring the previous logic over. Previous implementation

>   was incorrect. Let's fix it here.

> 

>  drivers/usb/host/xhci-hub.c | 237 +++++++++++++++++++++++++++++++++++-

>  1 file changed, 235 insertions(+), 2 deletions(-)

> 

> diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c

> index 74c497fd3476..eddd139c2260 100644

> --- a/drivers/usb/host/xhci-hub.c

> +++ b/drivers/usb/host/xhci-hub.c

> @@ -11,6 +11,7 @@

>  

>  #include <linux/slab.h>

>  #include <asm/unaligned.h>

> +#include <linux/bitfield.h>

>  

>  #include "xhci.h"

>  #include "xhci-trace.h"

> @@ -52,7 +53,239 @@ static u8 usb_bos_descriptor [] = {

>  	0xb5, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, tx, ID = 5 */

>  };

>  

> -static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,

> +/* Default sublink speed attribute of each lane */

> +static u32 ssp_cap_default_ssa[] = {

> +	0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */

> +	0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */

> +	0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */

> +	0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */

> +	0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */

> +	0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */

> +	0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */

> +	0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */

> +};

> +

> +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,

> +				      u16 wLength)

> +{

> +	struct usb_bos_descriptor	*bos;

> +	struct usb_ss_cap_descriptor	*ss_cap;

> +	struct usb_ssp_cap_descriptor	*ssp_cap;

> +	struct xhci_port_cap		*port_cap = NULL;

> +	u16				bcdUSB;

> +	u32				reg;

> +	u32				min_rate = 0;

> +	u8				min_ssid;

> +	u8				ssac;

> +	u8				ssic;

> +	int				offset;

> +	int				i;

> +

> +	/* BOS descriptor */

> +	bos = (struct usb_bos_descriptor *)buf;

> +	bos->bLength = USB_DT_BOS_SIZE;

> +	bos->bDescriptorType = USB_DT_BOS;

> +	bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

> +					USB_DT_USB_SS_CAP_SIZE);

> +	bos->bNumDeviceCaps = 1;

> +

> +	/* Create the descriptor for port with the highest revision */

> +	for (i = 0; i < xhci->num_port_caps; i++) {

> +		u8 major = xhci->port_caps[i].maj_rev;

> +		u8 minor = xhci->port_caps[i].min_rev;

> +		u16 rev = (major << 8) | minor;

> +

> +		if (i == 0 || bcdUSB < rev) {

> +			bcdUSB = rev;

> +			port_cap = &xhci->port_caps[i];

> +		}

> +	}

> +

> +	if (bcdUSB >= 0x0310) {

> +		if (port_cap->psi_count) {

> +			u8 num_sym_ssa = 0;

> +

> +			for (i = 0; i < port_cap->psi_count; i++) {

> +				if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM)

> +					num_sym_ssa++;

> +			}

> +

> +			ssac = port_cap->psi_count + num_sym_ssa - 1;

> +			ssic = port_cap->psi_uid_count - 1;

> +		} else {

> +			if (bcdUSB >= 0x0320)

> +				ssac = 7;

> +			else

> +				ssac = 3;

> +

> +			ssic = (ssac + 1) / 2 - 1;

> +		}

> +

> +		bos->bNumDeviceCaps++;

> +		bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

> +						USB_DT_USB_SS_CAP_SIZE +

> +						USB_DT_USB_SSP_CAP_SIZE(ssac));

> +	}

> +

> +	if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE)

> +		return wLength;

> +

> +	/* SuperSpeed USB Device Capability */

> +	ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE];

> +	ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;

> +	ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

> +	ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;

> +	ss_cap->bmAttributes = 0; /* set later */

> +	ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION);

> +	ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;

> +	ss_cap->bU1devExitLat = 0; /* set later */

> +	ss_cap->bU2DevExitLat = 0; /* set later */

> +

> +	reg = readl(&xhci->cap_regs->hcc_params);

> +	if (HCC_LTC(reg))

> +		ss_cap->bmAttributes |= USB_LTM_SUPPORT;

> +

> +	if ((xhci->quirks & XHCI_LPM_SUPPORT)) {

> +		reg = readl(&xhci->cap_regs->hcs_params3);

> +		ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);

> +		ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));

> +	}

> +

> +	if (wLength < le16_to_cpu(bos->wTotalLength))

> +		return wLength;

> +

> +	if (bcdUSB < 0x0310)

> +		return le16_to_cpu(bos->wTotalLength);

> +

> +	ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE +

> +		USB_DT_USB_SS_CAP_SIZE];

> +	ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac);

> +	ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

> +	ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE;

> +	ssp_cap->bReserved = 0;

> +	ssp_cap->wReserved = 0;

> +	ssp_cap->bmAttributes =

> +		cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) |

> +			    FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic));

> +

> +	if (!port_cap->psi_count) {

> +		for (i = 0; i < ssac + 1; i++)

> +			ssp_cap->bmSublinkSpeedAttr[i] =

> +				cpu_to_le32(ssp_cap_default_ssa[i]);

> +

> +		min_ssid = 4;

> +		goto out;

> +	}

> +

> +	offset = 0;

> +	for (i = 0; i < port_cap->psi_count; i++) {

> +		u32 psi;

> +		u32 attr;

> +		u8 ssid;

> +		u8 lp;

> +		u8 lse;

> +		u8 psie;

> +		u16 lane_mantissa;

> +		u16 psim;

> +		u16 plt;

> +

> +		psi = port_cap->psi[i];

> +		ssid = XHCI_EXT_PORT_PSIV(psi);

> +		lp = XHCI_EXT_PORT_LP(psi);

> +		psie = XHCI_EXT_PORT_PSIE(psi);

> +		psim = XHCI_EXT_PORT_PSIM(psi);

> +		plt = psi & PLT_MASK;

> +

> +		lse = psie;

> +		lane_mantissa = psim;

> +

> +		/* Shift to Gbps and set SSP Link Protocol if 10Gpbs */

> +		for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++)

> +			psim /= 1000;

> +

> +		if (!min_rate || psim < min_rate) {

> +			min_ssid = ssid;

> +			min_rate = psim;

> +		}

> +

> +		/* Some host controllers don't set the link protocol for SSP */

> +		if (psim >= 10)

> +			lp = USB_SSP_SUBLINK_SPEED_LP_SSP;


Maybe we should limit the above fix to older than USB 3.2 hosts.
xHCI supporting Gen 1x2 can in theory have psim==10 even if it only supports
SS link protocol, not SSP.

I'd guess newer USB 3.2 hosts have have fixed the LP bit.  

> +

> +		/*

> +		 * PSIM and PSIE represent the total speed of PSI. The BOS

> +		 * descriptor SSP sublink speed attribute lane mantissa

> +		 * describes the lane speed. E.g. PSIM and PSIE for gen2x2

> +		 * is 20Gbps, but the BOS descriptor lane speed mantissa is

> +		 * 10Gbps. Check and modify the mantissa value to match the

> +		 * lane speed.

> +		 */

> +		if (bcdUSB == 0x0320 && plt == PLT_SYM) {

> +			/*

> +			 * The PSI dword for gen1x2 and gen2x1 share the same

> +			 * values. But the lane speed for gen1x2 is 5Gbps while

> +			 * gen2x1 is 10Gbps. If the previous PSI dword SSID is

> +			 * 5 and the PSIE and PSIM match with SSID 6, let's

> +			 * assume that the controller follows the default speed

> +			 * id with SSID 6 for gen1x2.

> +			 */

> +			if (ssid == 6 && psie == 3 && psim == 10 && i) {

> +				u32 prev = port_cap->psi[i - 1];

> +

> +				if ((prev & PLT_MASK) == PLT_SYM &&

> +				    XHCI_EXT_PORT_PSIV(prev) == 5 &&

> +				    XHCI_EXT_PORT_PSIE(prev) == 3 &&

> +				    XHCI_EXT_PORT_PSIM(prev) == 10) {

> +					lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;

> +					lane_mantissa = 5;


This is a very specific workaround designed for one default case.

> +				}

> +			}

> +

> +			if (psie == 3 && psim > 10) {

> +				lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;

> +				lane_mantissa = 10;

> +			}


So is this.

Maybe something more generic could be figured out.
If we assume that bcdUSB >= 0x0320 cases have the Link Protocol (LP) bit is set correctly,
we could do something like:

if (bcdUSB >= 0x0320)
  if (LP && psim > 10) || (!LP && (psim > 5))
    psim = psim / 2  /* divide by lane count */
        
Not sure if above pseudocode works with SSIC cases 

We are also forcing the speeds to Gbps (current code does this as well)
This is rounding down non-standard speeds. (SSIC at 2915 Mbps for example)

> +		}

> +

> +		attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) |

> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) |

> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) |

> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa));

> +

> +		switch (plt) {

> +		case PLT_SYM:

> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

> +					   USB_SSP_SUBLINK_SPEED_ST_SYM_RX);

> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

> +

> +			attr &= ~USB_SSP_SUBLINK_SPEED_ST;

> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

> +					   USB_SSP_SUBLINK_SPEED_ST_SYM_TX);

> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

> +			break;

> +		case PLT_ASYM_RX:

> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

> +					   USB_SSP_SUBLINK_SPEED_ST_ASYM_RX);

> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

> +			break;

> +		case PLT_ASYM_TX:

> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

> +					   USB_SSP_SUBLINK_SPEED_ST_ASYM_TX);

> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

> +			break;

> +		}

> +	}

> +out:

> +	ssp_cap->wFunctionalitySupport =

> +		cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID,

> +				       min_ssid) |

> +			    FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) |

> +			    FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1));

> +

> +	return le16_to_cpu(bos->wTotalLength);

> +}

> +

> +static __maybe_unused int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,

>  				     u16 wLength)

>  {

>  	struct xhci_port_cap *port_cap = NULL;

> @@ -1137,7 +1370,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,

>  		if (hcd->speed < HCD_USB3)

>  			goto error;

>  

> -		retval = xhci_create_usb3_bos_desc(xhci, buf, wLength);

> +		retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength);

>  		spin_unlock_irqrestore(&xhci->lock, flags);

>  		return retval;

>  	case GetPortStatus:

> 


This is anyways a lot better than what we currently have.
Would be nice to get those xHCI PSIM values nicely converted to USB 3.x bmSuperSpeedAttr
Lane Speed Mantissa.

Otherwise everyting looks good

Thanks
-Mathias
Thinh Nguyen March 9, 2021, 7:32 p.m. UTC | #2
Mathias Nyman wrote:
> On 4.2.2021 6.11, Thinh Nguyen wrote:

>> The current xhci_create_usb3_bos_desc() uses a static bos u8 array and

>> various magic numbers and offsets making it difficult to extend support

>> for USB 3.2. Let's rewrite this entire function to support dual-lane in

>> USB 3.2.

>>

>> The hub driver matches the port speed ID from the extended port status

>> to the SSID of the sublink speed attributes to detect if the device

>> supports SuperSpeed Plus. Currently we don't provide the default gen1x2

>> and gen2x2 sublink speed capability descriptor for USB 3.2 roothub. The

>> USB stack depends on this to detect and match the correct speed.

>> In addition, if the xHCI host provides Protocol Speed ID (PSI)

>> capability, then make sure to convert Protocol Speed ID Mantissa and

>> Exponent (PSIM & PSIE) to lane speed for gen1x2 and gen2x2.

>>

>> Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>

>> ---

>> Changes in v2:

>> - Use static array for SSA per Mathias suggestion

>> - Check bcdUSB >= 0x320 instead of bcdUSB == 0x320 per Mathias suggestion

>> - When host uses custom PSI, SSIC is the psi_uid_count and not SSAC. I missed

>>   this when transferring the previous logic over. Previous implementation

>>   was incorrect. Let's fix it here.

>>

>>  drivers/usb/host/xhci-hub.c | 237 +++++++++++++++++++++++++++++++++++-

>>  1 file changed, 235 insertions(+), 2 deletions(-)

>>

>> diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c

>> index 74c497fd3476..eddd139c2260 100644

>> --- a/drivers/usb/host/xhci-hub.c

>> +++ b/drivers/usb/host/xhci-hub.c

>> @@ -11,6 +11,7 @@

>>  

>>  #include <linux/slab.h>

>>  #include <asm/unaligned.h>

>> +#include <linux/bitfield.h>

>>  

>>  #include "xhci.h"

>>  #include "xhci-trace.h"

>> @@ -52,7 +53,239 @@ static u8 usb_bos_descriptor [] = {

>>  	0xb5, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, tx, ID = 5 */

>>  };

>>  

>> -static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,

>> +/* Default sublink speed attribute of each lane */

>> +static u32 ssp_cap_default_ssa[] = {

>> +	0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */

>> +	0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */

>> +	0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */

>> +	0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */

>> +	0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */

>> +	0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */

>> +	0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */

>> +	0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */

>> +};

>> +

>> +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,

>> +				      u16 wLength)

>> +{

>> +	struct usb_bos_descriptor	*bos;

>> +	struct usb_ss_cap_descriptor	*ss_cap;

>> +	struct usb_ssp_cap_descriptor	*ssp_cap;

>> +	struct xhci_port_cap		*port_cap = NULL;

>> +	u16				bcdUSB;

>> +	u32				reg;

>> +	u32				min_rate = 0;

>> +	u8				min_ssid;

>> +	u8				ssac;

>> +	u8				ssic;

>> +	int				offset;

>> +	int				i;

>> +

>> +	/* BOS descriptor */

>> +	bos = (struct usb_bos_descriptor *)buf;

>> +	bos->bLength = USB_DT_BOS_SIZE;

>> +	bos->bDescriptorType = USB_DT_BOS;

>> +	bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

>> +					USB_DT_USB_SS_CAP_SIZE);

>> +	bos->bNumDeviceCaps = 1;

>> +

>> +	/* Create the descriptor for port with the highest revision */

>> +	for (i = 0; i < xhci->num_port_caps; i++) {

>> +		u8 major = xhci->port_caps[i].maj_rev;

>> +		u8 minor = xhci->port_caps[i].min_rev;

>> +		u16 rev = (major << 8) | minor;

>> +

>> +		if (i == 0 || bcdUSB < rev) {

>> +			bcdUSB = rev;

>> +			port_cap = &xhci->port_caps[i];

>> +		}

>> +	}

>> +

>> +	if (bcdUSB >= 0x0310) {

>> +		if (port_cap->psi_count) {

>> +			u8 num_sym_ssa = 0;

>> +

>> +			for (i = 0; i < port_cap->psi_count; i++) {

>> +				if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM)

>> +					num_sym_ssa++;

>> +			}

>> +

>> +			ssac = port_cap->psi_count + num_sym_ssa - 1;

>> +			ssic = port_cap->psi_uid_count - 1;

>> +		} else {

>> +			if (bcdUSB >= 0x0320)

>> +				ssac = 7;

>> +			else

>> +				ssac = 3;

>> +

>> +			ssic = (ssac + 1) / 2 - 1;

>> +		}

>> +

>> +		bos->bNumDeviceCaps++;

>> +		bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

>> +						USB_DT_USB_SS_CAP_SIZE +

>> +						USB_DT_USB_SSP_CAP_SIZE(ssac));

>> +	}

>> +

>> +	if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE)

>> +		return wLength;

>> +

>> +	/* SuperSpeed USB Device Capability */

>> +	ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE];

>> +	ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;

>> +	ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

>> +	ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;

>> +	ss_cap->bmAttributes = 0; /* set later */

>> +	ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION);

>> +	ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;

>> +	ss_cap->bU1devExitLat = 0; /* set later */

>> +	ss_cap->bU2DevExitLat = 0; /* set later */

>> +

>> +	reg = readl(&xhci->cap_regs->hcc_params);

>> +	if (HCC_LTC(reg))

>> +		ss_cap->bmAttributes |= USB_LTM_SUPPORT;

>> +

>> +	if ((xhci->quirks & XHCI_LPM_SUPPORT)) {

>> +		reg = readl(&xhci->cap_regs->hcs_params3);

>> +		ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);

>> +		ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));

>> +	}

>> +

>> +	if (wLength < le16_to_cpu(bos->wTotalLength))

>> +		return wLength;

>> +

>> +	if (bcdUSB < 0x0310)

>> +		return le16_to_cpu(bos->wTotalLength);

>> +

>> +	ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE +

>> +		USB_DT_USB_SS_CAP_SIZE];

>> +	ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac);

>> +	ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

>> +	ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE;

>> +	ssp_cap->bReserved = 0;

>> +	ssp_cap->wReserved = 0;

>> +	ssp_cap->bmAttributes =

>> +		cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) |

>> +			    FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic));

>> +

>> +	if (!port_cap->psi_count) {

>> +		for (i = 0; i < ssac + 1; i++)

>> +			ssp_cap->bmSublinkSpeedAttr[i] =

>> +				cpu_to_le32(ssp_cap_default_ssa[i]);

>> +

>> +		min_ssid = 4;

>> +		goto out;

>> +	}

>> +

>> +	offset = 0;

>> +	for (i = 0; i < port_cap->psi_count; i++) {

>> +		u32 psi;

>> +		u32 attr;

>> +		u8 ssid;

>> +		u8 lp;

>> +		u8 lse;

>> +		u8 psie;

>> +		u16 lane_mantissa;

>> +		u16 psim;

>> +		u16 plt;

>> +

>> +		psi = port_cap->psi[i];

>> +		ssid = XHCI_EXT_PORT_PSIV(psi);

>> +		lp = XHCI_EXT_PORT_LP(psi);

>> +		psie = XHCI_EXT_PORT_PSIE(psi);

>> +		psim = XHCI_EXT_PORT_PSIM(psi);

>> +		plt = psi & PLT_MASK;

>> +

>> +		lse = psie;

>> +		lane_mantissa = psim;

>> +

>> +		/* Shift to Gbps and set SSP Link Protocol if 10Gpbs */

>> +		for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++)

>> +			psim /= 1000;

>> +

>> +		if (!min_rate || psim < min_rate) {

>> +			min_ssid = ssid;

>> +			min_rate = psim;

>> +		}

>> +

>> +		/* Some host controllers don't set the link protocol for SSP */

>> +		if (psim >= 10)

>> +			lp = USB_SSP_SUBLINK_SPEED_LP_SSP;

> Maybe we should limit the above fix to older than USB 3.2 hosts.

> xHCI supporting Gen 1x2 can in theory have psim==10 even if it only supports

> SS link protocol, not SSP.


Gen 1x2 uses SuperSpeed Plus link protocol. See USB 3.2 spec section 3.2


>

> I'd guess newer USB 3.2 hosts have have fixed the LP bit.  

>

>> +

>> +		/*

>> +		 * PSIM and PSIE represent the total speed of PSI. The BOS

>> +		 * descriptor SSP sublink speed attribute lane mantissa

>> +		 * describes the lane speed. E.g. PSIM and PSIE for gen2x2

>> +		 * is 20Gbps, but the BOS descriptor lane speed mantissa is

>> +		 * 10Gbps. Check and modify the mantissa value to match the

>> +		 * lane speed.

>> +		 */

>> +		if (bcdUSB == 0x0320 && plt == PLT_SYM) {

>> +			/*

>> +			 * The PSI dword for gen1x2 and gen2x1 share the same

>> +			 * values. But the lane speed for gen1x2 is 5Gbps while

>> +			 * gen2x1 is 10Gbps. If the previous PSI dword SSID is

>> +			 * 5 and the PSIE and PSIM match with SSID 6, let's

>> +			 * assume that the controller follows the default speed

>> +			 * id with SSID 6 for gen1x2.

>> +			 */

>> +			if (ssid == 6 && psie == 3 && psim == 10 && i) {

>> +				u32 prev = port_cap->psi[i - 1];

>> +

>> +				if ((prev & PLT_MASK) == PLT_SYM &&

>> +				    XHCI_EXT_PORT_PSIV(prev) == 5 &&

>> +				    XHCI_EXT_PORT_PSIE(prev) == 3 &&

>> +				    XHCI_EXT_PORT_PSIM(prev) == 10) {

>> +					lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;

>> +					lane_mantissa = 5;

> This is a very specific workaround designed for one default case.


Since we can't differentiate between gen1x2 or gen2x1 PSI dword, I want
to the driver to assume by default that the PSI represents gen2x1 unless
it falls into this very specific condition for gen1x2. It's not often
that a device is connected in gen1x2.

>

>> +				}

>> +			}

>> +

>> +			if (psie == 3 && psim > 10) {

>> +				lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;

>> +				lane_mantissa = 10;

>> +			}

> So is this.


This case assumes psim > 10 is for gen2x2 and each lane is capable of up
to 10Gbps. We may change this and set lane_mantissa = psim / 2. But it
complicates the GenXxY description check a bit since it means that we're
expecting some odd number between 5Gbps or 10Gbps.

> Maybe something more generic could be figured out.

> If we assume that bcdUSB >= 0x0320 cases have the Link Protocol (LP) bit is set correctly,

> we could do something like:

>

> if (bcdUSB >= 0x0320)

>   if (LP && psim > 10) || (!LP && (psim > 5))

>     psim = psim / 2  /* divide by lane count */


This won't work well because gen1x2 uses the same SSP link protocol, and
we don't have lane count in PSI dword.

>         

> Not sure if above pseudocode works with SSIC cases 

>

> We are also forcing the speeds to Gbps (current code does this as well)

> This is rounding down non-standard speeds. (SSIC at 2915 Mbps for example)


We're only forcing the speed to Gbps for PSI dwords that we determined
to be gen1x2 and gen2x2. We're not rounding down for others.

>

>> +		}

>> +

>> +		attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) |

>> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) |

>> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) |

>> +			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa));

>> +

>> +		switch (plt) {

>> +		case PLT_SYM:

>> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

>> +					   USB_SSP_SUBLINK_SPEED_ST_SYM_RX);

>> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

>> +

>> +			attr &= ~USB_SSP_SUBLINK_SPEED_ST;

>> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

>> +					   USB_SSP_SUBLINK_SPEED_ST_SYM_TX);

>> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

>> +			break;

>> +		case PLT_ASYM_RX:

>> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

>> +					   USB_SSP_SUBLINK_SPEED_ST_ASYM_RX);

>> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

>> +			break;

>> +		case PLT_ASYM_TX:

>> +			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,

>> +					   USB_SSP_SUBLINK_SPEED_ST_ASYM_TX);

>> +			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);

>> +			break;

>> +		}

>> +	}

>> +out:

>> +	ssp_cap->wFunctionalitySupport =

>> +		cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID,

>> +				       min_ssid) |

>> +			    FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) |

>> +			    FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1));

>> +

>> +	return le16_to_cpu(bos->wTotalLength);

>> +}

>> +

>> +static __maybe_unused int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,

>>  				     u16 wLength)

>>  {

>>  	struct xhci_port_cap *port_cap = NULL;

>> @@ -1137,7 +1370,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,

>>  		if (hcd->speed < HCD_USB3)

>>  			goto error;

>>  

>> -		retval = xhci_create_usb3_bos_desc(xhci, buf, wLength);

>> +		retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength);

>>  		spin_unlock_irqrestore(&xhci->lock, flags);

>>  		return retval;

>>  	case GetPortStatus:

>>

> This is anyways a lot better than what we currently have.

> Would be nice to get those xHCI PSIM values nicely converted to USB 3.x bmSuperSpeedAttr

> Lane Speed Mantissa.

>

> Otherwise everyting looks good

>


Yes, it would be nice if the PSI dwords can just be converted to
bmSublinkSpeedAttr. Thanks for the review.

BR,
Thinh
Mathias Nyman March 10, 2021, 8:28 a.m. UTC | #3
On 9.3.2021 21.32, Thinh Nguyen wrote:
> Mathias Nyman wrote:

>> On 4.2.2021 6.11, Thinh Nguyen wrote:

>>> The current xhci_create_usb3_bos_desc() uses a static bos u8 array and

>>> various magic numbers and offsets making it difficult to extend support

>>> for USB 3.2. Let's rewrite this entire function to support dual-lane in

>>> USB 3.2.

>>>

>>> The hub driver matches the port speed ID from the extended port status

>>> to the SSID of the sublink speed attributes to detect if the device

>>> supports SuperSpeed Plus. Currently we don't provide the default gen1x2

>>> and gen2x2 sublink speed capability descriptor for USB 3.2 roothub. The

>>> USB stack depends on this to detect and match the correct speed.

>>> In addition, if the xHCI host provides Protocol Speed ID (PSI)

>>> capability, then make sure to convert Protocol Speed ID Mantissa and

>>> Exponent (PSIM & PSIE) to lane speed for gen1x2 and gen2x2.

>>>

>>> Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>

>>> ---

>>> Changes in v2:

>>> - Use static array for SSA per Mathias suggestion

>>> - Check bcdUSB >= 0x320 instead of bcdUSB == 0x320 per Mathias suggestion

>>> - When host uses custom PSI, SSIC is the psi_uid_count and not SSAC. I missed

>>>   this when transferring the previous logic over. Previous implementation

>>>   was incorrect. Let's fix it here.

>>>

>>>  drivers/usb/host/xhci-hub.c | 237 +++++++++++++++++++++++++++++++++++-

>>>  1 file changed, 235 insertions(+), 2 deletions(-)

>>>

>>> diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c

>>> index 74c497fd3476..eddd139c2260 100644

>>> --- a/drivers/usb/host/xhci-hub.c

>>> +++ b/drivers/usb/host/xhci-hub.c

>>> @@ -11,6 +11,7 @@

>>>  

>>>  #include <linux/slab.h>

>>>  #include <asm/unaligned.h>

>>> +#include <linux/bitfield.h>

>>>  

>>>  #include "xhci.h"

>>>  #include "xhci-trace.h"

>>> @@ -52,7 +53,239 @@ static u8 usb_bos_descriptor [] = {

>>>  	0xb5, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, tx, ID = 5 */

>>>  };

>>>  

>>> -static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,

>>> +/* Default sublink speed attribute of each lane */

>>> +static u32 ssp_cap_default_ssa[] = {

>>> +	0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */

>>> +	0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */

>>> +	0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */

>>> +	0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */

>>> +	0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */

>>> +	0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */

>>> +	0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */

>>> +	0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */

>>> +};

>>> +

>>> +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,

>>> +				      u16 wLength)

>>> +{

>>> +	struct usb_bos_descriptor	*bos;

>>> +	struct usb_ss_cap_descriptor	*ss_cap;

>>> +	struct usb_ssp_cap_descriptor	*ssp_cap;

>>> +	struct xhci_port_cap		*port_cap = NULL;

>>> +	u16				bcdUSB;

>>> +	u32				reg;

>>> +	u32				min_rate = 0;

>>> +	u8				min_ssid;

>>> +	u8				ssac;

>>> +	u8				ssic;

>>> +	int				offset;

>>> +	int				i;

>>> +

>>> +	/* BOS descriptor */

>>> +	bos = (struct usb_bos_descriptor *)buf;

>>> +	bos->bLength = USB_DT_BOS_SIZE;

>>> +	bos->bDescriptorType = USB_DT_BOS;

>>> +	bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

>>> +					USB_DT_USB_SS_CAP_SIZE);

>>> +	bos->bNumDeviceCaps = 1;

>>> +

>>> +	/* Create the descriptor for port with the highest revision */

>>> +	for (i = 0; i < xhci->num_port_caps; i++) {

>>> +		u8 major = xhci->port_caps[i].maj_rev;

>>> +		u8 minor = xhci->port_caps[i].min_rev;

>>> +		u16 rev = (major << 8) | minor;

>>> +

>>> +		if (i == 0 || bcdUSB < rev) {

>>> +			bcdUSB = rev;

>>> +			port_cap = &xhci->port_caps[i];

>>> +		}

>>> +	}

>>> +

>>> +	if (bcdUSB >= 0x0310) {

>>> +		if (port_cap->psi_count) {

>>> +			u8 num_sym_ssa = 0;

>>> +

>>> +			for (i = 0; i < port_cap->psi_count; i++) {

>>> +				if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM)

>>> +					num_sym_ssa++;

>>> +			}

>>> +

>>> +			ssac = port_cap->psi_count + num_sym_ssa - 1;

>>> +			ssic = port_cap->psi_uid_count - 1;

>>> +		} else {

>>> +			if (bcdUSB >= 0x0320)

>>> +				ssac = 7;

>>> +			else

>>> +				ssac = 3;

>>> +

>>> +			ssic = (ssac + 1) / 2 - 1;

>>> +		}

>>> +

>>> +		bos->bNumDeviceCaps++;

>>> +		bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +

>>> +						USB_DT_USB_SS_CAP_SIZE +

>>> +						USB_DT_USB_SSP_CAP_SIZE(ssac));

>>> +	}

>>> +

>>> +	if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE)

>>> +		return wLength;

>>> +

>>> +	/* SuperSpeed USB Device Capability */

>>> +	ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE];

>>> +	ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;

>>> +	ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

>>> +	ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;

>>> +	ss_cap->bmAttributes = 0; /* set later */

>>> +	ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION);

>>> +	ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;

>>> +	ss_cap->bU1devExitLat = 0; /* set later */

>>> +	ss_cap->bU2DevExitLat = 0; /* set later */

>>> +

>>> +	reg = readl(&xhci->cap_regs->hcc_params);

>>> +	if (HCC_LTC(reg))

>>> +		ss_cap->bmAttributes |= USB_LTM_SUPPORT;

>>> +

>>> +	if ((xhci->quirks & XHCI_LPM_SUPPORT)) {

>>> +		reg = readl(&xhci->cap_regs->hcs_params3);

>>> +		ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);

>>> +		ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));

>>> +	}

>>> +

>>> +	if (wLength < le16_to_cpu(bos->wTotalLength))

>>> +		return wLength;

>>> +

>>> +	if (bcdUSB < 0x0310)

>>> +		return le16_to_cpu(bos->wTotalLength);

>>> +

>>> +	ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE +

>>> +		USB_DT_USB_SS_CAP_SIZE];

>>> +	ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac);

>>> +	ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;

>>> +	ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE;

>>> +	ssp_cap->bReserved = 0;

>>> +	ssp_cap->wReserved = 0;

>>> +	ssp_cap->bmAttributes =

>>> +		cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) |

>>> +			    FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic));

>>> +

>>> +	if (!port_cap->psi_count) {

>>> +		for (i = 0; i < ssac + 1; i++)

>>> +			ssp_cap->bmSublinkSpeedAttr[i] =

>>> +				cpu_to_le32(ssp_cap_default_ssa[i]);

>>> +

>>> +		min_ssid = 4;

>>> +		goto out;

>>> +	}

>>> +

>>> +	offset = 0;

>>> +	for (i = 0; i < port_cap->psi_count; i++) {

>>> +		u32 psi;

>>> +		u32 attr;

>>> +		u8 ssid;

>>> +		u8 lp;

>>> +		u8 lse;

>>> +		u8 psie;

>>> +		u16 lane_mantissa;

>>> +		u16 psim;

>>> +		u16 plt;

>>> +

>>> +		psi = port_cap->psi[i];

>>> +		ssid = XHCI_EXT_PORT_PSIV(psi);

>>> +		lp = XHCI_EXT_PORT_LP(psi);

>>> +		psie = XHCI_EXT_PORT_PSIE(psi);

>>> +		psim = XHCI_EXT_PORT_PSIM(psi);

>>> +		plt = psi & PLT_MASK;

>>> +

>>> +		lse = psie;

>>> +		lane_mantissa = psim;

>>> +

>>> +		/* Shift to Gbps and set SSP Link Protocol if 10Gpbs */

>>> +		for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++)

>>> +			psim /= 1000;

>>> +

>>> +		if (!min_rate || psim < min_rate) {

>>> +			min_ssid = ssid;

>>> +			min_rate = psim;

>>> +		}

>>> +

>>> +		/* Some host controllers don't set the link protocol for SSP */

>>> +		if (psim >= 10)

>>> +			lp = USB_SSP_SUBLINK_SPEED_LP_SSP;

>> Maybe we should limit the above fix to older than USB 3.2 hosts.

>> xHCI supporting Gen 1x2 can in theory have psim==10 even if it only supports

>> SS link protocol, not SSP.

> 

> Gen 1x2 uses SuperSpeed Plus link protocol. See USB 3.2 spec section 3.2

> 


Yes, you're right. I got so used to Gen 1 always being SS, and Gen 2 SSP in USB 3.1.
 
Then those specific workarounds you made also makes sense.
From xhci POV this looks good.

Thanks
-Mathias
diff mbox series

Patch

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 74c497fd3476..eddd139c2260 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -11,6 +11,7 @@ 
 
 #include <linux/slab.h>
 #include <asm/unaligned.h>
+#include <linux/bitfield.h>
 
 #include "xhci.h"
 #include "xhci-trace.h"
@@ -52,7 +53,239 @@  static u8 usb_bos_descriptor [] = {
 	0xb5, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, tx, ID = 5 */
 };
 
-static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,
+/* Default sublink speed attribute of each lane */
+static u32 ssp_cap_default_ssa[] = {
+	0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */
+	0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */
+	0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */
+	0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */
+	0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */
+	0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */
+	0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */
+	0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */
+};
+
+static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,
+				      u16 wLength)
+{
+	struct usb_bos_descriptor	*bos;
+	struct usb_ss_cap_descriptor	*ss_cap;
+	struct usb_ssp_cap_descriptor	*ssp_cap;
+	struct xhci_port_cap		*port_cap = NULL;
+	u16				bcdUSB;
+	u32				reg;
+	u32				min_rate = 0;
+	u8				min_ssid;
+	u8				ssac;
+	u8				ssic;
+	int				offset;
+	int				i;
+
+	/* BOS descriptor */
+	bos = (struct usb_bos_descriptor *)buf;
+	bos->bLength = USB_DT_BOS_SIZE;
+	bos->bDescriptorType = USB_DT_BOS;
+	bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +
+					USB_DT_USB_SS_CAP_SIZE);
+	bos->bNumDeviceCaps = 1;
+
+	/* Create the descriptor for port with the highest revision */
+	for (i = 0; i < xhci->num_port_caps; i++) {
+		u8 major = xhci->port_caps[i].maj_rev;
+		u8 minor = xhci->port_caps[i].min_rev;
+		u16 rev = (major << 8) | minor;
+
+		if (i == 0 || bcdUSB < rev) {
+			bcdUSB = rev;
+			port_cap = &xhci->port_caps[i];
+		}
+	}
+
+	if (bcdUSB >= 0x0310) {
+		if (port_cap->psi_count) {
+			u8 num_sym_ssa = 0;
+
+			for (i = 0; i < port_cap->psi_count; i++) {
+				if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM)
+					num_sym_ssa++;
+			}
+
+			ssac = port_cap->psi_count + num_sym_ssa - 1;
+			ssic = port_cap->psi_uid_count - 1;
+		} else {
+			if (bcdUSB >= 0x0320)
+				ssac = 7;
+			else
+				ssac = 3;
+
+			ssic = (ssac + 1) / 2 - 1;
+		}
+
+		bos->bNumDeviceCaps++;
+		bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +
+						USB_DT_USB_SS_CAP_SIZE +
+						USB_DT_USB_SSP_CAP_SIZE(ssac));
+	}
+
+	if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE)
+		return wLength;
+
+	/* SuperSpeed USB Device Capability */
+	ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE];
+	ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
+	ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+	ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
+	ss_cap->bmAttributes = 0; /* set later */
+	ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION);
+	ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
+	ss_cap->bU1devExitLat = 0; /* set later */
+	ss_cap->bU2DevExitLat = 0; /* set later */
+
+	reg = readl(&xhci->cap_regs->hcc_params);
+	if (HCC_LTC(reg))
+		ss_cap->bmAttributes |= USB_LTM_SUPPORT;
+
+	if ((xhci->quirks & XHCI_LPM_SUPPORT)) {
+		reg = readl(&xhci->cap_regs->hcs_params3);
+		ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);
+		ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));
+	}
+
+	if (wLength < le16_to_cpu(bos->wTotalLength))
+		return wLength;
+
+	if (bcdUSB < 0x0310)
+		return le16_to_cpu(bos->wTotalLength);
+
+	ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE +
+		USB_DT_USB_SS_CAP_SIZE];
+	ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac);
+	ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+	ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE;
+	ssp_cap->bReserved = 0;
+	ssp_cap->wReserved = 0;
+	ssp_cap->bmAttributes =
+		cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) |
+			    FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic));
+
+	if (!port_cap->psi_count) {
+		for (i = 0; i < ssac + 1; i++)
+			ssp_cap->bmSublinkSpeedAttr[i] =
+				cpu_to_le32(ssp_cap_default_ssa[i]);
+
+		min_ssid = 4;
+		goto out;
+	}
+
+	offset = 0;
+	for (i = 0; i < port_cap->psi_count; i++) {
+		u32 psi;
+		u32 attr;
+		u8 ssid;
+		u8 lp;
+		u8 lse;
+		u8 psie;
+		u16 lane_mantissa;
+		u16 psim;
+		u16 plt;
+
+		psi = port_cap->psi[i];
+		ssid = XHCI_EXT_PORT_PSIV(psi);
+		lp = XHCI_EXT_PORT_LP(psi);
+		psie = XHCI_EXT_PORT_PSIE(psi);
+		psim = XHCI_EXT_PORT_PSIM(psi);
+		plt = psi & PLT_MASK;
+
+		lse = psie;
+		lane_mantissa = psim;
+
+		/* Shift to Gbps and set SSP Link Protocol if 10Gpbs */
+		for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++)
+			psim /= 1000;
+
+		if (!min_rate || psim < min_rate) {
+			min_ssid = ssid;
+			min_rate = psim;
+		}
+
+		/* Some host controllers don't set the link protocol for SSP */
+		if (psim >= 10)
+			lp = USB_SSP_SUBLINK_SPEED_LP_SSP;
+
+		/*
+		 * PSIM and PSIE represent the total speed of PSI. The BOS
+		 * descriptor SSP sublink speed attribute lane mantissa
+		 * describes the lane speed. E.g. PSIM and PSIE for gen2x2
+		 * is 20Gbps, but the BOS descriptor lane speed mantissa is
+		 * 10Gbps. Check and modify the mantissa value to match the
+		 * lane speed.
+		 */
+		if (bcdUSB == 0x0320 && plt == PLT_SYM) {
+			/*
+			 * The PSI dword for gen1x2 and gen2x1 share the same
+			 * values. But the lane speed for gen1x2 is 5Gbps while
+			 * gen2x1 is 10Gbps. If the previous PSI dword SSID is
+			 * 5 and the PSIE and PSIM match with SSID 6, let's
+			 * assume that the controller follows the default speed
+			 * id with SSID 6 for gen1x2.
+			 */
+			if (ssid == 6 && psie == 3 && psim == 10 && i) {
+				u32 prev = port_cap->psi[i - 1];
+
+				if ((prev & PLT_MASK) == PLT_SYM &&
+				    XHCI_EXT_PORT_PSIV(prev) == 5 &&
+				    XHCI_EXT_PORT_PSIE(prev) == 3 &&
+				    XHCI_EXT_PORT_PSIM(prev) == 10) {
+					lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;
+					lane_mantissa = 5;
+				}
+			}
+
+			if (psie == 3 && psim > 10) {
+				lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;
+				lane_mantissa = 10;
+			}
+		}
+
+		attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) |
+			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) |
+			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) |
+			FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa));
+
+		switch (plt) {
+		case PLT_SYM:
+			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+					   USB_SSP_SUBLINK_SPEED_ST_SYM_RX);
+			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+
+			attr &= ~USB_SSP_SUBLINK_SPEED_ST;
+			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+					   USB_SSP_SUBLINK_SPEED_ST_SYM_TX);
+			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+			break;
+		case PLT_ASYM_RX:
+			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+					   USB_SSP_SUBLINK_SPEED_ST_ASYM_RX);
+			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+			break;
+		case PLT_ASYM_TX:
+			attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+					   USB_SSP_SUBLINK_SPEED_ST_ASYM_TX);
+			ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+			break;
+		}
+	}
+out:
+	ssp_cap->wFunctionalitySupport =
+		cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID,
+				       min_ssid) |
+			    FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) |
+			    FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1));
+
+	return le16_to_cpu(bos->wTotalLength);
+}
+
+static __maybe_unused int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,
 				     u16 wLength)
 {
 	struct xhci_port_cap *port_cap = NULL;
@@ -1137,7 +1370,7 @@  int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 		if (hcd->speed < HCD_USB3)
 			goto error;
 
-		retval = xhci_create_usb3_bos_desc(xhci, buf, wLength);
+		retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength);
 		spin_unlock_irqrestore(&xhci->lock, flags);
 		return retval;
 	case GetPortStatus: