summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Formula/uhubctl.rb10
-rw-r--r--LICENSE2
-rw-r--r--Makefile36
-rw-r--r--README.md243
-rw-r--r--VERSION2
-rw-r--r--debian/README.source7
-rw-r--r--debian/changelog41
-rw-r--r--debian/control5
-rw-r--r--debian/copyright4
-rw-r--r--debian/gbp.conf6
-rw-r--r--debian/patches/debian-changes33
-rw-r--r--debian/patches/debian-ways32
-rw-r--r--debian/patches/series2
-rw-r--r--debian/source/options2
-rw-r--r--debian/source/patch-header12
-rw-r--r--debian/watch6
-rw-r--r--udev/rules.d/52-usb.rules24
-rw-r--r--uhubctl.c550
-rw-r--r--uhubctl_git.bb2
20 files changed, 744 insertions, 279 deletions
diff --git a/.gitignore b/.gitignore
index 0ab4458..eb6ef3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,7 @@ uhubctl
# Mac symbols
*.dSYM
+
+# Patches
+*.patch
+*.diff
diff --git a/Formula/uhubctl.rb b/Formula/uhubctl.rb
index 440b1cd..a997dbc 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.4.0.tar.gz"
+ sha256 "391f24fd1f89cacce801df38ecc289b34c3627bc08ee69eec515af7e1a283d97"
+ license "GPL-2.0"
depends_on "libusb"
+ depends_on "pkg-config" => :build
+
+ livecheck do
+ url :stable
+ end
def install
system "make"
diff --git a/LICENSE b/LICENSE
index ab3fc18..eff7035 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
uhubctl – USB hub per-port power control.
-Copyright (c) 2009-2019, Vadim Mikhailov
+Copyright (c) 2009-2022, 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
diff --git a/Makefile b/Makefile
index 021d836..fb64fc1 100644
--- a/Makefile
+++ b/Makefile
@@ -10,41 +10,37 @@ 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
CFLAGS += -Wall -Wextra -std=c99 -pedantic
-
-# # Gets the full version of the source package including debian 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-)
-# # Gets only the upstream version of the package
+# Gets only the upstream version of the package
DEB_UPSTREAM_VERSION := $(shell echo $(DEB_NOEPOCH_VERSION) | sed 's/-[^-]*$$//')
-CFLAGS += -DPROGRAM_VERSION=\"$(DEB_UPSTREAM_VERSION)\"
+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,-z,relro -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 c4469d5..bf39789 100644
--- a/README.md
+++ b/README.md
@@ -20,59 +20,103 @@ 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 | HU9003V1EBL, HUC9003V1EBL | 7 | 3.1 |`2109:2817`| 2018 | |
+| AmazonBasics | HU9002V1SBL, HU9002V1EBL, HU9002V1ESL ([note](https://bit.ly/3awM2Ei)) | 10 | 3.1 |`2109:2817`| 2018 | |
+| AmazonBasics | HUC9002V1SBL, HUC9002V1EBL, HUC9002V1ESL | 10 | 3.1 |`2109:2817`| 2018 | |
+| AmazonBasics | U3-7HUB (only works for 1 charge port) | 7 | 3.0 |`2109:2813`| 2020 | |
+| Anker | AK-A83650A1 ([note](https://git.io/JzpPb)) | 4 | 3.1 |`2109:0817`| 2020 | |
+| Anker | AK-68ANHUB-BV7A-0004 ([note](https://git.io/JLnZb)) | 7 | 3.0 |`2109:0812`| 2014 | |
+| Apple | Pro Display XDR MWPE2LL/A (internal USB hub) | 4 | 2.0 |`05AC:9139`| 2019 | |
| 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 | |
-| Belkin | F5U701-BLK | 7 | 2.0 | | 2008 | 2012 |
+| Basler | 2000036234 | 4 | 3.0 |`0451:8046`| 2016 | |
+| Belkin | F5U101 | 4 | 2.0 |`0451:2046`| 2005 | 2010 |
+| BenQ | PD2700U 4K Monitor (works only in USB2 mode) | 4 | 3.1 |`05E3:0610`| 2018 | |
+| BenQ | PD3220U | 4 | 3.1 |`05E3:0610`| 2019 | |
+| Buffalo | BSH4A05U3BK | 4 | 3.0 |`05E3:0610`| 2015 | |
+| Bytecc | BT-UH340 ([warning](https://bit.ly/35BNi5U)) | 4 | 3.0 |`2109:8110`| 2010 | |
+| Centech | CT-USB4HUB ReTRY HUB | 4 | 3.1 |`0424:2744`| 2017 | |
| 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 D1 (black edition) | 4 | 2.0 |`05E3:0608`| 2012 | |
-| D-Link | DUB-H7 rev A (silver edition) | 7 | 2.0 |`2001:F103`| 2005 | 2010 |
-| D-Link | DUB-H7 rev D1 (black edition) | 7 | 2.0 |`05E3:0608`| 2012 | |
-| Dell | P2416D 24" QHD Monitor | 4 | 2.0 | | 2017 | |
+| Cypress | CY4608 HX2VL devkit ([note](https://bit.ly/3sMPfpu)) | 4 | 2.0 |`04B4:6570`| 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,E (black). Rev B,C,F,G 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 | |
+| Dell | UltraSharp U3419W 34" Curved Monitor | 6 | 3.0 | | 2020 | |
+| Delock | 62537 | 4 | 3.1 | | 2020 | |
+| Delock | 87445 ([note](https://git.io/Jsuz5)) | 4 | 2.0 |`05E3:0608`| 2009 | 2013 |
| 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 | |
+| Hardkernel | ODROID-C4 ([note](https://git.io/JG0mP)) | 4 | 3.0 | | 2020 | |
| Hawking Technology | UH214 | 4 | 2.0 | | 2003 | 2008 |
+| Hewlett Packard | USB-C Dock G5 5TW10AA | 5 | 3.0 |`03F0:076B`| 2019 | |
+| Hewlett Packard | P5Q58UT | 3 | 3.0 | | 2019 | |
+| Inateck | HB2025A ([USB2 only](https://bit.ly/3wXF5UO)) | 4 | 3.1 |`2109:2822`| 2021 | |
| IOI | U3H415E1 | 4 | 3.0 | | 2012 | |
-| j5create | JUH470 (works only in USB2 mode) | 3 | 3.0 |`05E3:0610`| 2014 | |
-| Lenovo | ThinkPad EU Ultra Dockingstation (40A20090EU) | 6 | 2.0 |`17EF:100F`| 2015 | |
+| j5create | JUH377 ([note](https://bit.ly/3Mx9eQI)) | 7 | 3.0 | | 2016 | |
+| j5create | JUH470 ([note](https://bit.ly/3CRWamP)) | 3 | 3.0 |`05E3:0610`| 2014 | |
+| Juiced Systems | 6HUB-01 | 7 | 3.0 |`0BDA:0411`| 2014 | 2018 |
+| LG Electronics | 27MD5KL-B monitor | 4 | 3.1 |`043E:9A60`| 2019 | |
+| LG Electronics | 27UK850-W monitor | 2 | 3.0 | | 2018 | |
+| LG Electronics | 38WK95C-W monitor | 4 | 3.0 |`0451:8142`| 2018 | |
+| Lenovo | ThinkPad Ultra Docking Station (40A20090EU) | 6 | 2.0 |`17EF:100F`| 2015 | |
+| Lenovo | ThinkPad Ultra Docking Station (40AJ0135EU) | 7 | 3.1 |`17EF:3070`| 2018 | |
| Lenovo | ThinkPad X200 Ultrabase 42X4963 | 3 | 2.0 |`17EF:1005`| 2008 | 2011 |
| Lenovo | ThinkPad X6 Ultrabase 42W3107 | 4 | 2.0 |`17EF:1000`| 2006 | 2009 |
+| Lenovo | ThinkPlus 4-in-1 USB-C hub 4X90W86497 | 3 | 3.0 | | 2021 | |
+| Lenovo | ThinkVision T24i-10 Monitor | 4 | 2.0 |`17EF:0610`| 2018 | |
| Lindy | USB serial converter 4 port | 4 | 1.1 |`058F:9254`| 2008 | |
-| Linksys | USB2HUB4 | 4 | 2.0 | | 2004 | 2010 |
+| Linksys | USB2HUB4 ([note](https://git.io/JYiDZ)) | 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 | |
+| Microchip | EVB-USB5807 | 7 | 3.1 | | 2016 | |
| Moxa | Uport-407 | 7 | 2.0 |`110A:0407`| 2009 | |
+| NVidia | Jetson Nano B01 ([details](https://git.io/JJaFR)) | 4 | 3.0 | | 2019 | |
+| NVidia | Jetson Xavier NX ([details](https://bit.ly/3PN2DDp)) | 4 | 3.0 | | 2020 | |
| 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 | |
-| Plugable | USB2-HUB10S | 10 | 2.0 | | 2010 | |
-| Raspberry Pi | Model B+, 2 B, 3 B (port 2 only) | 4 | 2.0 | | 2011 | |
-| Raspberry Pi | Model 3 B+ | 6 | 2.0 |`0424:2514`| 2018 | |
+| Plugable | USB3-HUB7C (only works for 2 charge ports) | 7 | 3.0 |`2109:0813`| 2015 | |
+| Plugable | USBC-HUB7BC (works for 6/7 ports, not the rightmost) | 7 | 3.0 |`2109:0817`| 2021 | |
+| Plugable | USB3-HUB7-81X (only works for 2 charge ports) | 7 | 3.0 |`2109:0813`| 2012 | |
+| Plugable | USB3-HUB10-C2 (only works for 2 charge ports) | 10 | 3.0 | | 2014 | |
+| 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-518C ([note](https://bit.ly/3kYZUsA)) | 7 | 3.0 |`2109:0817`| 2021 | |
+| Rosonway | RSH-A13 ([warning](https://bit.ly/3OToUOL)) | 13 | 3.1 |`2109:2822`| 2021 | |
+| Rosonway | RSH-A16 ([note](https://git.io/JTawg), [warning](https://bit.ly/39B0tGS)) | 16 | 3.0 |`0bda:0411`| 2020 | |
+| Rosonway | RSH-A104 ([USB2 only](https://bit.ly/3A0qiKF)) | 4 | 3.1 |`2109:2822`| 2022 | |
| Sanwa Supply | USB-HUB14GPH | 4 | 1.1 | | 2001 | 2003 |
-| StarTech | ST4200USBM | 4 | 2.0 |`0409:005A`| 2004 | |
+| Seagate | Backup Plus Hub STEL8000100 | 2 | 3.0 |`0BC2:AB44`| 2016 | |
+| Seeed Studio | reTerminal CM4104032 | 2 | 2.0 |`0424:2514`| 2021 | |
| 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 | |
+| UUGear | MEGA4 (for Raspberry Pi 4B) | 4 | 3.1 |`2109:0817`| 2021 | |
This table is by no means complete.
If your hub works with `uhubctl`, but is not listed above, please report it
by opening new issue at https://github.com/mvp/uhubctl/issues,
so we can add it to supported table. In your report, please provide
-exact product model and add output from `uhubctl`.
+exact product model and add output from `uhubctl`
+and please test VBUS off support as described below in FAQ.
Note that quite a few modern motherboards have built-in root hubs that
do support this feature - you may not even need to buy any external hub.
-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
@@ -92,48 +136,72 @@ 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.
+
+> :warning: MacOS 12.4 x86 has [USB stack bug](https://github.com/libusb/libusb/issues/1156) which breaks `uhubctl` operation.
+Solution is to upgrade to MacOS 12.5 which has this bug fixed.
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.
-First, you need to install library libusb-1.0 (version 1.0.12 or later):
+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`
+* 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`.
+> :warning: Turning off built-in USB ports may cut off your keyboard or mouse,
+so be careful which ports you are turning off!
+
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.
@@ -147,14 +215,35 @@ Linux USB permissions
=====================
On Linux, you should configure `udev` USB permissions (otherwise you will have to run it as root using `sudo uhubctl`).
+
+Starting with Linux Kernel 6.0 there is a standard interface to turn USB hub ports on or off,
+and `uhubctl` will try to use it (instead of `libusb`) to set the port status.
+This is why there are additional rules for 6.0+ kernels.
+There is no harm in having these rules on systems running older kernel versions.
+
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 udev rules like below to file `/etc/udev/rules.d/52-usb.rules`
+(replace `2001` with your hub vendor id, or completely remove `ATTR{idVendor}` filter to allow any USB hub access):
+
+ SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="2001"
+ # Linux 6.0 or later (its ok to have this block present for older Linux kernels):
+ SUBSYSTEM=="usb", DRIVER=="hub", \
+ RUN="/bin/sh -c \"chmod -f 666 $sys$devpath/*-port*/disable || true\""
- 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 (or remove idVendor filter):
+
+ SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="2109"
+ SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="1d6b"
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"
+ SUBSYSTEM=="usb", DRIVER=="hub", MODE="0664", GROUP="dialout"
+ # Linux 6.0 or later (its ok to have this block present for older Linux kernels):
+ SUBSYSTEM=="usb", DRIVER=="hub", \
+ RUN+="/bin/sh -c \"chown -f root:dialout $sys$devpath/*-port*/disable || true\"" \
+ RUN+="/bin/sh -c \"chmod -f 660 $sys$devpath/*-port*/disable || true\""
and then add permitted users to `dialout` group:
@@ -164,6 +253,7 @@ For your `udev` rule changes to take effect, reboot or run:
sudo udevadm trigger --attr-match=subsystem=usb
+For your convenience, ready to use udev rule is provided [here](https://github.com/mvp/uhubctl/blob/master/udev/rules.d/52-usb.rules).
FAQ
@@ -173,7 +263,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:
@@ -212,10 +303,14 @@ 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_
+> :arrow_right: This is fixed by [#450](https://github.com/mvp/uhubctl/pull/450) if you are using Linux kernel 6.0 or later.
+
After powering down USB port, udev does not get any event, so it keeps the device files around.
However, trying to access the device files will lead to an IO error.
@@ -234,18 +329,29 @@ When you turn power back on, device should re-enumerate properly (no need to cal
#### _Power comes back on after few seconds on Linux_
+> :arrow_right: This is fixed by [#450](https://github.com/mvp/uhubctl/pull/450) if you are using Linux kernel 6.0 or later.
+
Some device drivers in kernel are surprised by USB device being turned off and automatically try to power it back on.
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).
+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 ...
+
#### _Multiple 4-port hubs are detected, but I only have one 7-port hub connected_
Many hub manufacturers build their USB hubs using basic 4 port USB chips.
E.g. to make 7 port hub, they daisy-chain two 4 port hubs - 1 port is lost to daisy-chaining,
-so it makes it 4+4-1=7 port hub. Simularly, 10 port hub could be built as 3 4-port hubs
+so it makes it 4+4-1=7 port hub. Similarly, 10 port hub could be built as 3 4-port hubs
daisy-chained together, which gives 4+4+4-2=10 usable ports.
Note that you should never try to change power state for ports used to daisy-chain internal hubs together.
@@ -254,24 +360,50 @@ 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.
-For reference, Raspberry Pi models have following internal USB topology:
-
-* B+/2B/3B: one USB hub `1-1`, with port `1` for Ethernet+wifi, and ports `2-5` ganged, controlled by port `2`.
- (Trying to control ports 3,4,5 won't do anything).
-* 3B+: 2 hubs - main hub `1-1`, all 4 ports ganged, all controlled by port `2`.
- Second hub `1-1.1` (daisy-chained to main): 3 independently controlled ports, `1` is used for Ethernet+wifi,
- and ports `2,3` are available with proper per-port power switching.
- In other words, 2 out of total 4 ports wired outside do support independent power switching on 3B+.
-* 4B: Hardware supports power switching, but current firmware is reporting broken USB descriptors.
- For more details see Raspberry Pi issue https://github.com/raspberrypi/linux/issues/3079.
- USB3 hub `2`, 4 ports, per-port power switching. BOS `ContainerID` not reported (required for USB3).
- USB2 hub `1`, 1 port, no usable ports, connects hub `1-1` below.
- USB2 hub `1-1`, 4 ports, dual to USB3 hub above. Hub descriptor incorrectly reports ganged power switching.
- USB2 hub `3`, 1 port, OTG controller, incorrectly reports ganged power switching.
-
-As a workaround, you can buy any external USB hub from supported list,
+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.
+Also, there are supported hubs designed specifically for Raspberry Pi, e.g. UUGear MEGA4.
+
+For reference, supported Raspberry Pi models have following internal USB topology:
+
+##### Raspberry Pi B+,2B,3B
+
+ * Single hub `1-1`, ports 2-5 ganged, all controlled by port `2`:
+
+ uhubctl -l 1-1 -p 2 -a 0
+
+ Trying to control ports `3`,`4`,`5` will not do anything.
+ Port `1` controls power for Ethernet+WiFi.
+
+##### Raspberry Pi 3B+
+
+ * Main hub `1-1`, all 4 ports ganged, all controlled by port `2` (turns off secondary hub ports as well).
+ Port `1` connects hub `1-1.1` below, ports `2` and `3` are wired outside, port `4` not wired.
+
+ uhubctl -l 1-1 -p 2 -a 0
+
+ * Secondary hub `1-1.1` (daisy-chained to main): 3 ports,
+ port `1` is used for Ethernet+WiFi, and ports `2` and `3` are wired outside.
+
+
+##### Raspberry Pi 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.
+
+ * USB2 hub `1-1`, 4 ports ganged, dual to USB3 hub `2` below:
+
+ uhubctl -l 1-1 -a 0
+
+ * USB3 hub `2`, 4 ports ganged, dual to USB2 hub `1-1` above:
+
+ uhubctl -l 2 -a 0
+
+ * USB2 hub `3`, 1 port, OTG controller. Power switching is [not supported](https://git.io/JUc5Q).
@@ -285,20 +417,27 @@ Notable projects using uhubctl
| [Build Status Light](https://goo.gl/3GA82o) | Create a build status light in under 10 minutes |
| [Buildenlights](https://git.io/fj1FC) | GitLab/GitHub project build status as green/red light |
| [Weather Station](https://goo.gl/3b1FzC) | Reset Weather Station when it freezes |
-| [sysmoQMOD](https://goo.gl/8wvcKA) | Reset cellular modems when necessary |
+| [sysmoQMOD](https://bit.ly/2VtWrVt) | Reset cellular modems when necessary |
| [Smog Sensor](https://bit.ly/2EMwgCk) | Raspberry Pi based smog sensor power reset |
| [Terrible Cluster](https://goo.gl/XjiXFu) | Power on/off Raspberry Pi cluster nodes as needed |
-| [Ideal Music Server](https://bit.ly/2UJq6Z9) | Turn off unused USB ports to improve audio quality |
+| [Ideal Music Server](https://bit.ly/39MeVFQ) | Turn off unused USB ports to improve audio quality |
| [USB drives with no phantom load](https://goo.gl/qfrmGK) | Power USB drives only when needed to save power |
| [USB drive data recovery](https://goo.gl/4MddLr) | Recover data from failing USB hard drive |
| [Control power to 3D printer](https://git.io/fh5Tr) | OctoPrint web plugin for USB power control |
| [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 |
+| [Brew beer with Raspberry Pi](https://git.io/JtbLd) | Automated beer brewing system using Raspberry Pi |
+| [Webcam On-Air Sign](https://bit.ly/3witNsa) | Automatically light up a sign when webcam is in use |
+| [Do it yourself PPPS](https://git.io/J3lHs) | Solder wires in your USB hub to support uhubctl |
+| [Python Wrapper for uhubctl](https://github.com/nbuchwitz/python3-uhubctl) | Module to use uhubctl with Python |
Copyright
=========
-Copyright (C) 2009-2019 Vadim Mikhailov
+Copyright (C) 2009-2022 Vadim Mikhailov
This file can be distributed under the terms and conditions of the
GNU General Public License version 2.
diff --git a/VERSION b/VERSION
index 3edcf5a..437459c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v2.1.0-dev
+2.5.0
diff --git a/debian/README.source b/debian/README.source
index 0786773..2d21f00 100644
--- a/debian/README.source
+++ b/debian/README.source
@@ -1,6 +1,13 @@
uhubctl for Debian
------------------
+On the release 2.2.0-1 I've switched from dgit and git debrebase to the workflow
+proposed in dgit-maint-merge(7)
+
+The integration branch is now master instead of sid.
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Sun, 24 May 2022 21:40:45 +0000
+
Since the release 2.0.0-2 I started to experiment using dgit and git debrebase
to maintain the packaging repository.
diff --git a/debian/changelog b/debian/changelog
index f8be78f..9a9c392 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,44 @@
+uhubctl (2.5.0-1) unstable; urgency=medium
+
+ * New upstream release
+ * [30fd30] Standards version 4.6.1.0 (no changes)
+ * [4c4cd2] Update copyright years
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Thu, 03 Nov 2022 20:57:54 +0100
+
+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.
+ * [4ef936] Commit Debian 3.0 (quilt) metadata
+ * [4fc164] Standards version 4.5.0 (no changes)
+ * [2a3c64] Use debhelper 13
+ * [5b85ad] Add a patch-header for the automatic quilt patches
+ * [d8728d] Removed applied patches to the source code
+ * [ad933e] Use the workflow documented in dgit-maint-merge(7),
+ update d/README.source to reflect that
+ * [c30bf0] Add recommended options to d/source/options from
+ dgit-maint-merge(7)
+ * [44d9d7] Remove d/gbp.conf
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Tue, 02 Jun 2020 20:27:38 +0000
+
uhubctl (2.1.0-1) unstable; urgency=medium
* Update to new upstream version 2.1.0.
diff --git a/debian/control b/debian/control
index fbcc568..b1c978d 100644
--- a/debian/control
+++ b/debian/control
@@ -2,11 +2,12 @@ Source: uhubctl
Section: electronics
Priority: optional
Maintainer: gustavo panizzo <gfa@zumbi.com.ar>
-Build-Depends: debhelper-compat (= 12), libusb-1.0-0-dev, autopkgtest, help2man
-Standards-Version: 4.4.0
+Build-Depends: debhelper-compat (= 13), libusb-1.0-0-dev, autopkgtest, help2man
+Standards-Version: 4.6.1.0
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/copyright b/debian/copyright
index 73566a6..013c97b 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -3,7 +3,7 @@ Upstream-Name: uhubctl
Source: <https://github.com/mvp/uhubctl>
Files: *
-Copyright: <2015-2018> Vadim Mikhailov <vadim.mikhailov@gmail.com>
+Copyright: <2015-2022> Vadim Mikhailov <vadim.mikhailov@gmail.com>
License: GPL-2
License: GPL-2
@@ -28,5 +28,5 @@ License: GPL-2
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
Files: debian/*
-Copyright: <2018> gustavo panizzo <gfa@zumbi.com.ar>
+Copyright: <2018-2022> gustavo panizzo <gfa@zumbi.com.ar>
License: GPL-2
diff --git a/debian/gbp.conf b/debian/gbp.conf
deleted file mode 100644
index f1de2b8..0000000
--- a/debian/gbp.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-upstream-branch = master
-debian-branch = sid
-upstream-tag = v%(version)s
-compression = gz
-pristine-tar = True
diff --git a/debian/patches/debian-changes b/debian/patches/debian-changes
new file mode 100644
index 0000000..0817127
--- /dev/null
+++ b/debian/patches/debian-changes
@@ -0,0 +1,33 @@
+The Debian packaging of uhubctl is maintained in git, using the merging workflow described in dgit-maint-merge(7). There isn't a patch queue that can be represented as a quilt series.
+
+A detailed breakdown of the changes is available from their canonical representation - git commits in the packaging repository. For example, to see the changes made by the Debian maintainer in
+the first upload of upstream version 1.2.3, you could use:
+
+% git clone https://git.dgit.debian.org/uhubctl
+% cd uhubctl
+% git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian'
+
+(If you have dgit, use `dgit clone uhubctl`, rather than plain `git clone`.)
+
+A single combined diff, containing all the changes, follows.
+--- uhubctl-2.5.0.orig/Makefile
++++ uhubctl-2.5.0/Makefile
+@@ -15,11 +15,17 @@ PKG_CONFIG ?= pkg-config
+ CC ?= gcc
+ CFLAGS ?= -g -O0
+ CFLAGS += -Wall -Wextra -std=c99 -pedantic
++# 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-)
++# Gets only the upstream version of the package
++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)
diff --git a/debian/patches/debian-ways b/debian/patches/debian-ways
deleted file mode 100644
index 30ee601..0000000
--- a/debian/patches/debian-ways
+++ /dev/null
@@ -1,32 +0,0 @@
-From: gustavo panizzo <gfa@zumbi.com.ar>
-Date: Tue, 31 Jul 2018 10:28:52 +0800
-Subject: debian-ways
-
----
- Makefile | 13 ++++++++-----
- 1 file changed, 8 insertions(+), 5 deletions(-)
-
-diff --git a/Makefile b/Makefile
-index f6d69d3..021d836 100644
---- a/Makefile
-+++ b/Makefile
-@@ -14,11 +14,14 @@ RM := rm -rf
- CC ?= gcc
- CFLAGS ?= -g -O0
- CFLAGS += -Wall -Wextra -std=c99 -pedantic
--GIT_VERSION := $(shell git describe --abbrev=8 --dirty --always --tags)
--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-)
-+# # Gets only the upstream version of the package
-+DEB_UPSTREAM_VERSION := $(shell echo $(DEB_NOEPOCH_VERSION) | sed 's/-[^-]*$$//')
-+
-+CFLAGS += -DPROGRAM_VERSION=\"$(DEB_UPSTREAM_VERSION)\"
-
- ifeq ($(UNAME_S),Linux)
- LDFLAGS += -Wl,-z,relro -lusb-1.0
diff --git a/debian/patches/series b/debian/patches/series
index b2e9488..7bb8252 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1 @@
-debian-ways
+debian-changes
diff --git a/debian/source/options b/debian/source/options
new file mode 100644
index 0000000..ce071fb
--- /dev/null
+++ b/debian/source/options
@@ -0,0 +1,2 @@
+single-debian-patch
+auto-commit
diff --git a/debian/source/patch-header b/debian/source/patch-header
new file mode 100644
index 0000000..2f7f410
--- /dev/null
+++ b/debian/source/patch-header
@@ -0,0 +1,12 @@
+The Debian packaging of uhubctl is maintained in git, using the merging workflow described in dgit-maint-merge(7). There isn't a patch queue that can be represented as a quilt series.
+
+A detailed breakdown of the changes is available from their canonical representation - git commits in the packaging repository. For example, to see the changes made by the Debian maintainer in
+the first upload of upstream version 1.2.3, you could use:
+
+% git clone https://git.dgit.debian.org/uhubctl
+% cd uhubctl
+% git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian'
+
+(If you have dgit, use `dgit clone uhubctl`, rather than plain `git clone`.)
+
+A single combined diff, containing all the changes, follows.
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/udev/rules.d/52-usb.rules b/udev/rules.d/52-usb.rules
new file mode 100644
index 0000000..0fac792
--- /dev/null
+++ b/udev/rules.d/52-usb.rules
@@ -0,0 +1,24 @@
+# uhubctl – USB hub per-port power control https://github.com/mvp/uhubctl
+#
+# Copyright (c) 2009-2022, Vadim Mikhailov
+#
+# This file can be distributed under the terms and conditions of the
+# GNU General Public License version 2.
+
+# uhubctl udev rules for rootless operation on Linux for users in group `dialout`.
+#
+# Copy this file to /etc/udev/rules.d, then reboot or run:
+#
+# sudo udevadm trigger --attr-match=subsystem=usb
+#
+# To add yourself to this permission group, run:
+#
+# sudo usermod -a -G dialout $USER
+
+# This is for Linux before 6.0:
+SUBSYSTEM=="usb", DRIVER=="hub", MODE="0664", GROUP="dialout"
+
+# This is for Linux 6.0 or later (ok to keep this block present for older Linux kernels):
+SUBSYSTEM=="usb", DRIVER=="hub", \
+ RUN+="/bin/sh -c \"chown -f root:dialout $sys$devpath/*-port*/disable || true\"" \
+ RUN+="/bin/sh -c \"chmod -f 660 $sys$devpath/*-port*/disable || true\""
diff --git a/uhubctl.c b/uhubctl.c
index 5f21bef..eba108d 100644
--- a/uhubctl.c
+++ b/uhubctl.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2019 Vadim Mikhailov
+ * Copyright (c) 2009-2022 Vadim Mikhailov
*
* Utility to turn USB port power on/off
* for USB hubs that support per-port power switching.
@@ -47,6 +47,10 @@ int snprintf(char * __restrict __str, size_t __size, const char * __restrict __f
#include <time.h> /* for nanosleep */
#endif
+#ifdef __gnu_linux__
+#include <fcntl.h> /* for open() / O_WRONLY */
+#endif
+
/* cross-platform sleep function */
void sleep_ms(int milliseconds)
@@ -75,6 +79,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 */
@@ -182,19 +187,22 @@ struct descriptor_strings {
char vendor[64];
char product[64];
char serial[64];
- char description[256];
+ char description[512];
};
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 +214,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 +224,24 @@ 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 int opt_nodesc = 0; /* skip querying device description */
+#ifdef __gnu_linux__
+static int opt_nosysfs = 0; /* don't use the Linux sysfs port disable interface, even if available */
+#endif
+
+
+static const char short_options[] =
+ "l:L:n:a:p:d:r:w:s:hvefRN"
+#ifdef __gnu_linux__
+ "S"
+#endif
+;
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 +249,11 @@ 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' },
+ { "nodesc", no_argument, NULL, 'N' },
+#ifdef __gnu_linux__
+ { "nosysfs", no_argument, NULL, 'S' },
+#endif
{ "reset", no_argument, NULL, 'R' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
@@ -236,30 +264,37 @@ 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"
+ "--nodesc, -N - do not query device description (helpful for unresponsive devices).\n"
+#ifdef __gnu_linux__
+ "--nosysfs, -S - do not use the Linux sysfs port disable interface.\n"
+#endif
"--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 +420,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,54 +432,68 @@ 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);
}
- info->ppps = 0;
+ /* Get container_id: */
+ bzero(info->container_id, sizeof(info->container_id));
+ struct libusb_bos_descriptor *bos;
+ rc = libusb_get_bos_descriptor(devh, &bos);
+ if (rc == 0) {
+ int cap;
+#ifdef __FreeBSD__
+ for (cap=0; cap < bos->bNumDeviceCapabilities; cap++) {
+#else
+ for (cap=0; cap < bos->bNumDeviceCaps; cap++) {
+#endif
+ if (bos->dev_capability[cap]->bDevCapabilityType == LIBUSB_BT_CONTAINER_ID) {
+ struct libusb_container_id_descriptor *container_id;
+ rc = libusb_get_container_id_descriptor(NULL, bos->dev_capability[cap], &container_id);
+ if (rc == 0) {
+ int i;
+ for (i=0; i<16; i++) {
+ sprintf(info->container_id+i*2, "%02x", container_id->ContainerID[i]);
+ }
+ info->container_id[i*2] = 0;
+ libusb_free_container_id_descriptor(container_id);
+ }
+ }
+ }
+ libusb_free_bos_descriptor(bos);
+
+ /* Raspberry Pi 4B hack for USB3 root hub: */
+ if (strlen(info->container_id)==0 &&
+ strcasecmp(info->vendor, "1d6b:0003")==0 &&
+ info->pn_len==0 &&
+ info->nports==4 &&
+ bcd_usb==USB_SS_BCD)
+ {
+ strcpy(info->container_id, "5cf3ee30d5074925b001802d79434c30");
+ }
+ }
+
/* Logical Power Switching Mode */
int lpsm = uhd->wHubCharacteristics[0] & HUB_CHAR_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;
+ 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 4B reports inconsistent descriptors, override: */
+ if (lpsm == HUB_CHAR_COMMON_LPSM && strcasecmp(info->vendor, "2109:3431")==0) {
+ lpsm = HUB_CHAR_INDV_PORT_LPSM;
+ }
+ info->lpsm = lpsm;
+ rc = 0;
} else {
rc = len;
}
- /* Get container_id: */
- bzero(info->container_id, sizeof(info->container_id));
- struct libusb_bos_descriptor *bos;
- rc = libusb_get_bos_descriptor(devh, &bos);
- if (rc == 0) {
- int cap;
- for (cap=0; cap < bos->bNumDeviceCaps; cap++) {
- if (bos->dev_capability[cap]->bDevCapabilityType == LIBUSB_BT_CONTAINER_ID) {
- struct libusb_container_id_descriptor *container_id;
- rc = libusb_get_container_id_descriptor(NULL, bos->dev_capability[cap], &container_id);
- if (rc == 0) {
- int i;
- for (i=0; i<16; i++) {
- sprintf(info->container_id+i*2, "%02x", container_id->ContainerID[i]);
- }
- info->container_id[i*2] = 0;
- libusb_free_container_id_descriptor(container_id);
- }
- }
- }
- libusb_free_bos_descriptor(bos);
- }
libusb_close(devh);
}
return rc;
@@ -479,6 +528,113 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
}
+#ifdef __gnu_linux__
+/*
+ * Try to use the Linux sysfs interface to power a port off/on.
+ * Returns 0 on success.
+ */
+
+static int set_port_status_linux(struct libusb_device_handle *devh, struct hub_info *hub, int port, int on)
+{
+ int configuration = 0;
+ char disable_path[PATH_MAX];
+
+ int rc = libusb_get_configuration(devh, &configuration);
+ if (rc < 0) {
+ return rc;
+ }
+
+ /*
+ * The "disable" sysfs interface is available only starting with kernel version 6.0.
+ * For earlier kernel versions the open() call will fail and we fall back to using libusb.
+ */
+ snprintf(disable_path, PATH_MAX,
+ "/sys/bus/usb/devices/%s:%d.0/%s-port%i/disable",
+ hub->location, configuration, hub->location, port
+ );
+
+ int disable_fd = open(disable_path, O_WRONLY);
+ if (disable_fd >= 0) {
+ rc = write(disable_fd, on ? "0" : "1", 1);
+ close(disable_fd);
+ }
+
+ if (disable_fd < 0 || rc < 0) {
+ /*
+ * ENOENT is the expected error when running on Linux kernel < 6.0 where
+ * sysfs disable interface does not exist yet - no need to report anything in this case.
+ * If the file exists but another error occurs it is most likely a permission issue.
+ * Print an error message mostly geared towards setting up udev.
+ */
+ if (errno != ENOENT) {
+ fprintf(stderr,
+ "Failed to set port status by writing to %s (%s).\n"
+ "Follow https://git.io/JIB2Z to make sure that udev is set up correctly.\n"
+ "Falling back to libusb based port control.\n"
+ "Use -S to skip trying the sysfs interface and printing this message.\n",
+ disable_path, strerror(errno)
+ );
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+
+/*
+ * Use a control transfer via libusb to turn a port off/on.
+ * Returns >= 0 on success.
+ */
+
+static int set_port_status_libusb(struct libusb_device_handle *devh, int port, int on)
+{
+ int rc = 0;
+ int request = on ? LIBUSB_REQUEST_SET_FEATURE
+ : LIBUSB_REQUEST_CLEAR_FEATURE;
+ int repeat = on ? 1 : opt_repeat;
+
+ while (repeat-- > 0) {
+ rc = libusb_control_transfer(devh,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
+ request, USB_PORT_FEAT_POWER,
+ port, NULL, 0, USB_CTRL_GET_TIMEOUT
+ );
+ if (rc < 0) {
+ perror("Failed to control port power!\n");
+ }
+ if (repeat > 0) {
+ sleep_ms(opt_wait);
+ }
+ }
+
+ return rc;
+}
+
+
+/*
+ * Try different methods to power a port off/on.
+ * Return >= 0 on success.
+ */
+
+static int set_port_status(struct libusb_device_handle *devh, struct hub_info *hub, int port, int on)
+{
+#ifdef __gnu_linux__
+ if (!opt_nosysfs) {
+ if (set_port_status_linux(devh, hub, port, on) == 0) {
+ return 0;
+ }
+ }
+#else
+ (void)hub;
+#endif
+
+ return set_port_status_libusb(devh, port, on);
+}
+
+
/*
* Get USB device descriptor strings and summary description.
*
@@ -499,7 +655,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);
@@ -510,27 +666,37 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
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*)ds->vendor, sizeof(ds->vendor));
- rtrim(ds->vendor);
- }
- if (desc.iProduct) {
- libusb_get_string_descriptor_ascii(devh,
- desc.iProduct, (unsigned char*)ds->product, sizeof(ds->product));
- rtrim(ds->product);
- }
- if (desc.iSerialNumber) {
- libusb_get_string_descriptor_ascii(devh,
- desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
- rtrim(ds->serial);
+ if (!opt_nodesc) {
+ if (desc.iManufacturer) {
+ rc = libusb_get_string_descriptor_ascii(devh,
+ desc.iManufacturer, (unsigned char*)ds->vendor, sizeof(ds->vendor));
+ rtrim(ds->vendor);
+ }
+ if (rc >= 0 && desc.iProduct) {
+ rc = libusb_get_string_descriptor_ascii(devh,
+ desc.iProduct, (unsigned char*)ds->product, sizeof(ds->product));
+ rtrim(ds->product);
+ }
+ if (rc >= 0 && desc.iSerialNumber) {
+ rc = libusb_get_string_descriptor_ascii(devh,
+ desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
+ rtrim(ds->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);
+ 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);
@@ -541,7 +707,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;
}
@@ -558,17 +724,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;
@@ -588,12 +746,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);
@@ -602,7 +763,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 {
@@ -680,30 +841,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: */
@@ -714,7 +908,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;
@@ -722,8 +917,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: */
@@ -739,6 +933,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)
@@ -746,22 +948,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 */
- /* And the same level: */
- if (hubs[i].level != hubs[j].level)
- continue;
+ if (best_score < 1) {
+ best_score = 1;
+ best_match = j;
+ }
- /* Finally, we claim a match: */
- match = j;
- break;
+ /* 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;
}
}
}
@@ -770,11 +1006,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;
@@ -788,8 +1032,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",
- long_options, &option_index);
+ c = getopt_long(argc, argv, short_options, long_options, &option_index);
if (c == -1)
break; /* no more options left */
switch (c) {
@@ -803,13 +1046,16 @@ int main(int argc, char *argv[])
printf("\n");
break;
case 'l':
- strncpy(opt_location, optarg, sizeof(opt_location));
+ snprintf(opt_location, sizeof(opt_location), "%s", optarg);
break;
case 'L':
opt_level = atoi(optarg);
break;
case 'n':
- strncpy(opt_vendor, optarg, sizeof(opt_vendor));
+ snprintf(opt_vendor, sizeof(opt_vendor), "%s", optarg);
+ break;
+ case 's':
+ snprintf(opt_search, sizeof(opt_search), "%s", optarg);
break;
case 'p':
if (!strcasecmp(optarg, "all")) { /* all ports is the default */
@@ -829,6 +1075,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);
@@ -836,6 +1085,17 @@ int main(int argc, char *argv[])
case 'r':
opt_repeat = atoi(optarg);
break;
+ case 'f':
+ opt_force = 1;
+ break;
+ case 'N':
+ opt_nodesc = 1;
+ break;
+#ifdef __gnu_linux__
+ case 'S':
+ opt_nosysfs = 1;
+ break;
+#endif
case 'e':
opt_exact = 1;
break;
@@ -889,23 +1149,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) {
- 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;
}
@@ -925,6 +1173,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)
@@ -941,44 +1192,29 @@ int main(int argc, char *argv[])
if (rc == 0) {
/* will operate on these ports */
int ports = ((1 << hubs[i].nports) - 1) & opt_ports;
- int request = (k == 0) ? LIBUSB_REQUEST_CLEAR_FEATURE
- : LIBUSB_REQUEST_SET_FEATURE;
+ int should_be_on = k;
+
int port;
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))
- continue;
- if (k == 1 && (port_status & power_mask))
- continue;
- int repeat = 1;
- if (k == 0)
- repeat = opt_repeat;
- if (!(port_status & ~power_mask))
- repeat = 1;
- while (repeat-- > 0) {
- rc = libusb_control_transfer(devh,
- LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
- request, USB_PORT_FEAT_POWER,
- port, NULL, 0, USB_CTRL_GET_TIMEOUT
- );
- if (rc < 0) {
- perror("Failed to control port power!\n");
- }
- if (repeat > 0) {
- sleep_ms(opt_wait);
- }
+ int power_mask = hubs[i].super_speed ? USB_SS_PORT_STAT_POWER
+ : USB_PORT_STAT_POWER;
+ int is_on = (port_status & power_mask) != 0;
+
+ if (opt_action == POWER_TOGGLE) {
+ should_be_on = !is_on;
+ }
+
+ if (is_on != should_be_on) {
+ rc = set_port_status(devh, &hubs[i], port, should_be_on);
}
}
}
/* 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"
- );
+ printf("Sent power %s request\n", should_be_on ? "on" : "off");
printf("New status for hub %s [%s]\n",
hubs[i].location, hubs[i].ds.description
);
diff --git a/uhubctl_git.bb b/uhubctl_git.bb
index c51f690..222f313 100644
--- a/uhubctl_git.bb
+++ b/uhubctl_git.bb
@@ -2,7 +2,7 @@ DESCRIPTION = "uhubctl - USB hub per-port power control"
HOMEPAGE = "https://github.com/mvp/uhubctl"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263 \
- file://LICENSE;md5=7a7d8e0fdffe495ff61f52ceee61b2f7"
+ file://LICENSE;md5=a79e6a142b69522fe7757fe7313895eb"
DEPENDS = "libusb1"
RDEPENDS_${PN} = "libusb1"