77 Commits

Author SHA1 Message Date
Vadim Mikhailov
c9fa3c68a1 Release version 2.1.0 2019-09-02 23:12:50 -07:00
Vadim Mikhailov
beea0c0fc7 Use longer git hash for dirty version 2019-09-02 23:12:46 -07:00
Vadim Mikhailov
1856ae9d59 Use --location for long option name
Add --location as long option name - it is still backwards compatible with --loc.
2019-09-02 23:09:56 -07:00
Jonathan Liu
2b9b336038 Add Phidgets HUB0003_0 as supported device 2019-08-29 22:25:16 -07:00
Vadim Mikhailov
1961aa02e9 Add explicit workaround for USB device not removed after port power down on Linux 2019-07-31 13:14:24 -07:00
Vadim Mikhailov
5db248771e Add support for NetBSD
This adds support to be able to build and use uhubctl on NetBSD by using pkg-config to get proper CFLAGS and LDFLAGS.
Perhaps we should use pkg-config universally across all platforms,
but I don't want to potentially break support for existing plaftorms
because pkg-config does not seem to be unconditionally available everywhere.
2019-07-26 18:00:49 -07:00
Vadim Mikhailov
9cae5e275f Add ThinkPad X6 Ultrabase as supported device
Reported in #165.
2019-07-23 21:31:20 -07:00
Vadim Mikhailov
324acc66fd Add status for Raspberry 4B support
Added a link to Raspberry 4B issue https://github.com/raspberrypi/linux/issues/3079.
Once this is fixed in firmware, uhubctl should start working on 4B.
2019-07-22 23:21:47 -07:00
Vadim Mikhailov
15f2d37d79 Add buildenlights (red/green project status lights) to notable projects 2019-07-22 22:53:00 -07:00
Vadim Mikhailov
5f8426105c USB2/3 duality: require serial numbers match only if both present
I have observed a hub which had serial number present for USB2 compatibility hub,
but not for USB3, which broke USB2/3 duality handling:

    Current status for hub 4-4 [0451:8140, USB 3.00, 4 ports]
      Port 1: 06a0 power Rx.Detect
      Port 2: 06a0 power Rx.Detect
      Port 3: 06a0 power Rx.Detect
      Port 4: 06a0 power Rx.Detect
    Current status for hub 3-13 [0451:8142 210C08692601, USB 2.10, 4 ports]
      Port 1: 0100 power
      Port 2: 0100 power
      Port 3: 0100 power
      Port 4: 0100 power

This fix will only compare serial numbers if they are both present.
2019-07-15 10:45:21 -07:00
Vadim Mikhailov
945d08e62c Add FAQ
Added FAQ with issues that are often asked  and tend to have duplicate issues opened.
2019-07-13 18:14:55 -07:00
Vadim Mikhailov
078f9ee9c6 Add Dell P2416D 24" QHD Monitor as supported device
Reported in #164.
2019-07-11 11:48:23 -07:00
Vadim Mikhailov
75023dc28c Stop searching if matching device is found
This is minor fix for issue #161.
2019-07-04 10:46:25 -07:00
Vadim Mikhailov
a510d091af Implemented reading ContainerID from BOS descriptor to detect USB2/3 dual devices
Per USB 3.0 spec, chapter 11.2:

> Within a USB 3.0 hub, both the SuperSpeed and USB 2.0 hub devices shall implement in the hub framework
> a common standardized ContainerID to enable software to identify the physical relationship of the hub devices.
> The ContainerID descriptor is a part of the BOS descriptor set.

* Read ContainerID from BOS descriptor.
* get_device_description() now returns struct with vendor, product, serial number as well as full description.
* Use ContainerID to improve finding dual hubs. This seems to work very well on any OS.
  However, this may still fail if two or more identical hubs use the same hardcoded ContainerID
  In this case, we still try to look for other clues like serial number match if possible.

This fixes issue #161.
2019-07-04 00:45:22 -07:00
Vadim Mikhailov
d8f75116c8 Add Dell UltraSharp 1704FPT as supported device
Reported in #157.
2019-06-16 22:50:05 -07:00
Vadim Mikhailov
94a95a7011 Fixed info about Moxa Uport-407 7-port hub 2019-06-16 21:12:17 -07:00
Vadim Mikhailov
04238bf939 Add VID:PID for Moxa Uport-407 and also update release year
This fixes issue #154.
2019-06-15 15:12:18 -07:00
Vadim Mikhailov
a9da51fb1c Fixed help text for level, some code style fixes 2019-05-17 14:38:17 -07:00
mvp
40cdb66d66 Merge pull request #152 from wjzhou/tux/level-match
add a level hub filter
2019-05-17 14:33:00 -07:00
Wujun Zhou
ee0287077b fix %d is used by the printf 2019-05-17 15:47:16 -04:00
Wujun Zhou
798d2323a9 fix help text for level 2019-05-17 15:43:23 -04:00
Wujun Zhou
bd3e398f4e add comment for the same level check in dual 2019-05-17 15:40:39 -04:00
Wujun Zhou
74a5d721c5 use 0 as opt_level wildcard 2019-05-17 15:39:10 -04:00
Wujun Zhou
134decf02b c style change 2019-05-17 15:27:38 -04:00
Wujun Zhou
dab7ab8320 check for level when match dual 2019-05-14 14:30:46 -04:00
Wujun Zhou
1926954998 add level parameter 2019-05-14 11:48:59 -04:00
Vadim Mikhailov
e93d1a06a2 Use version from VERSION file if git describe fails
This fixes issue #100.

