149 Commits

Author SHA1 Message Date
mvp
20276ad5ce Release version 2.5.0 2022-11-01 20:26:08 -07:00
mvp
914060b4b5 Add simpler way to configure udev rules and ready to use udev rule file
It turns out that we can allow udev access to any USB hub using DRIVER=="hub"
udev filter, this should make it easier for linux ditros to add proper udev rules.

This change adds more documentation to README, and also adds ready to use udev rule
that should work for any USB hub and any Linux kernel version.
2022-11-01 20:19:00 -07:00
gfrancesco
5a46019e15 ADD: BenQ PD3220U monitor to compatibility list (#464) 2022-10-26 22:55:09 -07:00
Vadim Mikhailov
b6189d1409 Consistently use only C style comments 2022-10-05 12:04:56 -07:00
Vadim Mikhailov
554a5c7eb3 Note that PR #450 has fixed few annoyances listed in FAQ 2022-09-22 16:22:09 -07:00
Vadim Mikhailov
732ad2d90e Fix crash on non-Linux if -S is used
Also move short options near long options table so it is easier to maintain.
2022-09-22 11:24:32 -07:00
Vadim Mikhailov
dd5ff8f79d Fix unused variable compilation warning on non Linux platforms 2022-09-22 11:09:24 -07:00
mvp
a802fa75aa Merge pull request #450 from hnez/linux-sysfs
Use sysfs interface on recent Linux kernels
2022-09-22 11:05:23 -07:00
Leonard Göhrs
24e1540006 Add udev rules for Linux sysfs power switching to README.md
The udev rules for the sysfs case are a bit more complex than those for the
libusb based interface, as udev has built-in support for changing permissions
on device files but not for sysfs attributes.
Instead we have to use chmod / chown to set permissions and owners.

You may notice the " || true" parts in the RUN part of the rules in addition
to the -f parameters. These are there to make sure that no error is logged by
udev even if the disable attribute does not exist. chmod/chown still return
an error exit code even with -f if the requested path does not exist.
" || true" makes sure this error is not propagated to udev.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
2022-09-22 11:40:03 +02:00
Leonard Göhrs
aa7fc0a126 Add support for Linux sysfs based power switching
Starting with Linux kernel 6.0[1] there will be a sysfs interface to power USB
ports off/on from userspace.
Try to use this interface before falling back to the usual libusb based
power switching (e.g. when running on a kernel <6.0 or if file permissions do
not allow using the sysfs interface).

The main benefit of using the sysfs interface is that the kernel does not get
confused about the state of a port, so retrying should no longer be required.

[1]: https://lore.kernel.org/all/20220607114522.3359148-1-m.grzeschik@pengutronix.de/

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
2022-09-22 11:40:03 +02:00
Vadim Mikhailov
7099809b24 Add Apple Pro Display XDR as compatible device
Closes #453.
2022-09-06 18:03:10 -07:00
mvp
a1bc86824b Add Inateck HB2025A as compatible device
This works only if connected via USB2.

Closes #451.
2022-09-05 10:46:29 -07:00
Leonard Göhrs
9d66811175 Split out port status setting logic into separate function
Split out current libusb based power on/off logic into a separate function
in anticipation of a other power switching implementations.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
2022-09-02 12:28:04 +02:00
Vadim Mikhailov
9171fc6bad Fix description for j5create JUH470 quirks 2022-09-01 22:35:15 -07:00
Vadim Mikhailov
d75b10d696 Fixed USB version for few supported devices to match their spec
Per USB 3.2 spec, any USB 3.x device can claim itself as USB 3.2 per following table:

| Old style USB version | USB 3.2 specification | Speed, gbps |
|-----------------------|-----------------------|-------------|
| USB 3.0               | USB 3.2 Gen 1         | 5           |
| USB 3.1               | USB 3.2 Gen 2         | 10          |
| USB 3.2               | USB 3.2 Gen 2x2       | 20          |

https://www.usb.org/sites/default/files/usb_3_2_language_product_and_packaging_guidelines_final.pdf

And this is what happens - some devices claim themselves to be USB 3.2 despite only supporting 5 or 10 gbps -
and unfortunately this is correct per USB 3.2 spec. But it is very confusing for people shopping for better product.

Not ideal solution, but a workaround is to use old style USB version designators in our compatibility table
so people do not choose USB 3.2 device listed if it cannot support speeds higher than 5 gbps.

Closes #447.
2022-08-31 15:58:13 -07:00
Tim Ruffing
88e5641fe1 Make sure option strings are null-terminated 2022-08-24 15:59:01 -07:00
Nicolai
b51b5addee Add python3-uhubctl to project list
Add python3-uhubctl to project list
2022-08-24 12:04:15 -07:00
Vadim Mikhailov
a76f45f377 Remove D-Link DUB-H4 rev B from the list
D-Link DUB-H4 rev B does not seem to support VBUS off, remove from supported list.
Closes #440.
2022-08-24 12:02:24 -07:00
mvp
941e356c24 Add HP USB-C Dock G5 as compatible device
Closes #441.
2022-08-22 14:58:56 -07:00
Vadim Mikhailov
e1e4d45066 Add Rosonway RSH-A13 as compatible device
This works only if connected via USB2.

Closes #434.
2022-08-05 11:03:34 -07:00
Vadim Mikhailov
9263ab266e Add Dell U3419W as compatible device
Closes #435.
2022-08-02 09:38:58 -07:00
Vadim Mikhailov
54248b8f29 Note that issue #419 is fixed in MacOS 12.5
Closes #419.
2022-07-20 16:47:09 -07:00
Vadim Mikhailov
06e7240db6 Add Seeed Studio reTerminal CM4104032 as compatible device
Closes #429.
2022-07-12 15:57:28 -07:00
Vadim Mikhailov
e1709c7b62 Add a note for AmazonBasics HU9002V1EBL
Closes #428.
2022-07-12 15:51:48 -07:00
Vadim Mikhailov
38ff03200d Add Rosonway RSH-A13 as compatible device (with some caveats)
Closes #423.
2022-07-01 17:17:45 -07:00
Vadim Mikhailov
24e3680c4b Add a warning that libusb (and uhubctl) are broken on MacOS 12.4 and later
Highlight issue #419 and https://github.com/libusb/libusb/issues/1156.
2022-06-20 11:56:44 -07:00
Vadim Mikhailov
5418edf9d1 Add a warning link that RSHTech RSH-A16 may not work
Rosonway RSH-A16 works, while RSHTech RSH-A16 may not.
Closes #417.
2022-06-17 17:55:52 -07:00
Vadim Mikhailov
a6159d68e7 Add NVidia Jetson Xavier NX as compatible device
Closes #420.
2022-05-27 13:24:16 -07:00
Vadim Mikhailov
ee98e43a43 Update copyright year 2022-05-10 18:13:08 -07:00
Vadim Mikhailov
4f38fde9da Add LG Electronics 27UK850-W monitor as compatible device
Closes #414.
2022-05-10 18:11:25 -07:00
Vadim Mikhailov
a022e60112 Add Lenovo ThinkPlus 4X90W86497 as compatible device
Closes #414.
2022-05-10 18:04:25 -07:00
Vadim Mikhailov
a0f8c87e08 Add Rosonway RSH-518C as compatible device
Closes #407.
2022-05-10 16:25:22 -07:00
Vadim Mikhailov
6f1c71d5a5 Add j5create JUH377 as compatible device
Closes #406.
2022-03-07 10:25:19 -08:00
Vadim Mikhailov
7496d53f87 Add a note for Cypress CY4608 HX2VL
Closes #404.
2022-03-07 10:10:33 -08:00
Vadim Mikhailov
56ea37fc46 Add a warning that some revisions of Bytecc BT-UH340 may not work
Closes #401.
2022-02-28 15:41:08 -08:00
Vadim Mikhailov
ed27158c11 Add Lenovo T24i-10 as compatible device
Closes #384.
2022-02-02 20:22:07 -08:00
Vadim Mikhailov
53fd1f39ce Remove StarTech ST4300USB3 from compatible list, does not seem to properly support VBUS off
Closes #390.
2022-02-01 17:16:22 -08:00
Vadim Mikhailov
950fce2325 Add Centech CT-USB4HUB as compatible device
Closes #393.
2022-02-01 17:13:50 -08:00
Mark Furland
61187d755c Add pluggable USB3-HUB7BC; note USB3-HUB7-81x is 2 port only
Closes #378.
2021-12-17 13:10:32 -08:00
Vadim Mikhailov
610e85ff2d Add StarTech ST4300USB3 as compatible device
Closes #376.
2021-11-29 11:22:47 -08:00
Vadim Mikhailov
5779054ccc Add a note about D-Link DUB-H7 rev. G not supported
Closes #377.
2021-11-29 11:16:41 -08:00
Vadim Mikhailov
f8cb89f4be Add option --nodesc to skip querying device string descriptors
Sometimes target devices may become unresponsive for control messages to read string descriptors.
Option --nodesc (-N) allows to still reset such devices.

Also, as preventative measure, check if libusb_get_string_descriptor_ascii returns any error,
then stop querying any other string descriptor from the same device.
2021-10-29 11:49:59 -07:00
Vadim Mikhailov
1a7d34780f Add Microchip EVB-USB5807 as compatible device
Closes #369.
2021-10-20 16:56:31 -07:00
Vadim Mikhailov
f6e99bc35d Add HP P5Q58UT and LG 27MD5KL-B as compatible devices
Adding two devices reported by @alanzchen in #366.
2021-10-18 22:20:00 -07:00
Vadim Mikhailov
db3ae900d8 Add BenQ PD2700U as compatible device
Closes #362.
2021-09-28 19:15:13 -07:00
Vadim Mikhailov
75b1f668f9 Add Anker AK-A83650A1 as compatible device
Closes #361.
2021-09-28 19:15:08 -07:00
Vadim Mikhailov
37f494ff31 Update UUGear MEGA4 info 2021-08-12 13:33:05 -07:00
Vadim Mikhailov
bc80baa9e9 Add UUGear MEGA4 as compatible device
Add UUGear MEGA4 USB 3.1 PPPS hub for Raspberry PI 4B to supported list,
it was specifically designed to support uhubctl in #316.
https://www.uugear.com/news/new-product-mega4-usb-3-1-ppps-hub-for-raspberry-pi-4
2021-08-09 09:57:41 -07:00
Vadim Mikhailov
6cd357e150 Remove UNITEK Y-3098ABK from supported list
Too many reports in #352 stating it is not supporting VBUS.
2021-08-09 09:32:34 -07:00
Vadim Mikhailov
5ba647ee27 Add Unitek Y-3098ABK as compatible device
Closes #352.
2021-07-07 13:26:55 -07:00
Vadim Mikhailov
69c444b75d Add Hardkernel ODROID-C4 as compatible device
Closes #348.
2021-05-31 20:31:10 -07:00
Vadim Mikhailov
f20007e25e Add Delock 62537 as compatible device
Closes #346.
2021-05-17 22:08:47 -07:00
Vadim Mikhailov
46d0de4331 Re-add Delock 87445 to supported list
It seems to work after all (originally reported in #107, then removed in #123).
It looks like there are different hardware revisions, some work well, some are not detected - added a note.
2021-05-17 22:04:11 -07:00
Vadim Mikhailov
23c15ffcf8 Add diyppps to notable projects
Closes #343.
2021-05-01 22:13:32 -07:00
Vadim Mikhailov
16f7a6dd23 Add AmazonBasics HUC9002V1ESL as compatible device
AmazonBasics HUC900xxx models seem to be identical to HU900xxx except for included USB-C cable.
2021-05-01 22:05:42 -07:00
Vadim Mikhailov
7bafc7ffb3 Add a note for Linksys USB2HUB4
Closes #331.
2021-04-02 14:27:53 -07:00
Vadim Mikhailov
0a42b91382 Add Webcam On-Air Sign to notable projects 2021-04-01 09:42:04 -07:00
Vadim Mikhailov
2820f9fa27 DLink DUB-H4 rev B4 does not work
Closes #328.
2021-03-29 15:48:24 -07:00
Vadim Mikhailov
fb85325629 Add AmazonBasics U3-7HUB as supported device
AmazonBasics U3-7HUB power switching works, but only on 1 charge port with lightning symbol.
This should also work for similar AmazonBasics hubs U3-4HUB and U3-10HUB.

Closes #325.
2021-03-18 17:02:38 -07:00
Vadim Mikhailov
e5ae7b9906 Add Plugable USB3-HUB10-C2 as supported device
Plugable USB3-HUB10-C2 power switching works, but only on 2 charge ports.
This is also true for USB3-HUB7C, updated note accordingly.

Closes #322.
2021-03-05 12:55:00 -08:00
Vadim Mikhailov
bf3971423a Add Autobrew to list of notable projects 2021-03-05 12:46:04 -08:00
Vadim Mikhailov
7757db54ac Release version 2.4.0 as stable brew tap 2021-02-13 16:48:24 -08:00
Vadim Mikhailov
014b55ac5d Release version 2.4.0 2021-02-13 16:21:16 -08:00
Vadim Mikhailov
92736a8604 Honor PKG_CONFIG variable if exists
Some build systems want to pass location for pkg-config.

Closes #315.
2021-02-11 17:48:11 -08:00
Vadim Mikhailov
9f1de74523 Clarify that we do not maintain list of unsupported hubs
Closes #312.
2021-02-05 15:40:21 -08:00
Vadim Mikhailov
9b73c8e314 Note that PAUH212 is also supported
Closes #311.
2021-02-05 15:32:05 -08:00
Vadim Mikhailov
fa449eac0b Add a note that D-Link DUB-H4 rev B7+ is not supported
Closes #307.
2021-01-21 13:16:46 -08:00
Vadim Mikhailov
9b5efa0ec0 Simplify code that powers on/off
* Simplified code that powers on/off.
* Fixed minor bug for repeat counter (always once to power off, and opt_repeat times to power on).
* Only warn about USB permissions if no compatible hubs are detected.
  Old behavior would always warn, even if permissions were missing for any hub (even unsupported ones).
2021-01-17 14:34:11 -08:00
comzine
b99a0805bb Added feature toggle mode (#305)
Added feature toggle mode

Co-authored-by: Tobias Weber <comzine@gmail.com>
2021-01-17 11:28:44 -08:00
Vadim Mikhailov
6ac502a07a Add ExSys EX-1113HMS as compatible device
Closes #303.
2021-01-15 09:28:02 -08:00
Vadim Mikhailov
f76dc98653 Refactor README section for Raspberry Pi
Move workaround closer to the question context.
2020-12-16 09:04:26 -08:00
Vadim Mikhailov
592d60b9ab Add a note to Anker AK-68ANHUB-BV7A-0004
Closes #300.
2020-12-16 08:49:30 -08:00
Vadim Mikhailov
4b663f3d15 Add Aukey CB-C59 as compatible device
Closes #299.
2020-12-15 11:43:05 -08:00
Vadim Mikhailov
6a43ea9c5c Add Controlling USB LED Strip to notable projects 2020-12-14 11:30:39 -08:00
Vadim Mikhailov
6f136f17fe Add Port NWUSB01 as compatible device
Closes #298.
2020-12-14 11:23:29 -08:00
Vadim Mikhailov
8d8b66c5c6 Release version 2.3.0 as stable brew tap 2020-12-13 14:56:43 -08:00
Vadim Mikhailov
1b52efddbd Release version 2.3.0 2020-12-13 14:31:05 -08:00
Vadim Mikhailov
71a4274615 Print uhubctl version as last item in --help output 2020-12-13 13:08:32 -08:00
Vadim Mikhailov
d98e6deb6e README clarifications
* Clarify how to build and install
* Clarify USB permissions
* Mention force flag -f
2020-12-13 12:53:28 -08:00
Vadim Mikhailov
ac5a704449 Fix homebrew formula
* Document installing from stable version by default.
* Automatically install pkg-config which benefits getting proper CFLAGS/LDFLAGS.
* Specify license field.
2020-12-11 00:53:40 -08:00
Vadim Mikhailov
1e65ff9b05 Remove MacOS libusb workaround
Now that libusb-1.0.24 is released https://github.com/libusb/libusb/issues/618,
workaround is no longer needed.

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

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

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

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

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

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

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

Closes #260, #280.
2020-12-07 18:43:22 -08:00
Vadim Mikhailov
44f963d00f Improve USB2/3 duality matching
Use priority based duality matching.

* Support Raspberry Pi 4B better (as it has USB2 hub one level deeper than its USB3 counterpart).
* Support M1 Macs (as they seem to place all USB devices to bus 2).
* Support Apple mini-dock (it advertises 2 USB2 ports but only 1 USB3 port).
* Should support multiple identical hubs (with the same container id) on Linux.
2020-12-07 11:32:29 -08:00
Vadim Mikhailov
d3a79ace46 Use pkg-config when available
Remove custom handling for different platforms for CFLAGS and LDFLAGS
and prefer using pkg-config when available.
However, this is tested to still build ok on Linux on Mac howebrew
even if pkg-config is not available.

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

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

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

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

Closes #246.
2020-06-25 13:25:48 -07:00
Vadim Mikhailov
5257d8567d Add D-Link DUB-H7 rev E as supported device
Closes #244.
2020-06-17 16:34:57 -07:00
Vadim Mikhailov
5ee8260c71 Release version 2.2.0 2020-05-22 13:39:47 -07:00
Vadim Mikhailov
8ba36bd8df Improve getting version from git describe
* To determine version, only consider tags starting with v{digit}
* Strip v prefix from version
2020-05-22 13:21:45 -07:00
Vadim Mikhailov
aaf87accad Add Seagate Backup Plus Hub STEL8000100 as supported device
Closes #238.
2020-05-16 15:16:46 -07:00
Sarah Foster
58385b87e6 Update README.md
Just a small typo correction
2020-04-27 22:03:45 -07:00
Vadim Mikhailov
7bf80a08d1 Update Rasperry Pi 4B USB topology note
Apparently, controlling only port 4 is not enough to actually turn power off for RPi4B.
Instead, note that all 4 ports should be turned off. Closes #233.
2020-04-24 17:45:01 -07:00
Vadim Mikhailov
b73213dd6d Mention testing VBUS off in README before reporting new devices 2020-04-14 22:53:38 -07:00
Vadim Mikhailov
e327f5aeb1 Move keyboard/mouse warning to usage section 2020-04-14 22:47:21 -07:00
Vadim Mikhailov
9b824b5587 Add Control USB Lamp With Voice to notable projects
Also fix some broken links.
2020-04-14 22:38:49 -07:00
Vadim Mikhailov
aa864285b1 Add LG Electronics 38WK95C-W as supported device
Closes #231.
2020-04-14 22:15:16 -07:00
Vadim Mikhailov
fa524cf7df Add AmazonBasics HUC9002V1ESL as supported device
AmazonBasics HUC9002V1ESL seems to be EU version of HU9002V1SBL and works the same.
Closes #229.
2020-04-04 13:27:11 -07:00
Vadim Mikhailov
7fd3080f90 Add Raspberry Pi Reboot Router into notable projects 2020-04-04 10:38:40 -07:00
Vadim Mikhailov
e3734e052f Provide workaround for using uhubctl on MacOS Catalina
This provides workaround for issue #227.
Will be fully solved only by https://github.com/libusb/libusb/issues/707.
2020-03-26 21:30:02 -07:00
Vadim Mikhailov
d576db98fb Mention udisksctl in README 2020-02-25 22:21:43 -08:00
Vadim Mikhailov
9e402917dc Add Bytecc BT-UH340 as compatible device
Reported in #217.
2020-02-25 22:11:56 -08:00
Vadim Mikhailov
b0d321a041 Add D-Link DUB-H4 rev E as supported device
Reported in #212.
2020-02-17 11:26:25 -08:00
Vadim Mikhailov
0d2aa24bae Remove statement about proper per-port switching on RPi3B+
Per discussion in issue #215, proper per-port power switching
is not necessarily supported on RPi3B+ secondary hub ports.
2020-02-17 11:21:17 -08:00
Vadim Mikhailov
bd92bcef2f Add a warning and link how to update RPi4B firmware to make power switching work 2020-01-28 11:24:49 -08:00
Vadim Mikhailov
ea70731854 Use in-document anchors to Raspberry Pi usage examples 2020-01-27 23:41:30 -08:00
Vadim Mikhailov
6366aea0bd Refactor Raspberry Pi USB topology table with sub-headers 2020-01-27 23:33:53 -08:00
Vadim Mikhailov
232dcc6f6d Clarify support for different Raspberry Pi models with example commands
* Reformat markdown
* Add example uhubctl commands to turn power off for affected port(s)
* Removed some low level techical details about USB descriptors
2020-01-21 23:58:17 -08:00
Vadim Mikhailov
5691db3379 Add Buffalo BSH4A05U3BK as supported device 2020-01-21 23:04:02 -08:00
Vadim Mikhailov
fae3da7e6f Bump copyright year 2020-01-20 12:14:48 -08:00
Vadim Mikhailov
c79c9ac667 Add Basler 2000036234 as supported device
Reported in #208.
2020-01-20 12:09:36 -08:00
Ryan O'Leary
7f25162ed3 Add AmazonBasics HU9002V1SBL as a Supported Device
Signed-off-by: Ryan O'Leary <ryanoleary@google.com>
2020-01-07 23:01:04 -08:00
Vadim Mikhailov
6f84663c6a Clarify VBUS off requirements for Raspberry Pi 4 2020-01-05 14:05:25 -08:00
Vadim Mikhailov
4aae44ced0 Add support for Raspberry Pi 4
Hack to support Raspberry Pi 4:

* Override ganged power switching reported by USB2 hub as per-port. This is not necessarily true,
  but is consistent with per-port power switching returned by dual USB3 root hub.
  Proper fix is for Raspberry Pi 4 USB firmware to return consistent descriptor information.

* When root USB3 hub is detected on RPi4 without ContainerID, it is assumed to have
  ContainerID of dual USB2 hub, so USB2/3 duality is still working as expected.
  Proper fix would be for USB3 root hub to report correct ContainerID.
2020-01-05 13:43:14 -08:00
Mark Deneen
9857849c0e Update license hash to correct value, fixing yocto building 2019-11-18 13:51:49 -08:00
Vadim Mikhailov
821cdb8833 Update Dell U2415 info 2019-11-15 00:11:09 -08:00
Vadim Mikhailov
c387442b0c Add D-Link DUB-H4 rev B as supported device
Reported in #188.
2019-11-12 17:58:21 -08:00
Vadim Mikhailov
b690bd376a Add Belkin F5U101 as supported device
Reported in #187.
2019-11-12 17:58:21 -08:00
Vadim Mikhailov
c220668adc Add Dell UltraSharp U2415 as supported device
Reported in #191
2019-11-12 12:01:07 -08:00
Karsten Hohmeier
3e10680f22 Fix compile warning about format truncation by increasing buffer size for description 2019-11-05 13:52:24 -08:00
Vadim Mikhailov
39b407a863 Add Juiced Systems 6+1+1 USB 3.0 Gigabit Ethernet Smart Charging Hub (6HUB-01) as supported device
Reported in #184.
2019-11-01 22:27:14 -07:00
Vadim Mikhailov
d227c8326f Don't complain about USB permission problems if we are already root 2019-10-23 22:52:44 -07:00
Vadim Mikhailov
743ecf226b Relax level check when looking for USB2/3 dual hubs
Some devices (notably Raspberry 4B) have USB3 hub that advertises
its USB2 compatibility partner at different depth level.
Raspberry 4B onboard hub doesn't yet support uhubctl directly
(but it should once https://github.com/raspberrypi/linux/issues/3079 is fixed),
but this breaks level check restriction for any external USB3 hub attached to RPi4B
- so this check has been removed.
2019-10-16 23:25:36 -07:00
Vadim Mikhailov
fd611df1e7 Remove Coolgear USBG-4U3MLR2 from supported list
After deeper look, it turns out that this hub doesn't really work,
see more in issue #177.
2019-10-16 23:20:53 -07:00
Vadim Mikhailov
a6cdc3dc4b Remove Plugable USB2-HUB10S from supported list
As reported in issue #181, Plugable USB2-HUB10S does not support per-port power switching.
2019-10-08 18:15:19 -07:00
Vadim Mikhailov
609d0bcae1 Clarify which revisions of D-Link hubs are working 2019-10-05 20:48:00 -07:00
Vadim Mikhailov
54365acb8d Update info for Coolgear USBG-4U3MLR2 2019-10-01 10:33:36 -07:00
Vadim Mikhailov
6e5782c3b7 Add Coolgear USBG-4U3MLR2 as supported device
Reported in #177.
2019-09-26 22:23:07 -07:00
Vadim Mikhailov
59447ce431 Add ThinkPad Ultra Docking Station 40AJ0135EU as supported device
Reported in #176.
2019-09-24 12:47:15 -07:00
Vadim Mikhailov
05647714b6 Apply debian hardening suggestions for Linux
Follow hardening tips per https://wiki.debian.org/Hardening.
We have already applied `-z relro` before, now add `-z now` as well.
2019-09-20 17:43:36 -07:00
Vadim Mikhailov
4818eddc03 FreeBSD is using bNumDeviceCapabilities instead of bNumDeviceCaps
FreeBSD libusb is different from mainline libusb, and needs hacks here and there :-(

This resolves issue #175.
2019-09-20 15:29:30 -07:00
9 changed files with 637 additions and 233 deletions

4
.gitignore vendored
View File

@@ -6,3 +6,7 @@ uhubctl
# Mac symbols
*.dSYM
# Patches
*.patch
*.diff

View File

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

View File

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

View File

@@ -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

245
README.md
View File

@@ -20,59 +20,103 @@ This is list of known compatible USB hubs:
|:-------------------|:-----------------------------------------------------|:------|:----|:----------|:--------|:-----|
| AmazonBasics | HU3641V1 ([RPi issue](https://goo.gl/CLt46M)) | 4 | 3.0 |`2109:2811`| 2013 | |
| AmazonBasics | HU3770V1 ([RPi issue](https://goo.gl/CLt46M)) | 7 | 3.0 |`2109:2811`| 2013 | |
| AmazonBasics | HU9003V1EBL, HUC9003V1EBL | 7 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HU9002V1SBL, HU9002V1EBL, HU9002V1ESL ([note](https://bit.ly/3awM2Ei)) | 10 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | HUC9002V1SBL, HUC9002V1EBL, HUC9002V1ESL | 10 | 3.1 |`2109:2817`| 2018 | |
| AmazonBasics | U3-7HUB (only works for 1 charge port) | 7 | 3.0 |`2109:2813`| 2020 | |
| Anker | AK-A83650A1 ([note](https://git.io/JzpPb)) | 4 | 3.1 |`2109:0817`| 2020 | |
| Anker | AK-68ANHUB-BV7A-0004 ([note](https://git.io/JLnZb)) | 7 | 3.0 |`2109:0812`| 2014 | |
| Apple | Pro Display XDR MWPE2LL/A (internal USB hub) | 4 | 2.0 |`05AC:9139`| 2019 | |
| Apple | Thunderbolt Display 27" (internal USB hub) | 6 | 2.0 | | 2011 | 2016 |
| Apple | USB Keyboard With Numeric Pad (internal USB hub) | 3 | 2.0 | | 2011 | |
| Asus | Z87-PLUS Motherboard (onboard USB hub) | 4 | 3.0 | | 2013 | 2016 |
| Aukey | CB-C59 | 4 | 3.1 |`2109:2813`| 2017 | |
| B+B SmartWorx | UHR204 | 4 | 2.0 |`0856:DB00`| 2013 | |
| B+B SmartWorx | USH304 | 4 | 3.0 |`04B4:6506`| 2017 | |
| Belkin | F5U701-BLK | 7 | 2.0 | | 2008 | 2012 |
| Basler | 2000036234 | 4 | 3.0 |`0451:8046`| 2016 | |
| Belkin | F5U101 | 4 | 2.0 |`0451:2046`| 2005 | 2010 |
| BenQ | PD2700U 4K Monitor (works only in USB2 mode) | 4 | 3.1 |`05E3:0610`| 2018 | |
| BenQ | PD3220U | 4 | 3.1 |`05E3:0610`| 2019 | |
| Buffalo | BSH4A05U3BK | 4 | 3.0 |`05E3:0610`| 2015 | |
| Bytecc | BT-UH340 ([warning](https://bit.ly/35BNi5U)) | 4 | 3.0 |`2109:8110`| 2010 | |
| Centech | CT-USB4HUB ReTRY HUB | 4 | 3.1 |`0424:2744`| 2017 | |
| Circuitco | Beagleboard-xM (internal USB hub) | 4 | 2.0 |`0424:9514`| 2010 | |
| Club3D | CSV-3242HD Dual Display Docking Station | 4 | 3.0 |`2109:2811`| 2015 | |
| CyberPower | CP-H420P | 4 | 2.0 |`0409:0059`| 2004 | |
| Cypress | CY4608 HX2VL development kit | 4 | 2.0 |`04B4:6570`| 2012 | |
| D-Link | DUB-H4 rev D1 (black edition) | 4 | 2.0 |`05E3:0608`| 2012 | |
| D-Link | DUB-H7 rev A (silver edition) | 7 | 2.0 |`2001:F103`| 2005 | 2010 |
| D-Link | DUB-H7 rev D1 (black edition) | 7 | 2.0 |`05E3:0608`| 2012 | |
| Dell | P2416D 24" QHD Monitor | 4 | 2.0 | | 2017 | |
| Cypress | CY4608 HX2VL devkit ([note](https://bit.ly/3sMPfpu)) | 4 | 2.0 |`04B4:6570`| 2012 | |
| D-Link | DUB-H4 rev D,E (black). Note: rev A,C,F not supported| 4 | 2.0 |`05E3:0608`| 2012 | |
| D-Link | DUB-H7 rev A (silver) | 7 | 2.0 |`2001:F103`| 2005 | 2010 |
| D-Link | DUB-H7 rev D,E (black). Rev B,C,F,G not supported | 7 | 2.0 |`05E3:0608`| 2012 | |
| Dell | P2416D 24" QHD Monitor ([note](https://git.io/JUAu8))| 4 | 2.0 | | 2017 | |
| Dell | S2719DGF 27" WQHD Gaming-Monitor | 5 | 3.1 |`0424:5734`| 2018 | |
| Dell | UltraSharp 1704FPT 17" LCD Monitor | 4 | 2.0 |`0424:A700`| 2005 | 2015 |
| Dell | UltraSharp U2415 24" LCD Monitor | 5 | 3.0 | | 2014 | |
| Dell | UltraSharp U3419W 34" Curved Monitor | 6 | 3.0 | | 2020 | |
| Delock | 62537 | 4 | 3.1 | | 2020 | |
| Delock | 87445 ([note](https://git.io/Jsuz5)) | 4 | 2.0 |`05E3:0608`| 2009 | 2013 |
| Elecom | U2H-G4S | 4 | 2.0 | | 2006 | 2011 |
| ExSys | EX-1113HMS | 16 | 3.1 | | 2018 | |
| GlobalScale | ESPRESSObin SBUD102 V5 | 1 | 3.0 |`1D6B:0003`| 2017 | |
| Hardkernel | ODROID-C4 ([note](https://git.io/JG0mP)) | 4 | 3.0 | | 2020 | |
| Hawking Technology | UH214 | 4 | 2.0 | | 2003 | 2008 |
| Hewlett Packard | USB-C Dock G5 5TW10AA | 5 | 3.0 |`03F0:076B`| 2019 | |
| Hewlett Packard | P5Q58UT | 3 | 3.0 | | 2019 | |
| Inateck | HB2025A ([USB2 only](https://bit.ly/3wXF5UO)) | 4 | 3.1 |`2109:2822`| 2021 | |
| IOI | U3H415E1 | 4 | 3.0 | | 2012 | |
| j5create | JUH470 (works only in USB2 mode) | 3 | 3.0 |`05E3:0610`| 2014 | |
| Lenovo | ThinkPad EU Ultra Dockingstation (40A20090EU) | 6 | 2.0 |`17EF:100F`| 2015 | |
| j5create | JUH377 ([note](https://bit.ly/3Mx9eQI)) | 7 | 3.0 | | 2016 | |
| j5create | JUH470 ([note](https://bit.ly/3CRWamP)) | 3 | 3.0 |`05E3:0610`| 2014 | |
| Juiced Systems | 6HUB-01 | 7 | 3.0 |`0BDA:0411`| 2014 | 2018 |
| LG Electronics | 27MD5KL-B monitor | 4 | 3.1 |`043E:9A60`| 2019 | |
| LG Electronics | 27UK850-W monitor | 2 | 3.0 | | 2018 | |
| LG Electronics | 38WK95C-W monitor | 4 | 3.0 |`0451:8142`| 2018 | |
| Lenovo | ThinkPad Ultra Docking Station (40A20090EU) | 6 | 2.0 |`17EF:100F`| 2015 | |
| Lenovo | ThinkPad Ultra Docking Station (40AJ0135EU) | 7 | 3.1 |`17EF:3070`| 2018 | |
| Lenovo | ThinkPad X200 Ultrabase 42X4963 | 3 | 2.0 |`17EF:1005`| 2008 | 2011 |
| Lenovo | ThinkPad X6 Ultrabase 42W3107 | 4 | 2.0 |`17EF:1000`| 2006 | 2009 |
| Lenovo | ThinkPlus 4-in-1 USB-C hub 4X90W86497 | 3 | 3.0 | | 2021 | |
| Lenovo | ThinkVision T24i-10 Monitor | 4 | 2.0 |`17EF:0610`| 2018 | |
| Lindy | USB serial converter 4 port | 4 | 1.1 |`058F:9254`| 2008 | |
| Linksys | USB2HUB4 | 4 | 2.0 | | 2004 | 2010 |
| Linksys | USB2HUB4 ([note](https://git.io/JYiDZ)) | 4 | 2.0 | | 2004 | 2010 |
| Maplin | A08CQ | 7 | 2.0 |`0409:0059`| 2008 | 2011 |
| Microchip | EVB-USB2517 | 7 | 2.0 | | 2008 | |
| Microchip | EVB-USB2534BC | 4 | 2.0 | | 2013 | |
| Microchip | EVB-USB5807 | 7 | 3.1 | | 2016 | |
| Moxa | Uport-407 | 7 | 2.0 |`110A:0407`| 2009 | |
| NVidia | Jetson Nano B01 ([details](https://git.io/JJaFR)) | 4 | 3.0 | | 2019 | |
| NVidia | Jetson Xavier NX ([details](https://bit.ly/3PN2DDp)) | 4 | 3.0 | | 2020 | |
| Phidgets | HUB0003_0 | 7 | 2.0 |`1A40:0201`| 2017 | |
| Plugable | USB3-HUB7BC | 7 | 3.0 |`2109:0813`| 2015 | |
| Plugable | USB3-HUB7C | 7 | 3.0 |`2109:0813`| 2015 | |
| Plugable | USB3-HUB7-81X | 7 | 3.0 |`2109:0813`| 2012 | |
| Plugable | USB2-HUB10S | 10 | 2.0 | | 2010 | |
| Raspberry Pi | Model B+, 2 B, 3 B (port 2 only) | 4 | 2.0 | | 2011 | |
| Raspberry Pi | Model 3 B+ | 6 | 2.0 |`0424:2514`| 2018 | |
| Plugable | USB3-HUB7C (only works for 2 charge ports) | 7 | 3.0 |`2109:0813`| 2015 | |
| Plugable | USBC-HUB7BC (works for 6/7 ports, not the rightmost) | 7 | 3.0 |`2109:0817`| 2021 | |
| Plugable | USB3-HUB7-81X (only works for 2 charge ports) | 7 | 3.0 |`2109:0813`| 2012 | |
| Plugable | USB3-HUB10-C2 (only works for 2 charge ports) | 10 | 3.0 | | 2014 | |
| Port Inc | NWUSB01 | 4 | 1.1 |`0451:1446`| 1999 | 2003 |
| Raspberry Pi | B+, 2B, 3B ([see below](#raspberry-pi-b2b3b)) | 4 | 2.0 | | 2011 | |
| Raspberry Pi | 3B+ ([see below](#raspberry-pi-3b)) | 4 | 2.0 |`0424:2514`| 2018 | |
| Raspberry Pi | 4B ([see below](#raspberry-pi-4b)) | 4 | 3.0 |`2109:3431`| 2019 | |
| Renesas | uPD720202 PCIe USB 3.0 host controller | 2 | 3.0 | | 2013 | |
| Rosewill | RHUB-210 | 4 | 2.0 |`0409:005A`| 2011 | 2014 |
| Rosonway | RSH-518C ([note](https://bit.ly/3kYZUsA)) | 7 | 3.0 |`2109:0817`| 2021 | |
| Rosonway | RSH-A13 ([warning](https://bit.ly/3OToUOL)) | 13 | 3.1 |`2109:2822`| 2021 | |
| Rosonway | RSH-A16 ([note](https://git.io/JTawg), [warning](https://bit.ly/39B0tGS)) | 16 | 3.0 |`0bda:0411`| 2020 | |
| Rosonway | RSH-A104 ([USB2 only](https://bit.ly/3A0qiKF)) | 4 | 3.1 |`2109:2822`| 2022 | |
| Sanwa Supply | USB-HUB14GPH | 4 | 1.1 | | 2001 | 2003 |
| StarTech | ST4200USBM | 4 | 2.0 |`0409:005A`| 2004 | |
| Seagate | Backup Plus Hub STEL8000100 | 2 | 3.0 |`0BC2:AB44`| 2016 | |
| Seeed Studio | reTerminal CM4104032 | 2 | 2.0 |`0424:2514`| 2021 | |
| Sunix | SHB4200MA | 4 | 2.0 |`0409:0058`| 2006 | 2009 |
| Targus | PAUH212U | 7 | 2.0 | | 2004 | 2009 |
| Targus | PAUH212/PAUH212U | 7 | 2.0 | | 2004 | 2009 |
| Texas Instruments | TUSB4041PAPEVM | 4 | 2.1 |`0451:8142`| 2015 | |
| UUGear | MEGA4 (for Raspberry Pi 4B) | 4 | 3.1 |`2109:0817`| 2021 | |
This table is by no means complete.
If your hub works with `uhubctl`, but is not listed above, please report it
by opening new issue at https://github.com/mvp/uhubctl/issues,
so we can add it to supported table. In your report, please provide
exact product model and add output from `uhubctl`.
exact product model and add output from `uhubctl`
and please test VBUS off support as described below in FAQ.
Note that quite a few modern motherboards have built-in root hubs that
do support this feature - you may not even need to buy any external hub.
WARNING: turning off built-in USB ports may cut off your keyboard or mouse,
so be careful what ports you are turning off!
USB 3.0 duality note
@@ -92,48 +136,72 @@ Compiling
=========
This utility was tested to compile and work on Linux
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD and Mac OS X.
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD, SunOS and MacOS.
> :warning: MacOS 12.4 x86 has [USB stack bug](https://github.com/libusb/libusb/issues/1156) which breaks `uhubctl` operation.
Solution is to upgrade to MacOS 12.5 which has this bug fixed.
While `uhubctl` compiles on Windows, USB power switching does not work on Windows because `libusb`
is using `winusb.sys` driver, which according to Microsoft does not support
[necessary USB control requests](https://social.msdn.microsoft.com/Forums/sqlserver/en-US/f680b63f-ca4f-4e52-baa9-9e64f8eee101).
This may be fixed if `libusb` starts supporting different driver on Windows.
First, you need to install library libusb-1.0 (version 1.0.12 or later):
Note that it is highly recommended to have `pkg-config` installed (many platforms provide it by default).
First, you need to install library libusb-1.0 (version 1.0.12 or later, 1.0.16 or later is recommended):
* Ubuntu: `sudo apt-get install libusb-1.0-0-dev`
* Redhat: `sudo yum install libusb1-devel`
* MacOSX: `brew install libusb`, or `sudo port install libusb-devel`
* MacOS: `brew install libusb`, or `sudo port install libusb-devel`
* FreeBSD: libusb is included by default
* NetBSD: `sudo pkgin install libusb1 gmake pkg-config`
* Windows: TBD?
To fetch uhubctl source:
To fetch uhubctl source and compile it:
git clone https://github.com/mvp/uhubctl
cd uhubctl
make
This should generate `uhubctl` binary.
You can install it in your system as `/usr/sbin/uhubctl` using:
sudo make install
To compile, simply run `make` - this will generate `uhubctl` binary.
Note that on some OS (e.g. FreeBSD/NetBSD) you need to use `gmake` instead to build.
Also, for Mac OS X you can install `uhubctl` with Homebrew custom tap:
Also, on MacOS you can install `uhubctl` with all necessary dependencies in one shot using Homebrew tap:
```
brew tap mvp/uhubctl https://github.com/mvp/uhubctl
brew install --HEAD uhubctl
brew install uhubctl
```
To build/install from master branch, use `--HEAD`:
```
brew install uhubctl --HEAD
```
Usage
=====
> :warning: On Linux, use `sudo` or configure USB permissions as described below!
To list all supported hubs:
uhubctl
You can control the power on a USB port(s) like this:
uhubctl -a off -p 2
This means operate on default smart hub and turn power off (`-a off`, or `-a 0`)
on port 2 (`-p 2`). Supported actions are `off`/`on`/`cycle` (or `0`/`1`/`2`).
on port 2 (`-p 2`). Supported actions are `off`/`on`/`cycle`/`toggle` (or `0`/`1`/`2`/`3`).
`cycle` means turn power off, wait some delay (configurable with `-d`) and turn it back on.
Ports can be comma separated list, and may use `-` for ranges e.g. `2`, or `2,4`, or `2-5`, or `1-2,5-8`.
> :warning: Turning off built-in USB ports may cut off your keyboard or mouse,
so be careful which ports you are turning off!
If you have more than one smart USB hub connected, you should choose
specific hub to control using `-l` (location) parameter.
To find hub locations, simply run `uhubctl` without any parameters.
@@ -147,14 +215,35 @@ Linux USB permissions
=====================
On Linux, you should configure `udev` USB permissions (otherwise you will have to run it as root using `sudo uhubctl`).
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"
Starting with Linux Kernel 6.0 there is a standard interface to turn USB hub ports on or off,
and `uhubctl` will try to use it (instead of `libusb`) to set the port status.
This is why there are additional rules for 6.0+ kernels.
There is no harm in having these rules on systems running older kernel versions.
To fix USB permissions, first run `sudo uhubctl` and note all `vid:pid` for hubs you need to control.
Then, add udev rules like below to file `/etc/udev/rules.d/52-usb.rules`
(replace `2001` with your hub vendor id, or completely remove `ATTR{idVendor}` filter to allow any USB hub access):
SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="2001"
# Linux 6.0 or later (its ok to have this block present for older Linux kernels):
SUBSYSTEM=="usb", DRIVER=="hub", \
RUN="/bin/sh -c \"chmod -f 666 $sys$devpath/*-port*/disable || true\""
Note that for USB3 hubs, some hubs use different vendor ID for USB2 vs USB3 components of the same chip,
and both need permissions to make uhubctl work properly.
E.g. for Raspberry Pi 4B, you need to add these 2 lines (or remove idVendor filter):
SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="2109"
SUBSYSTEM=="usb", DRIVER=="hub", MODE="0666", ATTR{idVendor}=="1d6b"
If you don't like wide open mode `0666`, you can restrict access by group like this:
SUBSYSTEM=="usb", ATTR{idVendor}=="2001", MODE="0664", GROUP="dialout"
SUBSYSTEM=="usb", DRIVER=="hub", MODE="0664", GROUP="dialout"
# Linux 6.0 or later (its ok to have this block present for older Linux kernels):
SUBSYSTEM=="usb", DRIVER=="hub", \
RUN+="/bin/sh -c \"chown -f root:dialout $sys$devpath/*-port*/disable || true\"" \
RUN+="/bin/sh -c \"chmod -f 660 $sys$devpath/*-port*/disable || true\""
and then add permitted users to `dialout` group:
@@ -164,6 +253,7 @@ For your `udev` rule changes to take effect, reboot or run:
sudo udevadm trigger --attr-match=subsystem=usb
For your convenience, ready to use udev rule is provided [here](https://github.com/mvp/uhubctl/blob/master/udev/rules.d/52-usb.rules).
FAQ
@@ -173,7 +263,8 @@ FAQ
According to USB 2.0 specification, USB hubs can advertise no power switching,
ganged (all ports at once) power switching or per-port (individual) power switching.
Note that `uhubctl` will only detect USB hubs which support per-port power switching.
Note that by default `uhubctl` will only detect USB hubs which support per-port power switching
(but you can force it to try operating on unsupported hubs with option `-f`).
You can find what kind of power switching your hardware supports by using `sudo lsusb -v`:
No power switching:
@@ -212,10 +303,14 @@ Per-port power switching:
3. If tests above were successful, please report your hub
by opening new issue at https://github.com/mvp/uhubctl/issues,
so we can add it to list of supported devices.
Please do not report unsupported hubs, unless it is different
hardware revision of some already listed supported model.
#### _USB devices are not removed after port power down on Linux_
> :arrow_right: This is fixed by [#450](https://github.com/mvp/uhubctl/pull/450) if you are using Linux kernel 6.0 or later.
After powering down USB port, udev does not get any event, so it keeps the device files around.
However, trying to access the device files will lead to an IO error.
@@ -234,18 +329,29 @@ When you turn power back on, device should re-enumerate properly (no need to cal
#### _Power comes back on after few seconds on Linux_
> :arrow_right: This is fixed by [#450](https://github.com/mvp/uhubctl/pull/450) if you are using Linux kernel 6.0 or later.
Some device drivers in kernel are surprised by USB device being turned off and automatically try to power it back on.
You can use option `-r N` where N is some number from 10 to 1000 to fix this -
`uhubctl` will try to turn power off many times in quick succession, and it should suppress that.
This may be eventually fixed in kernel, see more discussion [here](https://bit.ly/2JzczjZ).
Disabling USB authorization for device in question before turning power off with `uhubctl` should help:
echo 0 > sudo tee /sys/bus/usb/devices/${location}.${port}/authorized
If your device is USB mass storage, invoking `udisksctl` before calling `uhubctl` should help too:
sudo udisksctl power-off --block-device /dev/disk/...`
sudo uhubctl -a off ...
#### _Multiple 4-port hubs are detected, but I only have one 7-port hub connected_
Many hub manufacturers build their USB hubs using basic 4 port USB chips.
E.g. to make 7 port hub, they daisy-chain two 4 port hubs - 1 port is lost to daisy-chaining,
so it makes it 4+4-1=7 port hub. Simularly, 10 port hub could be built as 3 4-port hubs
so it makes it 4+4-1=7 port hub. Similarly, 10 port hub could be built as 3 4-port hubs
daisy-chained together, which gives 4+4+4-2=10 usable ports.
Note that you should never try to change power state for ports used to daisy-chain internal hubs together.
@@ -254,24 +360,50 @@ Doing so will confuse internal hub circuitry and will cause unpredictable behavi
#### _Raspberry Pi turns power off on all ports, not just the one I specified_
This is limitation of Raspberry Pi hardware design.
For reference, Raspberry Pi models have following internal USB topology:
* B+/2B/3B: one USB hub `1-1`, with port `1` for Ethernet+wifi, and ports `2-5` ganged, controlled by port `2`.
(Trying to control ports 3,4,5 won't do anything).
* 3B+: 2 hubs - main hub `1-1`, all 4 ports ganged, all controlled by port `2`.
Second hub `1-1.1` (daisy-chained to main): 3 independently controlled ports, `1` is used for Ethernet+wifi,
and ports `2,3` are available with proper per-port power switching.
In other words, 2 out of total 4 ports wired outside do support independent power switching on 3B+.
* 4B: Hardware supports power switching, but current firmware is reporting broken USB descriptors.
For more details see Raspberry Pi issue https://github.com/raspberrypi/linux/issues/3079.
USB3 hub `2`, 4 ports, per-port power switching. BOS `ContainerID` not reported (required for USB3).
USB2 hub `1`, 1 port, no usable ports, connects hub `1-1` below.
USB2 hub `1-1`, 4 ports, dual to USB3 hub above. Hub descriptor incorrectly reports ganged power switching.
USB2 hub `3`, 1 port, OTG controller, incorrectly reports ganged power switching.
As a workaround, you can buy any external USB hub from supported list,
This is the limitation of Raspberry Pi hardware design.
As a workaround, you can buy any external USB hub from supported list above,
attach it to any USB port of Raspberry Pi, and control power on its ports independently.
Also, there are supported hubs designed specifically for Raspberry Pi, e.g. UUGear MEGA4.
For reference, supported Raspberry Pi models have following internal USB topology:
##### Raspberry Pi B+,2B,3B
* Single hub `1-1`, ports 2-5 ganged, all controlled by port `2`:
uhubctl -l 1-1 -p 2 -a 0
Trying to control ports `3`,`4`,`5` will not do anything.
Port `1` controls power for Ethernet+WiFi.
##### Raspberry Pi 3B+
* Main hub `1-1`, all 4 ports ganged, all controlled by port `2` (turns off secondary hub ports as well).
Port `1` connects hub `1-1.1` below, ports `2` and `3` are wired outside, port `4` not wired.
uhubctl -l 1-1 -p 2 -a 0
* Secondary hub `1-1.1` (daisy-chained to main): 3 ports,
port `1` is used for Ethernet+WiFi, and ports `2` and `3` are wired outside.
##### Raspberry Pi 4B
> :warning: If your VL805 firmware is older than `00137ad` (check with `sudo rpi-eeprom-update`),
you have to [update firmware](https://www.raspberrypi.org/documentation/hardware/raspberrypi/booteeprom.md)
to make power switching work on RPi 4B.
* USB2 hub `1`, 1 port, only connects hub `1-1` below.
* USB2 hub `1-1`, 4 ports ganged, dual to USB3 hub `2` below:
uhubctl -l 1-1 -a 0
* USB3 hub `2`, 4 ports ganged, dual to USB2 hub `1-1` above:
uhubctl -l 2 -a 0
* USB2 hub `3`, 1 port, OTG controller. Power switching is [not supported](https://git.io/JUc5Q).
@@ -285,20 +417,27 @@ Notable projects using uhubctl
| [Build Status Light](https://goo.gl/3GA82o) | Create a build status light in under 10 minutes |
| [Buildenlights](https://git.io/fj1FC) | GitLab/GitHub project build status as green/red light |
| [Weather Station](https://goo.gl/3b1FzC) | Reset Weather Station when it freezes |
| [sysmoQMOD](https://goo.gl/8wvcKA) | Reset cellular modems when necessary |
| [sysmoQMOD](https://bit.ly/2VtWrVt) | Reset cellular modems when necessary |
| [Smog Sensor](https://bit.ly/2EMwgCk) | Raspberry Pi based smog sensor power reset |
| [Terrible Cluster](https://goo.gl/XjiXFu) | Power on/off Raspberry Pi cluster nodes as needed |
| [Ideal Music Server](https://bit.ly/2UJq6Z9) | Turn off unused USB ports to improve audio quality |
| [Ideal Music Server](https://bit.ly/39MeVFQ) | Turn off unused USB ports to improve audio quality |
| [USB drives with no phantom load](https://goo.gl/qfrmGK) | Power USB drives only when needed to save power |
| [USB drive data recovery](https://goo.gl/4MddLr) | Recover data from failing USB hard drive |
| [Control power to 3D printer](https://git.io/fh5Tr) | OctoPrint web plugin for USB power control |
| [USB fan for Raspberry Pi](https://bit.ly/2TRV6sM) | Control USB fan to avoid Raspberry Pi overheating |
| [Raspberry Pi Reboot Router](https://bit.ly/3aNbQqs) | Automatically reboot router if internet isn't working |
| [Control USB Lamp With Voice](https://bit.ly/2VtW2SX) | Voice Control of USB Lamp using Siri and Raspberry Pi |
| [Control USB LED Strip](https://bit.ly/3oVWfeZ) | Controlling USB powered LED Light Strip |
| [Brew beer with Raspberry Pi](https://git.io/JtbLd) | Automated beer brewing system using Raspberry Pi |
| [Webcam On-Air Sign](https://bit.ly/3witNsa) | Automatically light up a sign when webcam is in use |
| [Do it yourself PPPS](https://git.io/J3lHs) | Solder wires in your USB hub to support uhubctl |
| [Python Wrapper for uhubctl](https://github.com/nbuchwitz/python3-uhubctl) | Module to use uhubctl with Python |
Copyright
=========
Copyright (C) 2009-2019 Vadim Mikhailov
Copyright (C) 2009-2022 Vadim Mikhailov
This file can be distributed under the terms and conditions of the
GNU General Public License version 2.

View File

@@ -1 +1 @@
v2.1.0-dev
2.5.0

24
udev/rules.d/52-usb.rules Normal file
View File

@@ -0,0 +1,24 @@
# uhubctl USB hub per-port power control https://github.com/mvp/uhubctl
#
# Copyright (c) 2009-2022, Vadim Mikhailov
#
# This file can be distributed under the terms and conditions of the
# GNU General Public License version 2.
# uhubctl udev rules for rootless operation on Linux for users in group `dialout`.
#
# Copy this file to /etc/udev/rules.d, then reboot or run:
#
# sudo udevadm trigger --attr-match=subsystem=usb
#
# To add yourself to this permission group, run:
#
# sudo usermod -a -G dialout $USER
# This is for Linux before 6.0:
SUBSYSTEM=="usb", DRIVER=="hub", MODE="0664", GROUP="dialout"
# This is for Linux 6.0 or later (ok to keep this block present for older Linux kernels):
SUBSYSTEM=="usb", DRIVER=="hub", \
RUN+="/bin/sh -c \"chown -f root:dialout $sys$devpath/*-port*/disable || true\"" \
RUN+="/bin/sh -c \"chmod -f 660 $sys$devpath/*-port*/disable || true\""

552
uhubctl.c
View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2019 Vadim Mikhailov
* Copyright (c) 2009-2022 Vadim Mikhailov
*
* Utility to turn USB port power on/off
* for USB hubs that support per-port power switching.
@@ -47,6 +47,10 @@ int snprintf(char * __restrict __str, size_t __size, const char * __restrict __f
#include <time.h> /* for nanosleep */
#endif
#ifdef __gnu_linux__
#include <fcntl.h> /* for open() / O_WRONLY */
#endif
/* cross-platform sleep function */
void sleep_ms(int milliseconds)
@@ -75,6 +79,7 @@ void sleep_ms(int milliseconds)
#define POWER_OFF 0
#define POWER_ON 1
#define POWER_CYCLE 2
#define POWER_TOGGLE 3
#define MAX_HUB_CHAIN 8 /* Per USB 3.0 spec max hub chain is 7 */
@@ -182,19 +187,22 @@ struct descriptor_strings {
char vendor[64];
char product[64];
char serial[64];
char description[256];
char description[512];
};
struct hub_info {
struct libusb_device *dev;
int bcd_usb;
int super_speed; /* 1 if super speed hub, and 0 otherwise */
int nports;
int ppps;
int lpsm; /* logical power switching mode */
int actionable; /* true if this hub is subject to action */
char container_id[33]; /* container ID as hex string */
char vendor[16];
char location[32];
int level;
uint8_t bus;
uint8_t port_numbers[MAX_HUB_CHAIN];
int pn_len; /* length of port numbers */
struct descriptor_strings ds;
};
@@ -206,6 +214,7 @@ static int hub_phys_count = 0;
/* default options */
static char opt_vendor[16] = "";
static char opt_search[64] = ""; /* Search by attached device description */
static char opt_location[32] = ""; /* Hub location a-b.c.d */
static int opt_level = 0; /* Hub location level (e.g., a-b is level 2, a-b.c is level 3)*/
static int opt_ports = ALL_HUB_PORTS; /* Bitmask of ports to operate on */
@@ -215,10 +224,24 @@ static int opt_repeat = 1;
static int opt_wait = 20; /* wait before repeating in ms */
static int opt_exact = 0; /* exact location match - disable USB3 duality handling */
static int opt_reset = 0; /* reset hub after operation(s) */
static int opt_force = 0; /* force operation even on unsupported hubs */
static int opt_nodesc = 0; /* skip querying device description */
#ifdef __gnu_linux__
static int opt_nosysfs = 0; /* don't use the Linux sysfs port disable interface, even if available */
#endif
static const char short_options[] =
"l:L:n:a:p:d:r:w:s:hvefRN"
#ifdef __gnu_linux__
"S"
#endif
;
static const struct option long_options[] = {
{ "location", required_argument, NULL, 'l' },
{ "vendor", required_argument, NULL, 'n' },
{ "search", required_argument, NULL, 's' },
{ "level", required_argument, NULL, 'L' },
{ "ports", required_argument, NULL, 'p' },
{ "action", required_argument, NULL, 'a' },
@@ -226,6 +249,11 @@ static const struct option long_options[] = {
{ "repeat", required_argument, NULL, 'r' },
{ "wait", required_argument, NULL, 'w' },
{ "exact", no_argument, NULL, 'e' },
{ "force", no_argument, NULL, 'f' },
{ "nodesc", no_argument, NULL, 'N' },
#ifdef __gnu_linux__
{ "nosysfs", no_argument, NULL, 'S' },
#endif
{ "reset", no_argument, NULL, 'R' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
@@ -236,30 +264,37 @@ static const struct option long_options[] = {
static int print_usage()
{
printf(
"uhubctl %s: utility to control USB port power for smart hubs.\n"
"uhubctl: utility to control USB port power for smart hubs.\n"
"Usage: uhubctl [options]\n"
"Without options, show status for all smart hubs.\n"
"\n"
"Options [defaults in brackets]:\n"
"--action, -a - action to off/on/cycle (0/1/2) for affected ports.\n"
"--action, -a - action to off/on/cycle/toggle (0/1/2/3) for affected ports.\n"
"--ports, -p - ports to operate on [all hub ports].\n"
"--location, -l - limit hub by location [all smart hubs].\n"
"--level -L - limit hub by location level (e.g. a-b.c is level 3).\n"
"--vendor, -n - limit hub by vendor id [%s] (partial ok).\n"
"--search, -s - limit hub by attached device description.\n"
"--delay, -d - delay for cycle action [%g sec].\n"
"--repeat, -r - repeat power off count [%d] (some devices need it to turn off).\n"
"--exact, -e - exact location (no USB3 duality handling).\n"
"--force, -f - force operation even on unsupported hubs.\n"
"--nodesc, -N - do not query device description (helpful for unresponsive devices).\n"
#ifdef __gnu_linux__
"--nosysfs, -S - do not use the Linux sysfs port disable interface.\n"
#endif
"--reset, -R - reset hub after each power-on action, causing all devices to reassociate.\n"
"--wait, -w - wait before repeat power off [%d ms].\n"
"--version, -v - print program version.\n"
"--help, -h - print this text.\n"
"\n"
"Send bugs and requests to: https://github.com/mvp/uhubctl\n",
PROGRAM_VERSION,
"Send bugs and requests to: https://github.com/mvp/uhubctl\n"
"version: %s\n",
strlen(opt_vendor) ? opt_vendor : "any",
opt_delay,
opt_repeat,
opt_wait
opt_wait,
PROGRAM_VERSION
);
return 0;
}
@@ -385,9 +420,9 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
);
if (len >= minlen) {
unsigned char port_numbers[MAX_HUB_CHAIN] = {0};
info->dev = dev;
info->bcd_usb = bcd_usb;
info->super_speed = (bcd_usb >= USB_SS_BCD);
info->nports = uhd->bNbrPorts;
snprintf(
info->vendor, sizeof(info->vendor),
@@ -397,53 +432,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);
}
@@ -479,6 +528,113 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
}
#ifdef __gnu_linux__
/*
* Try to use the Linux sysfs interface to power a port off/on.
* Returns 0 on success.
*/
static int set_port_status_linux(struct libusb_device_handle *devh, struct hub_info *hub, int port, int on)
{
int configuration = 0;
char disable_path[PATH_MAX];
int rc = libusb_get_configuration(devh, &configuration);
if (rc < 0) {
return rc;
}
/*
* The "disable" sysfs interface is available only starting with kernel version 6.0.
* For earlier kernel versions the open() call will fail and we fall back to using libusb.
*/
snprintf(disable_path, PATH_MAX,
"/sys/bus/usb/devices/%s:%d.0/%s-port%i/disable",
hub->location, configuration, hub->location, port
);
int disable_fd = open(disable_path, O_WRONLY);
if (disable_fd >= 0) {
rc = write(disable_fd, on ? "0" : "1", 1);
close(disable_fd);
}
if (disable_fd < 0 || rc < 0) {
/*
* ENOENT is the expected error when running on Linux kernel < 6.0 where
* sysfs disable interface does not exist yet - no need to report anything in this case.
* If the file exists but another error occurs it is most likely a permission issue.
* Print an error message mostly geared towards setting up udev.
*/
if (errno != ENOENT) {
fprintf(stderr,
"Failed to set port status by writing to %s (%s).\n"
"Follow https://git.io/JIB2Z to make sure that udev is set up correctly.\n"
"Falling back to libusb based port control.\n"
"Use -S to skip trying the sysfs interface and printing this message.\n",
disable_path, strerror(errno)
);
}
return -1;
}
return 0;
}
#endif
/*
* Use a control transfer via libusb to turn a port off/on.
* Returns >= 0 on success.
*/
static int set_port_status_libusb(struct libusb_device_handle *devh, int port, int on)
{
int rc = 0;
int request = on ? LIBUSB_REQUEST_SET_FEATURE
: LIBUSB_REQUEST_CLEAR_FEATURE;
int repeat = on ? 1 : opt_repeat;
while (repeat-- > 0) {
rc = libusb_control_transfer(devh,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
request, USB_PORT_FEAT_POWER,
port, NULL, 0, USB_CTRL_GET_TIMEOUT
);
if (rc < 0) {
perror("Failed to control port power!\n");
}
if (repeat > 0) {
sleep_ms(opt_wait);
}
}
return rc;
}
/*
* Try different methods to power a port off/on.
* Return >= 0 on success.
*/
static int set_port_status(struct libusb_device_handle *devh, struct hub_info *hub, int port, int on)
{
#ifdef __gnu_linux__
if (!opt_nosysfs) {
if (set_port_status_linux(devh, hub, port, on) == 0) {
return 0;
}
}
#else
(void)hub;
#endif
return set_port_status_libusb(devh, port, on);
}
/*
* Get USB device descriptor strings and summary description.
*
@@ -499,7 +655,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
int rc;
int id_vendor = 0;
int id_product = 0;
char ports[64] = "";
char hub_specific[64] = "";
struct libusb_device_descriptor desc;
struct libusb_device_handle *devh = NULL;
rc = libusb_get_device_descriptor(dev, &desc);
@@ -510,27 +666,37 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
id_product = libusb_le16_to_cpu(desc.idProduct);
rc = libusb_open(dev, &devh);
if (rc == 0) {
if (desc.iManufacturer) {
libusb_get_string_descriptor_ascii(devh,
desc.iManufacturer, (unsigned char*)ds->vendor, sizeof(ds->vendor));
rtrim(ds->vendor);
}
if (desc.iProduct) {
libusb_get_string_descriptor_ascii(devh,
desc.iProduct, (unsigned char*)ds->product, sizeof(ds->product));
rtrim(ds->product);
}
if (desc.iSerialNumber) {
libusb_get_string_descriptor_ascii(devh,
desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
rtrim(ds->serial);
if (!opt_nodesc) {
if (desc.iManufacturer) {
rc = libusb_get_string_descriptor_ascii(devh,
desc.iManufacturer, (unsigned char*)ds->vendor, sizeof(ds->vendor));
rtrim(ds->vendor);
}
if (rc >= 0 && desc.iProduct) {
rc = libusb_get_string_descriptor_ascii(devh,
desc.iProduct, (unsigned char*)ds->product, sizeof(ds->product));
rtrim(ds->product);
}
if (rc >= 0 && desc.iSerialNumber) {
rc = libusb_get_string_descriptor_ascii(devh,
desc.iSerialNumber, (unsigned char*)ds->serial, sizeof(ds->serial));
rtrim(ds->serial);
}
}
if (desc.bDeviceClass == LIBUSB_CLASS_HUB) {
struct hub_info info;
rc = get_hub_info(dev, &info);
if (rc == 0) {
snprintf(ports, sizeof(ports), ", USB %x.%02x, %d ports",
info.bcd_usb >> 8, info.bcd_usb & 0xFF, info.nports);
const char * lpsm_type;
if (info.lpsm == HUB_CHAR_INDV_PORT_LPSM) {
lpsm_type = "ppps";
} else if (info.lpsm == HUB_CHAR_COMMON_LPSM) {
lpsm_type = "ganged";
} else {
lpsm_type = "nops";
}
snprintf(hub_specific, sizeof(hub_specific), ", USB %x.%02x, %d ports, %s",
info.bcd_usb >> 8, info.bcd_usb & 0xFF, info.nports, lpsm_type);
}
}
libusb_close(devh);
@@ -541,7 +707,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
ds->vendor[0] ? " " : "", ds->vendor,
ds->product[0] ? " " : "", ds->product,
ds->serial[0] ? " " : "", ds->serial,
ports
hub_specific
);
return 0;
}
@@ -558,17 +724,9 @@ static int print_port_status(struct hub_info * hub, int portmask)
int port_status;
struct libusb_device_handle * devh = NULL;
int rc = 0;
int hub_bus;
int dev_bus;
unsigned char hub_pn[MAX_HUB_CHAIN];
unsigned char dev_pn[MAX_HUB_CHAIN];
int hub_plen;
int dev_plen;
struct libusb_device *dev = hub->dev;
rc = libusb_open(dev, &devh);
if (rc == 0) {
hub_bus = libusb_get_bus_number(dev);
hub_plen = get_port_numbers(dev, hub_pn, sizeof(hub_pn));
int port;
for (port = 1; port <= hub->nports; port++) {
if (portmask > 0 && (portmask & (1 << (port-1))) == 0) continue;
@@ -588,12 +746,15 @@ static int print_port_status(struct hub_info * hub, int portmask)
struct libusb_device * udev;
int i = 0;
while ((udev = usb_devs[i++]) != NULL) {
uint8_t dev_bus;
uint8_t dev_pn[MAX_HUB_CHAIN];
int dev_plen;
dev_bus = libusb_get_bus_number(udev);
/* only match devices on the same bus: */
if (dev_bus != hub_bus) continue;
if (dev_bus != hub->bus) continue;
dev_plen = get_port_numbers(udev, dev_pn, sizeof(dev_pn));
if ((dev_plen == hub_plen + 1) &&
(memcmp(hub_pn, dev_pn, hub_plen) == 0) &&
if ((dev_plen == hub->pn_len + 1) &&
(memcmp(hub->port_numbers, dev_pn, hub->pn_len) == 0) &&
libusb_get_port_number(udev) == port)
{
rc = get_device_description(udev, &ds);
@@ -602,7 +763,7 @@ static int print_port_status(struct hub_info * hub, int portmask)
}
}
if (hub->bcd_usb < USB_SS_BCD) {
if (!hub->super_speed) {
if (port_status == 0) {
printf(" off");
} else {
@@ -680,30 +841,63 @@ static int usb_find_hubs()
rc = get_hub_info(dev, &info);
if (rc) {
perm_ok = 0; /* USB permission issue? */
continue;
}
get_device_description(dev, &info.ds);
if (info.ppps) { /* PPPS is supported */
if (hub_count < MAX_HUBS) {
info.actionable = 1;
if (strlen(opt_location) > 0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
if (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 +908,8 @@ static int usb_find_hubs()
/* Must have non empty container ID: */
if (strlen(hubs[i].container_id) == 0)
continue;
int match = -1;
int best_match = -1;
int best_score = -1;
for (j=0; j<hub_count; j++) {
if (i==j)
continue;
@@ -722,8 +917,7 @@ static int usb_find_hubs()
/* Find hub which is USB2/3 dual to the hub above */
/* Hub and its dual must be different types: one USB2, another USB3: */
if ((hubs[i].bcd_usb < USB_SS_BCD) ==
(hubs[j].bcd_usb < USB_SS_BCD))
if (hubs[i].super_speed == hubs[j].super_speed)
continue;
/* Must have non empty container ID: */
@@ -739,6 +933,14 @@ static int usb_find_hubs()
* We should do few more checks below if multiple such devices are present.
*/
/* Hubs should have the same number of ports */
if (hubs[i].nports != hubs[j].nports) {
/* Except for some weird hubs like Apple mini-dock (has 2 usb2 + 1 usb3 ports) */
if (hubs[i].nports + hubs[j].nports > 3) {
continue;
}
}
/* If serial numbers are both present, they must match: */
if ((strlen(hubs[i].ds.serial) > 0 && strlen(hubs[j].ds.serial) > 0) &&
strcmp(hubs[i].ds.serial, hubs[j].ds.serial) != 0)
@@ -746,22 +948,56 @@ static int usb_find_hubs()
continue;
}
/* Hubs should have the same number of ports: */
if (hubs[i].nports != hubs[j].nports)
continue;
/* We have first possible candidate, but need to keep looking for better one */
/* And the same level: */
if (hubs[i].level != hubs[j].level)
continue;
if (best_score < 1) {
best_score = 1;
best_match = j;
}
/* Finally, we claim a match: */
match = j;
break;
/* Checks for various levels of USB2 vs USB3 path similarity... */
uint8_t* p1 = hubs[i].port_numbers;
uint8_t* p2 = hubs[j].port_numbers;
int l1 = hubs[i].pn_len;
int l2 = hubs[j].pn_len;
int s1 = hubs[i].super_speed;
int s2 = hubs[j].super_speed;
/* Check if port path is the same after removing top level (needed for M1 Macs): */
if (l1 >= 1 && l1 == l2 && memcmp(p1 + 1, p2 + 1, l1 - 1)==0) {
if (best_score < 2) {
best_score = 2;
best_match = j;
}
}
/* Raspberry Pi 4B hack (USB2 hub is one level deeper than USB3): */
if (l1 + s1 == l2 + s2 && l1 >= s2 && memcmp(p1 + s2, p2 + s1, l1 - s2)==0) {
if (best_score < 3) {
best_score = 3;
best_match = j;
}
}
/* Check if port path is exactly the same: */
if (l1 == l2 && memcmp(p1, p2, l1)==0) {
if (best_score < 4) {
best_score = 4;
best_match = j;
}
/* Give even higher priority if `usb2bus + 1 == usb3bus` (Linux specific): */
if (hubs[i].bus - s1 == hubs[j].bus - s2) {
if (best_score < 5) {
best_score = 5;
best_match = j;
}
}
}
}
if (match >= 0) {
if (!hubs[match].actionable) {
if (best_match >= 0) {
if (!hubs[best_match].actionable) {
/* Use 2 to signify that this is derived dual device */
hubs[match].actionable = 2;
hubs[best_match].actionable = 2;
}
}
}
@@ -770,11 +1006,19 @@ static int usb_find_hubs()
for (i=0; i<hub_count; i++) {
if (!hubs[i].actionable)
continue;
if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
if (!hubs[i].super_speed || opt_exact) {
hub_phys_count++;
}
}
if (perm_ok == 0 && hub_phys_count == 0) {
#ifdef __gnu_linux__
if (geteuid() != 0) {
fprintf(stderr,
"There were permission problems while accessing USB.\n"
"Follow https://git.io/JIB2Z for a fix!\n"
);
}
#endif
return LIBUSB_ERROR_ACCESS;
}
return hub_phys_count;
@@ -788,8 +1032,7 @@ int main(int argc, char *argv[])
int option_index = 0;
for (;;) {
c = getopt_long(argc, argv, "l:L:n:a:p:d:r:w:hveR",
long_options, &option_index);
c = getopt_long(argc, argv, short_options, long_options, &option_index);
if (c == -1)
break; /* no more options left */
switch (c) {
@@ -803,13 +1046,16 @@ int main(int argc, char *argv[])
printf("\n");
break;
case 'l':
strncpy(opt_location, optarg, sizeof(opt_location));
snprintf(opt_location, sizeof(opt_location), "%s", optarg);
break;
case 'L':
opt_level = atoi(optarg);
break;
case 'n':
strncpy(opt_vendor, optarg, sizeof(opt_vendor));
snprintf(opt_vendor, sizeof(opt_vendor), "%s", optarg);
break;
case 's':
snprintf(opt_search, sizeof(opt_search), "%s", optarg);
break;
case 'p':
if (!strcasecmp(optarg, "all")) { /* all ports is the default */
@@ -829,6 +1075,9 @@ int main(int argc, char *argv[])
if (!strcasecmp(optarg, "cycle") || !strcasecmp(optarg, "2")) {
opt_action = POWER_CYCLE;
}
if (!strcasecmp(optarg, "toggle") || !strcasecmp(optarg, "3")) {
opt_action = POWER_TOGGLE;
}
break;
case 'd':
opt_delay = atof(optarg);
@@ -836,6 +1085,17 @@ int main(int argc, char *argv[])
case 'r':
opt_repeat = atoi(optarg);
break;
case 'f':
opt_force = 1;
break;
case 'N':
opt_nodesc = 1;
break;
#ifdef __gnu_linux__
case 'S':
opt_nosysfs = 1;
break;
#endif
case 'e':
opt_exact = 1;
break;
@@ -889,23 +1149,11 @@ int main(int argc, char *argv[])
rc = usb_find_hubs();
if (rc <= 0) {
fprintf(stderr,
"No compatible smart hubs detected%s%s!\n"
"No compatible devices detected%s%s!\n"
"Run with -h to get usage info.\n",
strlen(opt_location) ? " at location " : "",
opt_location
);
#ifdef __gnu_linux__
if (rc < 0) {
fprintf(stderr,
"There were permission problems while accessing USB.\n"
"To fix this, run this tool as root using 'sudo uhubctl',\n"
"or add one or more udev rules like below\n"
"to file '/etc/udev/rules.d/52-usb.rules':\n"
"SUBSYSTEM==\"usb\", ATTR{idVendor}==\"2001\", MODE=\"0666\"\n"
"then run 'sudo udevadm trigger --attr-match=subsystem=usb'\n"
);
}
#endif
rc = 1;
goto cleanup;
}
@@ -925,6 +1173,9 @@ int main(int argc, char *argv[])
continue;
if (k == 1 && opt_action == POWER_KEEP)
continue;
/* if toggle requested, do it only once when `k == 0` */
if (k == 1 && opt_action == POWER_TOGGLE)
continue;
int i;
for (i=0; i<hub_count; i++) {
if (hubs[i].actionable == 0)
@@ -941,44 +1192,29 @@ int main(int argc, char *argv[])
if (rc == 0) {
/* will operate on these ports */
int ports = ((1 << hubs[i].nports) - 1) & opt_ports;
int request = (k == 0) ? LIBUSB_REQUEST_CLEAR_FEATURE
: LIBUSB_REQUEST_SET_FEATURE;
int should_be_on = k;
int port;
for (port=1; port <= hubs[i].nports; port++) {
if ((1 << (port-1)) & ports) {
int port_status = get_port_status(devh, port);
int power_mask = hubs[i].bcd_usb < USB_SS_BCD ? USB_PORT_STAT_POWER
: USB_SS_PORT_STAT_POWER;
if (k == 0 && !(port_status & power_mask))
continue;
if (k == 1 && (port_status & power_mask))
continue;
int repeat = 1;
if (k == 0)
repeat = opt_repeat;
if (!(port_status & ~power_mask))
repeat = 1;
while (repeat-- > 0) {
rc = libusb_control_transfer(devh,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_OTHER,
request, USB_PORT_FEAT_POWER,
port, NULL, 0, USB_CTRL_GET_TIMEOUT
);
if (rc < 0) {
perror("Failed to control port power!\n");
}
if (repeat > 0) {
sleep_ms(opt_wait);
}
int power_mask = hubs[i].super_speed ? USB_SS_PORT_STAT_POWER
: USB_PORT_STAT_POWER;
int is_on = (port_status & power_mask) != 0;
if (opt_action == POWER_TOGGLE) {
should_be_on = !is_on;
}
if (is_on != should_be_on) {
rc = set_port_status(devh, &hubs[i], port, should_be_on);
}
}
}
/* USB3 hubs need extra delay to actually turn off: */
if (k==0 && hubs[i].bcd_usb >= USB_SS_BCD)
if (k==0 && hubs[i].super_speed)
sleep_ms(150);
printf("Sent power %s request\n",
request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"
);
printf("Sent power %s request\n", should_be_on ? "on" : "off");
printf("New status for hub %s [%s]\n",
hubs[i].location, hubs[i].ds.description
);

View File

@@ -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"