Hello to both alsa-devel and linux-usb-devel,
As most of you who have seen the words "USB Quattro" and "2.6.x" together know, these two don't mix well. I have been investigating this issue for some time and here are a few conclusions: 1. The Quattro's alternate settings (usb jargon) are wrong (not-in-spec), and kernel doesn't accept the device. 2. M-Audio says that it has done nothing wrong and haven't issued firmware updates (which as far as I know, are not possible on the Quattro). 3. USB-IF says that M-Audio are violating the specs, but they can't force them to correct these issues because the USB logo is not on the device (that requires licensing and compliance). Only the USB trident is on the box, and that doesn't apply. USB-IF has said, that these kinds of misinterpretations of the spec are normal, and most operating systems just work around them. This isn't 'the right way to do it', but at the moment it seems like the only way to do it. To remedy this situation I've been working on a patch to the usb core. The patch is attached as a part of this mail. This patch is not finished yet, it has an unloading issue! This is why I'm writing this mail to you all. This patch is for Linux 2.6.2, it patches drivers/usb/core/config.c (cd linux-2.6.2; patch -p0 < ../whereisit?/usbconfig.patch) The changes I've made are commented, but here's a short list: (All changes are for the code that configures new USB devices) - Instead of counting how many alternate settings each interface has, it sets the number of alternate settings to (largest found altsetting)+1. - Thus all the alternate settings are saved at the corresponding cell of the interface->altsetting pointer. If an altsetting is missing, that cell will be left blank. - After parsing each alternate setting, instead of reporting an error for unset altsettings, the code truncates the list. If the list was modified, a new pointer will be allocated and the old one will be freed. With this patch, I've got my Quattro working. The problem is this: If snd_usb_audio finds the soundcard, the card works well, until it is removed. The kernel will see the device disconnect, but after that you cannot reconnect the device. Nothing comes in the logs from connecting the cable. Also, removing the snd_usb_audio module will result in the command hanging. Not even 'kill -KILL [pid]' as root will kill the process. If snd_usb_audio isn't loaded, the soundcard can be reconnected again and again and again without any problems- I can think of a few things that could go wrong - I've missed something that the USB drivers do with the descriptor afterwards, especially when the descirptor is given to the approriate registered driver. Some checks aren't done because all devices should have all alternate settings?... - The snd_usb_audio driver does something wild when all alternative settings aren't set. - Not much testing has been done on snd_usb_audio on 2.[56].x because the Quattro hasn't worked at all. Could someone with more experience in this area help me? Thank you, Sampo Savolainen
--- ../ko/linux-2.6.2/drivers/usb/core/config.c 2004-02-04 05:43:06.000000000 +0200 +++ drivers/usb/core/config.c 2004-02-16 16:36:15.000000000 +0200 @@ -202,8 +202,9 @@ int usb_parse_configuration(struct usb_host_config *config, char *buffer, int size) { int nintf, nintf_orig; - int i, j; + int i, j, k; // added k, v2 18:52 2004-02-15 struct usb_interface *interface; + struct usb_host_interface *altsetbuf; // added, v2 12:17 2004-02-16 char *buffer2; int size2; struct usb_descriptor_header *header; @@ -265,8 +266,20 @@ i, nintf_orig); return -EINVAL; } - if (i < nintf) - ++config->interface[i]->num_altsetting; + if (i < nintf) { + // modified, v2 18:19 2004-02-15 + // instead of increasing the number of altsettings by one per alternate setting + // found, set the number to largest found altsetting plus one + // Example: + // d->bAlternateSetting = 0,1,3 + // config->interface[i]->num_altsetting = 4 + + if (config->interface[i]->num_altsetting < d->bAlternateSetting+1) + config->interface[i]->num_altsetting = d->bAlternateSetting+1; + + // old code + //++config->interface[i]->num_altsetting; + } } else if ((header->bDescriptorType == USB_DT_DEVICE || header->bDescriptorType == USB_DT_CONFIG) && j) { @@ -340,12 +353,40 @@ /* Check for missing altsettings */ for (i = 0; i < nintf; ++i) { interface = config->interface[i]; + size2 = interface->num_altsetting; for (j = 0; j < interface->num_altsetting; ++j) { if (!interface->altsetting[j].desc.bLength) { + // new code, v2 18:54 2004-02-15 + // move interfaces at j+1..interface->num_altsetting one left and + // decrease interface->num_altsetting by one + + warn("missing altsetting %d for interface %d, moving alternate settings left", j, i); + --interface->num_altsetting; + for (k=j; k<interface->num_altsetting; ++k) + interface->altsetting[k] = interface->altsetting[k+1]; + --j; + + /* old code warn("missing altsetting %d for interface %d", j, i); return -EINVAL; + */ } } + if (size2 != interface->num_altsetting) { + // new code, v2 12:34 2004-02-16 + // allocate new interface->altsetting with the truncated size and free + // the old buffer + len = sizeof(*interface->altsetting) * interface->num_altsetting; + + altsetbuf = interface->altsetting; + interface->altsetting = kmalloc(len, GFP_KERNEL); + memcpy(interface->altsetting,altsetbuf,len); + + kfree(altsetbuf); + + warn("truncated alternate settings on interface %d from %d to %d", + i,size2,interface->num_altsetting); + } } return size; @@ -484,3 +525,4 @@ return result; } +