We prefer to determine version from git tags (using git describe).
However, sometimes people don't use git or don't have it installed,
which leads to empty version used.
To solve this, we use version from VERSION file as a backup.
2019-04-28 11:38:17 -07:00
Vadim Mikhailov
fc7aabb529 Support fractional seconds for delay
Allow power cycle delay to be fractional seconds, e.g. not only 2 sec,
but also like 0.3 sec or 1.5 sec.
2019-04-16 15:23:19 -07:00
Vadim Mikhailov
f80467f0fb Fixed broken links in notable projects
Unfortunately, goo.gl does not allow to create new short links anymore,
so slowly switching over to bit.ly.
2019-03-18 11:13:06 -07:00
Vadim Mikhailov
8bbf37f301 Add "USB fan for Raspberry Pi" to notable projects 2019-03-14 18:29:26 -07:00
Vadim Mikhailov
ae4be0a9d5 Note that on *BSD gmake should be used to build. 2019-03-05 15:37:54 -08:00
Vadim Mikhailov
00d1a408b8 Fix build on FreeBSD
* FreeBSD has the same issue as Darwin for snprintf, and same ifdef hack to solve.
* FreeBSD's libusb is stuck at 1.0.12, and does not support LIBUSB_DT_SUPERSPEED_HUB.
  Use ifdef hack to bypass.
