15 Commits
v1.8 ... v2.0.0

Author SHA1 Message Date
Vadim Mikhailov
6e05aee30e uhubctl 2.0.0
* Clarify that uhubctl will automatically handle USB3 duality.
* Bump copyright year.
* Mention more Linux distributions where uhubctl was tested to work.
2018-03-10 18:23:01 -08:00
Vadim Mikhailov
812197fe4b Add support for automatic USB2/3 dual power switch
When compatible USB3 hub connected to USB3 upstream port,
it is detected as 2 independent virtual hubs: USB2 and USB3,
and USB devices will be connected to USB2 or USB3 virtual hub
depending on their capabilities and connection speed.

To control power for physical USB3 hubs, we must turn off/on power
on both USB2 and USB3 virtual hubs for power off/on changes to take effect.

Included fixes:

* Enumerate all USB hubs, but mark ones we will work on with actionable flag.
  Because of this, increased max size of hubs array to 128 (maybe need even more?).
* Moved all code that was getting hub info into get_hub_info().
* Display "power" status as first status flag if port is powered.
  (uhubctl is about USB port power, and we should display power as first flag).
* Display "off" status for USB3 port when it is off (was "5gbps SS.Disabled").
  This makes both USB2 and USB3 status to display "off" consistently.
* Added expert option --exact which disables USB3 duality handling.
* Added small wait (150ms) after turning off USB3 port.
  It seems that USB3 reacts slowly to power off command.
  Perhaps we need to implement more deterministic approach for this,
  e.g. keep querying new power state few times until it has finally changed.
  Oddly enough, power on is always instant and does not need waiting.

