summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgustavo panizzo <gfa@zumbi.com.ar>2021-04-05 16:33:37 +0200
committergustavo panizzo <gfa@zumbi.com.ar>2021-04-05 16:33:37 +0200
commit9d4a4f75c9bdee68a523e8ba68054a4b80e6ee0d (patch)
tree3227583f45bb2ac3ddf31fe8f331b3d11f886de7
parent81c15da78edbc7cd0fb847752eae221fa5e0c10d (diff)
parent296cf882a95f18582cb13da31b6b5bd61bcac6f1 (diff)
Record uhubctl (2.4.0-1) in archive suite sid
-rw-r--r--Formula/uhubctl.rb10
-rw-r--r--Makefile29
-rw-r--r--README.md102
-rw-r--r--VERSION2
-rw-r--r--debian/changelog17
-rw-r--r--debian/control3
-rw-r--r--debian/patches/debian-changes18
-rw-r--r--debian/watch6
-rw-r--r--uhubctl.c289
9 files changed, 310 insertions, 166 deletions
diff --git a/Formula/uhubctl.rb b/Formula/uhubctl.rb
index 440b1cd..dbe6010 100644
--- a/Formula/uhubctl.rb
+++ b/Formula/uhubctl.rb
@@ -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.3.0.tar.gz"
+ sha256 "714f733592d3cb6ba8efc84fbc03b1beed2323918ff33aef01cdb956755be7b7"
+ license "GPL-2.0"
depends_on "libusb"
+ depends_on "pkg-config" => :build
+
+ livecheck do
+ url :stable
+ end
def install
system "make"
diff --git a/Makefile b/Makefile
index 0da55c0..fb64fc1 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,7 @@ INSTALL := install
INSTALL_DIR := $(INSTALL) -m 755 -d
INSTALL_PROGRAM := $(INSTALL) -m 755
RM := rm -rf
+PKG_CONFIG ?= pkg-config
CC ?= gcc
CFLAGS ?= -g -O0
@@ -21,29 +22,25 @@ DEB_NOEPOCH_VERSION := $(shell echo $(DEB_VERSION) | cut -d: -f2-)
DEB_UPSTREAM_VERSION := $(shell echo $(DEB_NOEPOCH_VERSION) | sed 's/-[^-]*$$//')
CFLAGS += -DPROGRAM_VERSION=\"$(DEB_VERSION)\"
+GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
+ifeq ($(GIT_VERSION),)
+ GIT_VERSION := $(shell cat VERSION)
+endif
+# 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
diff --git a/README.md b/README.md
index 4b08c24..857ad1e 100644
--- a/README.md
+++ b/README.md
@@ -20,30 +20,34 @@ 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 ([note](https://git.io/JLnZb)) | 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 |
+| Aukey | CB-C59 | 4 | 3.1 |`2109:2813`| 2017 | |
| B+B SmartWorx | UHR204 | 4 | 2.0 |`0856:DB00`| 2013 | |
| 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 | |
| Club3D | CSV-3242HD Dual Display Docking Station | 4 | 3.0 |`2109:2811`| 2015 | |
| 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 B (silver). Note: rev B7+ not supported | 4 | 2.0 |`05E3:0605`| 2005 | 2010 |
+| 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 |
+| ExSys | EX-1113HMS | 16 | 3.1 | | 2018 | |
| GlobalScale | ESPRESSObin SBUD102 V5 | 1 | 3.0 |`1D6B:0003`| 2017 | |
| Hawking Technology | UH214 | 4 | 2.0 | | 2003 | 2008 |
| IOI | U3H415E1 | 4 | 3.0 | | 2012 | |
@@ -58,21 +62,24 @@ 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 | |
| Plugable | USB3-HUB7-81X | 7 | 3.0 |`2109:0813`| 2012 | |
+| Port Inc | NWUSB01 | 4 | 1.1 |`0451:1446`| 1999 | 2003 |
| Raspberry Pi | B+, 2B, 3B ([see below](#raspberry-pi-b2b3b)) | 4 | 2.0 | | 2011 | |
| Raspberry Pi | 3B+ ([see below](#raspberry-pi-3b)) | 4 | 2.0 |`0424:2514`| 2018 | |
| 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 |
+| Targus | PAUH212/PAUH212U | 7 | 2.0 | | 2004 | 2009 |
| Texas Instruments | TUSB4041PAPEVM | 4 | 2.1 |`0451:8142`| 2015 | |
This table is by no means complete.
@@ -103,52 +110,63 @@ 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
This means operate on default smart hub and turn power off (`-a off`, or `-a 0`)
-on port 2 (`-p 2`). Supported actions are `off`/`on`/`cycle` (or `0`/`1`/`2`).
+on port 2 (`-p 2`). Supported actions are `off`/`on`/`cycle`/`toggle` (or `0`/`1`/`2`/`3`).
`cycle` means turn power off, wait some delay (configurable with `-d`) and turn it back on.
Ports can be comma separated list, and may use `-` for ranges e.g. `2`, or `2,4`, or `2-5`, or `1-2,5-8`.
@@ -169,10 +187,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 +218,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:
@@ -233,6 +258,8 @@ Per-port power switching:
3. If tests above were successful, please report your hub
by opening new issue at https://github.com/mvp/uhubctl/issues,
so we can add it to list of supported devices.
+ Please do not report unsupported hubs, unless it is different
+ hardware revision of some already listed supported model.
#### _USB devices are not removed after port power down on Linux_
@@ -261,8 +288,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 ...
@@ -281,7 +311,9 @@ Doing so will confuse internal hub circuitry and will cause unpredictable behavi
#### _Raspberry Pi turns power off on all ports, not just the one I specified_
-This is limitation of Raspberry Pi hardware design.
+This is the limitation of Raspberry Pi hardware design.
+As a workaround, you can buy any external USB hub from supported list above,
+attach it to any USB port of Raspberry Pi, and control power on its ports independently.
For reference, supported Raspberry Pi models have following internal USB topology:
@@ -307,8 +339,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,13 +353,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
-
-
-As a workaround, you can buy any external USB hub from supported list,
-attach it to any USB port of Raspberry Pi, and control power on its ports independently.
+ * USB2 hub `3`, 1 port, OTG controller. Power switching is [not supported](https://git.io/JUc5Q).
@@ -350,6 +377,7 @@ Notable projects using uhubctl
| [USB fan for Raspberry Pi](https://bit.ly/2TRV6sM) | Control USB fan to avoid Raspberry Pi overheating |
| [Raspberry Pi Reboot Router](https://bit.ly/3aNbQqs) | Automatically reboot router if internet isn't working |
| [Control USB Lamp With Voice](https://bit.ly/2VtW2SX) | Voice Control of USB Lamp using Siri and Raspberry Pi |
+| [Control USB LED Strip](https://bit.ly/3oVWfeZ) | Controlling USB powered LED Light Strip |
Copyright
diff --git a/VERSION b/VERSION
index b0d4a3d..197c4d5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v2.2.0-dev
+2.4.0
diff --git a/debian/changelog b/debian/changelog
index dea8544..db41eae 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,20 @@
+uhubctl (2.4.0-1) experimental; urgency=medium
+
+ * New upstream release.
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Mon, 05 Apr 2021 14:33:37 +0000
+
+uhubctl (2.3.0-1) unstable; urgency=medium
+
+ [ gustavo panizzo ]
+ * New upstream release
+ * [30ee12] Standards version 4.5.1 (no changes)
+ * [579aa3] Use a version 4 of the watch file
+ * [7239e9] Set Rules-Requires-Requires no
+ * [d28e61] Refresh patches
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Tue, 15 Dec 2020 20:46:37 +0000
+
uhubctl (2.2.0-1) unstable; urgency=medium
* New upstream release.
diff --git a/debian/control b/debian/control
index cefb16b..b5e14d5 100644
--- a/debian/control
+++ b/debian/control
@@ -3,10 +3,11 @@ Section: electronics
Priority: optional
Maintainer: gustavo panizzo <gfa@zumbi.com.ar>
Build-Depends: debhelper-compat (= 13), libusb-1.0-0-dev, autopkgtest, help2man
-Standards-Version: 4.5.0
+Standards-Version: 4.5.1
Homepage: https://github.com/mvp/uhubctl
Vcs-Browser: https://salsa.debian.org/debian/uhubctl
Vcs-Git: https://salsa.debian.org/debian/uhubctl.git
+Rules-Requires-Root: no
Package: uhubctl
Architecture: any
diff --git a/debian/patches/debian-changes b/debian/patches/debian-changes
index 6afc77d..6bb409f 100644
--- a/debian/patches/debian-changes
+++ b/debian/patches/debian-changes
@@ -10,17 +10,12 @@ the first upload of upstream version 1.2.3, you could use:
(If you have dgit, use `dgit clone uhubctl`, rather than plain `git clone`.)
A single combined diff, containing all the changes, follows.
---- uhubctl-2.2.0.orig/Makefile
-+++ uhubctl-2.2.0/Makefile
-@@ -14,11 +14,13 @@ RM := rm -rf
+--- uhubctl-2.4.0.orig/Makefile
++++ uhubctl-2.4.0/Makefile
+@@ -15,11 +15,17 @@ PKG_CONFIG ?= pkg-config
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-)
--ifeq ($(GIT_VERSION),)
-- GIT_VERSION := $(shell cat VERSION)
--endif
--CFLAGS += -DPROGRAM_VERSION=\"$(GIT_VERSION)\"
+# Gets the full version of the source package including debian version
+DEB_VERSION := $(shell dpkg-parsechangelog | egrep '^Version:' | cut -f 2 -d ' ')
+DEB_NOEPOCH_VERSION := $(shell echo $(DEB_VERSION) | cut -d: -f2-)
@@ -28,6 +23,11 @@ A single combined diff, containing all the changes, follows.
+DEB_UPSTREAM_VERSION := $(shell echo $(DEB_NOEPOCH_VERSION) | sed 's/-[^-]*$$//')
+
+CFLAGS += -DPROGRAM_VERSION=\"$(DEB_VERSION)\"
+ GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
+ ifeq ($(GIT_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
diff --git a/debian/watch b/debian/watch
index 11ee23f..5a57406 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,3 +1,3 @@
-version=3
-opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/uhubctl-$1\.tar\.gz/ \
- https://github.com/mvp/uhubctl/releases .*/v?(\d\S*)\.tar\.gz
+version=4
+opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/uhubctl-$1\.tar\.gz/ \
+ https://github.com/mvp/uhubctl/releases .*/v?(\d\S+)\.tar\.gz
diff --git a/uhubctl.c b/uhubctl.c
index 3541fa8..c071bda 100644
--- a/uhubctl.c
+++ b/uhubctl.c
@@ -75,6 +75,7 @@ void sleep_ms(int milliseconds)
#define POWER_OFF 0
#define POWER_ON 1
#define POWER_CYCLE 2
+#define POWER_TOGGLE 3
#define MAX_HUB_CHAIN 8 /* Per USB 3.0 spec max hub chain is 7 */
@@ -188,13 +189,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 +210,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 +220,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 +233,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,30 +244,33 @@ 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"
"Options [defaults in brackets]:\n"
- "--action, -a - action to off/on/cycle (0/1/2) for affected ports.\n"
+ "--action, -a - action to off/on/cycle/toggle (0/1/2/3) for affected ports.\n"
"--ports, -p - ports to operate on [all hub ports].\n"
"--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 +396,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 +408,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 +444,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 +455,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 +524,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 +554,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 +574,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 +591,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 +613,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 +630,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 +708,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 (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;
+ 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;
}
}
- 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 +775,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 +784,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 +800,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 +815,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,11 +873,19 @@ 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++;
}
}
if (perm_ok == 0 && hub_phys_count == 0) {
+#ifdef __gnu_linux__
+ if (geteuid() != 0) {
+ fprintf(stderr,
+ "There were permission problems while accessing USB.\n"
+ "Follow https://git.io/JIB2Z for a fix!\n"
+ );
+ }
+#endif
return LIBUSB_ERROR_ACCESS;
}
return hub_phys_count;
@@ -807,7 +899,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 +922,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;
@@ -848,6 +943,9 @@ int main(int argc, char *argv[])
if (!strcasecmp(optarg, "cycle") || !strcasecmp(optarg, "2")) {
opt_action = POWER_CYCLE;
}
+ if (!strcasecmp(optarg, "toggle") || !strcasecmp(optarg, "3")) {
+ opt_action = POWER_TOGGLE;
+ }
break;
case 'd':
opt_delay = atof(optarg);
@@ -855,6 +953,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 +1009,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;
}
@@ -944,6 +1033,9 @@ int main(int argc, char *argv[])
continue;
if (k == 1 && opt_action == POWER_KEEP)
continue;
+ // if toggle requested, do it only once when `k == 0`
+ if (k == 1 && opt_action == POWER_TOGGLE)
+ continue;
int i;
for (i=0; i<hub_count; i++) {
if (hubs[i].actionable == 0)
@@ -966,17 +1058,18 @@ 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;
- if (k == 0 && !(port_status & power_mask))
+ int power_mask = hubs[i].super_speed ? USB_SS_PORT_STAT_POWER
+ : USB_PORT_STAT_POWER;
+ int powered_on = port_status & power_mask;
+ if (opt_action == POWER_TOGGLE) {
+ request = powered_on ? LIBUSB_REQUEST_CLEAR_FEATURE
+ : LIBUSB_REQUEST_SET_FEATURE;
+ }
+ if (k == 0 && !powered_on && opt_action != POWER_TOGGLE)
continue;
- if (k == 1 && (port_status & power_mask))
+ if (k == 1 && powered_on)
continue;
- int repeat = 1;
- if (k == 0)
- repeat = opt_repeat;
- if (!(port_status & ~power_mask))
- repeat = 1;
+ int repeat = powered_on ? opt_repeat : 1;
while (repeat-- > 0) {
rc = libusb_control_transfer(devh,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
@@ -993,7 +1086,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"