Skip to content

Commit a510d09

Browse files
committed
Implemented reading ContainerID from BOS descriptor to detect USB2/3 dual devices
Per USB 3.0 spec, chapter 11.2: > Within a USB 3.0 hub, both the SuperSpeed and USB 2.0 hub devices shall implement in the hub framework > a common standardized ContainerID to enable software to identify the physical relationship of the hub devices. > The ContainerID descriptor is a part of the BOS descriptor set. * Read ContainerID from BOS descriptor. * get_device_description() now returns struct with vendor, product, serial number as well as full description. * Use ContainerID to improve finding dual hubs. This seems to work very well on any OS. However, this may still fail if two or more identical hubs use the same hardcoded ContainerID In this case, we still try to look for other clues like serial number match if possible. This fixes issue #161.
1 parent d8f7511 commit a510d09

File tree

1 file changed

+79
-62
lines changed

1 file changed

+79
-62
lines changed

uhubctl.c

+79-62
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,24 @@ struct usb_port_status {
178178
/* List of all USB devices enumerated by libusb */
179179
static struct libusb_device **usb_devs = NULL;
180180

181+
struct descriptor_strings {
182+
char vendor[64];
183+
char product[64];
184+
char serial[64];
185+
char description[256];
186+
};
187+
181188
struct hub_info {
182189
struct libusb_device *dev;
183190
int bcd_usb;
184191
int nports;
185192
int ppps;
186193
int actionable; /* true if this hub is subject to action */
194+
char container_id[33]; /* container ID as hex string */
187195
char vendor[16];
188196
char location[32];
189197
int level;
190-
char description[256];
198+
struct descriptor_strings ds;
191199
};
192200

193201
/* Array of all enumerated USB hubs */
@@ -415,6 +423,28 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
415423
} else {
416424
rc = len;
417425
}
426+
/* Get container_id: */
427+
bzero(info->container_id, sizeof(info->container_id));
428+
struct libusb_bos_descriptor *bos;
429+
rc = libusb_get_bos_descriptor(devh, &bos);
430+
if (rc == 0) {
431+
int cap;
432+
for (cap=0; cap < bos->bNumDeviceCaps; cap++) {
433+
if (bos->dev_capability[cap]->bDevCapabilityType == LIBUSB_BT_CONTAINER_ID) {
434+
struct libusb_container_id_descriptor *container_id;
435+
rc = libusb_get_container_id_descriptor(NULL, bos->dev_capability[cap], &container_id);
436+
if (rc == 0) {
437+
int i;
438+
for (i=0; i<16; i++) {
439+
sprintf(info->container_id+i*2, "%02x", container_id->ContainerID[i]);
440+
}
441+
info->container_id[i*2] = 0;
442+
libusb_free_container_id_descriptor(container_id);
443+
}
444+
}
445+
}
446+
libusb_free_bos_descriptor(bos);
447+
}
418448
libusb_close(devh);
419449
}
420450
return rc;
@@ -450,52 +480,50 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
450480

451481

452482
/*
453-
* Get USB device description as a string.
483+
* Get USB device descriptor strings and summary description.
454484
*
455-
* It will use following format:
485+
* Summary will use following format:
456486
*
457487
* "<vid:pid> <vendor> <product> <serial>, <USB x.yz, N ports>"
458488
*
459489
* vid:pid will be always present, but vendor, product or serial
460490
* may be skipped if they are empty or not enough permissions to read them.
461491
* <USB x.yz, N ports> will be present only for USB hubs.
462492
*
463-
* returns 0 for success and error code for failure.
464-
* in case of failure description buffer is not altered.
493+
* Returns 0 for success and error code for failure.
494+
* In case of failure return buffer is not altered.
465495
*/
466496

