30 Commits

Author SHA1 Message Date
Vadim Mikhailov
1b52efddbd Release version 2.3.0 2020-12-13 14:31:05 -08:00
Vadim Mikhailov
71a4274615 Print uhubctl version as last item in --help output 2020-12-13 13:08:32 -08:00
Vadim Mikhailov
d98e6deb6e README clarifications
* Clarify how to build and install
* Clarify USB permissions
* Mention force flag -f
2020-12-13 12:53:28 -08:00
Vadim Mikhailov
ac5a704449 Fix homebrew formula
* Document installing from stable version by default.
* Automatically install pkg-config which benefits getting proper CFLAGS/LDFLAGS.
* Specify license field.
2020-12-11 00:53:40 -08:00
Vadim Mikhailov
1e65ff9b05 Remove MacOS libusb workaround
Now that libusb-1.0.24 is released https://github.com/libusb/libusb/issues/618,
workaround is no longer needed.

Also, replace MacOS X references to just MacOS (since it works on MacOS 11 Big Sur too).

This finally fixes issue #227.
2020-12-10 15:32:44 -08:00
Vadim Mikhailov
77aeb7f30b Implement searching by device description
Add switch --search (-s) to limit hubs by attached device description.
It is recommended to specify search string that is as unique as possible, e.g. device serial number.
It should work for "off" and "cycle" actions, but not for "on" because device would not be visible on USB.

Closes #209.
2020-12-08 18:40:13 -08:00
Vadim Mikhailov
e65d077712 Do not allow just commit id to be used as version
Homebrew is forcing `--no-tags` for cloned repos.
Because we used `git describe --always` brew was choosing raw commit id as version, which is quite bad.
By removing `--always` we will use contents of VERSION file as a backup.
2020-12-08 15:24:41 -08:00
Vadim Mikhailov
0cc09299fe Add a switch to allow operation on unsupported hubs
Show power switching type supported by a hub:

* ppps   - per-port power switching
* ganged - ganged power switching
* nops   - no power switching

Add --force (-f) switch to allow operation on non-ppps hubs.
Use `-f` at your own risk, this most likely will not work to actually switch power.

For ganged hubs, you may need to turn power off for all hub ports to get any effect.
For nops hubs, it is not likely to work at all, but might be useful for informational purposes.

Also, instead of printing recipe on how to fix USB permissions on Linux,
forward user to web page with detailed explanations.

Closes #260, #280.
2020-12-07 18:43:22 -08:00
Vadim Mikhailov
44f963d00f 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.
2020-12-07 11:32:29 -08:00
Vadim Mikhailov
d3a79ace46 Use pkg-config when available
Remove custom handling for different platforms for CFLAGS and LDFLAGS
and prefer using pkg-config when available.
However, this is tested to still build ok on Linux on Mac howebrew
even if pkg-config is not available.

Closes #292, should also simplify issues raised by #142, #147, #289 and 5db248771e.
2020-12-05 21:24:17 -08:00
Vadim Mikhailov
0e733b6901 Add a note that D-Link DUB-H7 rev F is not supported
Closes #291.
2020-12-01 11:37:07 -08:00
Vadim Mikhailov
50d5501d91 Clarify how to configure udev permissions for USB3 hubs 2020-11-27 11:07:36 -08:00
Jim Klimov
9183aef17e Makefile: add support for "SunOS" (e.g. with modern illumos) 2020-11-26 14:56:16 -08:00
Vadim Mikhailov
4b10fdbd24 Add Anker AK-68ANHUB-BV7A-0004 as supported device
Closes #287.
2020-11-25 09:12:04 -08:00
Julius Henke
3cc17baae1 Add Dell S2719DGF to compatible hubs list 2020-11-24 12:09:22 -08:00
Vadim Mikhailov
db8c4a59f3 Document another workaround for getting working libusb on MacOS
Previous workaround of getting working `libusb` on MacOS is no longer supported by Homebrew,
so now recommended fix is to install `libusb` from HEAD.

