Skip to content

Commit 44f963d

Browse files
committed
Improve USB2/3 duality matching
Use priority based duality matching. * Support Raspberry Pi 4B better (as it has USB2 hub one level deeper than its USB3 counterpart). * Support M1 Macs (as they seem to place all USB devices to bus 2). * Support Apple mini-dock (it advertises 2 USB2 ports but only 1 USB3 port). * Should support multiple identical hubs (with the same container id) on Linux.
1 parent d3a79ac commit 44f963d

File tree

1 file changed

+83
-40
lines changed

1 file changed

+83
-40
lines changed

uhubctl.c

+83-40
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,16 @@ struct descriptor_strings {
188188
struct hub_info {
189189
struct libusb_device *dev;
190190
int bcd_usb;
191+
int super_speed; /* 1 if super speed hub, and 0 otherwise */
191192
int nports;
192193
int ppps;
193194
int actionable; /* true if this hub is subject to action */
194195
char container_id[33]; /* container ID as hex string */
195196
char vendor[16];
196197
char location[32];
197-
int level;
198+
uint8_t bus;
199+
uint8_t port_numbers[MAX_HUB_CHAIN];
200+
int pn_len; /* length of port numbers */
198201
struct descriptor_strings ds;
199202
};
200203

@@ -385,9 +388,9 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
385388
);
386389

