25 Commits

Author SHA1 Message Date
Vadim Mikhailov
3e707e4a6e add --quiet option 2025-04-19 11:24:39 -07:00
Vadim Mikhailov
fbe812bd83 Add an option to search/limit hub by description
Adding new option to limit hub by description.
Existing -s option limits by attached device description,
and new feature -H limits by hub instead.
Thanks to @pendragonsound for submitting patch!

Closes #611.
2025-04-19 10:35:44 -07:00
Vadim Mikhailov
d96308569c uhubctl is in homebrew core now - remove obsolete files, update readme
* remove custom homebrew tap - uhubctl is included in homebrew core now.
* remove bitbake recipe - it does not seem to be used for years now,
  and it should not have been committed in first place.
* add `installing` paragraph to readme. since most relevant OS include uhubctl
  in standard package managers now, most people don't want to compile it,
  but instead know how to install it quickly.
* update short links to not use bit.ly as it became very spammy now.
* update few expired links to point to archive.org cache.
* add `all` target to Makefile (FreeBSD ports want `all` target to always exist).
2025-04-18 23:13:56 -07:00
Vadim Mikhailov
d85f100c98 homebrew audit: livecheck must be before depends_on 2025-04-17 13:51:05 -07:00
Vadim Mikhailov
65f6d48cd1 homebrew audit one more fix 2025-04-17 13:44:56 -07:00
Vadim Mikhailov
14852054e2 making homebrew audit happy 2025-04-17 13:42:33 -07:00
Vadim Mikhailov
d00a167ab4 homebrew audit insists on checksum before head 2025-04-17 13:35:31 -07:00
Vadim Mikhailov
16001b090d minor fixes to homebrew formula so it passes strict audit checks 2025-04-17 13:28:19 -07:00
Vadim Mikhailov
42cdfb0b82 Fix udev rules to support Linux kernels 6.8+
Linux kernels before had devpath containing `usbN-portM`.
Newer kernels just use `port` symlink instead, which breaks our udev rules.
Changing glob from `*-port*` to `*port*` makes it work for both old and new kernels.

Closes issues #608, #609.
2025-02-23 12:51:49 -08:00
Vadim Mikhailov
c74755be42 Fix FreeBSD build, update docs regarding pkg-config 2025-01-21 19:45:22 -08:00
Vadim Mikhailov
07abf4cbea Fix warnings when compiled in C++ mode
Fixed all warnings when compiling with `c++ -std=c++11 ...`:
* bzero() appears to be deprecated, replaced with memset()
* fixed few const warnings
* suppressed zero array warnings (emitted by libusb headers)
2025-01-21 19:25:50 -08:00
Vadim Mikhailov
d483981f95 Add thermal camera control as notable project
Described in #594.
2025-01-18 14:59:52 -08:00
Vadim Mikhailov
2ca14eb3dd Add Mac Mini M4 as compatible device
Closes #606.
2025-01-18 14:44:59 -08:00
Vadim Mikhailov
40f10578cb Remove Rosonway RSH-A107 (and ikuai A107) from supported list
There are multiple reports that RSH-A107 does not turn off VBUS:
While it is possible that some hardware revisions which are working,
looks like most don't. It is better to remove it from the list.

Closes #604.
2025-01-18 14:32:56 -08:00
Vadim Mikhailov
7604f84c95 Add StarTech HB31C2A2CB as compatible device
Closes #601.
2025-01-18 14:24:32 -08:00
Vadim Mikhailov
f4b0d2ad31 Add RSH-A37S as compatible device
Closes #598.
2025-01-18 14:15:35 -08:00
Vadim Mikhailov
a7df8f8367 Cleanup ifdef usage
* No need to check for both __gnu_linux__ and __linux__, latter is enough.
* Require libusb 1.0.13 to build - this makes LIBUSB_API_VERSION always defined.
* Include `<libusb.h>` not `<libusb-1.0/libusb.h>` - this will always work
  if `pkg-config` is detected. For backwards compatibility still try building
  without `pkg-config` present, but it is much less reliable and will not work on Mac.
