From 254db08e924c509de2bb589b04d39db35ebaedae Mon Sep 17 00:00:00 2001 From: Vadim Mikhailov Date: Wed, 25 Nov 2020 18:01:03 -0800 Subject: [PATCH] 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. --- uhubctl.c | 123 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 40 deletions(-) diff --git a/uhubctl.c b/uhubctl.c index 3541fa8..feeaef5 100644 --- a/uhubctl.c +++ b/uhubctl.c @@ -188,13 +188,16 @@ struct descriptor_strings { struct hub_info { struct libusb_device *dev; int bcd_usb; + int super_speed; /* 1 if super speed hub, and 0 otherwise */ int nports; int ppps; int actionable; /* true if this hub is subject to action */ char container_id[33]; /* container ID as hex string */ char vendor[16]; char location[32]; - int level; + uint8_t bus; + uint8_t port_numbers[MAX_HUB_CHAIN]; + int pn_len; /* length of port numbers */ struct descriptor_strings ds; }; @@ -385,9 +388,9 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info) ); if (len >= minlen) { - unsigned char port_numbers[MAX_HUB_CHAIN] = {0}; info->dev = dev; info->bcd_usb = bcd_usb; + info->super_speed = (bcd_usb >= USB_SS_BCD); info->nports = uhd->bNbrPorts; snprintf( info->vendor, sizeof(info->vendor), @@ -397,14 +400,13 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info) ); /* Convert bus and ports array into USB location string */ - int bus = libusb_get_bus_number(dev); - snprintf(info->location, sizeof(info->location), "%d", bus); - int pcount = get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN); - info->level = pcount + 1; + info->bus = libusb_get_bus_number(dev); + snprintf(info->location, sizeof(info->location), "%d", info->bus); + info->pn_len = get_port_numbers(dev, info->port_numbers, sizeof(info->port_numbers)); int k; - for (k=0; kpn_len; k++) { char s[8]; - snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", port_numbers[k]); + snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", info->port_numbers[k]); strcat(info->location, s); } @@ -434,10 +436,10 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info) } libusb_free_bos_descriptor(bos); - /* Raspberry Pi 4 hack for USB3 root hub: */ + /* Raspberry Pi 4B hack for USB3 root hub: */ if (strlen(info->container_id)==0 && strcasecmp(info->vendor, "1d6b:0003")==0 && - info->level==1 && + info->pn_len==0 && info->nports==4 && bcd_usb==USB_SS_BCD) { @@ -452,7 +454,7 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info) /* For 1 port hubs, ganged power switching is the same as per-port: */ lpsm = HUB_CHAR_INDV_PORT_LPSM; } - /* Raspberry Pi 4 reports inconsistent descriptors, override: */ + /* Raspberry Pi 4B reports inconsistent descriptors, override: */ if (lpsm == HUB_CHAR_COMMON_LPSM && strcasecmp(info->vendor, "2109:3431")==0) { lpsm = HUB_CHAR_INDV_PORT_LPSM; } @@ -581,17 +583,9 @@ static int print_port_status(struct hub_info * hub, int portmask) int port_status; struct libusb_device_handle * devh = NULL; int rc = 0; - int hub_bus; - int dev_bus; - unsigned char hub_pn[MAX_HUB_CHAIN]; - unsigned char dev_pn[MAX_HUB_CHAIN]; - int hub_plen; - int dev_plen; struct libusb_device *dev = hub->dev; rc = libusb_open(dev, &devh); if (rc == 0) { - hub_bus = libusb_get_bus_number(dev); - hub_plen = get_port_numbers(dev, hub_pn, sizeof(hub_pn)); int port; for (port = 1; port <= hub->nports; port++) { if (portmask > 0 && (portmask & (1 << (port-1))) == 0) continue; @@ -611,12 +605,15 @@ static int print_port_status(struct hub_info * hub, int portmask) struct libusb_device * udev; int i = 0; while ((udev = usb_devs[i++]) != NULL) { + uint8_t dev_bus; + uint8_t dev_pn[MAX_HUB_CHAIN]; + int dev_plen; dev_bus = libusb_get_bus_number(udev); /* only match devices on the same bus: */ - if (dev_bus != hub_bus) continue; + if (dev_bus != hub->bus) continue; dev_plen = get_port_numbers(udev, dev_pn, sizeof(dev_pn)); - if ((dev_plen == hub_plen + 1) && - (memcmp(hub_pn, dev_pn, hub_plen) == 0) && + if ((dev_plen == hub->pn_len + 1) && + (memcmp(hub->port_numbers, dev_pn, hub->pn_len) == 0) && libusb_get_port_number(udev) == port) { rc = get_device_description(udev, &ds); @@ -625,7 +622,7 @@ static int print_port_status(struct hub_info * hub, int portmask) } } - if (hub->bcd_usb < USB_SS_BCD) { + if (!hub->super_speed) { if (port_status == 0) { printf(" off"); } else { @@ -714,7 +711,7 @@ static int usb_find_hubs() } } if (opt_level > 0) { - if (opt_level != info.level) { + if (opt_level != info.pn_len + 1) { info.actionable = 0; } } @@ -737,7 +734,8 @@ static int usb_find_hubs() /* Must have non empty container ID: */ if (strlen(hubs[i].container_id) == 0) continue; - int match = -1; + int best_match = -1; + int best_score = -1; for (j=0; j 3) { + continue; + } + } + /* If serial numbers are both present, they must match: */ if ((strlen(hubs[i].ds.serial) > 0 && strlen(hubs[j].ds.serial) > 0) && strcmp(hubs[i].ds.serial, hubs[j].ds.serial) != 0) @@ -769,18 +774,56 @@ static int usb_find_hubs() continue; } - /* Hubs should have the same number of ports: */ - if (hubs[i].nports != hubs[j].nports) - continue; + /* We have first possible candidate, but need to keep looking for better one */ - /* Finally, we claim a match: */ - match = j; - break; + if (best_score < 1) { + best_score = 1; + best_match = j; + } + + /* Checks for various levels of USB2 vs USB3 path similarity... */ + + uint8_t* p1 = hubs[i].port_numbers; + uint8_t* p2 = hubs[j].port_numbers; + int l1 = hubs[i].pn_len; + int l2 = hubs[j].pn_len; + int s1 = hubs[i].super_speed; + int s2 = hubs[j].super_speed; + + /* Check if port path is the same after removing top level (needed for M1 Macs): */ + if (l1 >= 1 && l1 == l2 && memcmp(p1 + 1, p2 + 1, l1 - 1)==0) { + if (best_score < 2) { + best_score = 2; + best_match = j; + } + } + + /* Raspberry Pi 4B hack (USB2 hub is one level deeper than USB3): */ + if (l1 + s1 == l2 + s2 && l1 >= s2 && memcmp(p1 + s2, p2 + s1, l1 - s2)==0) { + if (best_score < 3) { + best_score = 3; + best_match = j; + } + } + /* Check if port path is exactly the same: */ + if (l1 == l2 && memcmp(p1, p2, l1)==0) { + if (best_score < 4) { + best_score = 4; + best_match = j; + } + /* Give even higher priority if `usb2bus + 1 == usb3bus` (Linux specific): */ + if (hubs[i].bus - s1 == hubs[j].bus - s2) { + if (best_score < 5) { + best_score = 5; + best_match = j; + } + } + } } - if (match >= 0) { - if (!hubs[match].actionable) { + if (best_match >= 0) { + if (!hubs[best_match].actionable) { /* Use 2 to signify that this is derived dual device */ - hubs[match].actionable = 2; + hubs[best_match].actionable = 2; } } } @@ -789,7 +832,7 @@ static int usb_find_hubs() for (i=0; i= USB_SS_BCD) + if (k==0 && hubs[i].super_speed) sleep_ms(150); printf("Sent power %s request\n", request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"