diff options
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
| -rw-r--r-- | drivers/usb/host/xhci-hub.c | 320 | 
1 files changed, 203 insertions, 117 deletions
| diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 74c497fd3476..e9b18fc17617 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" @@ -19,151 +20,236 @@  #define	PORT_RWC_BITS	(PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \  			 PORT_RC | PORT_PLC | PORT_PE) -/* USB 3 BOS descriptor and a capability descriptors, combined. - * Fields will be adjusted and added later in xhci_create_usb3_bos_desc() - */ -static u8 usb_bos_descriptor [] = { -	USB_DT_BOS_SIZE,		/*  __u8 bLength, 5 bytes */ -	USB_DT_BOS,			/*  __u8 bDescriptorType */ -	0x0F, 0x00,			/*  __le16 wTotalLength, 15 bytes */ -	0x1,				/*  __u8 bNumDeviceCaps */ -	/* First device capability, SuperSpeed */ -	USB_DT_USB_SS_CAP_SIZE,		/*  __u8 bLength, 10 bytes */ -	USB_DT_DEVICE_CAPABILITY,	/* Device Capability */ -	USB_SS_CAP_TYPE,		/* bDevCapabilityType, SUPERSPEED_USB */ -	0x00,				/* bmAttributes, LTM off by default */ -	USB_5GBPS_OPERATION, 0x00,	/* wSpeedsSupported, 5Gbps only */ -	0x03,				/* bFunctionalitySupport, -					   USB 3.0 speed only */ -	0x00,				/* bU1DevExitLat, set later. */ -	0x00, 0x00,			/* __le16 bU2DevExitLat, set later. */ -	/* Second device capability, SuperSpeedPlus */ -	0x1c,				/* bLength 28, will be adjusted later */ -	USB_DT_DEVICE_CAPABILITY,	/* Device Capability */ -	USB_SSP_CAP_TYPE,		/* bDevCapabilityType SUPERSPEED_PLUS */ -	0x00,				/* bReserved 0 */ -	0x23, 0x00, 0x00, 0x00,		/* bmAttributes, SSAC=3 SSIC=1 */ -	0x01, 0x00,			/* wFunctionalitySupport */ -	0x00, 0x00,			/* wReserved 0 */ -	/* Default Sublink Speed Attributes, overwrite if custom PSI exists */ -	0x34, 0x00, 0x05, 0x00,		/* 5Gbps, symmetric, rx, ID = 4 */ -	0xb4, 0x00, 0x05, 0x00,		/* 5Gbps, symmetric, tx, ID = 4 */ -	0x35, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, rx, ID = 5 */ -	0xb5, 0x40, 0x0a, 0x00,		/* 10Gbps, SSP, symmetric, tx, ID = 5 */ +/* 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_usb3_bos_desc(struct xhci_hcd *xhci, char *buf, -				     u16 wLength) +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, +				      u16 wLength)  { -	struct xhci_port_cap *port_cap = NULL; -	int i, ssa_count; -	u32 temp; -	u16 desc_size, ssp_cap_size, ssa_size = 0; -	bool usb3_1 = false; - -	desc_size = USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE; -	ssp_cap_size = sizeof(usb_bos_descriptor) - desc_size; - -	/* does xhci support USB 3.1 Enhanced SuperSpeed */ +	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++) { -		if (xhci->port_caps[i].maj_rev == 0x03 && -		    xhci->port_caps[i].min_rev >= 0x01) { -			usb3_1 = true; +		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]; -			break;  		}  	} -	if (usb3_1) { -		/* does xhci provide a PSI table for SSA speed attributes? */ +	if (bcdUSB >= 0x0310) {  		if (port_cap->psi_count) { -			/* two SSA entries for each unique PSI ID, RX and TX */ -			ssa_count = port_cap->psi_uid_count * 2; -			ssa_size = ssa_count * sizeof(u32); -			ssp_cap_size -= 16; /* skip copying the default SSA */ +			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;  		} -		desc_size += ssp_cap_size; -	} -	memcpy(buf, &usb_bos_descriptor, min(desc_size, wLength)); -	if (usb3_1) { -		/* modify bos descriptor bNumDeviceCaps and wTotalLength */ -		buf[4] += 1; -		put_unaligned_le16(desc_size + ssa_size, &buf[2]); +		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; -	/* Indicate whether the host has LTM support. */ -	temp = readl(&xhci->cap_regs->hcc_params); -	if (HCC_LTC(temp)) -		buf[8] |= USB_LTM_SUPPORT; +	/* 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; -	/* Set the U1 and U2 exit latencies. */  	if ((xhci->quirks & XHCI_LPM_SUPPORT)) { -		temp = readl(&xhci->cap_regs->hcs_params3); -		buf[12] = HCS_U1_LATENCY(temp); -		put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]); +		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 PSI table exists, add the custom speed attributes from it */ -	if (usb3_1 && port_cap->psi_count) { -		u32 ssp_cap_base, bm_attrib, psi, psi_mant, psi_exp; -		int offset; +	if (wLength < le16_to_cpu(bos->wTotalLength)) +		return wLength; -		ssp_cap_base = USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE; +	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; +	} -		if (wLength < desc_size) -			return wLength; -		buf[ssp_cap_base] = ssp_cap_size + ssa_size; +	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; +		} -		/* attribute count SSAC bits 4:0 and ID count SSIC bits 8:5 */ -		bm_attrib = (ssa_count - 1) & 0x1f; -		bm_attrib |= (port_cap->psi_uid_count - 1) << 5; -		put_unaligned_le32(bm_attrib, &buf[ssp_cap_base + 4]); +		/* Some host controllers don't set the link protocol for SSP */ +		if (psim >= 10) +			lp = USB_SSP_SUBLINK_SPEED_LP_SSP; -		if (wLength < desc_size + ssa_size) -			return wLength;  		/* -		 * Create the Sublink Speed Attributes (SSA) array. -		 * The xhci PSI field and USB 3.1 SSA fields are very similar, -		 * but link type bits 7:6 differ for values 01b and 10b. -		 * xhci has also only one PSI entry for a symmetric link when -		 * USB 3.1 requires two SSA entries (RX and TX) for every link +		 * 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.  		 */ -		offset = desc_size; -		for (i = 0; i < port_cap->psi_count; i++) { -			psi = port_cap->psi[i]; -			psi &= ~USB_SSP_SUBLINK_SPEED_RSVD; -			psi_exp = XHCI_EXT_PORT_PSIE(psi); -			psi_mant = XHCI_EXT_PORT_PSIM(psi); - -			/* Shift to Gbps and set SSP Link BIT(14) if 10Gpbs */ -			for (; psi_exp < 3; psi_exp++) -				psi_mant /= 1000; -			if (psi_mant >= 10) -				psi |= BIT(14); - -			if ((psi & PLT_MASK) == PLT_SYM) { -			/* Symmetric, create SSA RX and TX from one PSI entry */ -				put_unaligned_le32(psi, &buf[offset]); -				psi |= 1 << 7;  /* turn entry to TX */ -				offset += 4; -				if (offset >= desc_size + ssa_size) -					return desc_size + ssa_size; -			} else if ((psi & PLT_MASK) == PLT_ASYM_RX) { -				/* Asymetric RX, flip bits 7:6 for SSA */ -				psi ^= PLT_MASK; +		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;  			} -			put_unaligned_le32(psi, &buf[offset]); -			offset += 4; -			if (offset >= desc_size + ssa_size) -				return desc_size + ssa_size; +		} + +		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;  		}  	} -	/* ssa_size is 0 for other than usb 3.1 hosts */ -	return desc_size + ssa_size; +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 void xhci_common_hub_descriptor(struct xhci_hcd *xhci, @@ -1137,7 +1223,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: | 