* Bump copyright year.
2025-01-18 14:04:34 -08:00
Tormod Volden
a957b21815 Add --sysdev/-y option for direct device node access (#600)
* Add --sysdev/-u option for direct device node access

Only the device specified by the given device path will be accessed,
instead of scanning the USB bus.

Most useful if you use udev rules to create stable device node aliases for hubs,
independent of bus topology.

For instance, if your matching udev rules include SYMLINK+="MYSMARTHUB1"
you can call uhubctl with --sysdev /dev/MYSMARTHUB1 instead of using -l
and a non-stable bus location to specify it.

Signed-off-by: Tormod Volden <debian.tormod@gmail.com>
2025-01-18 11:50:25 -08:00
Vadim Mikhailov
2dbc6ce2d9 Add recipe for compiling on OpenSUSE
Closes #592.
2024-10-15 21:49:00 -07:00
mvp
302b3c0c77 always include <fcntl.h>
4a233824c4 fixed issue for Raspberry Pi on Linux, but broke compilation on Mac.
Fixing it by always including <fcntl.h>.
2024-10-13 13:10:22 -07:00
Vadim Mikhailov
4a233824c4 Add StarTech DKT30CSDHPD3 as compatible device
Closes #588.
2024-09-17 18:09:57 -07:00
Vadim Mikhailov
e0d1c34eed Improve support for Raspberry Pi 4B and 5:
* Detect if we are running on Raspberry 4B or 5
* Add RPi 4B and 5 conditionals for RPi hacks
* Relax some conditions which may vary depending on RPi hardware and/or OS
  (in particular Ubuntu hub numbering differs from Raspbian).
* For RPi5, changed README to suggest using busses 2 and 4 instead of 1 and 3.
  This makes the same recipe work on both Raspbian and Ubuntu.

This fixes #587.
2024-09-16 20:23:33 -07:00
mvp
153c5da267 Fixed snprintf override which breaks on old MacOS
`snprintf` is available in Mac/FreeBSD headers only if _XOPEN_SOURCE is 600 or higher.
This closes #586.
2024-09-12 10:42:23 -07:00
mvp
bfc11e6b05 Remove Buffalo BSH4A05U3BK from supported list
Buffalo BSH4A05U3BK does not seem to support VBUS off, removing from the list.

Closes #584.
2024-08-31 22:29:58 -07:00
mvp
c9abed425c Release version 2.6.0 as stable brew tap 2024-08-31 16:57:18 -07:00
8 changed files with 291 additions and 178 deletions

3
.gitignore vendored
View File

@@ -10,3 +10,6 @@ uhubctl
# Patches
*.patch
*.diff
# IDE config dirs
.*/

View File

@@ -1,24 +0,0 @@
class Uhubctl < Formula
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.5.0.tar.gz"
sha256 "d4452252f7862f7a45dd9c62f2ea7cd3a57ab5f5ab0e54a857d4c695699bbba3"
license "GPL-2.0"
depends_on "libusb"
depends_on "pkg-config" => :build
livecheck do
url :stable
end
def install
system "make"
bin.install "uhubctl"
end
test do
system "#{bin}/uhubctl", "-v"
end
end

View File

@@ -1,6 +1,6 @@
uhubctl USB hub per-port power control.
Copyright (c) 2009-2024, Vadim Mikhailov
Copyright (c) 2009-2025, 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,7 @@ PKG_CONFIG ?= pkg-config
CC ?= gcc
CFLAGS ?= -g -O0
CFLAGS += -Wall -Wextra -std=c99 -pedantic
CFLAGS += -Wall -Wextra -Wno-zero-length-array -std=c99 -pedantic
GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
ifeq ($(GIT_VERSION),)
GIT_VERSION := $(shell cat VERSION)
@@ -31,12 +31,17 @@ 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)
# But it should still build even if pkg-config is not available
CFLAGS += -I/usr/include/libusb-1.0
LDFLAGS += -lusb-1.0
endif
PROGRAM = uhubctl
.PHONY: all install clean
all: $(PROGRAM)
$(PROGRAM): $(PROGRAM).c
$(CC) $(CPPFLAGS) $(CFLAGS) $@.c -o $@ $(LDFLAGS)

125
README.md
View File

