Here is a new IAD support patch with an update to print an error message if multiple IADs reference the same interface. The IAD spec. along with the structure of the IAD mandates that associated interfaces be consecutively numbered. It is at least possible that someone writing firmware for a USB device could have the two or more IADs overlap each others group of interfaces. IMO the spec. strongly implies that this shouldn't be done, but just in case it happens an error message will be logged.
Best Regards, Craig Nadler
--- a/include/linux/usb.h 2006-03-14 23:54:54.000000000 -0500 +++ b/include/linux/usb.h 2006-03-15 00:04:57.000000000 -0500 @@ -138,6 +138,10 @@ * active alternate setting */ unsigned num_altsetting; /* number of alternate settings */ + /* If there is an interface association descriptor then it will list + * the associated interfaces */ + struct usb_interface_assoc_descriptor *intf_assoc; + int minor; /* minor number this interface is * bound to */ enum usb_interface_condition condition; /* state of binding */ @@ -163,6 +167,7 @@ /* this maximum is arbitrary */ #define USB_MAXINTERFACES 32 +#define USB_MAXIADS USB_MAXINTERFACES/2 /** * struct usb_interface_cache - long-term representation of a device interface @@ -233,6 +238,11 @@ struct usb_config_descriptor desc; char *string; /* iConfiguration string, if present */ + + /* List of any Interface Association Descriptors in this + * configuration. */ + struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; + /* the interfaces associated with this configuration, * stored in no particular order */ struct usb_interface *interface[USB_MAXINTERFACES]; --- a/drivers/usb/core/config.c 2006-01-02 22:21:10.000000000 -0500 +++ b/drivers/usb/core/config.c 2006-03-15 00:09:45.000000000 -0500 @@ -233,6 +233,7 @@ struct usb_descriptor_header *header; int len, retval; u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES]; + u8 iad_num = 0; memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); if (config->desc.bDescriptorType != USB_DT_CONFIG || @@ -310,6 +311,17 @@ ++n; } + } else if (header->bDescriptorType == + USB_DT_INTERFACE_ASSOCIATION) { + if (iad_num == USB_MAXIADS) { + dev_err(ddev, "found more Interface " + "Association Descriptors " + "than allocated for\n"); + } else { + config->intf_assoc[iad_num] = header; + iad_num++; + } + } else if (header->bDescriptorType == USB_DT_DEVICE || header->bDescriptorType == USB_DT_CONFIG) dev_warn(ddev, "config %d contains an unexpected " --- a/drivers/usb/core/devices.c 2006-03-14 23:54:52.000000000 -0500 +++ b/drivers/usb/core/devices.c 2006-03-15 00:47:30.000000000 -0500 @@ -101,6 +101,10 @@ /* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ "C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; +static const char *format_iad = +/* A: FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */ + "A: FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n"; + static const char *format_iface = /* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ "I: If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; @@ -145,6 +149,7 @@ {USB_CLASS_STILL_IMAGE, "still"}, {USB_CLASS_CSCID, "scard"}, {USB_CLASS_CONTENT_SEC, "c-sec"}, + {USB_CLASS_VIDEO, "video"}, {-1, "unk."} /* leave as last */ }; @@ -282,6 +287,21 @@ return start; } +static char *usb_dump_iad_descriptor(char *start, char *end, + const struct usb_interface_assoc_descriptor *iad) +{ + if (start > end) + return start; + start += sprintf(start, format_iad, + iad->bFirstInterface, + iad->bInterfaceCount, + iad->bFunctionClass, + class_decode(iad->bFunctionClass), + iad->bFunctionSubClass, + iad->bFunctionProtocol); + return start; +} + /* TBD: * 0. TBDs * 1. marking active interface altsettings (code lists all, but should mark @@ -318,6 +338,11 @@ if (!config) /* getting these some in 2.3.7; none in 2.3.6 */ return start + sprintf(start, "(null Cfg. desc.)\n"); start = usb_dump_config_descriptor(start, end, &config->desc, active); + for (i = 0; i < USB_MAXIADS; i++) { + if (config->intf_assoc[i] == NULL) break; + start = usb_dump_iad_descriptor(start, end, + config->intf_assoc[i]); + } for (i = 0; i < config->desc.bNumInterfaces; i++) { intfc = config->intf_cache[i]; interface = config->interface[i]; --- a/drivers/usb/core/message.c 2006-03-17 00:49:12.000000000 -0500 +++ b/drivers/usb/core/message.c 2006-03-17 00:38:38.000000000 -0500 @@ -1294,6 +1294,39 @@ kfree(intf); } + + +static struct usb_interface_assoc_descriptor *find_iad( + struct usb_device *dev, + struct usb_host_config *config, + u8 inum) +{ + u8 i; + struct usb_interface_assoc_descriptor *retval = NULL; + + for (i=0; (i < USB_MAXIADS && config->intf_assoc[i]); i++) { + struct usb_interface_assoc_descriptor *intf_assoc; + u8 first_intf, last_intf; + + intf_assoc = config->intf_assoc[i]; + if (intf_assoc->bInterfaceCount == 0) continue; + + first_intf = intf_assoc->bFirstInterface; + last_intf = first_intf + (intf_assoc->bInterfaceCount - 1); + if (inum >= first_intf && inum <= last_intf) { + if (!retval) { + retval = intf_assoc; + } else { + dev_err (&dev->dev, "Interface #%d referenced" + " by multiple IADs\n", inum); + } + } + } + + return retval; +} + + /* * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated @@ -1417,6 +1450,7 @@ intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; + intf->intf_assoc = find_iad(dev, cp, i); kref_get(&intfc->ref); alt = usb_altnum_to_altsetting(intf, 0); --- a/drivers/usb/core/sysfs.c 2006-01-02 22:21:10.000000000 -0500 +++ b/drivers/usb/core/sysfs.c 2006-03-15 02:02:25.000000000 -0500 @@ -439,6 +439,25 @@ device_remove_file (dev, &dev_attr_configuration); } +/* Interface Accociation Descriptor fields */ +#define usb_intf_assoc_attr(field, format_string) \ +static ssize_t \ +show_iad_##field (struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface (dev); \ + \ + return sprintf (buf, format_string, \ + intf->intf_assoc->field); \ +} \ +static DEVICE_ATTR(iad_##field, S_IRUGO, show_iad_##field, NULL); + +usb_intf_assoc_attr (bFirstInterface, "%02x\n") +usb_intf_assoc_attr (bInterfaceCount, "%02d\n") +usb_intf_assoc_attr (bFunctionClass, "%02x\n") +usb_intf_assoc_attr (bFunctionSubClass, "%02x\n") +usb_intf_assoc_attr (bFunctionProtocol, "%02x\n") + /* Interface fields */ #define usb_intf_attr(field, format_string) \ static ssize_t \ @@ -502,6 +521,18 @@ } static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); +static struct attribute *intf_assoc_attrs[] = { + &dev_attr_iad_bFirstInterface.attr, + &dev_attr_iad_bInterfaceCount.attr, + &dev_attr_iad_bFunctionClass.attr, + &dev_attr_iad_bFunctionSubClass.attr, + &dev_attr_iad_bFunctionProtocol.attr, + NULL, +}; +static struct attribute_group intf_assoc_attr_grp = { + .attrs = intf_assoc_attrs, +}; + static struct attribute *intf_attrs[] = { &dev_attr_bInterfaceNumber.attr, &dev_attr_bAlternateSetting.attr, @@ -549,6 +580,8 @@ alt->string = usb_cache_string(udev, alt->desc.iInterface); if (alt->string) device_create_file(&intf->dev, &dev_attr_interface); + if (intf->intf_assoc) + sysfs_create_group(&intf->dev.kobj, &intf_assoc_attr_grp); usb_create_intf_ep_files(intf, udev); } @@ -559,4 +592,6 @@ if (intf->cur_altsetting->string) device_remove_file(&intf->dev, &dev_attr_interface); + if (intf->intf_assoc) + sysfs_remove_group(&intf->dev.kobj, &intf_assoc_attr_grp); }