* FreeBSD does not have libusb_get_parent() - use libusb_get_port_numbers() to detect parent device.
2019-03-05 15:27:13 -08:00
Vadim Mikhailov
c237ec17ba Add explanation how to configure Linux USB udev permissions 2019-02-24 17:17:22 -08:00
Vadim Mikhailov
1773ac2605 Add ESPRESSObin SBUD102 as supported device 2019-02-24 17:13:56 -08:00
Vadim Mikhailov
343e02b634 Improve USB2/3 duality handling
If hub and its dual have identical description except for vid:pid and USB version strings,
we consider this preferred match.
2019-02-24 11:15:22 -08:00
Vadim Mikhailov
f2a50dd4cb Add OctoPrint-USBControl to notable projects 2019-02-15 11:11:13 -08:00
Vadim Mikhailov
2b88680d3b Add TI TUSB4041PAPEVM as supported device
Reported by @rshaub in issue #137.
2019-02-06 12:49:33 -08:00
Vadim Mikhailov
43edb16298 Remove Transcend TS-HUB3K from supported list
Remove Transcend TS-HUB3K from supported list per issue #135 (does not turn off VBUS).
2019-02-06 12:39:17 -08:00
Vadim Mikhailov
21336c7ab1 Remove TP-Link UH700 from supported list
It turns out that TP-Link UH700 does not support VBUS cut off (issue #133).
2019-01-11 11:32:03 -08:00
Vadim Mikhailov
6c4e5cdf75 Add "USB drive data recovery" to notable projects 2019-01-04 15:54:25 -08:00
Vadim Mikhailov
04320872f5 Add support for comma separated list of ranges to port list parser
* Add support for comma separated list of ports, and possibly ranges,
  e.g. it is possible to use syntax like `1,3-5,9-12`.
* Increase MAX_HUB_PORTS to 14.

WARNING: this will break your scripts if you used syntax like `-p 1234` before.
Replace that syntax with `1,2,3,4` or `1-4` instead.
2019-01-04 15:42:33 -08:00
Vadim Mikhailov
2de7def9df Improve USB3 support
* Fixed `uhubctl -l <hub>` displaying unrelated devices for USB 3.
* Some USB3 hubs use USB_SS_PORT_LS_SS_DISABLED when port is off, but some keep USB_SS_PORT_LS_RX_DETECT.
  To handle this, check USB_SS_PORT_STAT_POWER bit to detect if port is off.
2019-01-04 15:42:33 -08:00
Vadim Mikhailov
f2d016a27c Bump copyright year 2019-01-04 15:42:33 -08:00
mvp
72ac02c324 Merge pull request #128 from silum/master
Update company name and add another compatible hub
2018-11-16 10:17:39 -08:00
Deneys S. Maartens
7ad7d21336 README.md: add B+B USH304 2018-11-16 13:46:50 +02:00
Deneys S. Maartens
4b61cb9e0b README.md: update B+B UHR204
- rename 'B&B Electronics' to 'B+B SmartWorx' to reflect recent
rebranding
- add VID:POD
2018-11-16 13:46:41 +02:00
Vadim Mikhailov
cfee02f014 Fix typo, some spelling formatting issues 2018-11-14 22:06:51 -08:00
Vadim Mikhailov
1aaae4bbb8 Update link to https 2018-11-14 21:58:35 -08:00
Vadim Mikhailov
069c67c836 Respect CPPFLAGS
This should fix issue #125.
2018-11-14 21:53:44 -08:00
Vadim Mikhailov
465aa9bb95 Remove Delock 87445 from supported list
According to report from @vkahl: https://github.com/mvp/uhubctl/issues/123,
Delock 87445 is not detected on various Linux systems.

Originally this hub was reported to work in https://github.com/mvp/uhubctl/pull/107.
I don't know if there were different hardware revisions of Delock 87445,
but it is safer to remove it from supported devices list until proven otherwise.

Also, I am skeptical that it will work correctly as it does not have external power supply.
2018-11-14 11:08:24 -08:00
Vadim Mikhailov
a65c576ca3 Add Club3D CSV-3242HD as supported device
Reported by @matthijskooijman in https://github.com/mvp/uhubctl/issues/122.
2018-11-08 09:51:01 -08:00
Vadim Mikhailov
a2b3695595 Add TP-Link UH700 and Transcend TS-HUB3K as supported devices
Reported by @yrjola in https://github.com/mvp/uhubctl/issues/121.
2018-11-01 17:47:10 -07:00
Vadim Mikhailov
7bf08c0f0f Add USB drives with no phantom load to notable projects 2018-10-12 11:26:03 -07:00
mvp
a9474fe12f Add IOI StarTech U3H415E1 as supported device
Reported by serjepatoff in https://github.com/mvp/uhubctl/issues/116.
2018-09-24 22:53:21 -07:00
mvp
bfc4477189 Merge pull request #114 from Linumiz/bb
Add bitbake recipe file for building with Yocto
2018-09-21 14:33:25 -07:00
Parthiban Nallathambi
5a0995b7b2 Add bitbake recipe file for building with Yocto
- bitbake recipe for yocto building, pulls the latest
src from master

Signed-off-by: Parthiban Nallathambi <parthiban@linumiz.com>
2018-09-19 19:07:44 +02:00
Vadim Mikhailov
dda0ab22f2 Add a note about Raspberry Pi compatibility issue with AmazonBasics hubs
Apparently, Amazon basics hubs are not detectable on Raspberry Pi,
which is documented at https://elinux.org/RPi_Powered_USB_Hubs#Problem_USB_Hubs.
Add a note about this in supported devices table.
2018-09-18 17:47:34 -07:00
mvp
de2c0f96cc Add StarTech ST4200USBM as supported device
Reported by Joey Hess in https://github.com/mvp/uhubctl/issues/112.
2018-08-16 20:45:57 -07:00
Vadim Mikhailov
77c7a7ace8 Add build status light to notable projects 2018-07-19 17:51:47 -07:00
Vadim Mikhailov
8eb39a6138 Reformatted notable projects section of README 2018-07-13 13:36:02 -07:00
mvp
32bc22ea4f Add notable projects
Added list of interesting projects that use uhubctl.
2018-07-06 15:33:29 -07:00
mvp
eaa2795025 Add Delock 87445 as supported device
Originally reported in https://github.com/mvp/uhubctl/pull/107.
2018-07-06 15:03:05 -07:00
Vadim Mikhailov
17094135fc j5create JUH470 only works in USB2 mode
Issue #103: j5create JUH470 doesn't turn off in USB3 mode, but seems to work in USB2 mode.
2018-06-15 10:25:59 -07:00
Vadim Mikhailov
4e18d53426 Add D-Link DUB H7 rev D1 (black) as supported device
Add D-Link DUB H7 rev D1 (black) to supported list as reported in issue #104.
2018-06-15 10:22:36 -07:00
mvp
51ff805c79 Add Renesas uPD720202 PCIe USB 3.0 host controller as supported device
Added new device per this report: https://unix.stackexchange.com/q/165447/321945#comment765232_321945.
Perhaps we should replace this with specific products that use Renesas uPD720202 chipset.
2018-05-08 19:55:14 -07:00
mvp
0cfd1f7341 Add Raspberry Pi Model 3 B+ as supported device
Unlike previous models, Raspberry Pi 3 B+ has two internally daisy-chained hubs:

$ uhubctl
Current status for hub 1-1.1 [0424:2514, USB 2.00, 3 ports]
  Port 1: 0503 power highspeed enable connect [0424:7800]
  Port 2: 0100 power
  Port 3: 0100 power
Current status for hub 1-1 [0424:2514, USB 2.00, 4 ports]
  Port 1: 0503 power highspeed enable connect [0424:2514, USB 2.00, 3 ports]
  Port 2: 0503 power
  Port 3: 0100 power
  Port 4: 0100 power

and this would require using `-l 1-1` or `-l 1-1.1` to control ports on specific hub.

However, I don't have actual RPi 3B+ hardware at hand and can't test if per-port power
switching is fully supported - all previous models only supported ganged switching
for ports 2-5.
2018-05-08 19:49:52 -07:00
mvp
e6f102a55d Add columns for port count and USB version into device table
Also clarify what format is used for hub locations.
2018-04-30 16:07:02 -07:00
mvp
003ba1ff66 Code formatting cleanups 2018-04-30 11:49:50 -07:00
mvp
2311a1c76f Fixed displaying duplicate hubs in status output
This should fix issue #91.
2018-04-15 16:50:21 -07:00
mvp
4661d892bf Add Moxa Uport-407 as supported device 2018-04-15 16:05:37 -07:00
mvp
448e84933a Remove mention of building libusb 1.0.22 from head
Now that issue #33 is resolved (libusb-1.0.22 is released and is included in Homebrew),
we can remove workaround mention from README.
2018-04-05 15:17:35 -07:00
mvp
8da851a50d Merge pull request #87 from nunojpg/master
move action to outer loop and hub to inner loop (fixes #86)
2018-04-02 00:16:43 -07:00
Nuno Goncalves
91ba867ce2 move action to outer loop and hub to inner loop (fixes #86)
Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>
2018-04-02 08:09:31 +01:00
mvp
5efb1487e5 Merge pull request #84 from quintusfelix/master
Add Lindy USB serial converter as supported device
2018-04-01 00:14:07 -07:00
Boerje Sewing
c712868525 Add Lindy USB serial converter as supported device
Signed-off-by: Boerje Sewing <Boerje.Sewing@who-ing.de>
2018-03-28 10:47:48 +02:00
Vadim Mikhailov
fa90f13ba8 Add j5create JUH470 as supported device
This closes issue #78.
2018-03-12 19:34:24 -07:00
Vadim Mikhailov
16ffd56b4b Add Plugable USB3-HUB7C as supported device
Missed to add this model from issue #54.
2018-03-12 19:34:18 -07:00
6 changed files with 517 additions and 183 deletions

View File

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

View File

@@ -14,7 +14,10 @@ RM := rm -rf
CC ?= gcc
CFLAGS ?= -g -O0
CFLAGS += -Wall -Wextra -std=c99 -pedantic
GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
GIT_VERSION := $(shell git describe --abbrev=8 --dirty --always --tags)
ifeq ($(GIT_VERSION),)
GIT_VERSION := $(shell cat VERSION)
endif
CFLAGS += -DPROGRAM_VERSION=\"$(GIT_VERSION)\"
ifeq ($(UNAME_S),Linux)
@@ -34,10 +37,15 @@ 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
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)
$(CC) $(CPPFLAGS) $(CFLAGS) $@.c -o $@ $(LDFLAGS)
install:
$(INSTALL_DIR) $(DESTDIR)$(sbindir)

268
README.md
View File

@@ -1,11 +1,11 @@
uhubctl
=======
uhubctl is utility to control USB power per-port on smart USB hubs.
`uhubctl` is utility to control USB power per-port on smart USB hubs.
Smart hub is defined as one that implements per-port power switching.
Original idea for this code was inspired by hub-ctrl.c by Niibe Yutaka:
http://www.gniibe.org/development/ac-power-control-by-USB-hub
https://www.gniibe.org/development/ac-power-control-by-USB-hub
Compatible USB hubs
@@ -16,41 +16,58 @@ Some of them are no longer manufactured and can be hard to find.
This is list of known compatible USB hubs:
| Manufacturer | Product | VID:PID | Release | EOL |
|:-------------------|:-------------------------------------------------------|:----------|:--------|:-----|
| AmazonBasics | HU3641V1, 4 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
| AmazonBasics | HU3770V1, 7 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
| Apple | Thunderbolt Display 27" (internal USB hub) | | 2011 | 2016 |
| Apple | USB Keyboard With Numeric Pad (internal USB hub) | | 2011 | |
| Asus | Z87-PLUS Motherboard (onboard USB hubs) | | 2013 | 2016 |
| B&B Electronics | UHR204 | | 2013 | |
| Belkin | F5U701-BLK | | 2008 | 2012 |
| Circuitco | Beagleboard-xM (internal USB hub) |`0424:9514`| 2010 | |
| CyberPower | CP-H420P |`0409:0059`| 2004 | |
| Cypress | CY4608 HX2VL development kit |`04b4:6570`| 2012 | |
| D-Link | DUB-H4 rev D1 (black edition, old silver not working) |`05E3:0608`| 2012 | |
| D-Link | DUB-H7 (silver edition only, new black not working) |`2001:F103`| 2005 | 2010 |
| Elecom | U2H-G4S | | 2006 | 2011 |
| Hawking Technology | UH214 | | 2003 | 2008 |
| Lenovo | ThinkPad EU Ultra Dockingstation (40A20090EU) |`17EF:100F`| 2015 | |
| Lenovo | ThinkPad X200 Ultrabase 42X4963 |`17EF:1005`| 2008 | 2011 |
| Linksys | USB2HUB4 | | 2004 | 2010 |
| Maplin | A08CQ |`0409:0059`| 2008 | 2011 |
| Microchip | EVB-USB2517 | | 2008 | |
| Plugable | USB3-HUB7BC (see USB3 note below) |`2109:0813`| 2015 | |
| Plugable | USB3-HUB7-81X (see USB3 note below) |`2109:0813`| 2012 | |
| Plugable | USB2-HUB10S | | 2010 | |
| Raspberry Pi | Model B+, Model 2 B, Model 3 B (port 2 only) | | 2011 | |
| Rosewill | RHUB-210 |`0409:005A`| 2011 | 2014 |
| Sanwa Supply | USB-HUB14GPH | | 2001 | 2003 |
| Sunix | SHB4200MA |`0409:0058`| 2006 | 2009 |
| Targus | PAUH212U | | 2004 | 2009 |
| Manufacturer | Product | Ports | USB | VID:PID | Release | EOL |
|:-------------------|:-----------------------------------------------------|:------|:----|:----------|:--------|:-----|
| 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 | |
| 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 |
| 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 |
| 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 | |
| Dell | UltraSharp 1704FPT 17" LCD Monitor | 4 | 2.0 |`0424:A700`| 2005 | 2015 |
| Elecom | U2H-G4S | 4 | 2.0 | | 2006 | 2011 |
| 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 | |
| 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 | |
| Moxa | Uport-407 | 7 | 2.0 |`110A:0407`| 2009 | |
| 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 | |
| Renesas | uPD720202 PCIe USB 3.0 host controller | 2 | 3.0 | | 2013 | |
| Rosewill | RHUB-210 | 4 | 2.0 |`0409:005A`| 2011 | 2014 |
| Sanwa Supply | USB-HUB14GPH | 4 | 1.1 | | 2001 | 2003 |
| StarTech | ST4200USBM | 4 | 2.0 |`0409:005A`| 2004 | |
| Sunix | SHB4200MA | 4 | 2.0 |`0409:0058`| 2006 | 2009 |
| Targus | PAUH212U | 7 | 2.0 | | 2004 | 2009 |
| Texas Instruments | TUSB4041PAPEVM | 4 | 2.1 |`0451:8142`| 2015 | |
This table is by no means complete.
If your hub works with uhubctl, but is not listed above, please report it
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`.
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.
@@ -60,22 +77,22 @@ so be careful what ports you are turning off!
USB 3.0 duality note
====================
If you have compatible USB 3.0 hub connected to USB3 upstream port,
it will be detected as 2 independent virtual hubs: USB2 and USB3, and your USB devices will be
connected to USB2 or USB3 virtual hub depending on their capabilities and connection speed.
If you have USB 3.0 hub connected to USB3 upstream port, it will be detected
as 2 independent virtual hubs: USB2 and USB3, and your USB devices will be connected
to USB2 or USB3 virtual hub depending on their capabilities and connection speed.
To control power for such hubs, it is necessary to turn off/on power on **both** USB2 and USB3
virtual hubs for power off/on changes to take effect. `uhubctl` will try to do this automatically
(unless you disable this behavior with option `-e`).
Unfortunately, while most hubs will cut off data USB connection, some may still not cut off VBUS to port,
which means connected phone may still continue to charge from port that is powered off by uhubctl.
which means connected phone may still continue to charge from port that is powered off by `uhubctl`.
Compiling
=========
This utility was tested to compile and work on Linux
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, OpenSUSE, Buildroot), FreeBSD and Mac OS X.
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD and Mac OS X.
While `uhubctl` compiles on Windows, USB power switching does not work on Windows because `libusb`
is using `winusb.sys` driver, which according to Microsoft does not support
@@ -87,12 +104,16 @@ First, you need to install library libusb-1.0 (version 1.0.12 or later):
* Ubuntu: `sudo apt-get install libusb-1.0-0-dev`
* Redhat: `sudo yum install libusb1-devel`
* MacOSX: `brew install libusb`, or `sudo port install libusb-devel`
Note that Mac OS Sierra may be affected by issue https://github.com/libusb/libusb/issues/303.
Until libusb 1.0.22 is released, use `brew install --HEAD libusb` as a workaround.
* FreeBSD: libusb is included by default
* NetBSD: `sudo pkgin install libusb1 gmake pkg-config`
* Windows: TBD?
To fetch uhubctl source:
git clone https://github.com/mvp/uhubctl
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:
@@ -106,23 +127,178 @@ Usage
You can control the power on a USB port(s) like this:
uhubctl -a off -p 235
uhubctl -a off -p 2
This means operate on default smart hub and turn power off (`-a off`, or `-a 0`)
on ports 2,3,5 (`-p 235`). Supported actions are `off`/`on`/`cycle` (or `0`/`1`/`2`).
on port 2 (`-p 2`). Supported actions are `off`/`on`/`cycle` (or `0`/`1`/`2`).
`cycle` means turn power off, wait some delay (configurable with `-d`) and turn it back on.
On Linux, you may need to run it with `sudo`, or to configure `udev` USB permissions.
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`.
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.
To find hub locations, simply run `uhubctl` without any parameters.
Hub locations look like `b-x.y.z`, where `b` is USB bus number, and `x`, `y`, `z`...
are port numbers for all hubs in chain, starting from root hub for a given USB bus.
This address is semi-stable - it will not change if you unplug/replug (or turn off/on)
USB device into the same physical USB port (this method is also used in Linux kernel).
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):
SUBSYSTEM=="usb", ATTR{idVendor}=="2001", 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"
and then add permitted users to `dialout` group:
sudo usermod -a -G dialout $USER
For your `udev` rule changes to take effect, reboot or run:
sudo udevadm trigger --attr-match=subsystem=usb
FAQ
===
#### _What is USB per-port power switching?_
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.
You can find what kind of power switching your hardware supports by using `sudo lsusb -v`:
No power switching:
wHubCharacteristic 0x000a
No power switching (usb 1.0)
Per-port overcurrent protection
Ganged power switching:
wHubCharacteristic 0x0008
Ganged power switching
Per-port overcurrent protection
Per-port power switching:
wHubCharacteristic 0x0009
Per-port power switching
Per-port overcurrent protection
#### _How do I check if my USB hub is supported by `uhubctl`?_
1. Run `sudo uhubctl`. If your hub is not listed, it is not supported.
Alternatively, you can run `sudo lsusb -v` and check for
`Per-port power switching` - if you cannot see such line in lsusb output,
hub is not supported.
2. Check for VBUS (voltage) off support: plug a phone, USB light
or USB fan into USB port of your hub.
Try using `uhubctl` to turn power off on that port, and check
that phone stops charging, USB light stops shining or USB fan stops spinning.
If VBUS doesn't turn off, your hub manufacturer did not include circuitry
to actually cut power off. Such hub would still work
to cut off USB data connection, but it cannot turn off power,
and we do not consider this supported device.
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.
#### _USB devices are not removed after port power down on Linux_
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.
This is Linux kernel issue. It may be eventually fixed in kernel, see more discussion [here](https://bit.ly/2JzczjZ).
Basically what happens here is that kernel USB driver knows about power off,
but doesn't send notification about it to udev.
You can use this workaround for this issue:
sudo uhubctl -a off -l ${location} -p ${port}
sudo udevadm trigger --action=remove /sys/bus/usb/devices/${location}.${port}/
Device file will be removed by udev, but USB device will be still visible in `lsusb`.
Note that path `/sys/bus/usb/devices/${location}.${port}` will only exist if device was detected on that port.
When you turn power back on, device should re-enumerate properly (no need to call `udevadm` again).
#### _Power comes back on after few seconds on Linux_
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).
#### _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
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.
Doing so will confuse internal hub circuitry and will cause unpredictable behavior.
#### _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,
attach it to any USB port of Raspberry Pi, and control power on its ports independently.
Notable projects using uhubctl
==============================
| Project | Description |
|:---------------------------------------------------------|:------------------------------------------------------|
| [Morse code USB light](https://git.io/fj1F4) | Flash a message in Morse code with USB light |
| [Webcam USB light](https://git.io/fj1FB) | Turn on/off LED when webcam is turned on/off |
| [Cinema Lightbox](https://goo.gl/fjCvkz) | Turn on/off Cinema Lightbox from iOS Home app |
| [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 |
| [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 |
| [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 |
Copyright
=========
Copyright (C) 2009-2018 Vadim Mikhailov
Copyright (C) 2009-2019 Vadim Mikhailov
This file can be distributed under the terms and conditions of the
GNU General Public License version 2.

1
VERSION Normal file
View File

@@ -0,0 +1 @@
v2.1.0-dev

397
uhubctl.c
View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2018 Vadim Mikhailov
* Copyright (c) 2009-2019 Vadim Mikhailov
*
* Utility to turn USB port power on/off
* for USB hubs that support per-port power switching.
@@ -29,16 +29,20 @@
#include <unistd.h>
#endif
#if defined(__FreeBSD__) || defined(_WIN32)
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(_WIN32)
#include <libusb.h>
#else
#include <libusb-1.0/libusb.h>
#endif
#if defined(__APPLE__) /* snprintf is not available in pure C mode */
#if defined(__APPLE__) || defined(__FreeBSD__) /* snprintf is not available in pure C mode */
int snprintf(char * __restrict __str, size_t __size, const char * __restrict __format, ...) __printflike(3, 4);
#endif
#if !defined(LIBUSB_API_VERSION) || (LIBUSB_API_VERSION <= 0x01000103)
#define LIBUSB_DT_SUPERSPEED_HUB 0x2a
#endif
#if _POSIX_C_SOURCE >= 199309L
#include <time.h> /* for nanosleep */
#endif
@@ -59,14 +63,8 @@ void sleep_ms(int milliseconds)
#endif
}
/* Max number of hub ports supported.
* This is somewhat artifically limited by "-p" option parser.
* If "-p" parser is improved, we can support up to 32 ports.
* However, biggest number of ports on smart hub I've seen was 8.
* I've also observed onboard USB hub with whopping 14 ports,
* but that hub did not support per-port power switching.
*/
#define MAX_HUB_PORTS 9
/* Max number of hub ports supported */
#define MAX_HUB_PORTS 14
#define ALL_HUB_PORTS ((1 << MAX_HUB_PORTS) - 1) /* bitmask */
#define USB_CTRL_GET_TIMEOUT 5000
@@ -180,15 +178,24 @@ struct usb_port_status {
/* List of all USB devices enumerated by libusb */
static struct libusb_device **usb_devs = NULL;
struct descriptor_strings {
char vendor[64];
char product[64];
char serial[64];
char description[256];
};
struct hub_info {
struct libusb_device *dev;
int bcd_usb;
int nports;
int ppps;
int actionable; /* true if this hub is subject to action */
char container_id[33]; /* container ID as hex string */
char vendor[16];
char location[32];
char description[256];
int level;
struct descriptor_strings ds;
};
/* Array of all enumerated USB hubs */
@@ -200,17 +207,19 @@ static int hub_phys_count = 0;
/* default options */
static char opt_vendor[16] = "";
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 */
static int opt_action = POWER_KEEP;
static int opt_delay = 2;
static double opt_delay = 2;
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 const struct option long_options[] = {
{ "loc", required_argument, NULL, 'l' },
{ "location", required_argument, NULL, 'l' },
{ "vendor", required_argument, NULL, 'n' },
{ "level", required_argument, NULL, 'L' },
{ "ports", required_argument, NULL, 'p' },
{ "action", required_argument, NULL, 'a' },
{ "delay", required_argument, NULL, 'd' },
@@ -224,7 +233,7 @@ static const struct option long_options[] = {
};
int print_usage()
static int print_usage()
{
printf(
"uhubctl %s: utility to control USB port power for smart hubs.\n"
@@ -234,9 +243,10 @@ int print_usage()
"Options [defaults in brackets]:\n"
"--action, -a - action to off/on/cycle (0/1/2) for affected ports.\n"
"--ports, -p - ports to operate on [all hub ports].\n"
"--loc, -l - limit hub by location [all smart hubs].\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"
"--delay, -d - delay for cycle action [%d sec].\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"
"--reset, -R - reset hub after each power-on action, causing all devices to reassociate.\n"
@@ -261,11 +271,83 @@ static char* rtrim(char* str)
{
int i;
for (i = strlen(str)-1; i>=0 && isspace(str[i]); i--) {
str[i] = 0;
str[i] = 0;
}
return str;
}
/*
* Convert port list into bitmap.
* Following port list specifications are equivalent:
* 1,3,4,5,11,12,13
* 1,3-5,11-13
* Returns: bitmap of specified ports, max port is MAX_HUB_PORTS.
*/
static int ports2bitmap(char* const portlist)
{
int ports = 0;
char* position = portlist;
char* comma;
char* dash;
int len;
int i;
while (position) {
char buf[8] = {0};
comma = strchr(position, ',');
len = sizeof(buf) - 1;
if (comma) {
if (len > comma - position)
len = comma - position;
strncpy(buf, position, len);
position = comma + 1;
} else {
strncpy(buf, position, len);
position = NULL;
}
/* Check if we have port range, e.g.: a-b */
int a=0, b=0;
a = atoi(buf);
dash = strchr(buf, '-');
if (dash) {
b = atoi(dash+1);
} else {
b = a;
}
if (a > b) {
fprintf(stderr, "Bad port spec %d-%d, first port must be less than last\n", a, b);
exit(1);
}
if (a <= 0 || a > MAX_HUB_PORTS || b <= 0 || b > MAX_HUB_PORTS) {
fprintf(stderr, "Bad port spec %d-%d, port numbers must be from 1 to %d\n", a, b, MAX_HUB_PORTS);
exit(1);
}
for (i=a; i<=b; i++) {
ports |= (1 << (i-1));
}
}
return ports;
}
/*
* Compatibility wrapper around libusb_get_port_numbers()
*/
static int get_port_numbers(libusb_device *dev, uint8_t *buf, uint8_t bufsize)
{
int pcount;
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
/*
* libusb_get_port_path is deprecated since libusb v1.0.16,
* therefore use libusb_get_port_numbers when supported
*/
pcount = libusb_get_port_numbers(dev, buf, bufsize);
#else
pcount = libusb_get_port_path(NULL, dev, buf, bufsize);
#endif
return pcount;
}
/*
* get USB hub properties.
@@ -273,14 +355,13 @@ static char* rtrim(char* str)
* returns 0 for success and error code for failure.
*/
int get_hub_info(struct libusb_device *dev, struct hub_info *info)
static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
{
int rc = 0;
int len = 0;
struct libusb_device_handle *devh = NULL;
unsigned char buf[LIBUSB_DT_HUB_NONVAR_SIZE + 2 + 3] = {0};
struct usb_hub_descriptor *uhd =
(struct usb_hub_descriptor *)buf;
struct usb_hub_descriptor *uhd = (struct usb_hub_descriptor *)buf;
int minlen = LIBUSB_DT_HUB_NONVAR_SIZE + 2;
struct libusb_device_descriptor desc;
rc = libusb_get_device_descriptor(dev, &desc);
@@ -318,15 +399,8 @@ 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);
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
/*
* libusb_get_port_path is deprecated since libusb v1.0.16,
* therefore use libusb_get_port_numbers when supported
*/
int pcount = libusb_get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
#else
int pcount = libusb_get_port_path(NULL, dev, port_numbers, MAX_HUB_CHAIN);
#endif
int pcount = get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
info->level = pcount + 1;
int k;
for (k=0; k<pcount; k++) {
char s[8];
@@ -349,6 +423,28 @@ int get_hub_info(struct libusb_device *dev, struct hub_info *info)
} 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;
@@ -384,9 +480,9 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
/*
* Get USB device description as a string.
* Get USB device descriptor strings and summary description.
*
* It will use following format:
* Summary will use following format:
*
* "<vid:pid> <vendor> <product> <serial>, <USB x.yz, N ports>"
*
@@ -394,42 +490,40 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
* may be skipped if they are empty or not enough permissions to read them.
* <USB x.yz, N ports> will be present only for USB hubs.
*
* returns 0 for success and error code for failure.
* in case of failure description buffer is not altered.
* Returns 0 for success and error code for failure.
* In case of failure return buffer is not altered.
*/
static int get_device_description(struct libusb_device * dev, char* description, int desc_len)
static int get_device_description(struct libusb_device * dev, struct descriptor_strings * ds)
{
int rc;
int id_vendor = 0;
int id_product = 0;
char vendor[64] = "";
char product[64] = "";
char serial[64] = "";
char ports[64] = "";
struct libusb_device_descriptor desc;
struct libusb_device_handle *devh = NULL;
rc = libusb_get_device_descriptor(dev, &desc);
if (rc)
return rc;
bzero(ds, sizeof(*ds));
id_vendor = libusb_le16_to_cpu(desc.idVendor);
id_product = libusb_le16_to_cpu(desc.idProduct);
rc = libusb_open(dev, &devh);
if (rc == 0) {
if (desc.iManufacturer) {
libusb_get_string_descriptor_ascii(devh,
desc.iManufacturer, (unsigned char*)vendor, sizeof(vendor));
rtrim(vendor);
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*)product, sizeof(product));
rtrim(product);
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*)serial, sizeof(serial));
rtrim(serial);
desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
rtrim(ds->serial);
}
if (desc.bDeviceClass == LIBUSB_CLASS_HUB) {
struct hub_info info;
@@ -441,12 +535,12 @@ static int get_device_description(struct libusb_device * dev, char* description,
}
libusb_close(devh);
}
snprintf(description, desc_len,
snprintf(ds->description, sizeof(ds->description),
"%04x:%04x%s%s%s%s%s%s%s",
id_vendor, id_product,
vendor[0] ? " " : "", vendor,
product[0] ? " " : "", product,
serial[0] ? " " : "", serial,
ds->vendor[0] ? " " : "", ds->vendor,
ds->product[0] ? " " : "", ds->product,
ds->serial[0] ? " " : "", ds->serial,
ports
);
return 0;
@@ -464,9 +558,17 @@ 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;
@@ -481,14 +583,20 @@ static int print_port_status(struct hub_info * hub, int portmask)
printf(" Port %d: %04x", port, port_status);
char description[256] = "";
struct descriptor_strings ds;
bzero(&ds, sizeof(ds));
struct libusb_device * udev;
int i = 0;
while ((udev = usb_devs[i++]) != NULL) {
if (libusb_get_parent(udev) == dev &&
dev_bus = libusb_get_bus_number(udev);
/* only match devices on the same bus: */
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) &&
libusb_get_port_number(udev) == port)
{
rc = get_device_description(udev, description, sizeof(description));
rc = get_device_description(udev, &ds);
if (rc == 0)
break;
}
@@ -506,7 +614,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
if (port_status & USB_PORT_STAT_SUSPEND) printf(" suspend");
}
} else {
if (port_status == USB_SS_PORT_LS_SS_DISABLED) {
if (!(port_status & USB_SS_PORT_STAT_POWER)) {
printf(" off");
} else {
int link_state = port_status & USB_PORT_STAT_LINK_STATE;
@@ -535,7 +643,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
if (port_status & USB_PORT_STAT_ENABLE) printf(" enable");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" connect");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", description);
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", ds.description);
printf("\n");
}
@@ -573,16 +681,21 @@ static int usb_find_hubs()
if (rc) {
perm_ok = 0; /* USB permission issue? */
}
get_device_description(dev, info.description, sizeof(info.description));
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 (strlen(opt_location) > 0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
info.actionable = 0;
}
}
if (strlen(opt_vendor)>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;
}
@@ -592,54 +705,74 @@ static int usb_find_hubs()
}
}
}
if (!opt_exact) {
/* Handle USB2/3 duality: */
for (i=0; i<hub_count; i++) {
/* Check only actionable hubs: */
if (hubs[i].actionable != 1)
continue;
/* Must have non empty container ID: */
if (strlen(hubs[i].container_id) == 0)
continue;
int match = -1;
for (j=0; j<hub_count; j++) {
if (i==j)
continue;
/* Find hub which is USB2/3 dual to the hub above */
/* Hub and its dual must be different types: one USB2, another USB3: */
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
(hubs[j].bcd_usb < USB_SS_BCD))
continue;
/* Must have non empty container ID: */
if (strlen(hubs[j].container_id) == 0)
continue;
/* Per USB 3.0 spec chapter 11.2, container IDs must match: */
if (strcmp(hubs[i].container_id, hubs[j].container_id) != 0)
continue;
/* At this point, it should be enough to claim a match.
* However, some devices use hardcoded non-unique container ID.
* We should do few more checks below if multiple such devices are present.
*/
/* 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)
{
continue;
}
/* Hubs should have the same number of ports: */
if (hubs[i].nports != hubs[j].nports)
continue;
/* And the same level: */
if (hubs[i].level != hubs[j].level)
continue;
/* Finally, we claim a match: */
match = j;
break;
}
if (match >= 0) {
if (!hubs[match].actionable) {
/* Use 2 to signify that this is derived dual device */
hubs[match].actionable = 2;
}
}
}
}
hub_phys_count = 0;
for (i=0; i<hub_count; i++) {
/* Check only actionable USB3 hubs: */
if (!hubs[i].actionable)
continue;
if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
hub_phys_count++;
}
if (opt_exact)
continue;
int match = -1;
for (j=0; j<hub_count; j++) {
if (i==j)
continue;
/* Find hub which is USB2/3 dual to the hub above.
* This is quite reliable and predictable on Linux
* but not on Mac, where we may match wrong hub :(
* It will work reliably on Mac if there is
* only one compatible USB3 hub is connected.
* TODO: discover better way to find dual hub.
*/
/* Hub and its dual must be different types: one USB2, another USB3: */
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
(hubs[j].bcd_usb < USB_SS_BCD))
continue;
/* But they must have the same vendor: */
if (strncasecmp(hubs[i].vendor, hubs[j].vendor, 4))
continue;
/* Provisionally we choose this one as dual: */
if (match < 0 && !hubs[j].actionable)
match = j;
/* But if there is exact port path match,
* we prefer it (true for Linux but not Mac):
*/
char *p1 = strchr(hubs[i].location, '-');
char *p2 = strchr(hubs[j].location, '-');
if (p1 && p2 && strcasecmp(p1, p2)==0) {
match = j;
break;
}
}
if (match >= 0)
hubs[match].actionable = 1;
}
if (perm_ok == 0 && hub_phys_count == 0) {
return LIBUSB_ERROR_ACCESS;
@@ -655,7 +788,7 @@ int main(int argc, char *argv[])
int option_index = 0;
for (;;) {
c = getopt_long(argc, argv, "l:n:a:p:d:r:w:hveR",
c = getopt_long(argc, argv, "l:L:n:a:p:d:r:w:hveR",
long_options, &option_index);
if (c == -1)
break; /* no more options left */
@@ -672,6 +805,9 @@ int main(int argc, char *argv[])
case 'l':
strncpy(opt_location, optarg, sizeof(opt_location));
break;
case 'L':
opt_level = atoi(optarg);
break;
case 'n':
strncpy(opt_vendor, optarg, sizeof(opt_vendor));
break;
@@ -680,16 +816,7 @@ int main(int argc, char *argv[])
break;
}
if (strlen(optarg)) {
/* parse port list */
opt_ports = 0;
size_t i;
for (i=0; i<strlen(optarg); i++) {
if (!isdigit(optarg[i]) || optarg[i] == '0') {
printf("%s must be list of ports 1 to %d\n", optarg, MAX_HUB_PORTS);
}
int d = optarg[i]-'1';
opt_ports |= (1 << d);
}
opt_ports = ports2bitmap(optarg);
}
break;
case 'a':
@@ -704,7 +831,7 @@ int main(int argc, char *argv[])
}
break;
case 'd':
opt_delay = atoi(optarg);
opt_delay = atof(optarg);
break;
case 'r':
opt_repeat = atoi(optarg);
@@ -790,28 +917,30 @@ int main(int argc, char *argv[])
);
exit(1);
}
int i;
for (i=0; i<hub_count; i++) {
if (hubs[i].actionable == 0)
int k; /* k=0 for power OFF, k=1 for power ON */
for (k=0; k<2; k++) { /* up to 2 power actions - off/on */
if (k == 0 && opt_action == POWER_ON )
continue;
printf("Current status for hub %s [%s]\n",
hubs[i].location, hubs[i].description
);
print_port_status(&hubs[i], opt_ports);
if (opt_action == POWER_KEEP) { /* no action, show status */
if (k == 1 && opt_action == POWER_OFF)
continue;
}
struct libusb_device_handle * devh = NULL;
rc = libusb_open(hubs[i].dev, &devh);
if (rc == 0) {
/* will operate on these ports */
int ports = ((1 << hubs[i].nports) - 1) & opt_ports;
int k; /* k=0 for power OFF, k=1 for power ON */
for (k=0; k<2; k++) { /* up to 2 power actions - off/on */
if (k == 0 && opt_action == POWER_ON )
continue;
if (k == 1 && opt_action == POWER_OFF)
continue;
if (k == 1 && opt_action == POWER_KEEP)
continue;
int i;
for (i=0; i<hub_count; i++) {
if (hubs[i].actionable == 0)
continue;
printf("Current status for hub %s [%s]\n",
hubs[i].location, hubs[i].ds.description
);
print_port_status(&hubs[i], opt_ports);
if (opt_action == POWER_KEEP) { /* no action, show status */
continue;
}
struct libusb_device_handle * devh = NULL;
rc = libusb_open(hubs[i].dev, &devh);
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 port;
@@ -819,7 +948,7 @@ int main(int argc, char *argv[])
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;
: USB_SS_PORT_STAT_POWER;
if (k == 0 && !(port_status & power_mask))
continue;
if (k == 1 && (port_status & power_mask))
@@ -844,8 +973,6 @@ int main(int argc, char *argv[])
}
}
}
if (k==0 && opt_action == POWER_CYCLE)
sleep_ms(opt_delay * 1000);
/* USB3 hubs need extra delay to actually turn off: */
if (k==0 && hubs[i].bcd_usb >= USB_SS_BCD)
sleep_ms(150);
@@ -853,7 +980,7 @@ int main(int argc, char *argv[])
request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"
);
printf("New status for hub %s [%s]\n",
hubs[i].location, hubs[i].description
hubs[i].location, hubs[i].ds.description
);
print_port_status(&hubs[i], opt_ports);
@@ -869,6 +996,8 @@ int main(int argc, char *argv[])
}
libusb_close(devh);
}
if (k == 0 && opt_action == POWER_CYCLE)
sleep_ms((int)(opt_delay * 1000));
}
rc = 0;
cleanup:

20
uhubctl_git.bb Normal file
View File

@@ -0,0 +1,20 @@
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"
DEPENDS = "libusb1"
RDEPENDS_${PN} = "libusb1"
SRC_URI = "git://github.com/mvp/uhubctl.git"
SRCREV = "${AUTOREV}"
PV = "git"
S = "${WORKDIR}/git"
do_install_append () {
install -d ${D}${bindir}
install -Dm 0755 ${S}/uhubctl ${D}${bindir}/
}
FILES_${PN} += "${bindir}/*"