mirror of
https://github.com/mvp/uhubctl.git
synced 2026-05-17 08:26:29 +03:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
014b55ac5d | ||
|
|
92736a8604 | ||
|
|
9f1de74523 | ||
|
|
9b73c8e314 | ||
|
|
fa449eac0b | ||
|
|
9b5efa0ec0 | ||
|
|
b99a0805bb | ||
|
|
6ac502a07a | ||
|
|
f76dc98653 | ||
|
|
592d60b9ab | ||
|
|
4b663f3d15 | ||
|
|
6a43ea9c5c | ||
|
|
6f136f17fe | ||
|
|
8d8b66c5c6 | ||
|
|
1b52efddbd | ||
|
|
71a4274615 | ||
|
|
d98e6deb6e | ||
|
|
ac5a704449 | ||
|
|
1e65ff9b05 | ||
|
|
77aeb7f30b | ||
|
|
e65d077712 | ||
|
|
0cc09299fe | ||
|
|
44f963d00f | ||
|
|
d3a79ace46 | ||
|
|
0e733b6901 | ||
|
|
50d5501d91 | ||
|
|
9183aef17e | ||
|
|
4b10fdbd24 | ||
|
|
3cc17baae1 | ||
|
|
db8c4a59f3 | ||
|
|
77d8851c34 | ||
|
|
dcb1e611f7 | ||
|
|
a7b5f6b362 | ||
|
|
2a4df4ee6c | ||
|
|
f610598227 | ||
|
|
3bc22e3cd7 | ||
|
|
0f6218dfb8 | ||
|
|
91035987e4 | ||
|
|
c6927085ec | ||
|
|
5b1ae79b05 | ||
|
|
b8a1b808e7 | ||
|
|
f75bda6896 | ||
|
|
e171813596 | ||
|
|
5257d8567d | ||
|
|
5ee8260c71 | ||
|
|
8ba36bd8df | ||
|
|
aaf87accad | ||
|
|
58385b87e6 | ||
|
|
7bf80a08d1 | ||
|
|
b73213dd6d | ||
|
|
e327f5aeb1 | ||
|
|
9b824b5587 | ||
|
|
aa864285b1 | ||
|
|
fa524cf7df | ||
|
|
7fd3080f90 | ||
|
|
e3734e052f | ||
|
|
d576db98fb | ||
|
|
9e402917dc | ||
|
|
b0d321a041 | ||
|
|
0d2aa24bae | ||
|
|
bd92bcef2f | ||
|
|
ea70731854 | ||
|
|
6366aea0bd | ||
|
|
232dcc6f6d | ||
|
|
5691db3379 | ||
|
|
fae3da7e6f | ||
|
|
c79c9ac667 | ||
|
|
7f25162ed3 | ||
|
|
6f84663c6a | ||
|
|
4aae44ced0 | ||
|
|
9857849c0e | ||
|
|
821cdb8833 | ||
|
|
c387442b0c | ||
|
|
b690bd376a | ||
|
|
c220668adc | ||
|
|
3e10680f22 | ||
|
|
39b407a863 | ||
|
|
d227c8326f | ||
|
|
743ecf226b | ||
|
|
fd611df1e7 | ||
|
|
a6cdc3dc4b | ||
|
|
609d0bcae1 | ||
|
|
54365acb8d | ||
|
|
6e5782c3b7 | ||
|
|
59447ce431 | ||
|
|
05647714b6 | ||
|
|
4818eddc03 |
@@ -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"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
uhubctl – USB hub per-port power control.
|
||||
|
||||
Copyright (c) 2009-2019, Vadim Mikhailov
|
||||
Copyright (c) 2009-2020, 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
|
||||
|
||||
29
Makefile
29
Makefile
@@ -10,38 +10,31 @@ 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
|
||||
GIT_VERSION := $(shell git describe --abbrev=8 --dirty --always --tags)
|
||||
GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
|
||||
ifeq ($(GIT_VERSION),)
|
||||
GIT_VERSION := $(shell cat VERSION)
|
||||
GIT_VERSION := $(shell cat VERSION)
|
||||
endif
|
||||
CFLAGS += -DPROGRAM_VERSION=\"$(GIT_VERSION)\"
|
||||
|
||||
# Use hardening options on Linux
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
LDFLAGS += -Wl,-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
|
||||
|
||||
175
README.md
175
README.md
@@ -20,59 +20,77 @@ 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 | 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 | |
|
||||
| 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 |
|
||||
| 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 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 | |
|
||||
| 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,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 | |
|
||||
| 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 | |
|
||||
| Juiced Systems | 6HUB-01 | 7 | 3.0 |`0BDA:0411`| 2014 | 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 |
|
||||
| Lindy | USB serial converter 4 port | 4 | 1.1 |`058F:9254`| 2008 | |
|
||||
| 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 | |
|
||||
| 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 | |
|
||||
| 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 |
|
||||
| StarTech | ST4200USBM | 4 | 2.0 |`0409:005A`| 2004 | |
|
||||
| Seagate | Backup Plus Hub STEL8000100 | 2 | 3.0 |`0BC2:AB44`| 2016 | |
|
||||
| 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.
|
||||
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 +110,69 @@ 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.
|
||||
|
||||
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.
|
||||
@@ -148,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"
|
||||
@@ -173,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:
|
||||
@@ -212,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_
|
||||
@@ -240,12 +288,21 @@ 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,25 +311,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.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
|
||||
Notable projects using uhubctl
|
||||
@@ -285,20 +367,23 @@ 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 |
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
Copyright (C) 2009-2019 Vadim Mikhailov
|
||||
Copyright (C) 2009-2020 Vadim Mikhailov
|
||||
|
||||
This file can be distributed under the terms and conditions of the
|
||||
GNU General Public License version 2.
|
||||
|
||||
356
uhubctl.c
356
uhubctl.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 Vadim Mikhailov
|
||||
* Copyright (c) 2009-2020 Vadim Mikhailov
|
||||
*
|
||||
* Utility to turn USB port power on/off
|
||||
* for USB hubs that support per-port power switching.
|
||||
@@ -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 */
|
||||
|
||||
@@ -182,19 +183,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 +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,53 +408,67 @@ 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;
|
||||
/* 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;
|
||||
}
|
||||
} 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]);
|
||||
/* 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);
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
libusb_free_bos_descriptor(bos);
|
||||
|
||||
/* 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 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;
|
||||
}
|
||||
libusb_close(devh);
|
||||
}
|
||||
@@ -499,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);
|
||||
@@ -529,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);
|
||||
@@ -541,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;
|
||||
}
|
||||
@@ -558,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;
|
||||
@@ -588,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);
|
||||
@@ -602,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 {
|
||||
@@ -680,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 (info.lpsm != HUB_CHAR_INDV_PORT_LPSM && !opt_force) {
|
||||
continue;
|
||||
}
|
||||
info.actionable = 1;
|
||||
if (strlen(opt_search) > 0) {
|
||||
/* Search by attached device description */
|
||||
info.actionable = 0;
|
||||
struct libusb_device * udev;
|
||||
int k = 0;
|
||||
while ((udev = usb_devs[k++]) != NULL) {
|
||||
uint8_t dev_pn[MAX_HUB_CHAIN];
|
||||
uint8_t dev_bus = libusb_get_bus_number(udev);
|
||||
/* only match devices on the same bus: */
|
||||
if (dev_bus != info.bus) continue;
|
||||
int dev_plen = get_port_numbers(udev, dev_pn, sizeof(dev_pn));
|
||||
if ((dev_plen == info.pn_len + 1) &&
|
||||
(memcmp(info.port_numbers, dev_pn, info.pn_len) == 0))
|
||||
{
|
||||
struct descriptor_strings ds;
|
||||
bzero(&ds, sizeof(ds));
|
||||
rc = get_device_description(udev, &ds);
|
||||
if (rc != 0)
|
||||
break;
|
||||
if (strstr(ds.description, opt_search)) {
|
||||
info.actionable = 1;
|
||||
opt_ports &= 1 << (dev_pn[dev_plen-1] - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opt_level > 0) {
|
||||
if (opt_level != info.level) {
|
||||
info.actionable = 0;
|
||||
}
|
||||
}
|
||||
if (strlen(opt_vendor) > 0) {
|
||||
if (strncasecmp(opt_vendor, info.vendor, strlen(opt_vendor))) {
|
||||
info.actionable = 0;
|
||||
}
|
||||
}
|
||||
memcpy(&hubs[hub_count], &info, sizeof(info));
|
||||
hub_count++;
|
||||
}
|
||||
}
|
||||
if (strlen(opt_location) > 0) {
|
||||
if (strcasecmp(opt_location, info.location)) {
|
||||
info.actionable = 0;
|
||||
}
|
||||
}
|
||||
if (opt_level > 0) {
|
||||
if (opt_level != info.pn_len + 1) {
|
||||
info.actionable = 0;
|
||||
}
|
||||
}
|
||||
if (strlen(opt_vendor) > 0) {
|
||||
if (strncasecmp(opt_vendor, info.vendor, strlen(opt_vendor))) {
|
||||
info.actionable = 0;
|
||||
}
|
||||
}
|
||||
memcpy(&hubs[hub_count], &info, sizeof(info));
|
||||
if (hub_count < MAX_HUBS) {
|
||||
hub_count++;
|
||||
} else {
|
||||
/* That should be impossible - but we don't want to crash! */
|
||||
fprintf(stderr, "Too many hubs!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!opt_exact) {
|
||||
/* Handle USB2/3 duality: */
|
||||
@@ -714,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;
|
||||
@@ -722,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: */
|
||||
@@ -739,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)
|
||||
@@ -746,22 +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 */
|
||||
|
||||
/* 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 +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;
|
||||
@@ -788,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 */
|
||||
@@ -811,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;
|
||||
@@ -829,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);
|
||||
@@ -836,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;
|
||||
@@ -889,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) {
|
||||
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 +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)
|
||||
@@ -947,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,
|
||||
@@ -974,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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user