This workaround will be removed once libusb 1.0.24 is released and is provided by Homebrew by default.
2020-11-11 12:35:59 -08:00
Vadim Mikhailov
77d8851c34 Add Microchip EVB-USB2534BC as supported device
Closes #277.
2020-11-01 10:39:34 -08:00
Vadim Mikhailov
dcb1e611f7 Add Rosonway RSH-16 as compatible device
This hub is built from 5 * 4-port USB chips in daisy chain. Unfortunately, it advertises
identical bos container IDs for all 5 hubs of them, which will cause issues controlling power in USB3 mode -
one would have to call uhubctl twice with `-e` to turn off both USB2 and USB3 hub pair ports properly.

Rosonway also sells this hub under RSHTECH brand in some markets.

Closes #276.
2020-10-23 10:44:06 -07:00
Vadim Mikhailov
a7b5f6b362 Add a note about USB authorization on Linux 2020-10-06 10:54:08 -07:00
Vadim Mikhailov
2a4df4ee6c Warn about possible incompatibility for Dell P2416D 2020-10-06 10:53:57 -07:00
Vadim Mikhailov
f610598227 Fixed accidentally dropped AmazonBasics HUC9002V1ESL
Also split HU900* and HUC900* items to two separate lines to avoid table becoming too wide.
2020-09-14 23:35:18 -07:00
Vadim Mikhailov
3bc22e3cd7 Clarify RPi4B support for USB-C OTG port
Closes #266.
2020-09-09 12:10:17 -07:00
Vadim Mikhailov
0f6218dfb8 Add AmazonBasics HUC9003V1EBL 7-port USB 3.1 hub as supported device
Closes #261.
2020-08-11 20:48:36 -07:00
Vadim Mikhailov
91035987e4 Mention that D-Link DUB-H4 rev F is not supported
Closes #259.
2020-08-05 15:22:25 -07:00
Vadim Mikhailov
c6927085ec Add NVidia Jetson Nano B01 as supported device
Closes #258.
2020-08-01 01:24:40 -07:00
Vadim Mikhailov
5b1ae79b05 Clarify RPi4B firmware requirement and checking current version
Firmware VL805 00137ac was an alpha verson that was never released.
Change firmware requirement to stable version VL805 00137ad,
and provide easy way to check if it is compliant.
2020-07-25 13:34:43 -07:00
Vadim Mikhailov
b8a1b808e7 Remove StarTech ST4200USBM from supported table
There are multiple reports that VBUS off is not supported by ST4200USBM.
Closes #254.
2020-07-06 11:07:26 -07:00
Vadim Mikhailov
f75bda6896 Removed Belkin F5U701-BLK from supported devices list
Unfortunately, F5U701-BLK does not support per-port power switching, as reported in #252.
2020-06-29 18:15:17 -07:00
Vadim Mikhailov
e171813596 Update device table for AmazonBasics 10 port hub HU[C]9002V1{SBL,ESL}
Apparently all following hubs: HU9002V1SBL, HU9002V1ESL, HUC9002V1SBL, HUC9002V1EBL
are almost the same, with C models including USB-C cable.
Update device table to reflect this.

Closes #246.
2020-06-25 13:25:48 -07:00
Vadim Mikhailov
5257d8567d Add D-Link DUB-H7 rev E as supported device
Closes #244.
2020-06-17 16:34:57 -07:00
5 changed files with 248 additions and 139 deletions

View File

@@ -1,9 +1,17 @@
class Uhubctl < Formula
desc "control USB hubs powering per-port"
desc "USB hub per-port power control"
homepage "https://github.com/mvp/uhubctl"
head "https://github.com/mvp/uhubctl.git"
url "https://github.com/mvp/uhubctl/archive/v2.2.0.tar.gz"
sha256 "e5a722cb41967903bedbab4eea566ab332241a7f05fc7bc9c386b9a5e1762d8b"
license "GPL-2.0"
depends_on "libusb"
depends_on "pkg-config" => :build
livecheck do
url :stable
end
def install
system "make"

View File