@@ -11,7 +11,7 @@ https://www.gniibe.org/development/ac-power-control-by-USB-hub
Compatible USB hubs
===================
Note that very few hubs actually support per-port power switching.
Note that not many USB hubs correctly support per-port power switching.
Some of them are no longer manufactured and can be hard to find.
This is list of known compatible USB hubs:
@@ -22,10 +22,11 @@ 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 | HU9002V1SBL, HU9002V1EBL, HU9002V1ESL ([note](https://tinyurl.com/HU9002)) | 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-68ANHUB-BV7A-0004 ([note](https://git.io/JLnZb)) | 7 | 3.0 |`2109:0812`| 2014 | |
| Apple | Mac Mini M4 (2 front ports only) | 2 | 3.2 |`05AC:800B`| 2024 | |
| 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 | |
@@ -39,13 +40,12 @@ This is list of known compatible USB hubs:
| Belkin | F5U238UKCRL-MOB | 4 | 2.0 |`0409:0059`| 2004 | 2010 |
| BenQ | PD2700U 4K Monitor (works only in USB2 mode) | 4 | 3.0 |`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 | |
| Bytecc | BT-UH340 ([warning](https://tinyurl.com/BT-UH340-1)) | 4 | 3.0 |`2109:8110`| 2010 | |
| Centech | CT-USB4HUB ReTRY HUB | 4 | 3.0 |`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 | |
| Coolgear | USBG-12U2ML | 12 | 2.0 |`05e3:0607`| 2015 | |
| Cypress | CY4608 HX2VL devkit ([note](https://bit.ly/3sMPfpu)) | 4 | 2.0 |`04B4:6570`| 2012 | |
| Cypress | CY4608 HX2VL ([note](https://tinyurl.com/CY4608-1)) | 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 | |
@@ -58,16 +58,16 @@ This is list of known compatible USB hubs:
| Delock | 62537 | 4 | 3.0 | | 2017 | 2021 |
| Delock | 87445 ([note](https://git.io/Jsuz5)) | 4 | 2.0 |`05E3:0608`| 2009 | 2013 |
| Elecom | U2H-G4S | 4 | 2.0 | | 2006 | 2011 |
| Gigabyte | G27Q monitor ([see](http://tinyurl.com/G27Q551) | 4 | 3.0 |`2109:0817`| 2020 | |
| Gigabyte | G27Q monitor ([see](http://tinyurl.com/G27Q551)) | 4 | 3.0 |`2109:0817`| 2020 | |
| 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 | |
| Inateck | HB2025A ([USB2 only](https://tinyurl.com/HB2025A-1)) | 4 | 3.1 |`2109:2822`| 2021 | |
| IOI | U3H415E1 | 4 | 3.0 | | 2012 | |
| j5create | JUH377 ([note](https://bit.ly/3Mx9eQI)) | 7 | 3.0 | | 2016 | |
| j5create | JUH470 ([note](https://bit.ly/3CRWamP)) | 3 | 3.0 |`05E3:0610`| 2014 | |
| j5create | JUH377 ([note](https://tinyurl.com/JUH377)) | 7 | 3.0 | | 2016 | |
| j5create | JUH470 ([note](https://tinyurl.com/JUH470)) | 3 | 3.0 |`05E3:0610`| 2014 | |
| Juiced Systems | 6HUB-01 | 7 | 3.0 |`0BDA:0411`| 2014 | 2018 |
| KUNBUS GmbH | RevPi Connect (+) / S / SE | 2 | 2.0 |`0424:9514`| 2018 | |
| KUNBUS GmbH | RevPi Connect 4 | 2 | 3.0 | | 2022 | |
@@ -94,7 +94,7 @@ This is list of known compatible USB hubs:
| Microchip | EVB-USB5807 | 7 | 3.0 | | 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 | |
| NVidia | Jetson Xavier NX ([details](https://tinyurl.com/Xavier-NX)) | 4 | 3.0 | | 2020 | |
| Phidgets | HUB0003_0 | 7 | 2.0 |`1A40:0201`| 2017 | |
| Philips | 346B1C UltraWide 34" Curved Monitor | 4 | 3.0 |`05E3:0610`| 2019 | |
| Plugable | USB3-HUB7BC | 7 | 3.0 |`2109:0813`| 2015 | |
@@ -108,17 +108,19 @@ This is list of known compatible USB hubs:
| Raspberry Pi | 5 ([see below](#raspberry-pi-5)) | 4 | 3.0 |`1d6b:0002`| 2023 | |
| 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-518C ([note](https://tinyurl.com/RSH518)) | 7 | 3.0 |`2109:0817`| 2021 | |
| Rosonway | RSH-A10 ([see](https://tinyurl.com/2ppyyaj8)) | 10 | 3.0 |`0bda:0411`| 2020 | |
| 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 | |
| Rosonway | RSH-A107 (aka ikuai A107-5) | 7 | 3.1 |`0bda:5411`| 2022 | |
| Rosonway | RSH-A13 ([warning](https://tinyurl.com/RSH-A13)) | 13 | 3.1 |`2109:2822`| 2021 | |
| Rosonway | RSH-A16 ([note](https://git.io/JTawg), [warning](https://tinyurl.com/RSH-A16)) | 16 | 3.0 |`0bda:0411`| 2020 | |
| Rosonway | RSH-A37S | 7 | 3.0 |`2109:2822`| 2021 | |
| Rosonway | RSH-A104 ([USB2 only](https://tinyurl.com/RSH-A104)) | 4 | 3.1 |`2109:2822`| 2022 | |
| Rosonway | RSH-ST07C ([only 4](https://tinyurl.com/4pjnujrn)) | 7 | 3.0 |`2109:2822`| 2023 | |
| Sanwa Supply | USB-HUB14GPH | 4 | 1.1 | | 2001 | 2003 |
| Seagate | Backup Plus Hub STEL8000100 | 2 | 3.0 |`0BC2:AB44`| 2016 | |
| Seeed Studio | reTerminal CM4104032 | 2 | 2.0 |`0424:2514`| 2021 | |
| StarTech | DKT30CSDHPD3 USB-C Travel Dock | 3 | 3.0 |`2109:2817`| 2018 | |
| StarTech | HB30A4AIB ([warning](https://tinyurl.com/ycxravwk)) | 4 | 3.0 |`2109:2817`| 2018 | |
| StarTech | HB31C2A2CB ([note](https://github.com/mvp/uhubctl/issues/601)) | 5 | 3.0 |`14B0:013D`| 2020 | |
| Sunix | SHB4200MA | 4 | 2.0 |`0409:0058`| 2006 | 2009 |
| System Talks | Sugoi USB2-HUB4X | 4 | 2.0 | | 2007 | |
| Targus | PA095UZ | 2 | 2.0 | | 2004 | |
@@ -150,27 +152,47 @@ virtual hubs for power off/on changes to take effect. `uhubctl` will try to do t
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`.
Installing
==========
For Linux and MacOS uhubctl is available in standard package managers
and can be installed with following commands:
* MacOS: `brew install uhubctl` or `sudo port install uhubctl`
* Ubuntu/Debian/Raspbian: `sudo apt install uhubctl`
* Redhat/EPEL/Fedora/CentOS: `sudo yum install uhubctl`
* OpenSUSE: `sudo zypper install uhubctl`
However, uhubctl installed from standard package manager may not
necessarily be latest version, or even severely lag behind current version.
If [latest published](https://github.com/mvp/uhubctl/releases) uhubctl version
is newer than what your package manager offers, you may need to compile and install
from source as described below.
Compiling
=========
This utility was tested to compile and work on Linux
(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot), FreeBSD, NetBSD, SunOS and MacOS.
This utility was tested to compile and work on Linux (Ubuntu/Debian/Raspbian,
Redhat/EPEL/Fedora/CentOS, Arch Linux, Gentoo, openSUSE, Buildroot),
FreeBSD, NetBSD, SunOS and MacOS.
While `uhubctl` compiles on Windows, USB power switching does not work on Windows because `libusb`
is using `winusb.sys` driver, which according to Microsoft does not support
[necessary USB control requests](https://social.msdn.microsoft.com/Forums/sqlserver/en-US/f680b63f-ca4f-4e52-baa9-9e64f8eee101).
[necessary USB control requests](https://web.archive.org/web/20210225235523/https://social.msdn.microsoft.com/Forums/sqlserver/en-US/f680b63f-ca4f-4e52-baa9-9e64f8eee101/how-to-send-an-quotusb-control-requestquot-to-an-usbhub?forum=wdk).
This may be fixed if `libusb` starts supporting different driver on Windows.
Note that it is highly recommended to have `pkg-config` installed (many platforms provide it by default).
Note that it is highly recommended to have utility `pkgconf` (or `pkg-config`) installed
(often it is installed by default).
First, you need to install library libusb-1.0 (version 1.0.12 or later, 1.0.16 or later is recommended):
First, you need to install library libusb-1.0 (version 1.0.13 or later is required,
1.0.23 or later is recommended):
* Ubuntu: `sudo apt-get install libusb-1.0-0-dev`
* Redhat: `sudo yum install libusb1-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`
* Ubuntu: `sudo apt-get install libusb-1.0-0-dev pkgconf`
* Redhat: `sudo yum install libusb1-devel pkgconf`
* OpenSUSE: `sudo zypper install libusb-1_0-devel pkgconf`
* MacOS: `brew install libusb pkgconf`, or `sudo port install libusb-devel pkgconf`
* FreeBSD: `pkg install gmake pkgconf` (libusb is included by default)
* NetBSD: `sudo pkgin install libusb1 gmake pkgconf`
* Windows: TBD?
To fetch uhubctl source and compile it:
@@ -186,17 +208,6 @@ You can install it in your system as `/usr/sbin/uhubctl` using:
Note that on some OS (e.g. FreeBSD/NetBSD) you need to use `gmake` instead to build.
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 uhubctl
```
To build/install from master branch, use `--HEAD`:
```
brew install uhubctl --HEAD
```
Usage
=====
@@ -244,7 +255,7 @@ Then, add udev rules like below to file `/etc/udev/rules.d/52-usb.rules`
SUBSYSTEM=="usb", DRIVER=="usb", MODE="0666", ATTR{idVendor}=="2001"
# Linux 6.0 or later (its ok to have this block present for older Linux kernels):
SUBSYSTEM=="usb", DRIVER=="usb", \
RUN="/bin/sh -c \"chmod -f 666 $sys$devpath/*-port*/disable || true\""
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.
@@ -258,8 +269,8 @@ If you don't like wide open mode `0666`, you can restrict access by group like t
SUBSYSTEM=="usb", DRIVER=="usb", MODE="0664", GROUP="dialout"
# Linux 6.0 or later (its ok to have this block present for older Linux kernels):
SUBSYSTEM=="usb", DRIVER=="usb", \
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\""
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:
@@ -328,7 +339,7 @@ Per-port power switching:
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](https://bit.ly/2JzczjZ) and is [fixed](https://github.com/mvp/uhubctl/pull/450)
This is Linux kernel [issue](https://tinyurl.com/ym7yvuzw) and is [fixed](https://github.com/mvp/uhubctl/pull/450)
since uhubctl 2.5.0 for systems with Linux kernel 6.0 or later.
If you are still using Linux 5.x or older, you can use this workaround for this issue:
@@ -344,7 +355,7 @@ When you turn power back on, device should re-enumerate properly (no need to cal
Some device drivers in kernel are surprised by USB device being turned off and automatically try to power it back on.
This is Linux kernel [issue](https://bit.ly/2JzczjZ) and is [fixed](https://github.com/mvp/uhubctl/pull/450)
This is Linux kernel [issue](https://tinyurl.com/ym7yvuzw) and is [fixed](https://github.com/mvp/uhubctl/pull/450)
since uhubctl 2.5.0 for systems with Linux kernel 6.0 or later.
If you are still using Linux 5.x or older:
@@ -405,7 +416,7 @@ For reference, supported Raspberry Pi models have following internal USB topolog
##### 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)
you have to [update firmware](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#rpi-eeprom-update)
to make power switching work on RPi 4B.
* USB2 hub `1`, 1 port, only connects hub `1-1` below.
@@ -423,7 +434,8 @@ to make power switching work on RPi 4B.
##### Raspberry Pi 5
Raspberry Pi 5 has two USB2 ports and two USB3 ports (total 4).
These ports are connected to 4 distinct USB hubs `1`,`2`,`3`,`4` in really weird configuration.
These ports are connected to 4 distinct USB hubs `1`,`2`,`3`,`4` in really weird configuration
(but depending on OS and HW revision hubs of interest can be `2`,`3`,`4`,`5`).
If USB3 device is connected to blue socket, it will be detected on USB3 hub `2` or `4`.
If USB2 device is connected to any socket or USB3 device connected to black socket,
it will be detected on USB2 hub `1` or `3`.
@@ -437,15 +449,15 @@ despite belonging to 4 different logical USB hubs.
To turn off VBUS power it has to be disabled across all onboard hubs and ports with:
```
uhubctl -l 1 -a 0
uhubctl -l 3 -a 0
uhubctl -l 2 -a 0
uhubctl -l 4 -a 0
```
To turn it back on:
```
uhubctl -l 1 -a 1
uhubctl -l 3 -a 1
uhubctl -l 2 -a 1
uhubctl -l 4 -a 1
```
Note that VBUS power goes down only if all ports are off -
@@ -461,29 +473,30 @@ 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://bit.ly/2VtWrVt) | Reset cellular modems when necessary |
| [Smog Sensor](https://bit.ly/2EMwgCk) | Raspberry Pi based smog sensor power reset |
| [sysmoQMOD](https://tinyurl.com/sysmoQMOD) | Reset cellular modems when necessary |
| [Smog Sensor](https://tinyurl.com/smogsensor) | 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/39MeVFQ) | Turn off unused USB ports to improve audio quality |
| [Ideal Music Server](https://tinyurl.com/ideal-m-srv) | 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 |
| [USB fan for Raspberry Pi](https://tinyurl.com/fan-rpi) | Control USB fan to avoid Raspberry Pi overheating |
| [Raspberry Pi Reboot Router](https://tinyurl.com/rpi-rtr)| Automatically reboot router if internet isn't working |
| [Control USB Lamp With Voice](https://tinyurl.com/usblmp)| Voice Control of USB Lamp using Siri and Raspberry Pi |
| [Control USB LED Strip](https://tinyurl.com/usbleds) | 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 |
| [Webcam On-Air Sign](https://tinyurl.com/uonair) | 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 |
| [Open source PPPS hub](https://tinyurl.com/yckhystt) | Open source hardware project for uhubctl compatible hub |
| [Python Wrapper for uhubctl](https://github.com/nbuchwitz/python3-uhubctl) | Module to use uhubctl with Python |
| [labgrid](https://github.com/labgrid-project/labgrid) | Framework for testing embedded Linux on hardware |
| [Thermal Camera](https://tinyurl.com/5asne8hw) | Turn on/off robot's thermal camera when necessary |
Copyright
=========
Copyright (C) 2009-2024 Vadim Mikhailov
Copyright (C) 2009-2025 Vadim Mikhailov
This file can be distributed under the terms and conditions of the
GNU General Public License version 2.

View File

@@ -1,6 +1,6 @@
# uhubctl USB hub per-port power control https://github.com/mvp/uhubctl
#
# Copyright (c) 2009-2024, Vadim Mikhailov
# Copyright (c) 2009-2025, Vadim Mikhailov
#
# This file can be distributed under the terms and conditions of the
# GNU General Public License version 2.
@@ -20,5 +20,5 @@ SUBSYSTEM=="usb", DRIVER=="usb", 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=="usb", \
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\""
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\""

280
uhubctl.c
View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2024 Vadim Mikhailov
* Copyright (c) 2009-2025 Vadim Mikhailov
*
* Utility to turn USB port power on/off
* for USB hubs that support per-port power switching.
@@ -9,8 +9,9 @@
*
*/
#define _XOPEN_SOURCE 500
#define _XOPEN_SOURCE 600
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -18,6 +19,7 @@
#include <getopt.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#if defined(_WIN32)
#include <windows.h>
@@ -29,27 +31,27 @@
#include <unistd.h>
#endif
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(_WIN32)
#include <libusb.h>
#else
#include <libusb-1.0/libusb.h>
/* LIBUSBX_API_VERSION was first defined in libusb 1.0.13
and renamed to LIBUSB_API_VERSION since libusb 1.0.16 */
#if defined(LIBUSBX_API_VERSION) && !defined(LIBUSB_API_VERSION)
#define LIBUSB_API_VERSION LIBUSBX_API_VERSION
#endif
#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)
/* FreeBSD's libusb does not define LIBUSB_DT_SUPERSPEED_HUB */
#if !defined(LIBUSB_DT_SUPERSPEED_HUB)
#define LIBUSB_DT_SUPERSPEED_HUB 0x2a
#endif
#if !defined(LIBUSB_API_VERSION)
#error "libusb-1.0 is required!"
#endif
#if _POSIX_C_SOURCE >= 199309L
#include <time.h> /* for nanosleep */
#endif
#if defined(__gnu_linux__) || defined(__linux__)
#include <fcntl.h> /* for open() / O_WRONLY */
#endif
/* cross-platform sleep function */
@@ -216,26 +218,37 @@ 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_searchhub[64] = ""; /* Search by hub 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 */
static int opt_action = POWER_KEEP;
static double opt_delay = 2;
static int opt_quiet = 0; /* quiet operation if set */
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 */
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
static int opt_nosysfs = 0; /* don't use the Linux sysfs port disable interface, even if available */
#if (LIBUSB_API_VERSION >= 0x01000107) /* 1.0.23 */
static const char *opt_sysdev;
#endif
#endif
/* For Raspberry Pi detection and workarounds: */
static int is_rpi_4b = 0;
static int is_rpi_5 = 0;
static const char short_options[] =
"l:L:n:a:p:d:r:w:s:hvefRN"
#if defined(__gnu_linux__) || defined(__linux__)
"l:L:n:a:p:d:r:w:s:H:qhvefRN"
#if defined(__linux__)
"S"
#if (LIBUSB_API_VERSION >= 0x01000107) /* 1.0.23 */
"y:"
#endif
#endif
;
@@ -243,6 +256,7 @@ static const struct option long_options[] = {
{ "location", required_argument, NULL, 'l' },
{ "vendor", required_argument, NULL, 'n' },
{ "search", required_argument, NULL, 's' },
{ "searchhub",required_argument, NULL, 'H' },
{ "level", required_argument, NULL, 'L' },
{ "ports", required_argument, NULL, 'p' },
{ "action", required_argument, NULL, 'a' },
@@ -251,9 +265,13 @@ static const struct option long_options[] = {
{ "wait", required_argument, NULL, 'w' },
{ "exact", no_argument, NULL, 'e' },
{ "force", no_argument, NULL, 'f' },
{ "quiet", no_argument, NULL, 'q' },
{ "nodesc", no_argument, NULL, 'N' },
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
{ "nosysfs", no_argument, NULL, 'S' },
#if (LIBUSB_API_VERSION >= 0x01000107)
{ "sysdev", required_argument, NULL, 'y' },
#endif
#endif
{ "reset", no_argument, NULL, 'R' },
{ "version", no_argument, NULL, 'v' },
@@ -275,14 +293,19 @@ static int print_usage(void)
"--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"
"--searchhub,-H - limit hub by description.\n"
"--search, -s - limit hub by attached device description.\n"
"--delay, -d - delay for cycle/flash 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"
"--quiet, -q - quiet operation, most output is suppressed.\n"
"--nodesc, -N - do not query device description (helpful for unresponsive devices).\n"
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
"--nosysfs, -S - do not use the Linux sysfs port disable interface.\n"
#if (LIBUSB_API_VERSION >= 0x01000107)
"--sysdev, -y - open system device node instead of scanning.\n"
#endif
#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"
@@ -366,6 +389,60 @@ static int ports2bitmap(char* const portlist)
}
/* Standard printf but gated by quiet option */
int qprintf(const char *format, ...) {
int count = 0;
if (!opt_quiet) {
va_list args;
va_start(args, format);
count = vprintf(format, args);
va_end(args);
}
return count;
}
/*
* Get model of the computer we are currently running on.
* On success return 0 and fill model string (null terminated).
* If model is not known or error occurred returns -1.
*
* Currently this can only return successfully on Linux,
* but in the future we may need it on other operating systems too.
*/
static int get_computer_model(char *model, int len)
{
int fd = open("/sys/firmware/devicetree/base/model", O_RDONLY);
if (fd < 0) {
return fd;
}
int bytes_read = read(fd, model, len-1);
close(fd);
if (bytes_read < 0) {
return -1;
}
model[bytes_read] = 0;
return 0;
}
/*
* Check if we are running on given computer model using substring match.
* Returns 1 if yes and 0 otherwise.
*/
static int check_computer_model(const char *target)
{
char model[256] = "";
if (get_computer_model(model, sizeof(model)) == 0) {
if (strstr(model, target) != NULL) {
return 1;
}
}
return 0;
}
/*
* Compatibility wrapper around libusb_get_port_numbers()
*/
@@ -373,7 +450,7 @@ static int ports2bitmap(char* const portlist)
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)
#if (LIBUSB_API_VERSION >= 0x01000102)
/*
* libusb_get_port_path is deprecated since libusb v1.0.16,
* therefore use libusb_get_port_numbers when supported
@@ -444,7 +521,7 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
}
/* Get container_id: */
bzero(info->container_id, sizeof(info->container_id));
memset(info->container_id, 0, sizeof(info->container_id));
struct libusb_bos_descriptor *bos;
rc = libusb_get_bos_descriptor(devh, &bos);
if (rc == 0) {
@@ -470,7 +547,8 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
libusb_free_bos_descriptor(bos);
/* Raspberry Pi 4B hack for USB3 root hub: */
if (strlen(info->container_id)==0 &&
if (is_rpi_4b &&
strlen(info->container_id)==0 &&
strcasecmp(info->vendor, "1d6b:0003")==0 &&
info->pn_len==0 &&
info->nports==4 &&
@@ -487,32 +565,28 @@ static int get_hub_info(struct libusb_device *dev, struct hub_info *info)
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) {
if (is_rpi_4b && lpsm == HUB_CHAR_COMMON_LPSM && strcasecmp(info->vendor, "2109:3431")==0) {
lpsm = HUB_CHAR_INDV_PORT_LPSM;
}
info->lpsm = lpsm;
/* Raspberry Pi 5 hack */
/* TODO: make this hack more reliable by querying Raspberry Pi model */
if (strlen(info->container_id)==0 &&
if (is_rpi_5 &&
strlen(info->container_id)==0 &&
info->lpsm==HUB_CHAR_INDV_PORT_LPSM &&
info->pn_len==0)
{
/* USB2 */
if (strcasecmp(info->vendor, "1d6b:0002")==0 &&
info->nports==2 &&
!info->super_speed &&
(info->bus==1 || info->bus==3))
!info->super_speed)
{
strcpy(info->container_id, "Raspberry Pi 5 Fake Container Id");
}
/* USB3 */
if (strcasecmp(info->vendor, "1d6b:0003")==0 &&
info->nports==1 &&
info->super_speed &&
(info->bus==2 || info->bus==4))
info->super_speed)
{
strcpy(info->container_id, "Raspberry Pi 5 Fake Container Id");
}
@@ -555,7 +629,7 @@ static int get_port_status(struct libusb_device_handle *devh, int port)
}
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
/*
* Try to use the Linux sysfs interface to power a port off/on.
* Returns 0 on success.
@@ -655,7 +729,7 @@ static int set_port_status_libusb(struct libusb_device_handle *devh, int port, i
static int set_port_status(struct libusb_device_handle *devh, struct hub_info *hub, int port, int on)
{
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
if (!opt_nosysfs) {
if (set_port_status_linux(devh, hub, port, on) == 0) {
return 0;
@@ -695,7 +769,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
rc = libusb_get_device_descriptor(dev, &desc);
if (rc)
return rc;
bzero(ds, sizeof(*ds));
memset(ds, 0, sizeof(*ds));
id_vendor = desc.idVendor;
id_product = desc.idProduct;
rc = libusb_open(dev, &devh);
@@ -719,7 +793,7 @@ static int get_device_description(struct libusb_device * dev, struct descriptor_
}
if (desc.bDeviceClass == LIBUSB_CLASS_HUB) {
struct hub_info info;
bzero(&info, sizeof(info));
memset(&info, 0, sizeof(info));
rc = get_hub_info(dev, &info);
if (rc == 0) {
const char * lpsm_type;
@@ -774,10 +848,10 @@ static int print_port_status(struct hub_info * hub, int portmask)
break;
}
printf(" Port %d: %04x", port, port_status);
qprintf(" Port %d: %04x", port, port_status);
struct descriptor_strings ds;
bzero(&ds, sizeof(ds));
memset(&ds, 0, sizeof(ds));
struct libusb_device * udev;
int i = 0;
while ((udev = usb_devs[i++]) != NULL) {
@@ -800,48 +874,48 @@ static int print_port_status(struct hub_info * hub, int portmask)
if (!hub->super_speed) {
if (port_status == 0) {
printf(" off");
qprintf(" off");
} else {
if (port_status & USB_PORT_STAT_POWER) printf(" power");
if (port_status & USB_PORT_STAT_INDICATOR) printf(" indicator");
if (port_status & USB_PORT_STAT_TEST) printf(" test");
if (port_status & USB_PORT_STAT_HIGH_SPEED) printf(" highspeed");
if (port_status & USB_PORT_STAT_LOW_SPEED) printf(" lowspeed");
if (port_status & USB_PORT_STAT_SUSPEND) printf(" suspend");
if (port_status & USB_PORT_STAT_POWER) qprintf(" power");
if (port_status & USB_PORT_STAT_INDICATOR) qprintf(" indicator");
if (port_status & USB_PORT_STAT_TEST) qprintf(" test");
if (port_status & USB_PORT_STAT_HIGH_SPEED) qprintf(" highspeed");
if (port_status & USB_PORT_STAT_LOW_SPEED) qprintf(" lowspeed");
if (port_status & USB_PORT_STAT_SUSPEND) qprintf(" suspend");
}
} else {
if (!(port_status & USB_SS_PORT_STAT_POWER)) {
printf(" off");
} else {
int link_state = port_status & USB_PORT_STAT_LINK_STATE;
if (port_status & USB_SS_PORT_STAT_POWER) printf(" power");
if (port_status & USB_SS_PORT_STAT_POWER) qprintf(" power");
if ((port_status & USB_SS_PORT_STAT_SPEED)
== USB_PORT_STAT_SPEED_5GBPS)
{
printf(" 5gbps");
}
if (link_state == USB_SS_PORT_LS_U0) printf(" U0");
if (link_state == USB_SS_PORT_LS_U1) printf(" U1");
if (link_state == USB_SS_PORT_LS_U2) printf(" U2");
if (link_state == USB_SS_PORT_LS_U3) printf(" U3");
if (link_state == USB_SS_PORT_LS_SS_DISABLED) printf(" SS.Disabled");
if (link_state == USB_SS_PORT_LS_RX_DETECT) printf(" Rx.Detect");
if (link_state == USB_SS_PORT_LS_SS_INACTIVE) printf(" SS.Inactive");
if (link_state == USB_SS_PORT_LS_POLLING) printf(" Polling");
if (link_state == USB_SS_PORT_LS_RECOVERY) printf(" Recovery");
if (link_state == USB_SS_PORT_LS_HOT_RESET) printf(" HotReset");
if (link_state == USB_SS_PORT_LS_COMP_MOD) printf(" Compliance");
if (link_state == USB_SS_PORT_LS_LOOPBACK) printf(" Loopback");
if (link_state == USB_SS_PORT_LS_U0) qprintf(" U0");
if (link_state == USB_SS_PORT_LS_U1) qprintf(" U1");
if (link_state == USB_SS_PORT_LS_U2) qprintf(" U2");
if (link_state == USB_SS_PORT_LS_U3) qprintf(" U3");
if (link_state == USB_SS_PORT_LS_SS_DISABLED) qprintf(" SS.Disabled");
if (link_state == USB_SS_PORT_LS_RX_DETECT) qprintf(" Rx.Detect");
if (link_state == USB_SS_PORT_LS_SS_INACTIVE) qprintf(" SS.Inactive");
if (link_state == USB_SS_PORT_LS_POLLING) qprintf(" Polling");
if (link_state == USB_SS_PORT_LS_RECOVERY) qprintf(" Recovery");
if (link_state == USB_SS_PORT_LS_HOT_RESET) qprintf(" HotReset");
if (link_state == USB_SS_PORT_LS_COMP_MOD) qprintf(" Compliance");
if (link_state == USB_SS_PORT_LS_LOOPBACK) qprintf(" Loopback");
}
}
if (port_status & USB_PORT_STAT_RESET) printf(" reset");
if (port_status & USB_PORT_STAT_OVERCURRENT) printf(" oc");
if (port_status & USB_PORT_STAT_ENABLE) printf(" enable");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" connect");
if (port_status & USB_PORT_STAT_RESET) qprintf(" reset");
if (port_status & USB_PORT_STAT_OVERCURRENT) qprintf(" oc");
if (port_status & USB_PORT_STAT_ENABLE) qprintf(" enable");
if (port_status & USB_PORT_STAT_CONNECTION) qprintf(" connect");
if (port_status & USB_PORT_STAT_CONNECTION) printf(" [%s]", ds.description);
if (port_status & USB_PORT_STAT_CONNECTION) qprintf(" [%s]", ds.description);
printf("\n");
qprintf("\n");
}
libusb_close(devh);
}
@@ -872,7 +946,7 @@ static int usb_find_hubs(void)
if (rc == 0 && desc.bDeviceClass != LIBUSB_CLASS_HUB)
continue;
struct hub_info info;
bzero(&info, sizeof(info));
memset(&info, 0, sizeof(info));
rc = get_hub_info(dev, &info);
if (rc) {
perm_ok = 0; /* USB permission issue? */
@@ -898,7 +972,7 @@ static int usb_find_hubs(void)
(memcmp(info.port_numbers, dev_pn, info.pn_len) == 0))
{
struct descriptor_strings ds;
bzero(&ds, sizeof(ds));
memset(&ds, 0, sizeof(ds));
rc = get_device_description(udev, &ds);
if (rc != 0)
break;
@@ -910,6 +984,12 @@ static int usb_find_hubs(void)
}
}
}
if (strlen(opt_searchhub) > 0) {
/* Search by hub description */
if (strstr(info.ds.description, opt_searchhub) == NULL) {
info.actionable = 0;
}
}
if (strlen(opt_location) > 0) {
if (strcasecmp(opt_location, info.location)) {
info.actionable = 0;
@@ -1008,7 +1088,7 @@ static int usb_find_hubs(void)
}
/* 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 (is_rpi_4b && l1 + s1 == l2 + s2 && l1 >= s2 && memcmp(p1 + s2, p2 + s1, l1 - s2)==0) {
if (best_score < 3) {
best_score = 3;
best_match = j;
@@ -1046,7 +1126,7 @@ static int usb_find_hubs(void)
}
}
if (perm_ok == 0 && hub_phys_count == 0) {
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
if (geteuid() != 0) {
fprintf(stderr,
"There were permission problems while accessing USB.\n"
@@ -1065,6 +1145,10 @@ int main(int argc, char *argv[])
int rc;
int c = 0;
int option_index = 0;
#if defined(__linux__) && (LIBUSB_API_VERSION >= 0x01000107)
int sys_fd;
libusb_device_handle *sys_devh = NULL;
#endif
for (;;) {
c = getopt_long(argc, argv, short_options, long_options, &option_index);
@@ -1092,6 +1176,9 @@ int main(int argc, char *argv[])
case 's':
snprintf(opt_search, sizeof(opt_search), "%s", optarg);
break;
case 'H':
snprintf(opt_searchhub, sizeof(opt_searchhub), "%s", optarg);
break;
case 'p':
if (!strcasecmp(optarg, "all")) { /* all ports is the default */
break;
@@ -1129,10 +1216,15 @@ int main(int argc, char *argv[])
case 'N':
opt_nodesc = 1;
break;
#if defined(__gnu_linux__) || defined(__linux__)
#if defined(__linux__)
case 'S':
opt_nosysfs = 1;
break;
#if (LIBUSB_API_VERSION >= 0x01000107)
case 'y':
opt_sysdev = optarg;
break;
#endif
#endif
case 'e':
opt_exact = 1;
@@ -1140,6 +1232,9 @@ int main(int argc, char *argv[])
case 'R':
opt_reset = 1;
break;
case 'q':
opt_quiet = 1;
break;
case 'w':
opt_wait = atoi(optarg);
break;
@@ -1175,7 +1270,35 @@ int main(int argc, char *argv[])
exit(1);
}
#if defined(__linux__) && (LIBUSB_API_VERSION >= 0x01000107)
if (opt_sysdev) {
sys_fd = open(opt_sysdev, O_RDWR);
if (sys_fd < 0) {
fprintf(stderr, "Cannot open system node!\n");
rc = 1;
goto cleanup;
}
rc = libusb_wrap_sys_device(NULL, sys_fd, &sys_devh);
if (rc != 0) {
fprintf(stderr,
"Cannot use %s as USB hub device, failed to wrap system node!\n",
opt_sysdev);
rc = 1;
goto cleanup;
}
usb_devs = calloc(2, sizeof *usb_devs);
if (!usb_devs) {
fprintf(stderr, "Out of memory\n");
rc = 1;
goto cleanup;
}
usb_devs[0] = libusb_get_device(sys_devh);
} else {
rc = libusb_get_device_list(NULL, &usb_devs);
}
#else
rc = libusb_get_device_list(NULL, &usb_devs);
#endif
if (rc < 0) {
fprintf(stderr,
"Cannot enumerate USB devices!\n"
@@ -1184,6 +1307,9 @@ int main(int argc, char *argv[])
goto cleanup;
}
is_rpi_4b = check_computer_model("Raspberry Pi 4 Model B");
is_rpi_5 = check_computer_model("Raspberry Pi 5");
rc = usb_find_hubs();
if (rc <= 0) {
fprintf(stderr,
@@ -1218,7 +1344,7 @@ int main(int argc, char *argv[])
for (i=0; i<hub_count; i++) {
if (hubs[i].actionable == 0)
continue;
printf("Current status for hub %s [%s]\n",
qprintf("Current status for hub %s [%s]\n",
hubs[i].location, hubs[i].ds.description
);
print_port_status(&hubs[i], opt_ports);
@@ -1255,19 +1381,19 @@ int main(int argc, char *argv[])
/* USB3 hubs need extra delay to actually turn off: */
if (k==0 && hubs[i].super_speed)
sleep_ms(150);
printf("Sent power %s request\n", should_be_on ? "on" : "off");
printf("New status for hub %s [%s]\n",
qprintf("Sent power %s request\n", should_be_on ? "on" : "off");
qprintf("New status for hub %s [%s]\n",
hubs[i].location, hubs[i].ds.description
);
print_port_status(&hubs[i], opt_ports);
if (k == 1 && opt_reset == 1) {
printf("Resetting hub...\n");
qprintf("Resetting hub...\n");
rc = libusb_reset_device(devh);
if (rc < 0) {
perror("Reset failed!\n");
} else {
printf("Reset successful!\n");
qprintf("Reset successful!\n");
}
}
}
@@ -1278,8 +1404,18 @@ int main(int argc, char *argv[])
}
rc = 0;
cleanup:
#if defined(__linux__) && (LIBUSB_API_VERSION >= 0x01000107)
if (opt_sysdev && sys_fd >= 0) {
if (sys_devh)
libusb_close(sys_devh);
close(sys_fd);
free(usb_devs);
} else if (usb_devs)
libusb_free_device_list(usb_devs, 1);
#else
if (usb_devs)
libusb_free_device_list(usb_devs, 1);
#endif
usb_devs = NULL;
libusb_exit(NULL);
return rc;

View File

@@ -1,20 +0,0 @@
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=a79e6a142b69522fe7757fe7313895eb"
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}/*"