summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgustavo panizzo <gfa@zumbi.com.ar>2018-03-11 15:34:43 +0100
committergustavo panizzo <gfa@zumbi.com.ar>2018-03-11 15:34:43 +0100
commit2df10c27982d29db01aa6268d268a0e2b4e74d0d (patch)
tree1730c44cc8a681815964daeb3b8a67ee2b689403
Import uhubctl_2.0.0.orig.tar.gz
[dgit import orig uhubctl_2.0.0.orig.tar.gz]
-rw-r--r--.gitignore8
-rw-r--r--COPYING339
-rw-r--r--Formula/uhubctl.rb16
-rw-r--r--LICENSE16
-rw-r--r--Makefile47
-rw-r--r--README.md128
-rw-r--r--uhubctl.c880
7 files changed, 1434 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0ab4458
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+# Executables
+uhubctl
+
+# Object files
+*.o
+
+# Mac symbols
+*.dSYM
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Formula/uhubctl.rb b/Formula/uhubctl.rb
new file mode 100644
index 0000000..440b1cd
--- /dev/null
+++ b/Formula/uhubctl.rb
@@ -0,0 +1,16 @@
+class Uhubctl < Formula
+ desc "control USB hubs powering per-port"
+ homepage "https://github.com/mvp/uhubctl"
+ head "https://github.com/mvp/uhubctl.git"
+
+ depends_on "libusb"
+
+ def install
+ system "make"
+ bin.install "uhubctl"
+ end
+
+ test do
+ system "#{bin}/uhubctl", "-v"
+ end
+end
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a842461
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,16 @@
+uhubctl – USB hub per-port power control.
+
+Copyright (c) 2009-2018, 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
+the Free Software Foundation, version 2.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7074cbf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,47 @@
+# uhubctl Makefile
+#
+UNAME_S := $(shell uname -s)
+
+DESTDIR ?=
+prefix ?= /usr
+sbindir ?= $(prefix)/sbin
+
+INSTALL := install
+INSTALL_DIR := $(INSTALL) -m 755 -d
+INSTALL_PROGRAM := $(INSTALL) -m 755
+RM := rm -rf
+
+CC ?= gcc
+CFLAGS ?= -g -O0
+CFLAGS += -Wall -Wextra -std=c99 -pedantic
+GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
+CFLAGS += -DPROGRAM_VERSION=\"$(GIT_VERSION)\"
+
+ifeq ($(UNAME_S),Linux)
+ LDFLAGS += -Wl,-z,relro -lusb-1.0
+endif
+
+ifeq ($(UNAME_S),Darwin)
+ifneq ($(wildcard /opt/local/include),)
+ # MacPorts
+ CFLAGS += -I/opt/local/include
+ LDFLAGS += -L/opt/local/lib
+endif
+ LDFLAGS += -lusb-1.0
+endif
+
+ifeq ($(UNAME_S),FreeBSD)
+ LDFLAGS += -lusb
+endif
+
+PROGRAM = uhubctl
+
+$(PROGRAM): $(PROGRAM).c
+ $(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS)
+
+install:
+ $(INSTALL_DIR) $(DESTDIR)$(sbindir)
+ $(INSTALL_PROGRAM) $(PROGRAM) $(DESTDIR)$(sbindir)
+
+clean:
+ $(RM) $(PROGRAM).o $(PROGRAM).dSYM $(PROGRAM)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..06aec22
--- /dev/null
+++ b/README.md
@@ -0,0 +1,128 @@
+uhubctl
+=======
+
+uhubctl is utility to control USB power per-port on smart USB hubs.
+Smart hub is defined as one that implements per-port power switching.
+
+Original idea for this code was inspired by hub-ctrl.c by Niibe Yutaka:
+http://www.gniibe.org/development/ac-power-control-by-USB-hub
+
+
+Compatible USB hubs
+===================
+
+Note that very few hubs actually 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:
+
+| Manufacturer | Product | VID:PID | Release | EOL |
+|:-------------------|:-------------------------------------------------------|:----------|:--------|:-----|
+| AmazonBasics | HU3641V1, 4 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
+| AmazonBasics | HU3770V1, 7 Port USB3 Hub (see USB3 note below) |`2109:2811`| 2013 | |
+| Apple | Thunderbolt Display 27" (internal USB hub) | | 2011 | 2016 |
+| Apple | USB Keyboard With Numeric Pad (internal USB hub) | | 2011 | |
+| Asus | Z87-PLUS Motherboard (onboard USB hubs) | | 2013 | 2016 |
+| B&B Electronics | UHR204 | | 2013 | |
+| Belkin | F5U701-BLK | | 2008 | 2012 |
+| Circuitco | Beagleboard-xM (internal USB hub) |`0424:9514`| 2010 | |
+| CyberPower | CP-H420P |`0409:0059`| 2004 | |
+| Cypress | CY4608 HX2VL development kit |`04b4:6570`| 2012 | |
+| D-Link | DUB-H4 rev D1 (black edition, old silver not working) |`05E3:0608`| 2012 | |
+| D-Link | DUB-H7 (silver edition only, new black not working) |`2001:F103`| 2005 | 2010 |
+| Elecom | U2H-G4S | | 2006 | 2011 |
+| Hawking Technology | UH214 | | 2003 | 2008 |
+| Lenovo | ThinkPad EU Ultra Dockingstation (40A20090EU) |`17EF:100F`| 2015 | |
+| Lenovo | ThinkPad X200 Ultrabase 42X4963 |`17EF:1005`| 2008 | 2011 |
+| Linksys | USB2HUB4 | | 2004 | 2010 |
+| Maplin | A08CQ |`0409:0059`| 2008 | 2011 |
+| Microchip | EVB-USB2517 | | 2008 | |
+| Plugable | USB3-HUB7BC (see USB3 note below) |`2109:0813`| 2015 | |
+| Plugable | USB3-HUB7-81X (see USB3 note below) |`2109:0813`| 2012 | |
+| Plugable | USB2-HUB10S | | 2010 | |
+| Raspberry Pi | Model B+, Model 2 B, Model 3 B (port 2 only) | | 2011 | |
+| Rosewill | RHUB-210 |`0409:005A`| 2011 | 2014 |
+| Sanwa Supply | USB-HUB14GPH | | 2001 | 2003 |
+| Sunix | SHB4200MA |`0409:0058`| 2006 | 2009 |
+| Targus | PAUH212U | | 2004 | 2009 |
+
+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.
+
+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
+====================
+If you have compatible USB 3.0 hub connected to USB3 upstream port,
+it will be detected as 2 independent virtual hubs: USB2 and USB3, and your USB devices will be
+connected to USB2 or USB3 virtual hub depending on their capabilities and connection speed.
+To control power for such hubs, it is necessary to turn off/on power on **both** USB2 and USB3
+virtual hubs for power off/on changes to take effect. `uhubctl` will try to do this automatically
+(unless you disable this behavior with option `-e`).
+
+Unfortunately, while most hubs will cut off data USB connection, some may still not cut off VBUS to port,
+which means connected phone may still continue to charge from port that is powered off by uhubctl.
+
+
+Compiling
+=========
+
+This utility was tested to compile and work on Linux
+(Ubuntu/Debian, Redhat/Fedora/CentOS, Arch Linux, Gentoo, OpenSUSE, Buildroot), FreeBSD and Mac OS X.
+
+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):
+
+* Ubuntu: `sudo apt-get install libusb-1.0-0-dev`
+* Redhat: `sudo yum install libusb1-devel`
+* MacOSX: `brew install libusb`, or `sudo port install libusb-devel`
+ Note that Mac OS Sierra may be affected by issue https://github.com/libusb/libusb/issues/303.
+ Until libusb 1.0.22 is released, use `brew install --HEAD libusb` as a workaround.
+* FreeBSD: libusb is included by default
+* Windows: TBD?
+
+To compile, simply run `make` - this will generate `uhubctl` binary.
+
+Also, for Mac OS X you can install `uhubctl` with Homebrew custom tap:
+
+```
+brew tap mvp/uhubctl https://github.com/mvp/uhubctl
+brew install --HEAD uhubctl
+```
+
+Usage
+=====
+
+You can control the power on a USB port(s) like this:
+
+ uhubctl -a off -p 235
+
+This means operate on default smart hub and turn power off (`-a off`, or `-a 0`)
+on ports 2,3,5 (`-p 235`). Supported actions are `off`/`on`/`cycle` (or `0`/`1`/`2`).
+`cycle` means turn power off, wait some delay (configurable with `-d`) and turn it back on.
+
+On Linux, you may need to run it with `sudo`, or to configure `udev` USB permissions.
+
+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.
+
+
+Copyright
+=========
+
+Copyright (C) 2009-2018 Vadim Mikhailov
+
+This file can be distributed under the terms and conditions of the
+GNU General Public License version 2.
diff --git a/uhubctl.c b/uhubctl.c
new file mode 100644
index 0000000..96ffc3e
--- /dev/null
+++ b/uhubctl.c
@@ -0,0 +1,880 @@
+/*
+ * Copyright (c) 2009-2018 Vadim Mikhailov
+ *
+ * Utility to turn USB port power on/off
+ * for USB hubs that support per-port power switching.
+ *
+ * This file can be distributed under the terms and conditions of the
+ * GNU General Public License version 2.
+ *
+ */
+
+#define _XOPEN_SOURCE 500
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <getopt.h>
+#include <errno.h>
+#include <ctype.h>
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <io.h>
+#include <process.h>
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+#else
+#include <unistd.h>
+#endif
+
+#if defined(__FreeBSD__) || defined(_WIN32)
+#include <libusb.h>
+#else
+#include <libusb-1.0/libusb.h>
+#endif
+
+#if defined(__APPLE__) /* 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 _POSIX_C_SOURCE >= 199309L
+#include <time.h> /* for nanosleep */
+#endif
+
+/* cross-platform sleep function */
+
+void sleep_ms(int milliseconds)
+{
+#if defined(_WIN32)
+ Sleep(milliseconds);
+#elif _POSIX_C_SOURCE >= 199309L
+ struct timespec ts;
+ ts.tv_sec = milliseconds / 1000;
+ ts.tv_nsec = (milliseconds % 1000) * 1000000;
+ nanosleep(&ts, NULL);
+#else
+ usleep(milliseconds * 1000);
+#endif
+}
+
+/* Max number of hub ports supported.
+ * This is somewhat artifically limited by "-p" option parser.
+ * If "-p" parser is improved, we can support up to 32 ports.
+ * However, biggest number of ports on smart hub I've seen was 8.
+ * I've also observed onboard USB hub with whopping 14 ports,
+ * but that hub did not support per-port power switching.
+ */
+#define MAX_HUB_PORTS 9
+#define ALL_HUB_PORTS ((1 << MAX_HUB_PORTS) - 1) /* bitmask */
+
+#define USB_CTRL_GET_TIMEOUT 5000
+
+#define USB_PORT_FEAT_POWER (1 << 3)
+
+#define POWER_KEEP (-1)
+#define POWER_OFF 0
+#define POWER_ON 1
+#define POWER_CYCLE 2
+
+#define MAX_HUB_CHAIN 8 /* Per USB 3.0 spec max hub chain is 7 */
+
+/* Partially borrowed from linux/usb/ch11.h */
+
+#pragma pack(push,1)
+struct usb_hub_descriptor {
+ unsigned char bDescLength;
+ unsigned char bDescriptorType;
+ unsigned char bNbrPorts;
+ unsigned char wHubCharacteristics[2];
+ unsigned char bPwrOn2PwrGood;
+ unsigned char bHubContrCurrent;
+ unsigned char data[1]; /* use 1 to avoid zero-sized array warning */
+};
+#pragma pack(pop)
+
+/*
+ * Hub Status and Hub Change results
+ * See USB 2.0 spec Table 11-19 and Table 11-20
+ */
+#pragma pack(push,1)
+struct usb_port_status {
+ int16_t wPortStatus;
+ int16_t wPortChange;
+};
+#pragma pack(pop)
+
+/*
+ * wPortStatus bit field
+ * See USB 2.0 spec Table 11-21
+ */
+#define USB_PORT_STAT_CONNECTION 0x0001
+#define USB_PORT_STAT_ENABLE 0x0002
+#define USB_PORT_STAT_SUSPEND 0x0004
+#define USB_PORT_STAT_OVERCURRENT 0x0008
+#define USB_PORT_STAT_RESET 0x0010
+#define USB_PORT_STAT_L1 0x0020
+/* bits 6 to 7 are reserved */
+#define USB_PORT_STAT_POWER 0x0100
+#define USB_PORT_STAT_LOW_SPEED 0x0200
+#define USB_PORT_STAT_HIGH_SPEED 0x0400
+#define USB_PORT_STAT_TEST 0x0800
+#define USB_PORT_STAT_INDICATOR 0x1000
+/* bits 13 to 15 are reserved */
+
+
+#define USB_SS_BCD 0x0300
+/*
+ * Additions to wPortStatus bit field from USB 3.0
+ * See USB 3.0 spec Table 10-10
+ */
+#define USB_PORT_STAT_LINK_STATE 0x01e0
+#define USB_SS_PORT_STAT_POWER 0x0200
+#define USB_SS_PORT_STAT_SPEED 0x1c00
+#define USB_PORT_STAT_SPEED_5GBPS 0x0000
+/* Valid only if port is enabled */
+/* Bits that are the same from USB 2.0 */
+#define USB_SS_PORT_STAT_MASK (USB_PORT_STAT_CONNECTION | \
+ USB_PORT_STAT_ENABLE | \
+ USB_PORT_STAT_OVERCURRENT | \
+ USB_PORT_STAT_RESET)
+
+/*
+ * Definitions for PORT_LINK_STATE values
+ * (bits 5-8) in wPortStatus
+ */
+#define USB_SS_PORT_LS_U0 0x0000
+#define USB_SS_PORT_LS_U1 0x0020
+#define USB_SS_PORT_LS_U2 0x0040
+#define USB_SS_PORT_LS_U3 0x0060
+#define USB_SS_PORT_LS_SS_DISABLED 0x0080
+#define USB_SS_PORT_LS_RX_DETECT 0x00a0
+#define USB_SS_PORT_LS_SS_INACTIVE 0x00c0
+#define USB_SS_PORT_LS_POLLING 0x00e0
+#define USB_SS_PORT_LS_RECOVERY 0x0100
+#define USB_SS_PORT_LS_HOT_RESET 0x0120
+#define USB_SS_PORT_LS_COMP_MOD 0x0140
+#define USB_SS_PORT_LS_LOOPBACK 0x0160
+
+
+/*
+ * wHubCharacteristics (masks)
+ * See USB 2.0 spec Table 11-13, offset 3
+ */
+#define HUB_CHAR_LPSM 0x0003 /* Logical Power Switching Mode mask */
+#define HUB_CHAR_COMMON_LPSM 0x0000 /* All ports at once power switching */
+#define HUB_CHAR_INDV_PORT_LPSM 0x0001 /* Per-port power switching */
+#define HUB_CHAR_NO_LPSM 0x0002 /* No power switching */
+
+#define HUB_CHAR_COMPOUND 0x0004 /* hub is part of a compound device */
+
+#define HUB_CHAR_OCPM 0x0018 /* Over-Current Protection Mode mask */
+#define HUB_CHAR_COMMON_OCPM 0x0000 /* All ports at once over-current protection */
+#define HUB_CHAR_INDV_PORT_OCPM 0x0008 /* Per-port over-current protection */
+#define HUB_CHAR_NO_OCPM 0x0010 /* No over-current protection support */
+
+#define HUB_CHAR_TTTT 0x0060 /* TT Think Time mask */
+#define HUB_CHAR_PORTIND 0x0080 /* per-port indicators (LEDs) */
+
+/* List of all USB devices enumerated by libusb */
+static struct libusb_device **usb_devs = NULL;
+
+struct hub_info {
+ struct libusb_device *dev;
+ int bcd_usb;
+ int nports;
+ int ppps;
+ int actionable; /* true if this hub is subject to action */
+ char vendor[16];
+ char location[32];
+ char description[256];
+};
+
+/* Array of all enumerated USB hubs */
+#define MAX_HUBS 128
+static struct hub_info hubs[MAX_HUBS];
+static int hub_count = 0;
+static int hub_phys_count = 0;
+
+/* default options */
+static char opt_vendor[16] = "";
+static char opt_location[32] = ""; /* Hub location a-b.c.d */
+static int opt_ports = ALL_HUB_PORTS; /* Bitmask of ports to operate on */
+static int opt_action = POWER_KEEP;
+static int opt_delay = 2;
+static int opt_repeat = 1;
+static int opt_wait = 20; /* wait before repeating in ms */
+static int opt_exact = 0; /* exact location match - disable USB3 duality handling */
+static int opt_reset = 0; /* reset hub after operation(s) */
+
+static const struct option long_options[] = {
+ { "loc", required_argument, NULL, 'l' },
+ { "vendor", required_argument, NULL, 'n' },
+ { "ports", required_argument, NULL, 'p' },
+ { "action", required_argument, NULL, 'a' },
+ { "delay", required_argument, NULL, 'd' },
+ { "repeat", required_argument, NULL, 'r' },
+ { "wait", required_argument, NULL, 'w' },
+ { "exact", no_argument, NULL, 'e' },
+ { "reset", no_argument, NULL, 'R' },
+ { "version", no_argument, NULL, 'v' },
+ { "help", no_argument, NULL, 'h' },
+ { 0, 0, NULL, 0 },
+};
+
+
+int print_usage()
+{
+ printf(
+ "uhubctl %s: 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"
+ "--ports, -p - ports to operate on [all hub ports].\n"
+ "--loc, -l - limit hub by location [all smart hubs].\n"
+ "--vendor, -n - limit hub by vendor id [%s] (partial ok).\n"
+ "--delay, -d - delay for cycle action [%d sec].\n"
+ "--repeat, -r - repeat power off count [%d] (some devices need it to turn off).\n"
+ "--exact, -e - exact location (no USB3 duality handling).\n"
+ "--reset, -R - reset hub after each power-on action, causing all devices to reassociate.\n"
+ "--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,
+ strlen(opt_vendor) ? opt_vendor : "any",
+ opt_delay,
+ opt_repeat,
+ opt_wait
+ );
+ return 0;
+}
+
+
+/* trim trailing spaces from a string */
+
+static char* rtrim(char* str)
+{
+ int i;
+ for (i = strlen(str)-1; i>=0 && isspace(str[i]); i--) {
+ str[i] = 0;
+ }
+ return str;
+}
+
+
+/*
+ * get USB hub properties.
+ * most hub_info fields are filled, except for description.
+ * returns 0 for success and error code for failure.
+ */
+
+int get_hub_info(struct libusb_device *dev, struct hub_info *info)
+{
+ int rc = 0;
+ int len = 0;
+ struct libusb_device_handle *devh = NULL;
+ unsigned char buf[LIBUSB_DT_HUB_NONVAR_SIZE + 2 + 3] = {0};
+ struct usb_hub_descriptor *uhd =
+ (struct usb_hub_descriptor *)buf;
+ int minlen = LIBUSB_DT_HUB_NONVAR_SIZE + 2;
+ struct libusb_device_descriptor desc;
+ rc = libusb_get_device_descriptor(dev, &desc);
+ if (rc)
+ return rc;
+ if (desc.bDeviceClass != LIBUSB_CLASS_HUB)
+ return LIBUSB_ERROR_INVALID_PARAM;
+ int bcd_usb = libusb_le16_to_cpu(desc.bcdUSB);
+ int desc_type = bcd_usb >= USB_SS_BCD ? LIBUSB_DT_SUPERSPEED_HUB
+ : LIBUSB_DT_HUB;
+ rc = libusb_open(dev, &devh);
+ if (rc == 0) {
+ len = libusb_control_transfer(devh,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS
+ | LIBUSB_RECIPIENT_DEVICE, /* hub status */
+ LIBUSB_REQUEST_GET_DESCRIPTOR,
+ desc_type << 8,
+ 0,
+ buf, sizeof(buf),
+ USB_CTRL_GET_TIMEOUT
+ );
+
+ if (len >= minlen) {
+ unsigned char port_numbers[MAX_HUB_CHAIN] = {0};
+ info->dev = dev;
+ info->bcd_usb = bcd_usb;
+ info->nports = uhd->bNbrPorts;
+ snprintf(
+ info->vendor, sizeof(info->vendor),
+ "%04x:%04x",
+ libusb_le16_to_cpu(desc.idVendor),
+ libusb_le16_to_cpu(desc.idProduct)
+ );
+
+ /* Convert bus and ports array into USB location string */
+ int bus = libusb_get_bus_number(dev);
+ snprintf(info->location, sizeof(info->location), "%d", bus);
+#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
+ /*
+ * libusb_get_port_path is deprecated since libusb v1.0.16,
+ * therefore use libusb_get_port_numbers when supported
+ */
+ int pcount = libusb_get_port_numbers(dev, port_numbers, MAX_HUB_CHAIN);
+#else
+ int pcount = libusb_get_port_path(NULL, dev, port_numbers, MAX_HUB_CHAIN);
+#endif
+ int k;
+ for (k=0; k<pcount; k++) {
+ char s[8];
+ snprintf(s, sizeof(s), "%s%d", k==0 ? "-" : ".", 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;
+ }
+ libusb_close(devh);
+ }
+ return rc;
+}
+
+
+/*
+ * Assuming that devh is opened device handle for USB hub,
+ * return state for given hub port.
+ * In case of error, returns -1 (inspect errno for more information).
+ */
+
+static int get_port_status(struct libusb_device_handle *devh, int port)
+{
+ int rc;
+ struct usb_port_status ust;
+ if (devh == NULL)
+ return -1;
+
+ rc = libusb_control_transfer(devh,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS
+ | LIBUSB_RECIPIENT_OTHER, /* port status */
+ LIBUSB_REQUEST_GET_STATUS, 0,
+ port, (unsigned char*)&ust, sizeof(ust),
+ USB_CTRL_GET_TIMEOUT
+ );
+
+ if (rc < 0) {
+ return rc;
+ }
+ return ust.wPortStatus;
+}
+
+
+/*
+ * Get USB device description as a string.
+ *
+ * It will use following format:
+ *
+ * "<vid:pid> <vendor> <product> <serial>, <USB x.yz, N ports>"
+ *
+ * vid:pid will be always present, but vendor, product or serial
+ * may be skipped if they are empty or not enough permissions to read them.
+ * <USB x.yz, N ports> will be present only for USB hubs.
+ *
+ * returns 0 for success and error code for failure.
+ * in case of failure description buffer is not altered.
+ */
+
+static int get_device_description(struct libusb_device * dev, char* description, int desc_len)
+{
+ int rc;
+ int id_vendor = 0;
+ int id_product = 0;
+ char vendor[64] = "";
+ char product[64] = "";
+ char serial[64] = "";
+ char ports[64] = "";
+ struct libusb_device_descriptor desc;
+ struct libusb_device_handle *devh = NULL;
+ rc = libusb_get_device_descriptor(dev, &desc);
+ if (rc)
+ return rc;
+ id_vendor = libusb_le16_to_cpu(desc.idVendor);
+ id_product = libusb_le16_to_cpu(desc.idProduct);
+ rc = libusb_open(dev, &devh);
+ if (rc == 0) {
+ if (desc.iManufacturer) {
+ libusb_get_string_descriptor_ascii(devh,
+ desc.iManufacturer, (unsigned char*)vendor, sizeof(vendor));
+ rtrim(vendor);
+ }
+ if (desc.iProduct) {
+ libusb_get_string_descriptor_ascii(devh,
+ desc.iProduct, (unsigned char*)product, sizeof(product));
+ rtrim(product);
+ }
+ if (desc.iSerialNumber) {
+ libusb_get_string_descriptor_ascii(devh,
+ desc.iSerialNumber, (unsigned char*)serial, sizeof(serial));
+ rtrim(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);
+ }
+ }
+ libusb_close(devh);
+ }
+ snprintf(description, desc_len,
+ "%04x:%04x%s%s%s%s%s%s%s",
+ id_vendor, id_product,
+ vendor[0] ? " " : "", vendor,
+ product[0] ? " " : "", product,
+ serial[0] ? " " : "", serial,
+ ports
+ );
+ return 0;
+}
+
+
+/*
+ * show status for hub ports
+ * portmask is bitmap of ports to display
+ * if portmask is 0, show all ports
+ */
+
+static int print_port_status(struct hub_info * hub, int portmask)
+{
+ int port_status;
+ struct libusb_device_handle * devh = NULL;
+ int rc = 0;
+ struct libusb_device *dev = hub->dev;
+ rc = libusb_open(dev, &devh);
+ if (rc == 0) {
+ int port;
+ for (port = 1; port <= hub->nports; port++) {
+ if (portmask > 0 && (portmask & (1 << (port-1))) == 0) continue;
+
+ port_status = get_port_status(devh, port);
+ if (port_status == -1) {
+ fprintf(stderr,
+ "cannot read port %d status, %s (%d)\n",
+ port, strerror(errno), errno);
+ break;
+ }
+
+ printf(" Port %d: %04x", port, port_status);
+
+ char description[256] = "";
+ struct libusb_device * udev;
+ int i = 0;
+ while ((udev = usb_devs[i++]) != NULL) {
+ if (libusb_get_parent(udev) == dev &&
+ libusb_get_port_number(udev) == port)
+ {
+ rc = get_device_description(udev, description, sizeof(description));
+ if (rc == 0)
+ break;
+ }
+ }
+
+ if (hub->bcd_usb < USB_SS_BCD) {
+ if (port_status == 0) {
+ printf(" 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");
+ }
+ } else {
+ if (port_status == USB_SS_PORT_LS_SS_DISABLED) {
+ 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_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 (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_CONNECTION) printf(" [%s]", description);
+
+ printf("\n");
+ }
+ libusb_close(devh);
+ }
+ return 0;
+}
+
+
+/*
+ * Find all USB hubs and fill hubs[] array.
+ * Set actionable to 1 on all hubs that we are going to operate on
+ * (this applies possible constraints like location or vendor).
+ * Returns count of found actionable physical hubs
+ * (USB3 hubs are counted once despite having USB2 dual partner).
+ * In case of error returns negative error code.
+ */
+
+static int usb_find_hubs()
+{
+ struct libusb_device *dev;
+ int perm_ok = 1;
+ int rc = 0;
+ int i = 0;
+ int j = 0;
+ while ((dev = usb_devs[i++]) != NULL) {
+ struct libusb_device_descriptor desc;
+ rc = libusb_get_device_descriptor(dev, &desc);
+ /* only scan for hubs: */
+ if (rc == 0 && desc.bDeviceClass != LIBUSB_CLASS_HUB)
+ continue;
+ struct hub_info info;
+ bzero(&info, sizeof(info));
+ rc = get_hub_info(dev, &info);
+ if (rc) {
+ perm_ok = 0; /* USB permission issue? */
+ }
+ get_device_description(dev, info.description, sizeof(info.description));
+ 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 (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++;
+ }
+ }
+ }
+ hub_phys_count = 0;
+ for (i=0; i<hub_count; i++) {
+ /* Check only actionable USB3 hubs: */
+ if (!hubs[i].actionable)
+ continue;
+ if (hubs[i].bcd_usb < USB_SS_BCD || opt_exact) {
+ hub_phys_count++;
+ }
+ if (opt_exact)
+ continue;
+ int match = -1;
+ for (j=0; j<hub_count; j++) {
+ if (i==j)
+ continue;
+
+ /* Find hub which is USB2/3 dual to the hub above.
+ * This is quite reliable and predictable on Linux
+ * but not on Mac, where we may match wrong hub :(
+ * It will work reliably on Mac if there is
+ * only one compatible USB3 hub is connected.
+ * TODO: discover better way to find dual hub.
+ */
+
+ /* Hub and its dual must be different types: one USB2, another USB3: */
+ if ((hubs[i].bcd_usb < USB_SS_BCD) ==
+ (hubs[j].bcd_usb < USB_SS_BCD))
+ continue;
+
+ /* But they must have the same vendor: */
+ if (strncasecmp(hubs[i].vendor, hubs[j].vendor, 4))
+ continue;
+
+ /* Provisionally we choose this one as dual: */
+ if (match < 0 && !hubs[j].actionable)
+ match = j;
+
+ /* But if there is exact port path match,
+ * we prefer it (true for Linux but not Mac):
+ */
+ char *p1 = strchr(hubs[i].location, '-');
+ char *p2 = strchr(hubs[j].location, '-');
+ if (p1 && p2 && strcasecmp(p1, p2)==0) {
+ match = j;
+ break;
+ }
+ }
+ if (match >= 0)
+ hubs[match].actionable = 1;
+ }
+ if (perm_ok == 0 && hub_phys_count == 0) {
+ return LIBUSB_ERROR_ACCESS;
+ }
+ return hub_phys_count;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ int c = 0;
+ int option_index = 0;
+
+ for (;;) {
+ c = getopt_long(argc, argv, "l:n:a:p:d:r:w:hveR",
+ long_options, &option_index);
+ if (c == -1)
+ break; /* no more options left */
+ switch (c) {
+ case 0:
+ /* If this option set a flag, do nothing else now. */
+ if (long_options[option_index].flag != 0)
+ break;
+ printf("option %s", long_options[option_index].name);
+ if (optarg)
+ printf(" with arg %s", optarg);
+ printf("\n");
+ break;
+ case 'l':
+ strncpy(opt_location, optarg, sizeof(opt_location));
+ break;
+ case 'n':
+ strncpy(opt_vendor, optarg, sizeof(opt_vendor));
+ break;
+ case 'p':
+ if (!strcasecmp(optarg, "all")) { /* all ports is the default */
+ break;
+ }
+ if (strlen(optarg)) {
+ /* parse port list */
+ opt_ports = 0;
+ size_t i;
+ for (i=0; i<strlen(optarg); i++) {
+ if (!isdigit(optarg[i]) || optarg[i] == '0') {
+ printf("%s must be list of ports 1 to %d\n", optarg, MAX_HUB_PORTS);
+ }
+ int d = optarg[i]-'1';
+ opt_ports |= (1 << d);
+ }
+ }
+ break;
+ case 'a':
+ if (!strcasecmp(optarg, "off") || !strcasecmp(optarg, "0")) {
+ opt_action = POWER_OFF;
+ }
+ if (!strcasecmp(optarg, "on") || !strcasecmp(optarg, "1")) {
+ opt_action = POWER_ON;
+ }
+ if (!strcasecmp(optarg, "cycle") || !strcasecmp(optarg, "2")) {
+ opt_action = POWER_CYCLE;
+ }
+ break;
+ case 'd':
+ opt_delay = atoi(optarg);
+ break;
+ case 'r':
+ opt_repeat = atoi(optarg);
+ break;
+ case 'e':
+ opt_exact = 1;
+ break;
+ case 'R':
+ opt_reset = 1;
+ break;
+ case 'w':
+ opt_wait = atoi(optarg);
+ break;
+ case 'v':
+ printf("%s\n", PROGRAM_VERSION);
+ exit(0);
+ break;
+ case 'h':
+ print_usage();
+ exit(1);
+ break;
+ case '?':
+ /* getopt_long has already printed an error message here */
+ fprintf(stderr, "Run with -h to get usage info.\n");
+ exit(1);
+ break;
+ default:
+ abort();
+ }
+ }
+ if (optind < argc) {
+ /* non-option parameters are found? */
+ fprintf(stderr, "Invalid command line syntax!\n");
+ fprintf(stderr, "Run with -h to get usage info.\n");
+ exit(1);
+ }
+
+ rc = libusb_init(NULL);
+ if (rc < 0) {
+ fprintf(stderr,
+ "Error initializing USB!\n"
+ );
+ exit(1);
+ }
+
+ rc = libusb_get_device_list(NULL, &usb_devs);
+ if (rc < 0) {
+ fprintf(stderr,
+ "Cannot enumerate USB devices!\n"
+ );
+ rc = 1;
+ goto cleanup;
+ }
+
+ rc = usb_find_hubs();
+ if (rc <= 0) {
+ fprintf(stderr,
+ "No compatible smart hubs 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;
+ }
+
+ if (hub_phys_count > 1 && opt_action >= 0) {
+ fprintf(stderr,
+ "Error: changing port state for multiple hubs at once is not supported.\n"
+ "Use -l to limit operation to one hub!\n"
+ );
+ exit(1);
+ }
+ int i;
+ for (i=0; i<hub_count; i++) {
+ if (hubs[i].actionable == 0)
+ continue;
+ printf("Current status for hub %s [%s]\n",
+ hubs[i].location, hubs[i].description
+ );
+ print_port_status(&hubs[i], opt_ports);
+ if (opt_action == POWER_KEEP) { /* no action, show status */
+ continue;
+ }
+ struct libusb_device_handle * devh = NULL;
+ rc = libusb_open(hubs[i].dev, &devh);
+ if (rc == 0) {
+ /* will operate on these ports */
+ int ports = ((1 << hubs[i].nports) - 1) & opt_ports;
+ int k; /* k=0 for power OFF, k=1 for power ON */
+ for (k=0; k<2; k++) { /* up to 2 power actions - off/on */
+ if (k == 0 && opt_action == POWER_ON )
+ continue;
+ if (k == 1 && opt_action == POWER_OFF)
+ continue;
+ int request = (k == 0) ? LIBUSB_REQUEST_CLEAR_FEATURE
+ : LIBUSB_REQUEST_SET_FEATURE;
+ 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);
+ }
+ }
+ }
+ }
+ if (k==0 && opt_action == POWER_CYCLE)
+ sleep_ms(opt_delay * 1000);
+ /* USB3 hubs need extra delay to actually turn off: */
+ if (k==0 && hubs[i].bcd_usb >= USB_SS_BCD)
+ sleep_ms(150);
+ printf("Sent power %s request\n",
+ request == LIBUSB_REQUEST_CLEAR_FEATURE ? "off" : "on"
+ );
+ printf("New status for hub %s [%s]\n",
+ hubs[i].location, hubs[i].description
+ );
+ print_port_status(&hubs[i], opt_ports);
+
+ if (k == 1 && opt_reset == 1) {
+ printf("Resetting hub...\n");
+ rc = libusb_reset_device(devh);
+ if (rc < 0) {
+ perror("Reset failed!\n");
+ } else {
+ printf("Reset successful!\n");
+ }
+ }
+ }
+ libusb_close(devh);
+ }
+ }
+ rc = 0;
+cleanup:
+ if (usb_devs)
+ libusb_free_device_list(usb_devs, 1);
+ usb_devs = NULL;
+ libusb_exit(NULL);
+ return rc;
+}