diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 2025261e97a1..339df67b52a6 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -1086,6 +1086,15 @@ int usb_get_bos_descriptor(struct usb_device *dev) case USB_PTM_CAP_TYPE: dev->bos->ptm_cap = (struct usb_ptm_cap_descriptor *)buffer; + break; + case USB_CAP_TYPE_CONFIG_SUMMARY: + /* one such desc per function */ + if (!dev->bos->num_config_summary_desc) + dev->bos->config_summary = + (struct usb_config_summary_descriptor *)buffer; + + dev->bos->num_config_summary_desc++; + break; default: break; } diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index bc8242bc4564..761073d32eac 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -42,6 +42,36 @@ static int is_activesync(struct usb_interface_descriptor *desc) && desc->bInterfaceProtocol == 1; } +static int get_usb_audio_config(struct usb_host_bos *bos) +{ + unsigned int desc_cnt, num_cfg_desc, len = 0; + unsigned char *buffer; + struct usb_config_summary_descriptor *conf_summary; + + if (!bos || !bos->config_summary) + goto done; + + num_cfg_desc = bos->num_config_summary_desc; + conf_summary = bos->config_summary; + buffer = (unsigned char *)conf_summary; + for (desc_cnt = 0; desc_cnt < num_cfg_desc; desc_cnt++) { + conf_summary = + (struct usb_config_summary_descriptor *)(buffer + len); + + len += conf_summary->bLength; + + if (conf_summary->bcdVersion != USB_CONFIG_SUMMARY_DESC_REV || + conf_summary->bClass != USB_CLASS_AUDIO) + continue; + + /* return 1st config as per device preference */ + return conf_summary->bConfigurationIndex[0]; + } + +done: + return -EINVAL; +} + int usb_choose_configuration(struct usb_device *udev) { int i; @@ -145,7 +175,10 @@ int usb_choose_configuration(struct usb_device *udev) insufficient_power, plural(insufficient_power)); if (best) { - i = best->desc.bConfigurationValue; + /* choose device preferred config */ + i = get_usb_audio_config(udev->bos); + if (i < 0) + i = best->desc.bConfigurationValue; dev_dbg(&udev->dev, "configuration #%d chosen from %d choice%s\n", i, num_configs, plural(num_configs)); diff --git a/include/linux/usb.h b/include/linux/usb.h index 7e634dc59d6d..e4d958c47efe 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -402,6 +402,8 @@ struct usb_host_bos { struct usb_ssp_cap_descriptor *ssp_cap; struct usb_ss_container_id_descriptor *ss_id; struct usb_ptm_cap_descriptor *ptm_cap; + struct usb_config_summary_descriptor *config_summary; + unsigned int num_config_summary_desc; }; int __usb_get_extra_descriptor(char *buffer, unsigned size, diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index d5a5caec8fbc..321c7c16656e 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -1081,6 +1081,26 @@ struct usb_ptm_cap_descriptor { */ #define USB_DT_USB_SSP_CAP_SIZE(ssac) (12 + (ssac + 1) * 4) +/* + * Configuration Summary descriptors: Defines a list of device preferred + * configurations. This descriptor may be used by Host software to decide + * which Configuration to use to obtain the desired functionality. + */ +#define USB_CAP_TYPE_CONFIG_SUMMARY 0x10 +#define USB_CONFIG_SUMMARY_DESC_REV 0x100 + +struct usb_config_summary_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDevCapabilityType; + __u16 bcdVersion; + __u8 bClass; + __u8 bSubClass; + __u8 bProtocol; + __u8 bConfigurationCount; + __u8 bConfigurationIndex[]; +} __attribute__((packed)); + /*-------------------------------------------------------------------------*/ /* USB_DT_WIRELESS_ENDPOINT_COMP: companion descriptor associated with