@@ -14,34 +14,26 @@ RM := rm -rf
CC ?= gcc
CFLAGS ?= -g -O0
CFLAGS += -Wall -Wextra -std=c99 -pedantic
GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --always --tags | cut -c2-)
GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
ifeq ($(GIT_VERSION),)
GIT_VERSION := $(shell cat VERSION)
GIT_VERSION := $(shell cat VERSION)
endif
CFLAGS += -DPROGRAM_VERSION=\"$(GIT_VERSION)\"
# Use hardening options on Linux
ifeq ($(UNAME_S),Linux)
LDFLAGS += -Wl,-zrelro,-znow -lusb-1.0
LDFLAGS += -Wl,-zrelro,-znow
endif
ifeq ($(UNAME_S),Darwin)
ifneq ($(wildcard /opt/local/include),)
# MacPorts
CFLAGS += -I/opt/local/include
LDFLAGS += -L/opt/local/lib
endif
# Use pkg-config if available
ifneq (,$(shell which pkg-config))
CFLAGS += $(shell pkg-config --cflags libusb-1.0)
LDFLAGS += $(shell pkg-config --libs libusb-1.0)
else
# But it should still build if pkg-config is not available (e.g. Linux or Mac homebrew)
LDFLAGS += -lusb-1.0
endif
ifeq ($(UNAME_S),FreeBSD)
LDFLAGS += -lusb
endif
ifeq ($(UNAME_S),NetBSD)
CFLAGS += $(shell pkg-config --cflags libusb-1.0)
LDFLAGS += $(shell pkg-config --libs libusb-1.0)
endif
PROGRAM = uhubctl
$(PROGRAM): $(PROGRAM).c

View File

