This is a patch that Thomas Sailer has already integrated into his "usbutils" CVS tree (somewhere at tum.de I guess). I'm forwarding it since the location of that tree is not widely known, and since anyone using "lsusb" will benefit from at least the control timeout and string read fixes. So it'd be nice if the distros picked this up even if usbutils 0.12 takes a while to ship.
- Dave
Here's a patch, which fixes (in rough order of intent :)
- CDC descriptor parsing. This understands the descriptors required by CDC Ethernet, and some used by one ACM modem.
- More realistic control timeouts. They were only 1/10 second, which is *way* too short. Now they use the standard 5-second ceiling, with fewer retries.
- Gentler reads for string descriptors: + if reading all-at-once fails, try smaller chunks. + pass the language code in. it matters.
- Prints a few more endpoint descriptor fields. One was added in usb 2.0, another has been there all along.
- Some minor cleanup.
This makes a significant difference in robustness; the kernel logs don't fill up with so many timeout errors (with 50 retries!), many devices show strings they previously didn't seem to have, I can see the 1300 byte HID report descriptors from an MGE UPS (low speed), and of course now there's a decent start on CDC descriptor dumping!
--- usbutils-0.11/lsusb.c 2002-08-05 23:35:21.000000000 -0700 +++ usbutils.new/lsusb.c 2003-08-31 17:10:25.000000000 -0700 @@ -58,8 +58,8 @@ #define _GNU_SOURCE #include <getopt.h> -#define CTRL_RETRIES 50 -#define CTRL_TIMEOUT 100 /* milliseconds */ +#define CTRL_RETRIES 2 +#define CTRL_TIMEOUT (5*1000) /* milliseconds */ #define USB_DT_CS_DEVICE 0x21 #define USB_DT_CS_CONFIG 0x22 @@ -106,7 +106,6 @@ ctrl.value = value; ctrl.index = index; ctrl.length = size; - ctrl.timeout = 1000; ctrl.data = data; ctrl.timeout = CTRL_TIMEOUT; try = 0; @@ -133,13 +132,33 @@ if (!id || fd == -1) return 0; - b[0] = b[1] = 0xbf; - ret = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | id, 0, sizeof(b), b); + b[0] = b[1] = 0; + + /* try reading all at once ... some devices misbehave here */ + ret = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | id, lang, sizeof(b), b); + if (ret > 0) + goto got_all; + + /* read string length first ... most devices handle this ok */ + ret = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | id, lang, 2, b); + if (ret < 0) { + if (open_mode == O_RDWR) + fprintf(stderr, "cannot peek string descriptor %d, error = %s(%d)\n", id, strerror(errno), errno); + return 0; + } + if (ret < 2 || b[0] < 2 || b[1] != USB_DT_STRING) { + fprintf(stderr, "string descriptor %d invalid (%02x %02x; len=%d)\n", id, b[0], b[1], ret); + return 0; + } + + ret = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | id, lang, b[0], b); if (ret < 0) { if (open_mode == O_RDWR) fprintf(stderr, "cannot get string descriptor %d, error = %s(%d)\n", id, strerror(errno), errno); return 0; } + +got_all: if (ret < 2 || b[0] < 2 || b[1] != USB_DT_STRING) { fprintf(stderr, "string descriptor %d invalid (%02x %02x; len=%d)\n", id, b[0], b[1], ret); return 0; @@ -151,7 +170,9 @@ return wcstombs(buf, w, size); #else for (i = 0; i < ((b[0] - 2) / 2); i++) - buf[i] = b[2+2*i]; + buf[i] = b[3+2*i] + ? '?' /* character's not available in iso-8859/1 */ + : b[2+2*i]; buf[i] = 0; return i; #endif @@ -352,6 +373,9 @@ { static const char *typeattr[] = { "Control", "Isochronous", "Bulk", "Interrupt" }; static const char *syncattr[] = { "none", "Asynchronous", "Adaptive", "Synchronous" }; + static const char *usage[] = { "Data", "Feedback", "Implicit feedback Data", "(reserved)" }; + static const char *hb[] = { "once", "twice", "three times", "(??)" }; + unsigned wMaxPacket = buf[4] | (buf[5] << 8); if (buf[1] != USB_DT_ENDPOINT) printf(" Warning: Invalid descriptor\n"); @@ -364,15 +388,19 @@ " bmAttributes %5u\n" " Transfer Type %s\n" " Synch Type %s\n" - " wMaxPacketSize %5u\n" + " Usage Type %s\n" + " wMaxPacketSize 0x%04x bytes %d %s\n" " bInterval %5u\n", buf[0], buf[1], buf[2], buf[2] & 15, (buf[2] & 0x80) ? "IN" : "OUT", buf[3], typeattr[buf[3] & 3], syncattr[(buf[3] >> 2) & 3], - buf[4] | (buf[5] << 8), buf[6]); + usage[(buf[3] >> 4) & 3], + wMaxPacket, wMaxPacket & 0x3ff, hb [(wMaxPacket >> 11) & 3], + buf[6]); if (buf[0] < 9) { dump_junk(buf, " ", 7); return; } + /* only for audio endpoints */ printf(" bRefresh %5u\n" " bSynchAddress %5u\n", buf[7], buf[8]); @@ -1168,11 +1196,100 @@ } } +static char * +dump_comm_descriptor(int fd, unsigned char *buf, char *indent, u_int16_t lang) +{ + int tmp; + char str [128]; + + switch (buf[2]) { + case 0: + if (buf[0] != 5) + goto bad; + printf( "%sCDC Header:\n" + "%s bcdCDC %x.%02x\n", + indent, + indent, buf[4], buf[3]); + break; + case 0x01: /* call management functional desc */ + if (buf [0] != 5) + goto bad; + printf( "%sCDC Call Management:\n" + "%s bmCapabilities 0x%02x\n", + indent, + indent, buf[3]); + if (buf[3] & 0x01) + printf( "%s call management\n"); + if (buf[3] & 0x02) + printf( "%s use DataInterface\n"); + printf("%s bDataInterface %d\n", indent, buf[4]); + break; + case 0x02: /* acm functional desc */ + if (buf [0] != 4) + goto bad; + printf( "%sCDC ACM:\n" + "%s bmCapabilities %02x\n", + indent, + indent, buf[3]); + if (buf[3] & 0x08) + printf( "%s connection notifications\n", indent); + if (buf[3] & 0x04) + printf( "%s sends break\n", indent); + if (buf[3] & 0x02) + printf( "%s line coding and serial state\n", indent); + if (buf[3] & 0x01) + printf( "%s get/set/clear comm features\n", indent); + break; + case 0x06: /* union desc */ + if (buf [0] < 5) + goto bad; + printf( "%sCDC Union:\n" + "%s bMasterInterface %d\n" + "%s bSlaveInterface ", + indent, + indent, buf [3], + indent); + for (tmp = 4; tmp < buf [0]; tmp++) + printf("%d ", buf [tmp]); + printf("\n"); + break; + case 0x0f: /* ethernet functional desc */ + if (buf [0] != 13) + goto bad; + get_string(fd, str, sizeof str, buf[3], lang); + tmp = buf [7] << 8; + tmp |= buf [6]; tmp <<= 8; + tmp |= buf [5]; tmp <<= 8; + tmp |= buf [4]; + printf( "%sCDC Ethernet:\n" + "%s iMacAddress %d %s\n" + "%s bmEthernetStatistics 0x%08x\n", + indent, + indent, buf[3], (buf[3] && *str) ? str : "(??)", + indent, tmp); + /* FIXME dissect ALL 28 bits */ + printf( "%s wMaxSegmentSize %d\n" + "%s wNumberMCFilters 0x%04x\n" + "%s bNumberPowerFilters %d\n", + indent, (buf[9]<<8)|buf[8], + indent, (buf[11]<<8)|buf[10], + indent, buf[12]); + break; + /* FIXME there are about a dozen more descriptor types */ + default: + return "unsupported comm descriptor"; + } + return 0; + +bad: + return "corrupt comm descriptor"; +} + /* ---------------------------------------------------------------------- */ static void do_config(int fd, unsigned int nr, u_int16_t lang) { - unsigned char buf[1024],*p; + unsigned char buf[1024],*p, *err; unsigned int sz,curinterface; int l; u_int8_t curclass = 0xff, cursubclass = 0xff; @@ -1229,39 +1346,39 @@ break; case USB_DT_CS_DEVICE: - if (curclass == 3) { + if (curclass == USB_CLASS_HID) { dump_hid_device(fd, p, curinterface); break; } - printf(" unknown descriptor type:"); - dump_junk2(p, p[0]); - break; - - case USB_DT_CS_CONFIG: - printf(" unknown descriptor type:"); - dump_junk2(p, p[0]); - break; - - case USB_DT_CS_STRING: - printf(" unknown descriptor type:"); - dump_junk2(p, p[0]); - break; + err = "unknown device class descriptor"; + goto junk; case USB_DT_CS_INTERFACE: - if (curclass == 1 && cursubclass == 1) { - dump_audiocontrol_interface(fd, p, lang); - break; - } - if (curclass == 1 && cursubclass == 2) { - dump_audiostreaming_interface(fd, p); + err = "unknown interface class descriptor"; + switch (curclass) { + case USB_CLASS_AUDIO: + switch (cursubclass) { + case 1: + dump_audiocontrol_interface(fd, p, lang); + break; + case 2: + dump_audiostreaming_interface(fd, p); + break; + case 3: + dump_midistreaming_interface(fd, p, lang); + break; + default: + goto junk; + } break; - } - if (curclass == 1 && cursubclass == 3) { - dump_midistreaming_interface(fd, p, lang); + case USB_CLASS_COMM: + err = dump_comm_descriptor (fd, p, " ", lang); + if (err) + goto junk; break; + default: + goto junk; } - printf(" unknown descriptor type:"); - dump_junk2(p, p[0]); break; case USB_DT_CS_ENDPOINT: @@ -1273,16 +1390,17 @@ dump_midistreaming_endpoint(fd, p); break; } - printf(" unknown descriptor type:"); - dump_junk2(p, p[0]); - break; + err = "unknown endpoint class descriptor"; + goto junk; case USB_DT_HUB: dump_hub(p); break; default: - printf(" unknown descriptor type:"); + err = "unknown descriptor type"; +junk: + printf(" %s:", err); dump_junk2(p, p[0]); } sz -= p[0]; @@ -1303,21 +1421,39 @@ int i, l; u_int16_t lang; - b[0] = b[1] = 0xbf; - l = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, USB_DT_STRING << 8, 0, sizeof(b), b); + /* read string length first, like recent linuxes -- else some devices misbehave */ + b[0] = b[1] = 0; + l = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, USB_DT_STRING << 8, 0, 4, b); if (l < 0) { - if (open_mode == O_RDWR) + if (open_mode == O_RDWR && !quiet) printf(" Language IDs: none (cannot get min. string descriptor; got len=%d, error=%d:%s)\n", l, errno, strerror(errno)); return 0; } - if (l < 4 || b[0] != l) { + if (l == 0) { + if (!quiet) + printf(" Language IDs: none\n"); + return 0; + } + if (l < 4) { + if (!quiet) printf(" Language IDs: none (invalid length string descriptor %02x; len=%d)\n", b[0], l); return 0; } /* save first language ID for further get_string_descriptors */ lang = b[2] | (b[3] << 8); + + /* maybe there's more than one */ + if (b[0] > 4) { + l = usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR, USB_DT_STRING << 8, 0, b[0], b); + if (l < 4 || b[0] != l) { + fprintf(stderr, "string 0 broken re-read, l = %d, b[0] = %d \n", l, b[0]); + b[0] = 4; + b[2] = lang; + b[3] = lang >> 8; + } + } #if 0 printf ("dump_langids: ret=%d:%d, lang=0x%x, length=%d\n", l, errno, lang, b[0]); dump_junk2 (b, 32); @@ -1346,6 +1482,9 @@ dump_device(fd, devdesc, lang); for (i = 0; i < maxcfg; i++) do_config(fd, i, lang); + /* FIXME if it's usb 2.0 and there's a device qualifier, + * optionally dump it and the other-speed config data + */ lang = dump_langids(fd, 0); }