467-
static int get_device_description(struct libusb_device * dev, char* description, int desc_len)
497+
static int get_device_description(struct libusb_device * dev, struct descriptor_strings * ds)
468498
{
469499
int rc;
470500
int id_vendor = 0;
471501
int id_product = 0;
472-
char vendor[64] = "";
473-
char product[64] = "";
474-
char serial[64] = "";
475502
char ports[64] = "";
476503
struct libusb_device_descriptor desc;
477504
struct libusb_device_handle *devh = NULL;
478505
rc = libusb_get_device_descriptor(dev, &desc);
479506
if (rc)
480507
return rc;
508+
bzero(ds, sizeof(*ds));
481509
id_vendor = libusb_le16_to_cpu(desc.idVendor);
482510
id_product = libusb_le16_to_cpu(desc.idProduct);
483511
rc = libusb_open(dev, &devh);
484512
if (rc == 0) {
485513
if (desc.iManufacturer) {
486514
libusb_get_string_descriptor_ascii(devh,
487-
desc.iManufacturer, (unsigned char*)vendor, sizeof(vendor));
488-
rtrim(vendor);
515+
desc.iManufacturer, (unsigned char*)ds->vendor, sizeof(ds->vendor));
516+
rtrim(ds->vendor);
489517
}
490518
if (desc.iProduct) {
491519
libusb_get_string_descriptor_ascii(devh,
492-
desc.iProduct, (unsigned char*)product, sizeof(product));
493-
rtrim(product);
520+
desc.iProduct, (unsigned char*)ds->product, sizeof(ds->product));
521+
rtrim(ds->product);
494522
}
495523
if (desc.iSerialNumber) {
496524
libusb_get_string_descriptor_ascii(devh,
497-
desc.iSerialNumber, (unsigned char*)serial, sizeof(serial));
498-
rtrim(serial);
525+
desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
526+
rtrim(ds->serial);
499527
}
500528
if (desc.bDeviceClass == LIBUSB_CLASS_HUB) {
501529
struct hub_info info;
@@ -507,12 +535,12 @@ static int get_device_description(struct libusb_device * dev, char* description,
507535
}
508536
libusb_close(devh);
509537
}
510-
snprintf(description, desc_len,
538+
snprintf(ds->description, sizeof(ds->description),
511539
"%04x:%04x%s%s%s%s%s%s%s",
512540
id_vendor, id_product,
513-
vendor[0] ? " " : "", vendor,
514-
product[0] ? " " : "", product,
515-
serial[0] ? " " : "", serial,
541+
ds->vendor[0] ? " " : "", ds->vendor,
542+
ds->product[0] ? " " : "", ds->product,
543+
ds->serial[0] ? " " : "", ds->serial,
516544
ports
517545
);
518546
return 0;
@@ -555,7 +583,8 @@ static int print_port_status(struct hub_info * hub, int portmask)
555583

556584
printf(" Port %d: %04x", port, port_status);
557585

558-
char description[256] = "";
586+
struct descriptor_strings ds;
587+
bzero(&ds, sizeof(ds));
559588
struct libusb_device * udev;
560589
int i = 0;
561590
while ((udev = usb_devs[i++]) != NULL) {
@@ -567,7 +596,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
567596
(memcmp(hub_pn, dev_pn, hub_plen) == 0) &&
568597
libusb_get_port_number(udev) == port)
569598
{
570-
rc = get_device_description(udev, description, sizeof(description));
599+
rc = get_device_description(udev, &ds);
571600
if (rc == 0)
572601
break;
573602
}
@@ -614,7 +643,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
614643
if (port_status & USB_PORT_STAT_ENABLE) printf(" enable");
615644
if (port_status & USB_PORT_STAT_CONNECTION) printf(" connect");
616645

617-
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", description);
646+
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", ds.description);
618647

619648
printf("\n");
620649
}
@@ -652,7 +681,7 @@ static int usb_find_hubs()
652681
if (rc) {
653682
perm_ok = 0; /* USB permission issue? */
654683
}
655-
get_device_description(dev, info.description, sizeof(info.description));
684+
get_device_description(dev, &info.ds);
656685
if (info.ppps) { /* PPPS is supported */
657686
if (hub_count < MAX_HUBS) {
658687
info.actionable = 1;
@@ -682,63 +711,51 @@ static int usb_find_hubs()
682711
/* Check only actionable hubs: */
683712
if (hubs[i].actionable != 1)
684713
continue;
714+
/* Must have non empty container ID: */
715+
if (strlen(hubs[i].container_id) == 0)
716+
continue;
685717
int match = -1;
686718
for (j=0; j<hub_count; j++) {
687719
if (i==j)
688720
continue;
689721

690-
/* Find hub which is USB2/3 dual to the hub above.
691-
* This is quite reliable and predictable on Linux
692-
* but not on Mac, where we may match wrong hub :(
693-
* It will work reliably on Mac if there is
694-
* only one compatible USB3 hub is connected.
695-
* Unfortunately, libusb does not provide any way
696-
* to detect USB2/3 dual hubs.
697-
* TODO: discover better way to find dual hub.
698-
*/
722+
/* Find hub which is USB2/3 dual to the hub above */
699723

700724
/* Hub and its dual must be different types: one USB2, another USB3: */
701725
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
702726
(hubs[j].bcd_usb < USB_SS_BCD))
703727
continue;
704728

705-
/* But they must have the same vendor: */
706-
if (strncasecmp(hubs[i].vendor, hubs[j].vendor, 4))
729+
/* Must have non empty container ID: */
730+
if (strlen(hubs[j].container_id) == 0)
731+
continue;
732+
733+
/* Per USB 3.0 spec chapter 11.2, container IDs must match: */
734+
if (strcmp(hubs[i].container_id, hubs[j].container_id) != 0)
707735
continue;
708736

709-
/* And the same number of ports: */
737+
/* At this point, it should be enough to claim a match.
738+
* However, some devices use hardcoded non-unique container ID.
739+
* We should do few more checks below if multiple such devices are present.
740+
*/
741+
742+
/* If serial number is present, it must match: */
743+
if ((strlen(hubs[i].ds.serial) > 0 || strlen(hubs[j].ds.serial) > 0) &&
744+
strcmp(hubs[i].ds.serial, hubs[j].ds.serial) != 0)
745+
{
746+
continue;
747+
}
748+
749+
/* Hubs should have the same number of ports: */
710750
if (hubs[i].nports != hubs[j].nports)
711751
continue;
712752

713753
/* And the same level: */
714754
if (hubs[i].level != hubs[j].level)
715755
continue;
716756

717-
/* If description is the same, provisionally we choose this one as dual.
718-
* If description contained serial number, this will be most reliable matching.
719-
*/
720-
if (strlen(hubs[i].description) == strlen(hubs[j].description)) {
721-
/* strlen("vvvv:pppp ") + strlen(", USB x.yz, N ports") = 10+19 = 29 */
722-
if (strlen(hubs[i].description) >= 29) {
723-
if (strncmp(hubs[i].description+10, hubs[j].description+10, strlen(hubs[i].description)-29) == 0) {
724-
match = j;
725-
}
726-
}
727-
}
728-
729-
/* Running out of options - provisionally we choose this one as dual: */
730-
if (match < 0 && !hubs[j].actionable)
731-
match = j;
732-
733-
/* But if there is exact port path match,
734-
* we prefer it (true for Linux but not Mac):
735-
*/
736-
char *p1 = strchr(hubs[i].location, '-');
737-
char *p2 = strchr(hubs[j].location, '-');
738-
if (p1 && p2 && strcasecmp(p1, p2)==0) {
739-
match = j;
740-
break;
741-
}
757+
/* Finally, we claim a match: */
758+
match = j;
742759
}
743760
if (match >= 0) {
744761
if (!hubs[match].actionable) {
@@ -912,7 +929,7 @@ int main(int argc, char *argv[])
912929
if (hubs[i].actionable == 0)
913930
continue;
914931
printf("Current status for hub %s [%s]\n",
915-
hubs[i].location, hubs[i].description
932+
hubs[i].location, hubs[i].ds.description
916933
);
917934
print_port_status(&hubs[i], opt_ports);
918935
if (opt_action == POWER_KEEP) { /* no action, show status */
@@ -962,7 +979,7 @@ int main(int argc, char *argv[])
962979
request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"
963980
);
964981
printf("New status for hub %s [%s]\n",
965-
hubs[i].location, hubs[i].description
982+
hubs[i].location, hubs[i].ds.description
966983
);
967984
print_port_status(&hubs[i], opt_ports);
968985

0 commit comments

Comments
 (0)