@@ -20,8 +20,10 @@ This is list of known compatible USB hubs:
|:-------------------|:-----------------------------------------------------|:------|:----|:----------|:--------|:-----|
| AmazonBasics | HU3641V1 ([RPi issue](https://goo.gl/CLt46M)) | 4 | 3.0 |`2109:2811`| 2013 | |
| AmazonBasics | HU3770V1 ([RPi issue](https://goo.gl/CLt46M)) | 7 | 3.0 |`2109:2811`| 2013 | |
| AmazonBasics | HU9002V1SBL ([RPi issue](https://goo.gl/CLt46M)) | 10 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HUC9002V1ESL ([RPi issue](https://goo.gl/CLt46M)) | 10 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HU9003V1EBL | 7 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HU9002V1SBL, HU9002V1ESL | 10 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HUC9002V1SBL, HUC9002V1EBL, HUC9002V1ESL | 10 | 3.1 |`2109:2817`| 2018 | |
| Anker | AK-68ANHUB-BV7A-0004 | 7 | 3.0 |`2109:0812`| 2014 | |
| Apple | Thunderbolt Display 27" (internal USB hub) | 6 | 2.0 | | 2011 | 2016 |
| Apple | USB Keyboard With Numeric Pad (internal USB hub) | 3 | 2.0 | | 2011 | |
| Asus | Z87-PLUS Motherboard (onboard USB hub) | 4 | 3.0 | | 2013 | 2016 |
@@ -29,7 +31,6 @@ This is list of known compatible USB hubs:
| B+B SmartWorx | USH304 | 4 | 3.0 |`04B4:6506`| 2017 | |
| Basler | 2000036234 | 4 | 3.0 |`0451:8046`| 2016 | |
| Belkin | F5U101 | 4 | 2.0 |`0451:2046`| 2005 | 2010 |
| Belkin | F5U701-BLK | 7 | 2.0 | | 2008 | 2012 |
| Buffalo | BSH4A05U3BK | 4 | 3.0 |`05E3:0610`| 2015 | |
| Bytecc | BT-UH340 | 4 | 3.0 |`2109:8110`| 2010 | |
| Circuitco | Beagleboard-xM (internal USB hub) | 4 | 2.0 |`0424:9514`| 2010 | |
@@ -37,10 +38,11 @@ This is list of known compatible USB hubs:
| CyberPower | CP-H420P | 4 | 2.0 |`0409:0059`| 2004 | |
| Cypress | CY4608 HX2VL development kit | 4 | 2.0 |`04B4:6570`| 2012 | |
| D-Link | DUB-H4 rev B (silver) | 4 | 2.0 |`05E3:0605`| 2005 | 2010 |
| D-Link | DUB-H4 rev D,E (black). Note: rev A,C not supported | 4 | 2.0 |`05E3:0608`| 2012 | |
| D-Link | DUB-H4 rev D,E (black). Note: rev A,C,F not supported| 4 | 2.0 |`05E3:0608`| 2012 | |
| D-Link | DUB-H7 rev A (silver) | 7 | 2.0 |`2001:F103`| 2005 | 2010 |
| D-Link | DUB-H7 rev D (black). Note: rev B,C not supported | 7 | 2.0 |`05E3:0608`| 2012 | |
| Dell | P2416D 24" QHD Monitor | 4 | 2.0 | | 2017 | |
| D-Link | DUB-H7 rev D,E (black). Note: rev B,C,F not supported| 7 | 2.0 |`05E3:0608`| 2012 | |
| Dell | P2416D 24" QHD Monitor ([note](https://git.io/JUAu8))| 4 | 2.0 | | 2017 | |
| Dell | S2719DGF 27" WQHD Gaming-Monitor | 5 | 3.1 |`0424:5734`| 2018 | |
| Dell | UltraSharp 1704FPT 17" LCD Monitor | 4 | 2.0 |`0424:A700`| 2005 | 2015 |
| Dell | UltraSharp U2415 24" LCD Monitor | 5 | 3.0 | | 2014 | |
| Elecom | U2H-G4S | 4 | 2.0 | | 2006 | 2011 |
@@ -58,7 +60,9 @@ This is list of known compatible USB hubs:
| Linksys | USB2HUB4 | 4 | 2.0 | | 2004 | 2010 |
| Maplin | A08CQ | 7 | 2.0 |`0409:0059`| 2008 | 2011 |
| Microchip | EVB-USB2517 | 7 | 2.0 | | 2008 | |
| Microchip | EVB-USB2534BC | 4 | 2.0 | | 2013 | |
| Moxa | Uport-407 | 7 | 2.0 |`110A:0407`| 2009 | |
| NVidia | Jetson Nano B01 ([details](https://git.io/JJaFR)) | 4 | 3.0 | | 2019 | |
| Phidgets | HUB0003_0 | 7 | 2.0 |`1A40:0201`| 2017 | |
| Plugable | USB3-HUB7BC | 7 | 3.0 |`2109:0813`| 2015 | |
| Plugable | USB3-HUB7C | 7 | 3.0 |`2109:0813`| 2015 | |
@@ -68,9 +72,9 @@ This is list of known compatible USB hubs:
| Raspberry Pi | 4B ([see below](#raspberry-pi-4b)) | 4 | 3.0 |`2109:3431`| 2019 | |
| Renesas | uPD720202 PCIe USB 3.0 host controller | 2 | 3.0 | | 2013 | |
| Rosewill | RHUB-210 | 4 | 2.0 |`0409:005A`| 2011 | 2014 |
| Rosonway | RSH-A16 ([note](https://git.io/JTawg)) | 16 | 3.2 |`0bda:0411`| 2020 | |
| Sanwa Supply | USB-HUB14GPH | 4 | 1.1 | | 2001 | 2003 |
| Seagate | Backup Plus Hub STEL8000100 | 2 | 3.0 |`0BC2:AB44`| 2016 | |
| StarTech | ST4200USBM | 4 | 2.0 |`0409:005A`| 2004 | |
| Sunix | SHB4200MA | 4 | 2.0 |`0409:0058`| 2006 | 2009 |
| Targus | PAUH212U | 7 | 2.0 | | 2004 | 2009 |
| Texas Instruments | TUSB4041PAPEVM | 4 | 2.1 |`0451:8142`| 2015 | |
@@ -103,46 +107,57 @@ Compiling
=========
This utility was tested to compile and work on Linux
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD and Mac OS X.
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD, SunOS and MacOS.
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
[necessary USB control requests](https://social.msdn.microsoft.com/Forums/sqlserver/en-US/f680b63f-ca4f-4e52-baa9-9e64f8eee101).
This may be fixed if `libusb` starts supporting different driver on Windows.
Note that it is highly recommended to have `pkg-config` installed (many platforms provide it by default).
First, you need to install library libusb-1.0 (version 1.0.12 or later, 1.0.16 or later is recommended):
* 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`
> :warning: `libusb-1.0.23` is [broken](https://github.com/libusb/libusb/issues/707) on MacOS Catalina!
You have to install `libusb-1.0.22` until [libusb issue 707](https://github.com/libusb/libusb/issues/707) is fixed,
or use this workaround to force use of older Mojave build:
brew uninstall --ignore-dependencies libusb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/5314f1d/Formula/libusb.rb
* MacOS: `brew install libusb`, or `sudo port install libusb-devel`
* FreeBSD: libusb is included by default
* NetBSD: `sudo pkgin install libusb1 gmake pkg-config`
* Windows: TBD?
To fetch uhubctl source:
To fetch uhubctl source and compile it:
git clone https://github.com/mvp/uhubctl
cd uhubctl
make
This should generate `uhubctl` binary.
You can install it in your system as `/usr/sbin/uhubctl` using:
sudo make install
To compile, simply run `make` - this will generate `uhubctl` binary.
Note that on some OS (e.g. FreeBSD/NetBSD) you need to use `gmake` instead to build.
Also, for Mac OS X you can install `uhubctl` with Homebrew custom tap:
Also, on MacOS you can install `uhubctl` with all necessary dependencies in one shot using Homebrew tap:
```
brew tap mvp/uhubctl https://github.com/mvp/uhubctl
brew install --HEAD uhubctl
brew install uhubctl
```
To build/install from master branch, use `--HEAD`:
```
brew install uhubctl --HEAD
```
Usage
=====
> :warning: On Linux, use `sudo` or configure USB permissions as described below!
To list all supported hubs:
uhubctl
You can control the power on a USB port(s) like this:
uhubctl -a off -p 2
@@ -169,10 +184,16 @@ Linux USB permissions
On Linux, you should configure `udev` USB permissions (otherwise you will have to run it as root using `sudo uhubctl`).
To fix USB permissions, first run `sudo uhubctl` and note all `vid:pid` for hubs you need to control.
Then, add one or more udev rules like below to file `/etc/udev/rules.d/52-usb.rules` (replace with your vendor id):
Then, add one or more udev rules like below to file `/etc/udev/rules.d/52-usb.rules` (replace 2001 with your vendor id):
SUBSYSTEM=="usb", ATTR{idVendor}=="2001", MODE="0666"
Note that for USB3 hubs, some hubs use different vendor ID for USB2 vs USB3 components of the same chip,
and both need permissions to make uhubctl work properly. E.g. for Raspberry Pi 4B, you need to add these 2 lines:
SUBSYSTEM=="usb", ATTR{idVendor}=="2109", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="1d6b", MODE="0666"
If you don't like wide open mode `0666`, you can restrict access by group like this:
SUBSYSTEM=="usb", ATTR{idVendor}=="2001", MODE="0664", GROUP="dialout"
@@ -194,7 +215,8 @@ FAQ
According to USB 2.0 specification, USB hubs can advertise no power switching,
ganged (all ports at once) power switching or per-port (individual) power switching.
Note that `uhubctl` will only detect USB hubs which support per-port power switching.
Note that by default `uhubctl` will only detect USB hubs which support per-port power switching
(but you can force it to try operating on unsupported hubs with option `-f`).
You can find what kind of power switching your hardware supports by using `sudo lsusb -v`:
No power switching:
@@ -261,8 +283,11 @@ You can use option `-r N` where N is some number from 10 to 1000 to fix this -
`uhubctl` will try to turn power off many times in quick succession, and it should suppress that.
This may be eventually fixed in kernel, see more discussion [here](https://bit.ly/2JzczjZ).
If your device is USB mass storage, invoking `udisksctl` before calling `uhubctl`
might help to mitigate this issue:
Disabling USB authorization for device in question before turning power off with `uhubctl` should help:
echo 0 > sudo tee /sys/bus/usb/devices/${location}.${port}/authorized
If your device is USB mass storage, invoking `udisksctl` before calling `uhubctl` should help too:
sudo udisksctl power-off --block-device /dev/disk/...`
sudo uhubctl -a off ...
@@ -307,8 +332,9 @@ For reference, supported Raspberry Pi models have following internal USB topolog
##### Raspberry Pi 4B
> :warning: You may need to [update firmware](https://www.raspberrypi.org/documentation/hardware/raspberrypi/booteeprom.md)
to vl805 00137ac or later to make power switching work on RPi 4B.
> :warning: If your VL805 firmware is older than `00137ad` (check with `sudo rpi-eeprom-update`),
you have to [update firmware](https://www.raspberrypi.org/documentation/hardware/raspberrypi/booteeprom.md)
to make power switching work on RPi 4B.
* USB2 hub `1`, 1 port, only connects hub `1-1` below.
@@ -320,9 +346,7 @@ to vl805 00137ac or later to make power switching work on RPi 4B.
uhubctl -l 2 -a 0
* USB2 hub `3`, 1 port, OTG controller:
uhubctl -l 3 -p 1 -a 0
* USB2 hub `3`, 1 port, OTG controller. Power switching is [not supported](https://git.io/JUc5Q).
As a workaround, you can buy any external USB hub from supported list,

View File

@@ -1 +1 @@
v2.2.0-dev
2.3.0

265
uhubctl.c
View File

@@ -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 lpsm; /* logical power switching mode */
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;
};
@@ -206,6 +209,7 @@ static int hub_phys_count = 0;
/* default options */
static char opt_vendor[16] = "";
static char opt_search[64] = ""; /* Search by attached device description */
static char opt_location[32] = ""; /* Hub location a-b.c.d */
static int opt_level = 0; /* Hub location level (e.g., a-b is level 2, a-b.c is level 3)*/
static int opt_ports = ALL_HUB_PORTS; /* Bitmask of ports to operate on */
@@ -215,10 +219,12 @@ static int opt_repeat = 1;
static int opt_wait = 20; /* wait before repeating in ms */
static int opt_exact = 0; /* exact location match - disable USB3 duality handling */
static int opt_reset = 0; /* reset hub after operation(s) */
static int opt_force = 0; /* force operation even on unsupported hubs */
static const struct option long_options[] = {
{ "location", required_argument, NULL, 'l' },
{ "vendor", required_argument, NULL, 'n' },
{ "search", required_argument, NULL, 's' },
{ "level", required_argument, NULL, 'L' },
{ "ports", required_argument, NULL, 'p' },
{ "action", required_argument, NULL, 'a' },
@@ -226,6 +232,7 @@ static const struct option long_options[] = {
{ "repeat", required_argument, NULL, 'r' },
{ "wait", required_argument, NULL, 'w' },
{ "exact", no_argument, NULL, 'e' },
{ "force", no_argument, NULL, 'f' },
{ "reset", no_argument, NULL, 'R' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
@@ -236,7 +243,7 @@ static const struct option long_options[] = {
static int print_usage()
{
printf(
"uhubctl %s: utility to control USB port power for smart hubs.\n"
"uhubctl: utility to control USB port power for smart hubs.\n"
"Usage: uhubctl [options]\n"
"Without options, show status for all smart hubs.\n"
"\n"
@@ -246,20 +253,23 @@ static int print_usage()
"--location, -l - limit hub by location [all smart hubs].\n"
"--level -L - limit hub by location level (e.g. a-b.c is level 3).\n"
"--vendor, -n - limit hub by vendor id [%s] (partial ok).\n"
"--search, -s - limit hub by attached device description.\n"
"--delay, -d - delay for cycle action [%g 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"
"--force, -f - force operation even on unsupported hubs.\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"
"--help, -h - print this text.\n"
"\n"
"Send bugs and requests to: https://github.com/mvp/uhubctl\n",
PROGRAM_VERSION,
"Send bugs and requests to: https://github.com/mvp/uhubctl\n"
"version: %s\n",
strlen(opt_vendor) ? opt_vendor : "any",
opt_delay,
opt_repeat,
opt_wait
opt_wait,
PROGRAM_VERSION
);
return 0;
}
@@ -385,9 +395,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 +407,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; k<pcount; k++) {
for (k=0; k < info->pn_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 +443,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)
{
@@ -445,26 +454,18 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
}
}
info->ppps = 0;
/* Logical Power Switching Mode */
int lpsm = uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM;
if (lpsm == HUB_CHAR_COMMON_LPSM && info->nports == 1) {
/* 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;
}
/* Over-Current Protection Mode */
int ocpm = uhd->wHubCharacteristics[0] & HUB_CHAR_OCPM;
/* LPSM must be supported per-port, and OCPM per port or ganged */
if ((lpsm == HUB_CHAR_INDV_PORT_LPSM) &&
(ocpm == HUB_CHAR_INDV_PORT_OCPM ||
ocpm == HUB_CHAR_COMMON_OCPM))
{
info->ppps = 1;
}
info->lpsm = lpsm;
rc = 0;
} else {
rc = len;
}
@@ -522,7 +523,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
int rc;
int id_vendor = 0;
int id_product = 0;
char ports[64] = "";
char hub_specific[64] = "";
struct libusb_device_descriptor desc;
struct libusb_device_handle *devh = NULL;
rc = libusb_get_device_descriptor(dev, &desc);
@@ -552,8 +553,16 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
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);
const char * lpsm_type;
if (info.lpsm == HUB_CHAR_INDV_PORT_LPSM) {
lpsm_type = "ppps";
} else if (info.lpsm == HUB_CHAR_COMMON_LPSM) {
lpsm_type = "ganged";
} else {
lpsm_type = "nops";
}
snprintf(hub_specific, sizeof(hub_specific), ", USB %x.%02x, %d ports, %s",
info.bcd_usb >> 8, info.bcd_usb & 0xFF, info.nports, lpsm_type);
}
}
libusb_close(devh);
@@ -564,7 +573,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
ds->vendor[0] ? " " : "", ds->vendor,
ds->product[0] ? " " : "", ds->product,
ds->serial[0] ? " " : "", ds->serial,
ports
hub_specific
);
return 0;
}
@@ -581,17 +590,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 +612,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 +629,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 {
@@ -703,30 +707,63 @@ static int usb_find_hubs()
rc = get_hub_info(dev, &info);
if (rc) {
perm_ok = 0; /* USB permission issue? */
continue;
}
get_device_description(dev, &info.ds);
if (info.ppps) { /* PPPS is supported */
if (hub_count < MAX_HUBS) {
info.actionable = 1;
if (strlen(opt_location) > 0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
if (info.lpsm != HUB_CHAR_INDV_PORT_LPSM && !opt_force) {
continue;
}
info.actionable = 1;
if (strlen(opt_search) > 0) {
/* Search by attached device description */
info.actionable = 0;
struct libusb_device * udev;
int k = 0;
while ((udev = usb_devs[k++]) != NULL) {
uint8_t dev_pn[MAX_HUB_CHAIN];
uint8_t dev_bus = libusb_get_bus_number(udev);
/* only match devices on the same bus: */
if (dev_bus != info.bus) continue;
int dev_plen = get_port_numbers(udev, dev_pn, sizeof(dev_pn));
if ((dev_plen == info.pn_len + 1) &&
(memcmp(info.port_numbers, dev_pn, info.pn_len) == 0))
{
struct descriptor_strings ds;
bzero(&ds, sizeof(ds));
rc = get_device_description(udev, &ds);
if (rc != 0)
break;
if (strstr(ds.description, opt_search)) {
info.actionable = 1;
opt_ports &= 1 << (dev_pn[dev_plen-1] - 1);
break;
}
}
if (opt_level > 0) {
if (opt_level != info.level) {
info.actionable = 0;
}
}
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 (strlen(opt_location) > 0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
}
}
if (opt_level > 0) {
if (opt_level != info.pn_len + 1) {
info.actionable = 0;
}
}
if (strlen(opt_vendor) > 0) {
if (strncasecmp(opt_vendor, info.vendor, strlen(opt_vendor))) {
info.actionable = 0;
}
}
memcpy(&hubs[hub_count], &info, sizeof(info));
if (hub_count < MAX_HUBS) {
hub_count++;
} else {
/* That should be impossible - but we don't want to crash! */
fprintf(stderr, "Too many hubs!");
break;
}
}
if (!opt_exact) {
/* Handle USB2/3 duality: */
@@ -737,7 +774,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<hub_count; j++) {
if (i==j)
continue;
@@ -745,8 +783,7 @@ static int usb_find_hubs()
/* Find hub which is USB2/3 dual to the hub above */
/* 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))
if (hubs[i].super_speed == hubs[j].super_speed)
continue;
/* Must have non empty container ID: */
@@ -762,6 +799,14 @@ static int usb_find_hubs()
* We should do few more checks below if multiple such devices are present.
*/
/* Hubs should have the same number of ports */
if (hubs[i].nports != hubs[j].nports) {
/* Except for some weird hubs like Apple mini-dock (has 2 usb2 + 1 usb3 ports) */
if (hubs[i].nports + hubs[j].nports > 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 +814,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,10 +872,18 @@ static int usb_find_hubs()
for (i=0; i<hub_count; i++) {
if (!hubs[i].actionable)
continue;
if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
if (!hubs[i].super_speed || opt_exact) {
hub_phys_count++;
}
}
#ifdef __gnu_linux__
if (perm_ok == 0 && geteuid() != 0) {
fprintf(stderr,
"There were permission problems while accessing USB.\n"
"Follow https://git.io/JIB2Z for a fix!\n"
);
}
#endif
if (perm_ok == 0 && hub_phys_count == 0) {
return LIBUSB_ERROR_ACCESS;
}
@@ -807,7 +898,7 @@ int main(int argc, char *argv[])
int option_index = 0;
for (;;) {
c = getopt_long(argc, argv, "l:L:n:a:p:d:r:w:hveR",
c = getopt_long(argc, argv, "l:L:n:a:p:d:r:w:s:hvefR",
long_options, &option_index);
if (c == -1)
break; /* no more options left */
@@ -830,6 +921,9 @@ int main(int argc, char *argv[])
case 'n':
strncpy(opt_vendor, optarg, sizeof(opt_vendor));
break;
case 's':
strncpy(opt_search, optarg, sizeof(opt_search));
break;
case 'p':
if (!strcasecmp(optarg, "all")) { /* all ports is the default */
break;
@@ -855,6 +949,9 @@ int main(int argc, char *argv[])
case 'r':
opt_repeat = atoi(optarg);
break;
case 'f':
opt_force = 1;
break;
case 'e':
opt_exact = 1;
break;
@@ -908,23 +1005,11 @@ int main(int argc, char *argv[])
rc = usb_find_hubs();
if (rc <= 0) {
fprintf(stderr,
"No compatible smart hubs detected%s%s!\n"
"No compatible devices detected%s%s!\n"
"Run with -h to get usage info.\n",
strlen(opt_location) ? " at location " : "",
opt_location
);
#ifdef __gnu_linux__
if (rc < 0 && geteuid() != 0) {
fprintf(stderr,
"There were permission problems while accessing USB.\n"
"To fix this, run this tool as root using 'sudo uhubctl',\n"
"or add one or more udev rules like below\n"
"to file '/etc/udev/rules.d/52-usb.rules':\n"
"SUBSYSTEM==\"usb\", ATTR{idVendor}==\"2001\", MODE=\"0666\"\n"
"then run 'sudo udevadm trigger --attr-match=subsystem=usb'\n"
);
}
#endif
rc = 1;
goto cleanup;
}
@@ -966,8 +1051,8 @@ 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);
int power_mask = hubs[i].bcd_usb < USB_SS_BCD ? USB_PORT_STAT_POWER
: USB_SS_PORT_STAT_POWER;
int power_mask = hubs[i].super_speed ? USB_SS_PORT_STAT_POWER
: USB_PORT_STAT_POWER;
if (k == 0 && !(port_status & power_mask))
continue;
if (k == 1 && (port_status & power_mask))
@@ -993,7 +1078,7 @@ int main(int argc, char *argv[])
}
}
/* USB3 hubs need extra delay to actually turn off: */
if (k==0 && hubs[i].bcd_usb >= 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"