This fixes issue #72.
2018-03-10 17:37:46 -08:00
Vadim Mikhailov
4d84b62fc3 Add Plugable USB3-HUB7-81X as supported device
Adding this hub to supported list per this report:
https://github.com/mvp/uhubctl/issues/54#issuecomment-369695596
2018-03-01 12:32:09 -08:00
Vadim Mikhailov
60ba7c915d Add Plugable USB3-HUB7BC as supported device
It turns out that with uhubctl improved USB3 support
this hub actually works great if we turn off power
on both USB2 and USB3 virtual hubs ports (see issue #54).
2018-03-01 11:34:59 -08:00
Vadim Mikhailov
eeff46fa0c Clarify support status for AmazonBasics hubs
It turns out that AmazonBasics hubs actually work well -
USB3 hubs require changing power state for both USB2 and USB3
virtual hubs when connected to USB3 upstream port.

Perhaps we should implement changing state for both USB2 and USB3
automatically, but this will be tracked in separate issue.
2018-02-18 13:02:53 -08:00
Vadim Mikhailov
41dadcea34 Add a note about USB 3.0 hubs duality and usage quirks 2018-02-15 18:30:19 -08:00
mvp
bccb920526 Merge pull request #71 from mvp/usb3
USB 3.0 support
2018-02-15 11:46:27 -08:00
Vadim Mikhailov
fb5d521290 Use appropriate power mask for USB2 vs USB3
USB3 should use USB_SS_PORT_STAT_POWER,
which is different from USB2 value USB_PORT_STAT_POWER.

Thanks to Stanislas Bertrand for spotting this bug!
2018-02-14 15:54:23 -08:00
Vadim Mikhailov
8e667cde10 Add support for USB 3.0
* add support for USB 3.0 (superspeed USB)
* use snprintf (which is weird on Mac)
* get device description in unified way
* trim trailing whitespace from vendor/product strings
* finally remove deprecated -i option
* require hub location to change power if more than one supported hub detected
2018-02-13 12:01:33 -08:00
Vadim Mikhailov
022e321f71 Clarify support status for AmazonBasics hubs
AmazonBasics hubs have conflicting reports about
power switching support (issues #17, #26, #50, #54, #57, #58).
Apparently, there are some hardware revisions
that support it properly, and others do not.

To avoid confusion, we say that not all AmazonBasics hubs
work as expected. That said, it would be great to find out
which hardware revisions actually work.
2018-02-02 17:23:02 -08:00
Vadim Mikhailov
91e987527a Add D-Link DUB-H4 rev D1 as supported device 2018-02-02 11:14:37 -08:00
Vadim Mikhailov
a1b7411c49 Add Cypress CY7C65632 HX2VL as supported device 2018-02-01 15:23:17 -08:00
mvp
79e44f13c7 Merge pull request #63 from jlmcgehee21/patch-1
Add 4 Port Amazon Basics Hub
2018-01-15 14:11:42 -08:00
Jeff McGehee
5aaa9c54e8 Add 4 Port Amazon Basics Hub
```
$ uhubctl
Current status for hub 20-1, vendor 2109:2811, 4 ports
   Port 1: 0100 power
   Port 2: 0100 power
   Port 3: 0100 power
   Port 4: 0100 power
```
2018-01-15 10:24:15 -05:00
Vadim Mikhailov
e53d6d2f36 Added note about Raspberry Pi behavior
Raspberry Pi can only use USB port 2 to control power on all ports.
Trying to control any other port is not guaranteed.
For some models, USB port 1 is wired to control Ethernet/Wifi.

Also, mention Mac support issues and workarounds.
2017-10-02 13:03:12 -07:00
3 changed files with 365 additions and 143 deletions

View File

@@ -1,6 +1,6 @@
uhubctl USB hub per-port power control.
Copyright (c) 2009-2017, Vadim Mikhailov
Copyright (c) 2009-2018, Vadim Mikhailov
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -18,7 +18,8 @@ This is list of known compatible USB hubs:
| Manufacturer | Product | VID:PID | Release | EOL |
|:-------------------|:-------------------------------------------------------|:----------|:--------|:-----|
| AmazonBasics | HU3770V1, 7 Port USB 3.0 Hub with 12V/3A Power Adapter |`2109:2811`| 2013 | |
| AmazonBasics | HU3641V1, 4 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
| AmazonBasics | HU3770V1, 7 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
| Apple | Thunderbolt Display 27" (internal USB hub) | | 2011 | 2016 |
| Apple | USB Keyboard With Numeric Pad (internal USB hub) | | 2011 | |
| Asus | Z87-PLUS Motherboard (onboard USB hubs) | | 2013 | 2016 |
@@ -26,6 +27,8 @@ This is list of known compatible USB hubs:
| Belkin | F5U701-BLK | | 2008 | 2012 |
| Circuitco | Beagleboard-xM (internal USB hub) |`0424:9514`| 2010 | |
| CyberPower | CP-H420P |`0409:0059`| 2004 | |
| Cypress | CY4608 HX2VL development kit |`04b4:6570`| 2012 | |
| D-Link | DUB-H4 rev D1 (black edition, old silver not working) |`05E3:0608`| 2012 | |
| D-Link | DUB-H7 (silver edition only, new black not working) |`2001:F103`| 2005 | 2010 |
| Elecom | U2H-G4S | | 2006 | 2011 |
| Hawking Technology | UH214 | | 2003 | 2008 |
@@ -34,8 +37,10 @@ This is list of known compatible USB hubs:
| Linksys | USB2HUB4 | | 2004 | 2010 |
| Maplin | A08CQ |`0409:0059`| 2008 | 2011 |
| Microchip | EVB-USB2517 | | 2008 | |
| Plugable | USB3-HUB7BC (see USB3 note below) |`2109:0813`| 2015 | |
| Plugable | USB3-HUB7-81X (see USB3 note below) |`2109:0813`| 2012 | |
| Plugable | USB2-HUB10S | | 2010 | |
| Raspberry Pi | Model B+, Model 2 B, Model 3 B | | 2011 | |
| Raspberry Pi | Model B+, Model 2 B, Model 3 B (port 2 only) | | 2011 | |
| Rosewill | RHUB-210 |`0409:005A`| 2011 | 2014 |
| Sanwa Supply | USB-HUB14GPH | | 2001 | 2003 |
| Sunix | SHB4200MA |`0409:0058`| 2006 | 2009 |
@@ -53,11 +58,24 @@ WARNING: turning off built-in USB ports may cut off your keyboard or mouse,
so be careful what ports you are turning off!
USB 3.0 duality note
====================
If you have compatible USB 3.0 hub connected to USB3 upstream port,
it will be detected as 2 independent virtual hubs: USB2 and USB3, and your USB devices will be
connected to USB2 or USB3 virtual hub depending on their capabilities and connection speed.
To control power for such hubs, it is necessary to turn off/on power on **both** USB2 and USB3
virtual hubs for power off/on changes to take effect. `uhubctl` will try to do this automatically
(unless you disable this behavior with option `-e`).
Unfortunately, while most hubs will cut off data USB connection, some may still not cut off VBUS to port,
which means connected phone may still continue to charge from port that is powered off by uhubctl.
Compiling
=========
This utility was tested to compile and work on Linux
(Ubuntu, Redhat/Fedora/CentOS), FreeBSD and Mac OS X.
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, OpenSUSE, Buildroot), FreeBSD and Mac OS X.
While `uhubctl` compiles on Windows, USB power switching does not work on Windows because `libusb`
is using `winusb.sys` driver, which according to Microsoft does not support
@@ -69,15 +87,17 @@ First, you need to install library libusb-1.0 (version 1.0.12 or later):
* Ubuntu: `sudo apt-get install libusb-1.0-0-dev`
* Redhat: `sudo yum install libusb1-devel`
* MacOSX: `brew install libusb`, or `sudo port install libusb-devel`
Note that Mac OS Sierra may be affected by issue https://github.com/libusb/libusb/issues/303.
Until libusb 1.0.22 is released, use `brew install --HEAD libusb` as a workaround.
* FreeBSD: libusb is included by default
* Windows: TBD?
To compile, simply run `make` - this will generate `uhubctl` binary.
Alternatively, for macOS you can build an executable with homebrew's custom tap:
Also, for Mac OS X you can install `uhubctl` with Homebrew custom tap:
```
brew tap mvp/uhubctl https://github.com/mvp/uhubctl/
brew tap mvp/uhubctl https://github.com/mvp/uhubctl
brew install --HEAD uhubctl
```
@@ -96,12 +116,13 @@ On Linux, you may need to run it with `sudo`, or to configure `udev` USB permiss
If you have more than one smart USB hub connected, you should choose
specific hub to control using `-l` (location) parameter.
To find hub locations, simply run uhubctl without any parameters.
Copyright
=========
Copyright (C) 2009-2017 Vadim Mikhailov
Copyright (C) 2009-2018 Vadim Mikhailov
This file can be distributed under the terms and conditions of the
GNU General Public License version 2.

473
uhubctl.c
View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2017 Vadim Mikhailov
* Copyright (c) 2009-2018 Vadim Mikhailov
*
* Utility to turn USB port power on/off
* for USB hubs that support per-port power switching.
@@ -35,6 +35,10 @@
#include <libusb-1.0/libusb.h>
#endif
#if defined(__APPLE__) /* snprintf is not available in pure C mode */
int snprintf(char * __restrict __str, size_t __size, const char * __restrict __format, ...) __printflike(3, 4);
#endif
#if _POSIX_C_SOURCE >= 199309L
#include <time.h> /* for nanosleep */
#endif
@@ -78,6 +82,7 @@ void sleep_ms(int milliseconds)
/* Partially borrowed from linux/usb/ch11.h */
#pragma pack(push,1)
struct usb_hub_descriptor {
unsigned char bDescLength;
unsigned char bDescriptorType;
@@ -87,6 +92,7 @@ struct usb_hub_descriptor {
unsigned char bHubContrCurrent;
unsigned char data[1]; /* use 1 to avoid zero-sized array warning */
};
#pragma pack(pop)
/*
* Hub Status and Hub Change results
@@ -117,6 +123,41 @@ struct usb_port_status {
#define USB_PORT_STAT_INDICATOR 0x1000
/* bits 13 to 15 are reserved */
#define USB_SS_BCD 0x0300
/*
* Additions to wPortStatus bit field from USB 3.0
* See USB 3.0 spec Table 10-10
*/
#define USB_PORT_STAT_LINK_STATE 0x01e0
#define USB_SS_PORT_STAT_POWER 0x0200
#define USB_SS_PORT_STAT_SPEED 0x1c00
#define USB_PORT_STAT_SPEED_5GBPS 0x0000
/* Valid only if port is enabled */
/* Bits that are the same from USB 2.0 */
#define USB_SS_PORT_STAT_MASK (USB_PORT_STAT_CONNECTION | \
USB_PORT_STAT_ENABLE | \
USB_PORT_STAT_OVERCURRENT | \
USB_PORT_STAT_RESET)
/*
* Definitions for PORT_LINK_STATE values
* (bits 5-8) in wPortStatus
*/
#define USB_SS_PORT_LS_U0 0x0000
#define USB_SS_PORT_LS_U1 0x0020
#define USB_SS_PORT_LS_U2 0x0040
#define USB_SS_PORT_LS_U3 0x0060
#define USB_SS_PORT_LS_SS_DISABLED 0x0080
#define USB_SS_PORT_LS_RX_DETECT 0x00a0
#define USB_SS_PORT_LS_SS_INACTIVE 0x00c0
#define USB_SS_PORT_LS_POLLING 0x00e0
#define USB_SS_PORT_LS_RECOVERY 0x0100
#define USB_SS_PORT_LS_HOT_RESET 0x0120
#define USB_SS_PORT_LS_COMP_MOD 0x0140
#define USB_SS_PORT_LS_LOOPBACK 0x0160
/*
* wHubCharacteristics (masks)
* See USB 2.0 spec Table 11-13, offset 3
@@ -141,42 +182,48 @@ static struct libusb_device **usb_devs = NULL;
struct hub_info {
struct libusb_device *dev;
int bcd_usb;
int nports;
int ppps;
int actionable; /* true if this hub is subject to action */
char vendor[16];
char location[32];
char description[256];
};
/* Array of USB hubs we are going to operate on */
#define MAX_HUBS 64
/* Array of all enumerated USB hubs */
#define MAX_HUBS 128
static struct hub_info hubs[MAX_HUBS];
static int hub_count = 0;
static int hub_phys_count = 0;
/* default options */
static char opt_vendor[16] = "";
static char opt_location[16] = ""; /* Hub location a-b.c.d */
static char opt_location[32] = ""; /* Hub location a-b.c.d */
static int opt_ports = ALL_HUB_PORTS; /* Bitmask of ports to operate on */
static int opt_action = POWER_KEEP;
static int opt_delay = 2;
static int opt_repeat = 1;
static int opt_wait = 20; /* wait before repeating in ms */
static int opt_reset = 0; /* reset hub after operation(s) */
static int opt_exact = 0; /* exact location match - disable USB3 duality handling */
static int opt_reset = 0; /* reset hub after operation(s) */
static const struct option long_options[] = {
{ "loc", required_argument, NULL, 'l' },
{ "vendor", required_argument, NULL, 'n' },
/* -i is deprecated, keep it only for backwards compatibility: */
{ "internal", no_argument, NULL, 'i' },
{ "ports", required_argument, NULL, 'p' },
{ "action", required_argument, NULL, 'a' },
{ "delay", required_argument, NULL, 'd' },
{ "repeat", required_argument, NULL, 'r' },
{ "wait", required_argument, NULL, 'w' },
{ "exact", no_argument, NULL, 'e' },
{ "reset", no_argument, NULL, 'R' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, NULL, 0 },
};
int print_usage()
{
printf(
@@ -191,6 +238,7 @@ int print_usage()
"--vendor, -n - limit hub by vendor id [%s] (partial ok).\n"
"--delay, -d - delay for cycle action [%d sec].\n"
"--repeat, -r - repeat power off count [%d] (some devices need it to turn off).\n"
"--exact, -e - exact location (no USB3 duality handling).\n"
"--reset, -R - reset hub after each power-on action, causing all devices to reassociate.\n"
"--wait, -w - wait before repeat power off [%d ms].\n"
"--version, -v - print program version.\n"
@@ -206,40 +254,87 @@ int print_usage()
return 0;
}
/* trim trailing spaces from a string */
static char* rtrim(char* str)
{
int i;
for (i = strlen(str)-1; i>=0 && isspace(str[i]); i--) {
str[i] = 0;
}
return str;
}
/*
* checks if hub is smart hub
* use min_current above 0 to only consider external hubs
* (external hubs have non-zero bHubContrCurrent)
* return value is number of hub ports
* 0 means hub is not smart
* -1 means there is access error
* get USB hub properties.
* most hub_info fields are filled, except for description.
* returns 0 for success and error code for failure.
*/
int is_smart_hub(struct libusb_device *dev, int min_current)
int get_hub_info(struct libusb_device *dev, struct hub_info *info)
{
int rc = 0;
int len = 0;
struct libusb_device_handle *devh = NULL;
unsigned char buf[256] = {0};
unsigned char buf[LIBUSB_DT_HUB_NONVAR_SIZE + 2 + 3] = {0};
struct usb_hub_descriptor *uhd =
(struct usb_hub_descriptor *)buf;
int minlen = sizeof(struct usb_hub_descriptor) - 1;
int minlen = LIBUSB_DT_HUB_NONVAR_SIZE + 2;
struct libusb_device_descriptor desc;
rc = libusb_get_device_descriptor(dev, &desc);
if (rc)
return rc;
if (desc.bDeviceClass != LIBUSB_CLASS_HUB)
return 0;
return LIBUSB_ERROR_INVALID_PARAM;
int bcd_usb = libusb_le16_to_cpu(desc.bcdUSB);
int desc_type = bcd_usb >= USB_SS_BCD ? LIBUSB_DT_SUPERSPEED_HUB
: LIBUSB_DT_HUB;
rc = libusb_open(dev, &devh);
if (rc == 0) {
len = libusb_control_transfer(devh,
LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS
| LIBUSB_RECIPIENT_DEVICE, /* hub status */
LIBUSB_REQUEST_GET_DESCRIPTOR,
LIBUSB_DT_HUB << 8,
desc_type << 8,
0,
buf, sizeof(buf),
USB_CTRL_GET_TIMEOUT
);
if (len >= minlen) {
unsigned char port_numbers[MAX_HUB_CHAIN] = {0};
info->dev = dev;
info->bcd_usb = bcd_usb;
info->nports = uhd->bNbrPorts;
snprintf(
info->vendor, sizeof(info->vendor),
"%04x:%04x",
libusb_le16_to_cpu(desc.idVendor),
libusb_le16_to_cpu(desc.idProduct)
);
/* Convert bus and ports array into USB location string */
int bus = libusb_get_bus_number(dev);
snprintf(info->location, sizeof(info->location), "%d", bus);
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
/*
* libusb_get_port_path is deprecated since libusb v1.0.16,
* therefore use libusb_get_port_numbers when supported
*/
int pcount = libusb_get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
#else
int pcount = libusb_get_port_path(NULL, dev, port_numbers, MAX_HUB_CHAIN);
#endif
int k;
for (k=0; k<pcount; k++) {
char s[8];
snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", port_numbers[k]);
strcat(info->location, s);
}
info->ppps = 0;
/* Logical Power Switching Mode */
int lpsm = uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM;
/* Over-Current Protection Mode */
@@ -249,13 +344,7 @@ int is_smart_hub(struct libusb_device *dev, int min_current)
(ocpm == HUB_CHAR_INDV_PORT_OCPM ||
ocpm == HUB_CHAR_COMMON_OCPM))
{
rc = uhd->bNbrPorts;
/* Internal hubs have zero bHubContrCurrent.
* Ignore them if requested:
*/
if (min_current > 0 && uhd->bHubContrCurrent < min_current) {
rc = -1;
}
info->ppps = 1;
}
} else {
rc = len;
@@ -265,6 +354,7 @@ int is_smart_hub(struct libusb_device *dev, int min_current)
return rc;
}
/*
* Assuming that devh is opened device handle for USB hub,
* return state for given hub port.
@@ -292,22 +382,93 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
return ust.wPortStatus;
}
/*
* Get USB device description as a string.
*
* It will use following format:
*
* "<vid:pid> <vendor> <product> <serial>, <USB x.yz, N ports>"
*
* vid:pid will be always present, but vendor, product or serial
* may be skipped if they are empty or not enough permissions to read them.
* <USB x.yz, N ports> will be present only for USB hubs.
*
* returns 0 for success and error code for failure.
* in case of failure description buffer is not altered.
*/
static int get_device_description(struct libusb_device * dev, char* description, int desc_len)
{
int rc;
int id_vendor = 0;
int id_product = 0;
char vendor[64] = "";
char product[64] = "";
char serial[64] = "";
char ports[64] = "";
struct libusb_device_descriptor desc;
struct libusb_device_handle *devh = NULL;
rc = libusb_get_device_descriptor(dev, &desc);
if (rc)
return rc;
id_vendor = libusb_le16_to_cpu(desc.idVendor);
id_product = libusb_le16_to_cpu(desc.idProduct);
rc = libusb_open(dev, &devh);
if (rc == 0) {
if (desc.iManufacturer) {
libusb_get_string_descriptor_ascii(devh,
desc.iManufacturer, (unsigned char*)vendor, sizeof(vendor));
rtrim(vendor);
}
if (desc.iProduct) {
libusb_get_string_descriptor_ascii(devh,
desc.iProduct, (unsigned char*)product, sizeof(product));
rtrim(product);
}
if (desc.iSerialNumber) {
libusb_get_string_descriptor_ascii(devh,
desc.iSerialNumber, (unsigned char*)serial, sizeof(serial));
rtrim(serial);
}
if (desc.bDeviceClass == LIBUSB_CLASS_HUB) {
struct hub_info info;
rc = get_hub_info(dev, &info);
if (rc == 0) {
snprintf(ports, sizeof(ports), ", USB %x.%02x, %d ports",
info.bcd_usb >> 8, info.bcd_usb & 0xFF, info.nports);
}
}
libusb_close(devh);
}
snprintf(description, desc_len,
"%04x:%04x%s%s%s%s%s%s%s",
id_vendor, id_product,
vendor[0] ? " " : "", vendor,
product[0] ? " " : "", product,
serial[0] ? " " : "", serial,
ports
);
return 0;
}
/*
* show status for hub ports
* nports is number of hub ports
* portmask is bitmap of ports to display
* if portmask is 0, show all ports
*/
static int hub_port_status(struct libusb_device * dev, int nports, int portmask)
static int print_port_status(struct hub_info * hub, int portmask)
{
int port_status;
struct libusb_device_handle * devh = NULL;
int rc = 0;
struct libusb_device *dev = hub->dev;
rc = libusb_open(dev, &devh);
if (rc == 0) {
int port;
for (port = 1; port <= nports; port++) {
for (port = 1; port <= hub->nports; port++) {
if (portmask > 0 && (portmask & (1 << (port-1))) == 0) continue;
port_status = get_port_status(devh, port);
@@ -318,67 +479,64 @@ static int hub_port_status(struct libusb_device * dev, int nports, int portmask)
break;
}
printf(" Port %d: %04x", port, port_status);
printf(" Port %d: %04x", port, port_status);
int id_vendor = 0;
int id_product = 0;
unsigned char vendor[64] = "";
unsigned char product[64] = "";
unsigned char serial[64] = "";
char description[256] = "";
struct libusb_device * udev;
int i = 0;
while ((udev = usb_devs[i++]) != NULL) {
if (libusb_get_parent(udev) == dev &&
libusb_get_port_number(udev) == port)
{
struct libusb_device_descriptor desc;
struct libusb_device_handle *devh = NULL;
rc = libusb_get_device_descriptor(udev, &desc);
if (rc)
continue;
id_vendor = desc.idVendor;
id_product = desc.idProduct;
rc = libusb_open(udev, &devh);
if (rc)
continue;
if (desc.iManufacturer) {
libusb_get_string_descriptor_ascii(devh,
desc.iManufacturer, vendor, sizeof(vendor));
}
if (desc.iProduct) {
libusb_get_string_descriptor_ascii(devh,
desc.iProduct, product, sizeof(product));
}
if (desc.iSerialNumber) {
libusb_get_string_descriptor_ascii(devh,
desc.iSerialNumber, serial, sizeof(serial));
}
libusb_close(devh);
break;
rc = get_device_description(udev, description, sizeof(description));
if (rc == 0)
break;
}
}
printf("%s%s%s%s%s%s%s%s%s%s%s ",
port_status & USB_PORT_STAT_INDICATOR ? " indicator" : "",
port_status & USB_PORT_STAT_TEST ? " test" : "",
port_status & USB_PORT_STAT_HIGH_SPEED ? " highspeed" : "",
port_status & USB_PORT_STAT_LOW_SPEED ? " lowspeed" : "",
port_status & USB_PORT_STAT_POWER ? " power" : "",
port_status & USB_PORT_STAT_RESET ? " reset" : "",
port_status & USB_PORT_STAT_OVERCURRENT ? " oc" : "",
port_status & USB_PORT_STAT_SUSPEND ? " suspend" : "",
port_status & USB_PORT_STAT_ENABLE ? " enable" : "",
port_status & USB_PORT_STAT_CONNECTION ? " connect" : "",
port_status == 0 ? " off" : ""
);
if (port_status & USB_PORT_STAT_CONNECTION) {
printf("[%04x:%04x%s%s%s%s%s%s]",
id_vendor, id_product,
vendor[0] ? " " : "", vendor,
product[0] ? " " : "", product,
serial[0] ? " " : "", serial
);
if (hub->bcd_usb < USB_SS_BCD) {
if (port_status == 0) {
printf(" off");
} else {
if (port_status & USB_PORT_STAT_POWER) printf(" power");
if (port_status & USB_PORT_STAT_INDICATOR) printf(" indicator");
if (port_status & USB_PORT_STAT_TEST) printf(" test");
if (port_status & USB_PORT_STAT_HIGH_SPEED) printf(" highspeed");
if (port_status & USB_PORT_STAT_LOW_SPEED) printf(" lowspeed");
if (port_status & USB_PORT_STAT_SUSPEND) printf(" suspend");
}
} else {
if (port_status == USB_SS_PORT_LS_SS_DISABLED) {
printf(" off");
} else {
int link_state = port_status & USB_PORT_STAT_LINK_STATE;
if (port_status & USB_SS_PORT_STAT_POWER) printf(" power");
if ((port_status & USB_SS_PORT_STAT_SPEED)
== USB_PORT_STAT_SPEED_5GBPS)
{
printf(" 5gbps");
}
if (link_state == USB_SS_PORT_LS_U0) printf(" U0");
if (link_state == USB_SS_PORT_LS_U1) printf(" U1");
if (link_state == USB_SS_PORT_LS_U2) printf(" U2");
if (link_state == USB_SS_PORT_LS_U3) printf(" U3");
if (link_state == USB_SS_PORT_LS_SS_DISABLED) printf(" SS.Disabled");
if (link_state == USB_SS_PORT_LS_RX_DETECT) printf(" Rx.Detect");
if (link_state == USB_SS_PORT_LS_SS_INACTIVE) printf(" SS.Inactive");
if (link_state == USB_SS_PORT_LS_POLLING) printf(" Polling");
if (link_state == USB_SS_PORT_LS_RECOVERY) printf(" Recovery");
if (link_state == USB_SS_PORT_LS_HOT_RESET) printf(" HotReset");
if (link_state == USB_SS_PORT_LS_COMP_MOD) printf(" Compliance");
if (link_state == USB_SS_PORT_LS_LOOPBACK) printf(" Loopback");
}
}
if (port_status & USB_PORT_STAT_RESET) printf(" reset");
if (port_status & USB_PORT_STAT_OVERCURRENT) printf(" oc");
if (port_status & USB_PORT_STAT_ENABLE) printf(" enable");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" connect");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", description);
printf("\n");
}
libusb_close(devh);
@@ -386,75 +544,110 @@ static int hub_port_status(struct libusb_device * dev, int nports, int portmask)
return 0;
}
/*
* Find all smart hubs that we are going to work on and fill hubs[] array.
* This applies possible constraints like location or vendor.
* Returns count of found hubs or negative error code.
* Find all USB hubs and fill hubs[] array.
* Set actionable to 1 on all hubs that we are going to operate on
* (this applies possible constraints like location or vendor).
* Returns count of found actionable physical hubs
* (USB3 hubs are counted once despite having USB2 dual partner).
* In case of error returns negative error code.
*/
static int usb_find_hubs()
{
struct libusb_device *dev;
unsigned char port_numbers[MAX_HUB_CHAIN] = {0};
int perm_ok = 1;
int rc = 0;
int i = 0;
int j = 0;
while ((dev = usb_devs[i++]) != NULL) {
struct libusb_device_descriptor desc;
rc = libusb_get_device_descriptor(dev, &desc);
/* only scan for hubs: */
if (rc == 0 && desc.bDeviceClass != LIBUSB_CLASS_HUB)
continue;
int nports = is_smart_hub(dev, 0);
if (nports < 0) {
continue;
struct hub_info info;
bzero(&info, sizeof(info));
rc = get_hub_info(dev, &info);
if (rc) {
perm_ok = 0; /* USB permission issue? */
}
if (nports > 0) { /* smart hub */
get_device_description(dev, info.description, sizeof(info.description));
if (info.ppps) { /* PPPS is supported */
if (hub_count < MAX_HUBS) {
hubs[hub_count].dev = dev;
hubs[hub_count].nports = nports;
/* Convert bus and ports array into USB location string */
sprintf(
hubs[hub_count].vendor,
"%04x:%04x",
desc.idVendor, desc.idProduct
);
int bus = libusb_get_bus_number(dev);
sprintf(hubs[hub_count].location, "%d", bus);
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
/*
* libusb_get_port_path is deprecated since libusb v1.0.16,
* therefore use libusb_get_port_numbers when supported
*/
int pcount = libusb_get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
#else
int pcount = libusb_get_port_path(NULL, dev, port_numbers, MAX_HUB_CHAIN);
#endif
int k;
for (k=0; k<pcount; k++) {
char s[8];
sprintf(s, "%s%d", k==0 ? "-" : ".", port_numbers[k]);
strcat(hubs[hub_count].location, s);
info.actionable = 1;
if (strlen(opt_location)>0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
}
}
/* apply location and other filters: */
if (strlen(opt_location)>0 && strcasecmp(opt_location, hubs[hub_count].location))
continue;
if (strlen(opt_vendor)>0 && strncasecmp(opt_vendor, hubs[hub_count].vendor, strlen(opt_vendor)))
continue;
if (strlen(opt_vendor)>0) {
if (strncasecmp(opt_vendor, info.vendor, strlen(opt_vendor))) {
info.actionable = 0;
}
}
memcpy(&hubs[hub_count], &info, sizeof(info));
hub_count++;
}
}
}
if (perm_ok == 0 && hub_count == 0) {
hub_phys_count = 0;
for (i=0; i<hub_count; i++) {
/* Check only actionable USB3 hubs: */
if (!hubs[i].actionable)
continue;
if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
hub_phys_count++;
}
if (opt_exact)
continue;
int match = -1;
for (j=0; j<hub_count; j++) {
if (i==j)
continue;
/* Find hub which is USB2/3 dual to the hub above.
* This is quite reliable and predictable on Linux
* but not on Mac, where we may match wrong hub :(
* It will work reliably on Mac if there is
* only one compatible USB3 hub is connected.
* TODO: discover better way to find dual hub.
*/
/* Hub and its dual must be different types: one USB2, another USB3: */
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
(hubs[j].bcd_usb < USB_SS_BCD))
continue;
/* But they must have the same vendor: */
if (strncasecmp(hubs[i].vendor, hubs[j].vendor, 4))
continue;
/* Provisionally we choose this one as dual: */
if (match < 0 && !hubs[j].actionable)
match = j;
/* But if there is exact port path match,
* we prefer it (true for Linux but not Mac):
*/
char *p1 = strchr(hubs[i].location, '-');
char *p2 = strchr(hubs[j].location, '-');
if (p1 && p2 && strcasecmp(p1, p2)==0) {
match = j;
break;
}
}
if (match >= 0)
hubs[match].actionable = 1;
}
if (perm_ok == 0 && hub_phys_count == 0) {
return LIBUSB_ERROR_ACCESS;
}
return hub_count;
return hub_phys_count;
}
int main(int argc, char *argv[])
{
int rc;
@@ -462,7 +655,7 @@ int main(int argc, char *argv[])
int option_index = 0;
for (;;) {
c = getopt_long(argc, argv, "l:n:a:p:d:r:w:hviR",
c = getopt_long(argc, argv, "l:n:a:p:d:r:w:hveR",
long_options, &option_index);
if (c == -1)
break; /* no more options left */
@@ -482,9 +675,6 @@ int main(int argc, char *argv[])
case 'n':
strncpy(opt_vendor, optarg, sizeof(opt_vendor));
break;
case 'i':
/* ignored for backwards compatibility */
break;
case 'p':
if (!strcasecmp(optarg, "all")) { /* all ports is the default */
break;
@@ -519,6 +709,9 @@ int main(int argc, char *argv[])
case 'r':
opt_repeat = atoi(optarg);
break;
case 'e':
opt_exact = 1;
break;
case 'R':
opt_reset = 1;
break;
@@ -590,18 +783,21 @@ int main(int argc, char *argv[])
goto cleanup;
}
if (hub_count > 1 && opt_action >= 0) {
if (hub_phys_count > 1 && opt_action >= 0) {
fprintf(stderr,
"Warning: changing port state for multiple hubs at once.\n"
"Error: changing port state for multiple hubs at once is not supported.\n"
"Use -l to limit operation to one hub!\n"
);
exit(1);
}
int i;
for (i=0; i<hub_count; i++) {
printf("Current status for hub %s, vendor %s, %d ports\n",
hubs[i].location, hubs[i].vendor, hubs[i].nports
if (hubs[i].actionable == 0)
continue;
printf("Current status for hub %s [%s]\n",
hubs[i].location, hubs[i].description
);
hub_port_status(hubs[i].dev, hubs[i].nports, opt_ports);
print_port_status(&hubs[i], opt_ports);
if (opt_action == POWER_KEEP) { /* no action, show status */
continue;
}
@@ -622,14 +818,16 @@ int main(int argc, char *argv[])
for (port=1; port <= hubs[i].nports; port++) {
if ((1 << (port-1)) & ports) {
int port_status = get_port_status(devh, port);
if (k == 0 && !(port_status & USB_PORT_STAT_POWER))
int power_mask = hubs[i].bcd_usb < USB_SS_BCD ? USB_PORT_STAT_POWER
: USB_SS_PORT_STAT_POWER;
if (k == 0 && !(port_status & power_mask))
continue;
if (k == 1 && (port_status & USB_PORT_STAT_POWER))
if (k == 1 && (port_status & power_mask))
continue;
int repeat = 1;
if (k == 0)
repeat = opt_repeat;
if (!(port_status & ~USB_PORT_STAT_POWER))
if (!(port_status & ~power_mask))
repeat = 1;
while (repeat-- > 0) {
rc = libusb_control_transfer(devh,
@@ -648,13 +846,16 @@ int main(int argc, char *argv[])
}
if (k==0 && opt_action == POWER_CYCLE)
sleep_ms(opt_delay * 1000);
/* USB3 hubs need extra delay to actually turn off: */
if (k==0 && hubs[i].bcd_usb >= USB_SS_BCD)
sleep_ms(150);
printf("Sent power %s request\n",
request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"
);
printf("New status for hub %s, vendor %s, %d ports\n",
hubs[i].location, hubs[i].vendor, hubs[i].nports
printf("New status for hub %s [%s]\n",
hubs[i].location, hubs[i].description
);
hub_port_status(hubs[i].dev, hubs[i].nports, opt_ports);
print_port_status(&hubs[i], opt_ports);
if (k == 1 && opt_reset == 1) {
printf("Resetting hub...\n");