387390
if (len >= minlen) {
388-
unsigned char port_numbers[MAX_HUB_CHAIN] = {0};
389391
info->dev = dev;
390392
info->bcd_usb = bcd_usb;
393+
info->super_speed = (bcd_usb >= USB_SS_BCD);
391394
info->nports = uhd->bNbrPorts;
392395
snprintf(
393396
info->vendor, sizeof(info->vendor),
@@ -397,14 +400,13 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
397400
);
398401

399402
/* Convert bus and ports array into USB location string */
400-
int bus = libusb_get_bus_number(dev);
401-
snprintf(info->location, sizeof(info->location), "%d", bus);
402-
int pcount = get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
403-
info->level = pcount + 1;
403+
info->bus = libusb_get_bus_number(dev);
404+
snprintf(info->location, sizeof(info->location), "%d", info->bus);
405+
info->pn_len = get_port_numbers(dev, info->port_numbers, sizeof(info->port_numbers));
404406
int k;
405-
for (k=0; k<pcount; k++) {
407+
for (k=0; k < info->pn_len; k++) {
406408
char s[8];
407-
snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", port_numbers[k]);
409+
snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", info->port_numbers[k]);
408410
strcat(info->location, s);
409411
}
410412

@@ -434,10 +436,10 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
434436
}
435437
libusb_free_bos_descriptor(bos);
436438

437-
/* Raspberry Pi 4 hack for USB3 root hub: */
439+
/* Raspberry Pi 4B hack for USB3 root hub: */
438440
if (strlen(info->container_id)==0 &&
439441
strcasecmp(info->vendor, "1d6b:0003")==0 &&
440-
info->level==1 &&
442+
info->pn_len==0 &&
441443
info->nports==4 &&
442444
bcd_usb==USB_SS_BCD)
443445
{
@@ -452,7 +454,7 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
452454
/* For 1 port hubs, ganged power switching is the same as per-port: */
453455
lpsm = HUB_CHAR_INDV_PORT_LPSM;
454456
}
455-
/* Raspberry Pi 4 reports inconsistent descriptors, override: */
457+
/* Raspberry Pi 4B reports inconsistent descriptors, override: */
456458
if (lpsm == HUB_CHAR_COMMON_LPSM && strcasecmp(info->vendor, "2109:3431")==0) {
457459
lpsm = HUB_CHAR_INDV_PORT_LPSM;
458460
}
@@ -581,17 +583,9 @@ static int print_port_status(struct hub_info * hub, int portmask)
581583
int port_status;
582584
struct libusb_device_handle * devh = NULL;
583585
int rc = 0;
584-
int hub_bus;
585-
int dev_bus;
586-
unsigned char hub_pn[MAX_HUB_CHAIN];
587-
unsigned char dev_pn[MAX_HUB_CHAIN];
588-
int hub_plen;
589-
int dev_plen;
590586
struct libusb_device *dev = hub->dev;
591587
rc = libusb_open(dev, &devh);
592588
if (rc == 0) {
593-
hub_bus = libusb_get_bus_number(dev);
594-
hub_plen = get_port_numbers(dev, hub_pn, sizeof(hub_pn));
595589
int port;
596590
for (port = 1; port <= hub->nports; port++) {
597591
if (portmask > 0 && (portmask & (1 << (port-1))) == 0) continue;
@@ -611,12 +605,15 @@ static int print_port_status(struct hub_info * hub, int portmask)
611605
struct libusb_device * udev;
612606
int i = 0;
613607
while ((udev = usb_devs[i++]) != NULL) {
608+
uint8_t dev_bus;
609+
uint8_t dev_pn[MAX_HUB_CHAIN];
610+
int dev_plen;
614611
dev_bus = libusb_get_bus_number(udev);
615612
/* only match devices on the same bus: */
616-
if (dev_bus != hub_bus) continue;
613+
if (dev_bus != hub->bus) continue;
617614
dev_plen = get_port_numbers(udev, dev_pn, sizeof(dev_pn));
618-
if ((dev_plen == hub_plen + 1) &&
619-
(memcmp(hub_pn, dev_pn, hub_plen) == 0) &&
615+
if ((dev_plen == hub->pn_len + 1) &&
616+
(memcmp(hub->port_numbers, dev_pn, hub->pn_len) == 0) &&
620617
libusb_get_port_number(udev) == port)
621618
{
622619
rc = get_device_description(udev, &ds);
@@ -625,7 +622,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
625622
}
626623
}
627624

628-
if (hub->bcd_usb < USB_SS_BCD) {
625+
if (!hub->super_speed) {
629626
if (port_status == 0) {
630627
printf(" off");
631628
} else {
@@ -714,7 +711,7 @@ static int usb_find_hubs()
714711
}
715712
}
716713
if (opt_level > 0) {
717-
if (opt_level != info.level) {
714+
if (opt_level != info.pn_len + 1) {
718715
info.actionable = 0;
719716
}
720717
}
@@ -737,16 +734,16 @@ static int usb_find_hubs()
737734
/* Must have non empty container ID: */
738735
if (strlen(hubs[i].container_id) == 0)
739736
continue;
740-
int match = -1;
737+
int best_match = -1;
738+
int best_score = -1;
741739
for (j=0; j<hub_count; j++) {
742740
if (i==j)
743741
continue;
744742

745743
/* Find hub which is USB2/3 dual to the hub above */
746744

747745
/* Hub and its dual must be different types: one USB2, another USB3: */
748-
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
749-
(hubs[j].bcd_usb < USB_SS_BCD))
746+
if (hubs[i].super_speed == hubs[j].super_speed)
750747
continue;
751748

752749
/* Must have non empty container ID: */
@@ -762,25 +759,71 @@ static int usb_find_hubs()
762759
* We should do few more checks below if multiple such devices are present.
763760
*/
764761

762+
/* Hubs should have the same number of ports */
763+
if (hubs[i].nports != hubs[j].nports) {
764+
/* Except for some weird hubs like Apple mini-dock (has 2 usb2 + 1 usb3 ports) */
765+
if (hubs[i].nports + hubs[j].nports > 3) {
766+
continue;
767+
}
768+
}
769+
765770
/* If serial numbers are both present, they must match: */
766771
if ((strlen(hubs[i].ds.serial) > 0 && strlen(hubs[j].ds.serial) > 0) &&
767772
strcmp(hubs[i].ds.serial, hubs[j].ds.serial) != 0)
768773
{
769774
continue;
770775
}
771776

772-
/* Hubs should have the same number of ports: */
773-
if (hubs[i].nports != hubs[j].nports)
774-
continue;
777+
/* We have first possible candidate, but need to keep looking for better one */
775778

776-
/* Finally, we claim a match: */
777-
match = j;
778-
break;
779+
if (best_score < 1) {
780+
best_score = 1;
781+
best_match = j;
782+
}
783+
784+
/* Checks for various levels of USB2 vs USB3 path similarity... */
785+
786+
uint8_t* p1 = hubs[i].port_numbers;
787+
uint8_t* p2 = hubs[j].port_numbers;
788+
int l1 = hubs[i].pn_len;
789+
int l2 = hubs[j].pn_len;
790+
int s1 = hubs[i].super_speed;
791+
int s2 = hubs[j].super_speed;
792+
793+
/* Check if port path is the same after removing top level (needed for M1 Macs): */
794+
if (l1 >= 1 && l1 == l2 && memcmp(p1 + 1, p2 + 1, l1 - 1)==0) {
795+
if (best_score < 2) {
796+
best_score = 2;
797+
best_match = j;
798+
}
799+
}
800+
801+
/* Raspberry Pi 4B hack (USB2 hub is one level deeper than USB3): */
802+
if (l1 + s1 == l2 + s2 && l1 >= s2 && memcmp(p1 + s2, p2 + s1, l1 - s2)==0) {
803+
if (best_score < 3) {
804+
best_score = 3;
805+
best_match = j;
806+
}
807+
}
808+
/* Check if port path is exactly the same: */
809+
if (l1 == l2 && memcmp(p1, p2, l1)==0) {
810+
if (best_score < 4) {
811+
best_score = 4;
812+
best_match = j;
813+
}
814+
/* Give even higher priority if `usb2bus + 1 == usb3bus` (Linux specific): */
815+
if (hubs[i].bus - s1 == hubs[j].bus - s2) {
816+
if (best_score < 5) {
817+
best_score = 5;
818+
best_match = j;
819+
}
820+
}
821+
}
779822
}
780-
if (match >= 0) {
781-
if (!hubs[match].actionable) {
823+
if (best_match >= 0) {
824+
if (!hubs[best_match].actionable) {
782825
/* Use 2 to signify that this is derived dual device */
783-
hubs[match].actionable = 2;
826+
hubs[best_match].actionable = 2;
784827
}
785828
}
786829
}
@@ -789,7 +832,7 @@ static int usb_find_hubs()
789832
for (i=0; i<hub_count; i++) {
790833
if (!hubs[i].actionable)
791834
continue;
792-
if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
835+
if (!hubs[i].super_speed || opt_exact) {
793836
hub_phys_count++;
794837
}
795838
}
@@ -966,8 +1009,8 @@ int main(int argc, char *argv[])
9661009
for (port=1; port <= hubs[i].nports; port++) {
9671010
if ((1 << (port-1)) & ports) {
9681011
int port_status = get_port_status(devh, port);
969-
int power_mask = hubs[i].bcd_usb < USB_SS_BCD ? USB_PORT_STAT_POWER
970-
: USB_SS_PORT_STAT_POWER;
1012+
int power_mask = hubs[i].super_speed ? USB_SS_PORT_STAT_POWER
1013+
: USB_PORT_STAT_POWER;
9711014
if (k == 0 && !(port_status & power_mask))
9721015
continue;
9731016
if (k == 1 && (port_status & power_mask))
@@ -993,7 +1036,7 @@ int main(int argc, char *argv[])
9931036
}
9941037
}
9951038
/* USB3 hubs need extra delay to actually turn off: */
996-
if (k==0 && hubs[i].bcd_usb >= USB_SS_BCD)
1039+
if (k==0 && hubs[i].super_speed)
9971040
sleep_ms(150);
9981041
printf("Sent power %s request\n",
9991042
request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"

0 commit comments

Comments
 (0)