diff options
author | Didier Raboud <odyx@debian.org> | 2018-01-07 12:32:11 +0100 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2018-01-07 12:32:11 +0100 |
commit | 97c310418e4266fd2a69633786e2b09e03584ed9 (patch) | |
tree | 79a19de13087c8bcda1057301af1c81de9bdfa9e |
Import usb-modeswitch_2.5.2+repack0.orig.tar.xz
[dgit import orig usb-modeswitch_2.5.2+repack0.orig.tar.xz]
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 321 | ||||
-rw-r--r-- | Makefile | 105 | ||||
-rw-r--r-- | README | 279 | ||||
-rw-r--r-- | dispatcher.c | 88 | ||||
-rwxr-xr-x | make_string.tcl | 31 | ||||
-rw-r--r-- | usb-modeswitch-upstart.conf | 5 | ||||
-rw-r--r-- | usb_modeswitch.1 | 175 | ||||
-rw-r--r-- | usb_modeswitch.c | 2115 | ||||
-rw-r--r-- | usb_modeswitch.conf | 40 | ||||
-rw-r--r-- | usb_modeswitch.h | 116 | ||||
-rwxr-xr-x | usb_modeswitch.sh | 75 | ||||
-rwxr-xr-x | usb_modeswitch.tcl | 1005 | ||||
-rw-r--r-- | usb_modeswitch@.service | 8 | ||||
-rw-r--r-- | usb_modeswitch_dispatcher.1 | 21 |
15 files changed, 4724 insertions, 0 deletions
@@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..066d38a --- /dev/null +++ b/ChangeLog @@ -0,0 +1,321 @@ + +History of USB_ModeSwitch +========================= + +Version 2.5.2, 2017/12/31 + Bugfix release: fixed additional MessageContent parameters not working, + reported by Frank Schmirler (see + http://draisberghof.de/usb_modeswitch/bb/viewtopic.php?p=18369 ); + fixed "early crash" when killed before libusb context initialization + (see https://bugzilla.redhat.com/show_bug.cgi?id=1358472 ), reported + by Lubomir Rintel; + fixed bad string reference, reported by Lubomir Rintel, (see + http://draisberghof.de/usb_modeswitch/bb/viewtopic.php?p=18238 ); + fixed detection of systemd if /sbin/init is a cascaded symlink + (see http://draisberghof.de/usb_modeswitch/bb/viewtopic.php?p=18218 ); + fixed quirk in help message from usb_modeswitch binary +Version 2.5.1, 2017/08/06 + New parameter "HuaweiAltMode", uses an alternative Huawei standard bulk + message which will either provide NCM (newer modems) or plain PPP ports + (older modems); new option in the global configuration file + "HuaweiAltModeGlobal", allowing easy override of "HuwaeiNewMode" with + "HuaweiAltMode"; reworked USB configuration switching, configuring a + device reset first before setting the target configuration; improved + driver detachment, now taking all configured interfaces into account +Version 2.5.0, 2017/01/17 + ATTENTION: Parameter transmitted from udev now reduced to %k (kernel + name), however, rules file parameters can be '%b/%k' or '%k' - this + fixes issues with parameter handling by the systemd unit file; + remove endpoint reset ahead of bulk message transmission, only reset + if endpoints are actually stalled, helping with quirky device behaviour + (see www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?f=2&t=2582 ); + stability fix by making a buffer in the config parser static, preventing + problems seen in Fedora (thanks to Lubomir Rintel for patches and hints + regarding the three previous issues); removed premature driver unbinding + in wrapper which could disrupt certain devices already in target mode + (reported by Aleksander Morgado); improved check for determining install + mode (essential with generic config files present, e.g. 12d1:#linux) +Version 2.4.0, 2016/06/12 + ATTENTION: All ad-hoc driver binding code (using new_id driver attribute) + removed - was a potential source of side effects and should now be + obsoleted by good kernel support for modems; Added "dummy" setting for + config files, to conditionally refrain from handling a device (see this + topic: www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?f=4&t=2458 ); + extended StandardEject sequence to include LUN 1, required for some D-Link + devices; add device class 239 ("miscellaneous") to sanity check (thanks to + Daniel Drake for reporting); udev shell script - removed driver binding, + removed almost all waiting and forking, improved check for systemd (thanks + to Daniel Drake for problem analysis and solution, see this topic/patch: + www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?p=16777#p16777 , + gist.github.com/dsd/9f83c4830ab78ce94078aedb2cf16a8f ) +Version 2.3.0, 2016/01/12 + ATTENTION: -I flag is now history and being ignored - determining SCSI + attributes is really an 'outside task'; -n flag (NeedResponse) is being + ignored, CSW response will now always be read; introduction of parameter + "OptionMode", wrapping the standard bulk message for all newer Huawei + devices; fixed missing variable initialization in dispatcher script which + could lead to crash (thanks, Dmitry Kunilov!); fixed bug which prevented + early logging; fixed success report for Cisco AM10; some source code + formatting and clean-up +Version 2.2.6, 2015/11/01 + Renamed function abort(), avoiding possible conflicts in static builds + with libjim (thanks, Gustavo Zacharias); removed storage class check of + interface 0 from dispatcher, enabling new multi-config devices in data + package 20151101 +Version 2.2.5, 2015/07/16 + Fixed bug in configuration check, possibly leading to segfault (thanks, + Leonid Lisovskiy); fixed Pantech commandline parameter evaluation (was + not working at all); added driver unbind step via sysfs in wrapper, + getting rid of the USB subsystem complaint "interface 0 claimed by + usb-storage while 'usb_modeswitch' <does this and that>" +Version 2.2.4, 2015/07/14 + Fixed buggy check of USB configuration selection (possibly leading + to segfault), tested with Alcatel X602D; removed call to 'libusb_strerror' + from libusb initialization - not available in earlier libusb1 versions +Version 2.2.3, 2015/06/29 + Fixed problem arising with systemd version 221 (220 untested), which + affects starting the usb_modeswitch systemd unit from the sh script + (reported by Archlinux users) +Version 2.2.2, 2015/06/27 + Added catch for libusb init error (thanks, Henrik Gustafsson); removed + global function result variable; added catch for USB configuration + read error (both thanks to "otila"); fixed wrapper script where port + search for symlinking modem port was broken ("/dev/gsmmodem"); changed + PantechMode parameter to represent different targets; added global + config option to disable MBIM checking and setting alltogether (request + from "kai"); changed udev sh script so that systemd processing takes + precedence over upstart; changed systemd template unit parameter to + avoid escaping problems +Version 2.2.1, 2015/01/15 + Fixed unreliable switching function for Cisco AM10 +Version 2.2.0, 2014/05/29 + Introduction of parameter "HuaweiNewMode", wrapping the standard bulk + message for all newer Huawei devices; support for generic fall-back + config files, combined with OS switch (per vendor ID), implementation + to use a specific switching command on Android for all Huawei devices + (see README of data package for details); this change was suggested + by Huawei +Version 2.1.1, 2014/03/27 + Code cleanup, better use of libusb1; this also fixes problems with + determination of the active USB configuration (Samsung, Option modems); + "Interface" parameter was not working as expected, fixed (see also: + https://bugs.launchpad.net/bugs/1261923 ); fixed bogus interface release + in "inquire" function, again a report by "Sonya@zte" +Version 2.1.0, 2014/01/28 + ATTENTION: -I flag meaning reversed, default is to skip SCSI inquiry; + introduction of StandardEject, replacing many MessageContents with the + same function, reducing size of device config files, and always including + the 'Allow Medium Removal' before ejecting (thanks to Lars Melin for + the idea); fix in "bulk_read", removing bogus CSW request (report from + "Sonya@zte") +Version 2.0.1, 2013/09/03 + Fixed stupid string size bug which could lead to memory corruption + (thanks to Leonid Myravjev) +Version 2.0.0, 2013/09/01 + Switched to libusb1.0, with much help from folks of the wl500g project + (http://wl500g.googlecode.com): Vladislav Grishenko, Leonid Lisovskiy, + Roman Samarev, Andrey Tikhomirov; + major code and debug output cleanup; man page corrections and additions + (thanks to Thomas Haller); + experimental systemd and upstart integration, if present + (Explanation: newer udev versions kill all subprocesses, detached or + not. The suggested way to handle longer running processes like the + usb_modeswitch_dispatcher is to add simple services or tasks and start + these by sending signals from the udev rule) + !! Attention, system integrators: a crude install facility is included + in the Makefile to check if "upstart" or "systemd" is active and to + install the matching service file; you may want to adapt it better to + your respective system, possibly adding dependencies/targets to the + services. Note that the udev starter script usb_modeswitch.sh also + checks for the existence of the service/unit files +Version 1.2.7, 2013/08/07 + Two new dedicated control message functions to support Pantech LTE + (thanks to Adam Goode) and Blackberry Q10/Z10 (thanks to Daniel Mende) +Version 1.2.6, 2013/06/02 + Several changes to streamline compiling as part of larger projects + (thanks to Nicolas Carrier), mostly in Makefile; fix compiler warnings + appearing in certain build environments (N. Carrier); new Quanta + procedure (thanks to Andrey Tikhomirov) for Quanta 1K3 LTE; fix for + error with cascaded hubs in dispatcher script (hint from N. Carrier) +Version 1.2.5, 2012/11/09 + Initial support for MBIM devices, use with data package >= 20121109; + checking for these is the automatic default, new parameter NoMBIMCheck + prevents the check per device in case of problems; new global option + to set "delay_use" of usb-storage (as low values may prevent + mode-switching); fix for handling multi-configuration devices (thanks + to Bjørn Mork for advice) +Version 1.2.4, 2012/08/12 + Additional interface checks to prevent sending UFI commands to non- + storage interfaces (prompted by more ambiguous device IDs popping up); + change in SierraMode for handling newer devices which caused an error + abort before; Makefile fix for parallelized make runs +Version 1.2.3, 2012/01/28 + Fixed two bugs both causing the embedded-jimsh install variant of the + dispatcher crash (the "pure-script" install variant was NOT affected); + fixed some "regexp" incompatibilities with Debian's libjim +Version 1.2.2, 2012/01/19 + Fixed bad bug preventing mode switch for devices using TargetClass; + improved logging in case of negative success check +Version 1.2.1, 2011/12/26 + Fixed possible ambiguities when multiple identical modems are plugged + synchronously; this is achieved by adding -b and -g parameter (busnum + and devnum) for proper id'ing; some resulting workflow changes and + shortcuts in "system integration" mode (use sysfs/wrapper for success + control, reduce informative checks); improved hub usage robustness; + future data packages can be ridded of redundancies (default ID, success + check parameters), resulting in smaller files; + fixed bad bug which may prevent switching completely when logging is + not activated; fixed possible overflow in dispatcher C code (thanks to + Gilles Espinasse) +Version 1.2.0, 2011/10/23 + Added QisdaMode for Qisda H21 (thanks to Chi-Hang Long for the code); + dispatcher can now be installed with an embedded interpreter, so that + Tcl is no longer required; added command line options for binary program + to accept configuration data via stdin or as a long string parameter - + this fixes the bug with non-writable temporary file during boot; + reversed skipping of pre-switching delay, which has caused problems; + fixed potential buffer overflow (thanks to Rafael Silva for the find); + get first interface right even on some broken devices (thanks to + Alexander Gordeev for the patch); increased post-switch delay before + driver binding to avoid possible conflict with usb-storage +Version 1.1.9, 2011/08/05 + Added CiscoMode for Valet device; additional checking for CDC ACM device + to prevent erroneous driver loading after switching; no more post-switch + check for access to initial device if target parameters are given +Version 1.1.8, 2011/06/19 + Cleaned up switchSendMessage(); added workaround for quirky devices not + reporting configuration setting; added non-CSW response for arbitrary + bulk transfers; added SequansMode and MobileActionMode; check for active + configuration will be skipped if bNumConfigurations is 1 (most cases) +Version 1.1.7, 2011/02/27 + Attention: paths for runtime files and database have changed! Old places + will be found but are deprecated; /usr/share/usb_modeswitch for database, + /var/lib/usb_modeswitch for "remembered" IDs; + fix for configuration setting race (thanks to Amit Mendapara); discovered + incompatibility between Tcl versions <= 8.3 and >=8.4, so 8.4 is the + minimum prerequisite now; + first availability of an alternative source pack which includes "jimsh", + a fast Tcl mini shell, intended for resource-constrained systems +Version 1.1.6, 2010/12/22 + Moved warm-boot driver binding to sh wrapper, was unreliable in 1.1.5; + sh wrapper overhaul, made compatible with Ubuntu's "dash" shell, tclsh + calls reduced further; initial device checking includes current + bConfigurationValue now, should work with config setting for multiple + devices; made tcl script conform to limitations of "jimsh", the minimal + tcl shell (hint from Barry Kauler); fixes for "usbserial" fallback + (driver binding for old systems); in the C program, changed parameter + "MessageDelay" (hitherto unused) to "ReleaseDelay", to be used in one + device configuration (delay interface release after bulk message sending) +Version 1.1.5, 2010/11/28 + Added special control message for Kobil devices (patch from Filip Aben); + try to get active configuration for interface class checking (there are + some new devices 'switching' via configuration selection); + fixed "0000" target product ID - again; new bash and tcl wrapper logic: + the convenience functions for driver binding and symlinking will now + start the tcl shell ONLY for known devices; changed and appended logging + capabilities of said convenience functions; add loading of "usbserial" + as a fallback for older systems to support new devices; + add workaround for bug in libusb1 which affects device search during + success check +Version 1.1.4, 2010/08/17 + The package should work at boot time now (cold and warm); + product IDs of "0000" do exist but were not accepted, fixed (thanks to + Sakis Dimopoulos); response endpoint is now always detected (led to + possible error report when resetting all endpoints in version 1.1.3); + wrapper script can now work with a packed collection of config files as + well as with the plain folder of files; use with the "install-packed" + make target of the data package (for use on systems with resource + constraints); wrapper fix for the symlink feature: handling of multiple + interrupt ports was incomplete; wrapper does not longer use a temporary + file for the symlink feature (security considerations, Marco d'Itri) +Version 1.1.3, 2010/06/21 + Added delay option to separate multiple message transfers by millisecs; + fixed (possibly dangerous) sloppy string handling (thanks to Christophe + Fergeau); added "clear_halt" for response endpoint; small additions in + Makefile (install with -D); changes in option handling (NO MORE DEFAULT + CONFIG FILE!) and help text; symlink feature in wrapper can now cope + with devices providing more than one interrupt port; wrapper now ignores + package manager leftovers in config folder; replaced bash-specific syntax + in wrapper; changed ZTE skipping (if existing rules are found) to warning +Version 1.1.2, 2010/04/18 + Added support for two additional bulk messages; wrapper handles special + ZTE case; generalized driver loading, new parameter "DriverModule" and + "DriverIDPath"; new wrapper facility to add symlink pointing to interrupt + port (used in rule file from data pack >= 20100418) +Version 1.1.1, 2010/03/17 + Attention: old usb_modeswitch.conf renamed to usb_modeswitch.setup! + Add separate config file for wrapper (global settings for switching and + logging); add config file option to disable driver loading; handling of + kernel attribute AVOID_RESET_QUIRK added; bug fixed in SonyMode (reported + by "no-0n3"); bug fixed in SuccessCheck logic; minor flow alignments and + fixes; new devices +Version 1.1.0, 2010/01/24 + Attention: wrapper script location changed, uninstall old versions! + Major fixes in the wrapper script (stabilizing and time-saving); + back to unified installation, defaults to "integrated" approach; + new -D parameter to enable "integrated" behaviour; bugs fixed in + success check; man file included (borrowed from the Debian package); + C code and binary works with the compat library from libusb-1.0; + some new devices +Version 1.0.7, 2010/01/06 + Bug fixed for Sony mode, thanks to Marco Chiaranda; fix for parameter + substitution in newer udev versions, fix for bad bug in wrapper script + practically disabling automatic mode +Version 1.0.6, 2009/12/21 + New "GCT Mode", fixes for device quirks (NXP Dragonfly), fix for multiple + Huawei devices, cleanups, loads of new devices in config file and database, + minor tcl script changes +Version 1.0.5, 2009/08/26 + More changes and fixes regarding success check; "--version" option; + config "database" updated +Version 1.0.4, 2009/08/23 + Success check bugs (and others) fixed +Version 1.0.3, 2009/08/20 + Success check improved; experimental system integration (fully automated), + optional; new parameter "TargetProductList" needed for this; other + necessary small adaptations; more devices +Version 1.0.2, 2009/06/10 + Output bugs fixed +Version 1.0.1, 2009/06/08 + Added output of descriptor strings for further identification +Version 1.0.0, 2009/06/01 + Attention: possible incompatibilities for command line control! + On/off flags don't require arguments anymore (-H, -S, -O, -d, -R, + -n, new: -I), meaning "-R 0" does a reset like "-R 1" or "-R"; + long option names changed to standard format (e.g. --HuaweiMode to + --huawei-mode); added device inquiry, for future help with device + identification; catch error -19 as possible success; send and response + endpoints now autoselected (consequently NeedResponse is back); + new devices +Version 0.9.7, 2009/04/15 + Updated SonyMode, MD 400 now stable; automatic default endpoint + detection from Andrew Bird +Version 0.9.7beta, 2009/03/15 + Major code clean up, optional success control (both suggested + by Daniel Cooper), new devices +Version 0.9.6, 2009/01/08 + Special modes added for Sierra and Sony Ericsson, new devices +Version 0.9.5, 2008/10/27 + New options for USB tuning (jokedst), lots of new devices, clean up +Version 0.9.4, 2008/06/09 + Compat fix for libusb on FreeBSD quirks, more devices +Version 0.9.4beta2, 2008/03/19 + Successful udev device release fix +Version 0.9.4beta, 2008/03/16 + Multiple device support +Version 0.9.3, 2008/03/09 + More devices, no changes from beta version +Version 0.9.3beta, 2007/12/30 + New TargetClass parameter for recent Option firmware (Paul Hardwick), more + devices +Version 0.9.2, 2007/11/02 + New Huawei mode (code from Miroslav Bobovsky, added by Denis Sutter), more + devices +Version 0.9.1beta, 2007/09/04 (jokedst) + Added command line parsing, cleaned up config stuff, doc updates +Version 0.9beta, 2007/08/15 + Name change from "icon_switch", parameter file and generalizing +Version 0.2, 2006/09/25 + Code cleaning, more messages +Version 0.1, 2006/09/24 + (as "icon_switch") Just very basic functionality ... diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..463a11f --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +PROG = usb_modeswitch +VERS = 2.5.2 +CC ?= gcc +CFLAGS += -Wall +LIBS = `pkg-config --libs --cflags libusb-1.0` +RM = /bin/rm -f +OBJS = usb_modeswitch.c +PREFIX = $(DESTDIR)/usr +ETCDIR = $(DESTDIR)/etc +SYSDIR = $(ETCDIR)/systemd/system +UPSDIR = $(ETCDIR)/init +UDEVDIR = $(DESTDIR)/lib/udev +SBINDIR = $(PREFIX)/sbin +MANDIR = $(PREFIX)/share/man/man1 +VPATH = jimtcl +HOST_TCL := $(shell cd jim && ./autosetup/find-tclsh) +ifeq (,$(findstring jimsh0,$(HOST_TCL))) +TCL ?= $(HOST_TCL) +else +TCL ?= /usr/bin/tclsh +endif +JIM_CONFIGURE_OPTS = --disable-lineedit \ + --with-out-jim-ext="stdlib posix load signal syslog" --prefix=/usr + +.PHONY: clean install install-common uninstall \ + script shared static \ + dispatcher-script dispatcher-shared dispatcher-static \ + install-script install-shared install-static + +all: script + +script: $(PROG) dispatcher-script + +shared: $(PROG) dispatcher-shared + +static: $(PROG) dispatcher-static + +$(PROG): $(OBJS) usb_modeswitch.h + $(CC) -o $(PROG) $(OBJS) $(CFLAGS) $(LIBS) $(LDFLAGS) + +jim/libjim.so: + cd jim && CFLAGS="$(CFLAGS)" CC="$(CC)" ./configure $(JIM_CONFIGURE_OPTS) --shared + $(MAKE) -C jim lib + +jim/libjim.a: + cd jim && CFLAGS="$(CFLAGS)" CC="$(CC)" ./configure $(JIM_CONFIGURE_OPTS) + $(MAKE) -C jim lib + +dispatcher-script: usb_modeswitch.tcl + sed 's_!/usr/bin/tclsh_!'"$(TCL)"'_' < usb_modeswitch.tcl > usb_modeswitch_dispatcher + +dispatcher-shared: jim/libjim.so dispatcher.c usb_modeswitch.string + $(CC) dispatcher.c $(LDFLAGS) -Ljim -ljim -Ijim -o usb_modeswitch_dispatcher $(CFLAGS) + +dispatcher-static: jim/libjim.a dispatcher.c usb_modeswitch.string + $(CC) dispatcher.c $(LDFLAGS) jim/libjim.a -Ijim -o usb_modeswitch_dispatcher $(CFLAGS) + +usb_modeswitch.string: usb_modeswitch.tcl + $(HOST_TCL) make_string.tcl usb_modeswitch.tcl > $@ + +clean: + $(RM) usb_modeswitch + $(RM) usb_modeswitch_dispatcher + $(RM) usb_modeswitch.string + $(RM) jim/autosetup/jimsh0 + $(RM) jim/autosetup/jimsh0.c + +distclean: clean + -$(MAKE) -C jim distclean + +ums-clean: + $(RM) usb_modeswitch + $(RM) usb_modeswitch_dispatcher + $(RM) usb_modeswitch.string + +# If the systemd folder is present, install the service for starting the dispatcher +# If not, use the dispatcher directly from the udev rule as in previous versions + +install-common: $(PROG) usb_modeswitch_dispatcher + install -D --mode=755 usb_modeswitch $(SBINDIR)/usb_modeswitch + install -D --mode=755 usb_modeswitch.sh $(UDEVDIR)/usb_modeswitch + install -D --mode=644 usb_modeswitch.conf $(ETCDIR)/usb_modeswitch.conf + install -D --mode=644 usb_modeswitch.1 $(MANDIR)/usb_modeswitch.1 + install -D --mode=644 usb_modeswitch_dispatcher.1 $(MANDIR)/usb_modeswitch_dispatcher.1 + install -D --mode=755 usb_modeswitch_dispatcher $(SBINDIR)/usb_modeswitch_dispatcher + install -d $(DESTDIR)/var/lib/usb_modeswitch + test -d $(UPSDIR) -a -e /sbin/initctl && install --mode=644 usb-modeswitch-upstart.conf $(UPSDIR) || test 1 + test -d $(SYSDIR) -a \( -e /usr/bin/systemctl -o -e /bin/systemctl \) && install --mode=644 usb_modeswitch@.service $(SYSDIR) || test 1 + +install: install-script + +install-script: dispatcher-script install-common + +install-shared: dispatcher-shared install-common + +install-static: dispatcher-static install-common + +uninstall: + $(RM) $(SBINDIR)/usb_modeswitch + $(RM) $(SBINDIR)/usb_modeswitch_dispatcher + $(RM) $(UDEVDIR)/usb_modeswitch + $(RM) $(ETCDIR)/usb_modeswitch.conf + $(RM) $(MANDIR)/usb_modeswitch.1 + $(RM) -R $(DESTDIR)/var/lib/usb_modeswitch + $(RM) $(SYSDIR)/usb_modeswitch@.service @@ -0,0 +1,279 @@ +README for USB_ModeSwitch + +For up-to-date and more detailed information (plus a friendly forum) visit +http://www.draisberghof.de/usb_modeswitch + + +What it is +========== + +USB_ModeSwitch is - hardly surprising - a mode switching tool for controlling +USB devices with multiple "modes". Now, what does THAT mean? + +More and more USB devices have their MS Windows drivers onboard; when plugged +in for the first time they act like a flash storage and offer their driver +installation from there. +After installation (and on every consecutive plugging) the driver switches the +mode internally by sending a certain command sequence; the storage device +vanishes (in most cases) and a different device - like a USB modem - shows up. +To the host, this is like unplugging one device and then plugging annother. + +At first this feature appeared on devices with cell phone chipsets, presumably +because some of them were already able to change the mode of their USB port +from storage to communication - so why not make use of this in a modem stick? +Modem maker "Option" calls that feature "ZeroCD (TM)" since it eliminates the +need for shipping a separate driver carrier. + +In the beginning, nothing of this was documented in any form and there was +hardly any Linux/Unix support available. +On the good side, most of the known devices are working out of the box in all +modes with available Linux modules like "usb-storage" or serial USB drivers. +That leaves only the problem of the mode-switching from storage to whatever +the thing is supposed to do. + +Fortunately there are things like human smartness, USB sniffing programs and +LibUSB. The obvious way is to eavesdrop on the communication of the MS Windows +driver, to isolate the command or action that does the switching, and then re- +play the same sequence in a non-Windows system. + +In theory, this task could also be handled on the kernel driver level, but a +userspace program is much more flexible and can easily be disabled if access +to the initial mode of those devices should be desired. There has been a +principle decision by kernel developers to keep mode-switching outside of the +kernel. + +So USB_ModeSwitch has evolved to make this process easy to handle by taking the +relevant parameters from configuration files and handling all initialization +and communication business, with essential help from "libusb". + +In Linux and friends it is intended to work automatically - via udev events +and rules - and doing the mode switch without any user interaction. +However, the core C program should be as portable als libusb itself; it does not +rely on specific Linux features. +It can also be run as an interactive command line tool, which can be useful +when trying to tinker with hitherto unknown devices. + +We have already collected a wide range of information on how to mode-switch all +sorts of devices. If you run into a new one that is unknown yet, don't despair: +we can find out what you need to do! + + +How to install +============== + +If you only need the core C program, just run "make". All further steps de- +scribed below are referring to a common, fairly current Linux system where +USB_ModeSwitch is expected to do its work automatically. + +!! You need the usb-modeswitch-data package from the same source as this !! + +If you have an earlier version installed, de-installation is recommended ("make +uninstall") but not mandatory. The wrapper script location changed in 1.1.0; +old files might be orphaned but will not do any harm. + +The main prerequisite for installing from source is the development package for +"libusb". It may be called "libusb-dev" or similar in your distribution. From +usb_modeswitch 2.0.0 on, it should have an "1.x" in the name to reflect the change +to libusb-1. There are also variants around called "libusbx" if libusb-1 is not +available on your distribution. + +To install the tool set, unpack and run the install command (see below) in the +newly created directory. + +From version 1.2.0, there are three flavours of installing. The only difference +between those is the way the dispatcher is installed, but this affects the +dependencies as well: + +1. If you have the "Tcl" scripting language available on your system (packages + "tcl" or "jimsh"), use the light-weight installation: + + # make install + +2. If you are size-constrained and have the Jimsh library on your system + (package "libjim-dev"), you can have the Tcl interpreter embedded with the + dispatcher, using its shared lib: + + # make install-shared + +3. If you are size-constrained and definitely don't need a Tcl interpreter + for anything else, choose the statically embedded flavour. This will have + no further dependency as it uses the included interpreter code, which is + configured for small size: + + # make install-static + +Note that the "static"/"shared" targets are NOT referring to the usb_modeswitch +program, only to the dispatcher! +Any one of these commands will install a small posix shell script, the +dispatcher (wrapper) as script or as binary, a global config file, the core +program and a man page. + +Install the data package as well and you are set. + +NOTE: installing over (possibly outdated) distribution packages of this +program and the data collection should generally not be a problem. + + +How to use +========== + +If your device is known, you should be able to just plug it and use it. If +it doesn't work - we will find out why. + +For manual use just run "usb_modeswitch" (as root). Work with the command +line interface or with a setup file. You can use any file and give its path +with the "-c" parameter. + +The file named "device_reference.txt" that you can find on the home site of +this package is a device and configuration reference containing most known +devices; you can use it as a "database" to create your own configuration file. +It's heavily commented and should tell you what to do. It also contains a +thorough explanation of all the parameters. +However, the device list in that reference file is no longer maintained. +See the data package for the USB IDs of devices known to usb_modeswitch. + +Run "usb_modeswitch -h" to list the command line parameters. +See also the provided man page. + +Important note: Manual use is mainly intended for testing and analyzing!! +The program puts no limits on what you can send to your USB device, so I +assume it is possible to screw it up profoundly. You have been warned. + +Once your new device is switching fine you can add it to the data files to +make the process automatic. + +For this to work, add a rule entry to the rules file to let udev run +usb_modeswitch as soon as the default IDs are found (when the device is +plugged). If you look into the rules file you will see immediately how +your new entry should look like. +The location is: +/lib/udev/rules.d/40-usb_modeswitch.rules + +Then add your new config file to the folder +"/etc/usb_modeswitch.d" (only for custom config files!). +And don't forget to report your success !! + +Again, remember that the rules file and the default device config folder +(/usr/share/usb_modeswitch) are installed by the usb_modeswitch data package. + + +########## +Important: libusb programs - like this tool - want to be run with administrative +privileges (as root or with sudo)! +########## + + + +Known working hardware, Troubleshooting +======================================= + +Please go to the homepage (see link at the top). Read carefully. +For support questions use ONLY public posts in the forum. + +ONCE AGAIN: The USB IDs of devices are the only halfway reliable indicator to +determine if they are known/supported. Brand and model names are often not +helpful, due to rebranding and fimrware variations. +See the data package, folder "usb_modeswitch.d" for known IDs. + + + +Contribute +========== + +USB_ModeSwitch comes quite handy for experimenting with your own hardware if +not supported yet. You could try this approach: + +Note the device's vendor and product ID from /proc/bus/usb/devices (or from the +output of lsusb); the assigned driver is usually "usb-storage". Then try spying +on the USB communication to the device with the same ID inside MS Windoze. +Nowadays the standard tool for this is Wireshark/USBPcap. + +PLEASE post any improvements, new device information and/or bug reports to the +forum (see above) or send it to the author (see below)! + + +Whodunit +======== + +Copyright 2007 - 2017 Josua Dietze (for non-support notifications write a personal +message through the forum to "Josh"; everything else only in a forum thread) + + !!! NO SUPPORT QUESTIONS VIA E-MAIL, use the forum !!! + +Major contributions: + +Command line parsing and other essential contributions: + Joakim Wennergren + +TargetClass parameter implementation to support new Option devices/firmware: + Paul Hardwick (http://www.pharscape.org) + +Created with initial help from: + "usbsnoop2libusb.pl" by Timo Lindfors + (http://iki.fi/lindi/usb/usbsnoop2libusb.pl) + +Config file parsing code borrowed from: + Guillaume Dargaud (http://www.gdargaud.net/Hack/SourceCode.html) + +Hexstr2bin function borrowed from: + Jouni Malinen (http://hostap.epitest.fi/wpa_supplicant, from "common.c") + +Indispensable help with device research and compilation: + Lars Melin + +Code, fixes and ideas contributed by: + Aki Makkonen + Denis Sutter + Lucas Benedicic + Roman Laube + Luigi Iotti + Vincent Teoh + Tommy Cheng + Daniel Cooper + Andrew Bird + Yaroslav Levandovskiy + Sakis Dimopoulos + Steven Fernandez + Christophe Fergeau + Nils Radtke + Filip Aben + Amit Mendapara + Roman S. Samarev + Chi-Hang Long + Andrey Tikhomirov + Daniel Mende + Nicholas Carrier + Adam Goode + Leonid Lisovskiy + Vladislav Grishenko + Lubomir Rintel + Frank Schmirler + +Device information contributors are named in the "device_reference.txt" file. + +JimTcl is currently maintained by Steve Bennett; see README in subfolder + for details. It is released under a FreeBSD-style license. + Visit http://jim.tcl.tk/ + + + +Legal +===== + +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: + +http://www.gnu.org/licenses/gpl.txt + +Or find it as the file COPYING in this folder. + + + + +Last revised: 2017-12-31, Josua Dietze diff --git a/dispatcher.c b/dispatcher.c new file mode 100644 index 0000000..f30132f --- /dev/null +++ b/dispatcher.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011-2017 Josua Dietze, usb_modeswitch version 2.5.2 + * Contains code under + * Copyright (c) 2010 Wojciech A. Koszek <wkoszek@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <jim.h> + +/* RAW is defined to the complete Tcl code in that file: */ +#include "usb_modeswitch.string" + +#define MAX_ARGSIZE 1024 + +int main(int argc, char **argv) +{ + int i, retval; + char arg[MAX_ARGSIZE]; + char arglist[MAX_ARGSIZE]; + + Jim_Interp *interp; + + interp = NULL; + arglist[0] = '\0'; + + for (i=1; i<argc; i++) { + if (strlen(argv[i]) > MAX_ARGSIZE-4) { + printf("Argument #%d extends maximum size, skip it\n", i); + continue; + } + if ( (strlen(arglist) + strlen(argv[i])) > MAX_ARGSIZE-4) { + printf("Argument #%d would extend maximum list size, skip it\n", i); + continue; + } + sprintf(arg,"{%s} ",argv[i]); + strncat(arglist,arg,MAX_ARGSIZE-1); + } + + char code[sizeof(RAW) + sizeof(arglist) + 28]; + sprintf(code, "set argv {%s}\nset argc %d\n", arglist, argc-1); + strncat(code, RAW, sizeof(RAW)); + + /* Create Jim instance */ + interp = Jim_CreateInterp(); + assert(interp != NULL && "Could not create interpreter!"); + + /* Register base commands, actually implementing Tcl */ + Jim_RegisterCoreCommands(interp); + + /* Initialize any static extensions */ + Jim_InitStaticExtensions(interp); + + /* Evaluate the script that's now in "code" */ + retval = Jim_Eval(interp, code); + if (retval < 0) + printf("Evaluation returned error %d\n", retval); + + /* Free the interpreter */ + Jim_FreeInterp(interp); + return (EXIT_SUCCESS); +} diff --git a/make_string.tcl b/make_string.tcl new file mode 100755 index 0000000..5a1b5ff --- /dev/null +++ b/make_string.tcl @@ -0,0 +1,31 @@ +#!/usr/bin/env tclsh + +# (c) Josua Dietze 2012 +# +# Usage: make_string.tcl source.tcl >jim-source.c + +# Converts a Tcl source file into C source suitable +# for using as an embedded script. + +set source [lindex $argv 0] + +if {![string match *.tcl $source]} { + error "Source $source is not a .tcl file" +} + +# Read the Tcl source and convert to C macro +set sourcelines {} +set f [open $source] +while {[gets $f buf] >= 0} { + # Remove comment lines + regsub {^[ \t]*#.*$} $buf "" buf + # Remove leading whitespaces + set buf [string trimleft $buf] + # Escape quotes and backlashes + set buf [string map [list \\ \\\\ \" \\"] $buf] + if [string length $buf] { + lappend sourcelines "$buf\\n" + } +} +close $f +puts "#define RAW \"[join $sourcelines ""]\"" diff --git a/usb-modeswitch-upstart.conf b/usb-modeswitch-upstart.conf new file mode 100644 index 0000000..0d82b69 --- /dev/null +++ b/usb-modeswitch-upstart.conf @@ -0,0 +1,5 @@ +start on usb-modeswitch-upstart +task +script + exec /usr/sbin/usb_modeswitch_dispatcher --switch-mode $UMS_PARAM +end script diff --git a/usb_modeswitch.1 b/usb_modeswitch.1 new file mode 100644 index 0000000..d91135f --- /dev/null +++ b/usb_modeswitch.1 @@ -0,0 +1,175 @@ +.TH "USB_MODESWITCH" "1" +.SH "NAME" +usb_modeswitch - control the mode of 'multi-state' USB devices +.SH "SYNOPSIS" +.PP +\fBusb_modeswitch\fR [\fB\-heWQDIvpVPmM23rwKdHSOBGTNALnsRiuagft\fP] [\fB\-c \fIfilename\fP] +.SH "DESCRIPTION" +.PP +Several new USB devices have their proprietary Windows drivers onboard, +most of them WWAN and WLAN dongles. When plugged in for the first time, +they act like a flash storage and start installing the Windows driver from +there. If the driver is installed, it makes the storage device disappear +and a new device, mainly composite (e.g. with modem ports), shows up. +.PP +On Linux, in most cases the drivers are available as kernel modules, +such as "usbserial" or "option". However, the device initially binds to +"usb-storage" by default. \fBusb_modeswitch\fR can then send a provided bulk +message (most likely a mass storage command) to the device; this message +has to be determined by analyzing the actions of the Windows driver. +.PP +In some cases, USB control commands are used for switching. These cases are +handled by custom functions, and no bulk message needs to be provided. +.PP +Usually, the program is distributed with a set of configurations for many +known devices, which allows a fully automatic handling of a device upon +insertion, made possible by combining usb_modeswitch with the wrapper script +\fBusb_modeswitch_dispatcher\fR which is launched by the udev daemon. This +requires a Linux-flavoured system though. +.PP +Note that \fBusb_modeswitch\fR itself has no specific Linux dependencies. + +.SH "OPTIONS" +.PP +This program follows the usual GNU command line syntax, +with long options starting with two dashes ('--'). A summary of +options is included below. +.IP "\fB-h\fP \fB\-\-help\fP " 10 +Show summary of options. +.IP "\fB-e\fP \fB\-\-version\fP " 10 +Print version information and exit +.IP "\fB-v\fP \fB\-\-default-vendor NUM\fP " 10 +Vendor ID to look for (mandatory), usually given as hex number (example: 0x12d1). +Each USB device is identified by a number +officialy assigned to the vendor by the USB association and a number for the +respective model (product ID) chosen by the vendor +.IP "\fB-p\fP \fB\-\-default-product NUM\fP " 10 +Product ID to look for (mandatory) +.IP "\fB-V\fP \fB\-\-target-vendor NUM\fP " 10 +Target vendor ID. When given will be searched for and detected initially +for information purposes. If success checking (option \-s) is active, +providing target IDs (vendor/product) or target class is recommended +.IP "\fB-j\fP \fB\-\-find-mbim\fP " 10 +Return configuration number with MBIM interface and exit. +.IP "\fB-P\fP \fB\-\-target-product NUM\fP " 10 +Target product ID +.IP "\fB-b\fP \fB\-\-bus-num NUM\fP " 10 +.IP "\fB-g\fP \fB\-\-device-num NUM\fP " 10 +If bus and device number are provided, the handling of a specific device on +a specific USB port is guaranteed, in contrast to using only the USB ID. This +is important if there are multiple similar devices on a system +.IP "\fB-C\fP \fB\-\-target-class NUM\fP " 10 +Target Device Class according to the USB specification. Some devices keep +their original vendor/product ID after successful switching. To prevent +them from being treated again, the device class can be checked. +For unswitched devices it is always 8 (storage class), for switched +modems it is often 0xff (vendor specific). In composite modes, +the class of the first interface is watched +.IP "\fB-m\fP \fB\-\-message-endpoint NUM\fP " 10 +A specific endpoint to use for data transfers. Only for testing purposes; usually +endpoints are determined from the device attributes +.IP "\fB-M\fP \fB\-\-message-content STRING\fP " 10 +A bulk message to send as a switching command. Provided as a hexadecimal string +.IP "\fB-2, -3\fP \fB\-\-message-content2, \-\-message-content3 STRING\fP " 10 +Additional bulk messages to send as switching commands. Provided as hexadecimal strings. +When used with mass storage commands, setting \fB\-\-need-response\fR is +strongly advised to comply with specifications and to avoid likely errors +.IP "\fB-w\fP \fB\-\-release-delay NUM\fP " 10 +After issuing all bulk messages, wait for NUM milliseconds before releasing the interface. +Required for some modems on older systems (especially after an EJECT message) +.IP "\fB-n\fP \fB\-\-need-response\fP " 10 +Obsolete. CSW is always attempted to being read after mass storage transfers. No downside +.IP "\fB-r\fP \fB\-\-response-endpoint NUM\fP " 10 +Try to read the response to a storage command from there. Only for testing purposes; +usually endpoints are determined from the device attributes +.IP "\fB-K\fP \fB\-\-std-eject\fP " 10 +Apply the standard SCSI sequence of "Allow Medium Removal" and +"Eject". Implies \fB-n\fP. One 'Message' can be added with \fB-M\fP +that will be transmitted after the eject sequence. Used by many modems +.IP "\fB-d\fP \fB\-\-detach-only\fP " 10 +Just detach the current driver. This is sufficient for some early +devices to switch successfully. Otherwise this feature can +be used as a 'scalpel' for special cases, like separating the +driver from individual interfaces +.IP "\fB-H\fP \fB\-\-huawei-mode\fP " 10 +Send a special control message used by older Huawei devices +.IP "\fB-J\fP \fB\-\-huawei-new-mode\fP " 10 +Send a specific bulk message used by all newer Huawei devices +.IP "\fB-X\fP \fB\-\-huawei-alt-mode\fP " 10 +Send an alternative bulk message to Huawei devices +.IP "\fB-S\fP \fB\-\-sierra-mode\fP " 10 +Send a special control message used by Sierra devices +.IP "\fB-G\fP \fB\-\-gct-mode\fP " 10 +Send a special control message used by GCT chipsets +.IP "\fB-T\fP \fB\-\-kobil-mode\fP " 10 +Send a special control message used by Kobil devices +.IP "\fB-N\fP \fB\-\-sequans-mode\fP " 10 +Send a special control message used by Sequans chipset +.IP "\fB-A\fP \fB\-\-mobileaction-mode\fP " 10 +Send a special control message used by the MobileAction device +.IP "\fB-B\fP \fB\-\-qisda-mode\fP " 10 +Send a special control message used by Qisda devices +.IP "\fB-E\fP \fB\-\-quanta-mode\fP " 10 +Send a special control message used by Quanta devices +.IP "\fB-F\fP \fB\-\-pantech-mode NUM\fP " 10 +Send a special control message used by Pantech devices. +Value NUM will be used in control message as 'wValue' +.IP "\fB-Z\fP \fB\-\-blackberry-mode\fP " 10 +Send a special control message used by some newer Blackberry devices +.IP "\fB-S\fP \fB\-\-option-mode\fP " 10 +Send a special control message used by all Option devices +.IP "\fB-O\fP \fB\-\-sony-mode\fP " 10 +Apply a special sequence used by Sony Ericsson devices. Implies option \--check-success +.IP "\fB-L\fP \fB\-\-cisco-mode\fP " 10 +Send a sequence of bulk messages used by Cisco devices +.IP "\fB-R\fP \fB\-\-reset-usb\fP " 10 +Send a USB reset command to the device. Can be combined with any switching +method or stand alone. It is always done as the last step of all device +interactions. +Few devices need it to complete the switching; apart from that it may be +useful during testing +.IP "\fB-c\fP \fB\-\-config-file FILENAME\fP " 10 +Use a specific config file. If any ID or switching options are given as +command line parameters, this option is ignored. +In that case all mandatory parameters have to be provided on +the command line +.IP "\fB-f\fP \fB\-\-long-config STRING\fP " 10 +Provide device details in config file syntax as a multiline string +on the command line +.IP "\fB-t\fP \fB\-\-stdinput\fP " 10 +Read the device details in config file syntax from standard input, e.g. redirected from +a command pipe (multiline text) +.IP "\fB-Q\fP \fB\-\-quiet\fP " 10 +Don't show progress or error messages +.IP "\fB-W\fP \fB\-\-verbose\fP " 10 +Print all settings before running and show libusb debug messages +.IP "\fB-D\fP \fB\-\-sysmode\fP " 10 +Changes the behaviour of the program slightly. A success message including the +effective target device ID is put out and a syslog notice is issued. Mainly for +integration with a wrapper script +.IP "\fB-s\fP \fB\-\-check-success NUM\fP " 10 +After switching, keep checking for the result up to max. NUM seconds. If target IDs +or target class were provided, their appearance indicates certain success. Otherwise +the disconnection of the original device is rated as likely proof +.IP "\fB-I\fP \fB\-\-inquire\fP " 10 +Obsolete. Formerly obtained SCSI attributes, now ignored +.IP "\fB-i\fP \fB\-\-interface NUM\fP " 10 +Select initial USB interface (default: 0). Only for testing purposes +.IP "\fB-u\fP \fB\-\-configuration NUM\fP " 10 +Select USB configuration (applied after any other possible switching actions) +.IP "\fB-a\fP \fB\-\-altsetting NUM\fP " 10 +Select alternative USB interface setting (applied after switching). Mainly +for testing +.SH "AUTHOR" +.PP +This manual page was originally written by Didier Raboud (didier@raboud.com) for +the \fBDebian\fP system. Additions made by Josua Dietze. Permission is +granted to copy, distribute and/or modify this document under +the terms of the GNU General Public License, Version 2 or any +later version published by the Free Software Foundation. + +.PP +The complete text of the current GNU General Public +License can be found in http://www.gnu.org/licenses/gpl.txt + +.\" last edited 2017-08-06 for version 2.5.1 diff --git a/usb_modeswitch.c b/usb_modeswitch.c new file mode 100644 index 0000000..3239946 --- /dev/null +++ b/usb_modeswitch.c @@ -0,0 +1,2115 @@ +/* + Mode switching tool for controlling mode of 'multi-state' USB devices + Version 2.5.2, 2017/12/31 + + Copyright (C) 2007 - 2017 Josua Dietze (mail to "usb_admin" at the domain + of the home page; or write a personal message through the forum to "Josh". + NO SUPPORT VIA E-MAIL - please use the forum for that) + + Major contributions: + + Command line parsing, decent usage/config output/handling, bugfixes and advanced + options added by: + Joakim Wennergren + + TargetClass parameter implementation to support new Option devices/firmware: + Paul Hardwick (http://www.pharscape.org) + + Created with initial help from: + "usbsnoop2libusb.pl" by Timo Lindfors (http://iki.fi/lindi/usb/usbsnoop2libusb.pl) + + Config file parsing code borrowed from: + Guillaume Dargaud (http://www.gdargaud.net/Hack/SourceCode.html) + + Hexstr2bin function borrowed from: + Jouni Malinen (http://hostap.epitest.fi/wpa_supplicant, from "common.c") + + Other contributions: see README + + Device information contributors are named in the "device_reference.txt" file. See + homepage. + + 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: + + http://www.gnu.org/licenses/gpl.txt + +*/ + +/* Recommended tab size: 4 */ + +#define VERSION "2.5.2" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> +#include <getopt.h> +#include <syslog.h> +#include <unistd.h> + +#include "usb_modeswitch.h" + + +// Little helpers + +int usb_bulk_io(struct libusb_device_handle *handle, int ep, unsigned char *bytes, + int size, int timeout) +{ + int actual_length; + int r; +// usbi_dbg("endpoint %x size %d timeout %d", ep, size, timeout); + r = libusb_bulk_transfer(handle, ep & 0xff, bytes, size, + &actual_length, timeout); + + /* if we timed out but did transfer some data, report as successful short + * read. FIXME: is this how libusb-0.1 works? */ + if (r == 0 || (r == LIBUSB_ERROR_TIMEOUT && actual_length > 0)) + return actual_length; + + return r; +} + + +static int usb_interrupt_io(libusb_device_handle *handle, int ep, unsigned char *bytes, + int size, int timeout) +{ + int actual_length; + int r; +// usbi_dbg("endpoint %x size %d timeout %d", ep, size, timeout); + r = libusb_interrupt_transfer(handle, ep & 0xff, bytes, size, + &actual_length, timeout); + + /* if we timed out but did transfer some data, report as successful short + * read. FIXME: is this how libusb-0.1 works? */ + if (r == 0 || (r == LIBUSB_ERROR_TIMEOUT && actual_length > 0)) + return actual_length; + + return (r); +} + + +#define LINE_DIM 1024 +#define MSG_DIM 11 +#define MAXLINES 50 +#define BUF_SIZE 4096 +#define DESCR_MAX 129 + +#define SEARCH_DEFAULT 0 +#define SEARCH_TARGET 1 +#define SEARCH_BUSDEV 2 + +#define SWITCH_CONFIG_MAXTRIES 5 + +#define SHOW_PROGRESS if (show_progress) fprintf + +char *TempPP=NULL; + +static struct libusb_context *ctx = NULL; +static struct libusb_device *dev = NULL; +static struct libusb_device_handle *devh = NULL; +static struct libusb_config_descriptor *active_config = NULL; + +int DefaultVendor=0, DefaultProduct=0, TargetVendor=0, TargetProduct=-1, TargetClass=0; +int MessageEndpoint=0, ResponseEndpoint=0, ReleaseDelay=0; +int targetDeviceCount=0, searchMode; +int devnum=-1, busnum=-1; + +unsigned int ModeMap = 0; +#define DETACHONLY_MODE 0x00000001 +#define HUAWEI_MODE 0x00000002 +#define SIERRA_MODE 0x00000004 +#define SONY_MODE 0x00000008 +#define GCT_MODE 0x00000010 +#define KOBIL_MODE 0x00000020 +#define SEQUANS_MODE 0x00000040 +#define MOBILEACTION_MODE 0x00000080 +#define CISCO_MODE 0x00000100 +#define QISDA_MODE 0x00000200 +#define QUANTA_MODE 0x00000400 +#define BLACKBERRY_MODE 0x00000800 +#define PANTECH_MODE 0x00001000 +#define HUAWEINEW_MODE 0x00002000 +#define OPTION_MODE 0x00004000 +#define HUAWEIALT_MODE 0x00008000 + + +int PantechMode=0; +char verbose=0, show_progress=1, ResetUSB=0, CheckSuccess=0, config_read=0; +char NoDriverLoading=0, sysmode=0, mbim=0; +char StandardEject=0; + +char MessageContent[LINE_DIM]; +char MessageContent2[LINE_DIM]; +char MessageContent3[LINE_DIM]; +char TargetProductList[LINE_DIM]; +char DefaultProductList[5]; +unsigned char ByteString[LINE_DIM/2]; +unsigned char buffer[BUF_SIZE]; +char **Messages = NULL; + +FILE *output; + + +/* Settable interface, altsetting (for debugging mostly) and configuration */ +int Interface = -1, Configuration = 0, AltSetting = -1; + + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'e'}, + {"default-vendor", required_argument, 0, 'v'}, + {"default-product", required_argument, 0, 'p'}, + {"target-vendor", required_argument, 0, 'V'}, + {"target-product", required_argument, 0, 'P'}, + {"target-class", required_argument, 0, 'C'}, + {"message-endpoint", required_argument, 0, 'm'}, + {"message-content", required_argument, 0, 'M'}, + {"message-content2", required_argument, 0, '2'}, + {"message-content3", required_argument, 0, '3'}, + {"release-delay", required_argument, 0, 'w'}, + {"response-endpoint", required_argument, 0, 'r'}, + {"bus-num", required_argument, 0, 'b'}, + {"device-num", required_argument, 0, 'g'}, + {"detach-only", no_argument, 0, 'd'}, + {"huawei-mode", no_argument, 0, 'H'}, + {"huawei-new-mode", no_argument, 0, 'J'}, + {"huawei-alt-mode", no_argument, 0, 'X'}, + {"sierra-mode", no_argument, 0, 'S'}, + {"sony-mode", no_argument, 0, 'O'}, + {"qisda-mode", no_argument, 0, 'B'}, + {"quanta-mode", no_argument, 0, 'E'}, + {"kobil-mode", no_argument, 0, 'T'}, + {"gct-mode", no_argument, 0, 'G'}, + {"sequans-mode", no_argument, 0, 'N'}, + {"mobileaction-mode", no_argument, 0, 'A'}, + {"cisco-mode", no_argument, 0, 'L'}, + {"blackberry-mode", no_argument, 0, 'Z'}, + {"option-mode", no_argument, 0, 'U'}, + {"pantech-mode", required_argument, 0, 'F'}, + {"std-eject", no_argument, 0, 'K'}, + {"need-response", no_argument, 0, 'n'}, + {"reset-usb", no_argument, 0, 'R'}, + {"config-file", required_argument, 0, 'c'}, + {"verbose", no_argument, 0, 'W'}, + {"quiet", no_argument, 0, 'Q'}, + {"sysmode", no_argument, 0, 'D'}, + {"inquire", no_argument, 0, 'I'}, + {"stdinput", no_argument, 0, 't'}, + {"find-mbim", no_argument, 0, 'j'}, + {"long-config", required_argument, 0, 'f'}, + {"check-success", required_argument, 0, 's'}, + {"interface", required_argument, 0, 'i'}, + {"configuration", required_argument, 0, 'u'}, + {"altsetting", required_argument, 0, 'a'}, + {0, 0, 0, 0} +}; + + +void readConfigFile(const char *configFilename) +{ + ParseParamHex(configFilename, TargetVendor); + ParseParamHex(configFilename, TargetProduct); + ParseParamString(configFilename, TargetProductList); + ParseParamHex(configFilename, TargetClass); + ParseParamHex(configFilename, DefaultVendor); + ParseParamHex(configFilename, DefaultProduct); + ParseParamBoolMap(configFilename, DetachStorageOnly, ModeMap, DETACHONLY_MODE); + ParseParamBoolMap(configFilename, HuaweiMode, ModeMap, HUAWEI_MODE); + ParseParamBoolMap(configFilename, HuaweiNewMode, ModeMap, HUAWEINEW_MODE); + ParseParamBoolMap(configFilename, HuaweiAltMode, ModeMap, HUAWEIALT_MODE); + ParseParamBoolMap(configFilename, SierraMode, ModeMap, SIERRA_MODE); + ParseParamBoolMap(configFilename, SonyMode, ModeMap, SONY_MODE); + ParseParamBoolMap(configFilename, GCTMode, ModeMap, GCT_MODE); + ParseParamBoolMap(configFilename, KobilMode, ModeMap, KOBIL_MODE); + ParseParamBoolMap(configFilename, SequansMode, ModeMap, SEQUANS_MODE); + ParseParamBoolMap(configFilename, MobileActionMode, ModeMap, MOBILEACTION_MODE); + ParseParamBoolMap(configFilename, CiscoMode, ModeMap, CISCO_MODE); + ParseParamBoolMap(configFilename, QisdaMode, ModeMap, QISDA_MODE); + ParseParamBoolMap(configFilename, QuantaMode, ModeMap, QUANTA_MODE); + ParseParamBoolMap(configFilename, OptionMode, ModeMap, OPTION_MODE); + ParseParamBoolMap(configFilename, BlackberryMode, ModeMap, BLACKBERRY_MODE); + ParseParamInt(configFilename, PantechMode); + if (PantechMode) + ModeMap |= PANTECH_MODE; + ParseParamBool(configFilename, StandardEject); + ParseParamBool(configFilename, NoDriverLoading); + ParseParamHex(configFilename, MessageEndpoint); + ParseParamString(configFilename, MessageContent); + ParseParamString(configFilename, MessageContent2); + ParseParamString(configFilename, MessageContent3); + ParseParamInt(configFilename, ReleaseDelay); + ParseParamHex(configFilename, ResponseEndpoint); + ParseParamHex(configFilename, ResetUSB); + ParseParamInt(configFilename, CheckSuccess); + ParseParamHex(configFilename, Interface); + ParseParamHex(configFilename, Configuration); + ParseParamHex(configFilename, AltSetting); + + /* TargetProductList has priority over TargetProduct */ + if (TargetProduct != -1 && TargetProductList[0] != '\0') { + TargetProduct = -1; + SHOW_PROGRESS(output,"Warning: TargetProductList overrides TargetProduct!\n"); + } + + config_read = 1; +} + + +void printConfig() +{ + if ( DefaultVendor ) + fprintf (output,"DefaultVendor= 0x%04x\n", DefaultVendor); + if ( DefaultProduct ) + fprintf (output,"DefaultProduct= 0x%04x\n", DefaultProduct); + if ( TargetVendor ) + fprintf (output,"TargetVendor= 0x%04x\n", TargetVendor); + if ( TargetProduct > -1 ) + fprintf (output,"TargetProduct= 0x%04x\n", TargetProduct); + if ( TargetClass ) + fprintf (output,"TargetClass= 0x%02x\n", TargetClass); + if ( strlen(TargetProductList) ) + fprintf (output,"TargetProductList=\"%s\"\n", TargetProductList); + if (StandardEject) + fprintf (output,"\nStandardEject=1\n"); + if (ModeMap & DETACHONLY_MODE) + fprintf (output,"\nDetachStorageOnly=1\n"); + if (ModeMap & HUAWEI_MODE) + fprintf (output,"HuaweiMode=1\n"); + if (ModeMap & HUAWEINEW_MODE) + fprintf (output,"HuaweiNewMode=1\n"); + if (ModeMap & HUAWEIALT_MODE) + fprintf (output,"HuaweiAltMode=1\n"); + if (ModeMap & SIERRA_MODE) + fprintf (output,"SierraMode=1\n"); + if (ModeMap & SONY_MODE) + fprintf (output,"SonyMode=1\n"); + if (ModeMap & QISDA_MODE) + fprintf (output,"QisdaMode=1\n"); + if (ModeMap & QUANTA_MODE) + fprintf (output,"QuantaMode=1\n"); + if (ModeMap & GCT_MODE) + fprintf (output,"GCTMode=1\n"); + if (ModeMap & KOBIL_MODE) + fprintf (output,"KobilMode=1\n"); + if (ModeMap & SEQUANS_MODE) + fprintf (output,"SequansMode=1\n"); + if (ModeMap & MOBILEACTION_MODE) + fprintf (output,"MobileActionMode=1\n"); + if (ModeMap & CISCO_MODE) + fprintf (output,"CiscoMode=1\n"); + if (ModeMap & BLACKBERRY_MODE) + fprintf (output,"BlackberryMode=1\n"); + if (ModeMap & OPTION_MODE) + fprintf (output,"OptionMode=1\n"); + if (ModeMap & PANTECH_MODE) + fprintf (output,"PantechMode=1\n"); + if ( MessageEndpoint ) + fprintf (output,"MessageEndpoint=0x%02x\n", MessageEndpoint); + if ( strlen(MessageContent) ) + fprintf (output,"MessageContent=\"%s\"\n", MessageContent); + if ( strlen(MessageContent2) ) + fprintf (output,"MessageContent2=\"%s\"\n", MessageContent2); + if ( strlen(MessageContent3) ) + fprintf (output,"MessageContent3=\"%s\"\n", MessageContent3); + if ( ResponseEndpoint ) + fprintf (output,"ResponseEndpoint=0x%02x\n", ResponseEndpoint); + if ( Interface > -1 ) + fprintf (output,"Interface=0x%02x\n", Interface); + if ( Configuration > 0 ) + fprintf (output,"Configuration=0x%02x\n", Configuration); + if ( AltSetting > -1 ) + fprintf (output,"AltSetting=0x%02x\n", AltSetting); + if ( CheckSuccess ) + fprintf (output,"Success check enabled, max. wait time %d seconds\n", CheckSuccess); + if ( sysmode ) + fprintf (output,"System integration mode enabled\n"); +} + + +int readArguments(int argc, char **argv) +{ + int c, option_index = 0, count=0; + char *longConfig = NULL; + if (argc==1) + { + printHelp(); + printVersion(); + exit(1); + } + + while (1) + { + c = getopt_long (argc, argv, "hejWQDndKHJSOBEGTNALZUXF:RItv:p:V:P:C:m:M:2:3:w:r:c:i:u:a:s:f:b:g:", + long_options, &option_index); + + /* Detect the end of the options. */ + if (c == -1) + break; + count++; + switch (c) + { + case 'R': ResetUSB = 1; break; + case 'v': DefaultVendor = strtol(optarg, NULL, 16); break; + case 'p': DefaultProduct = strtol(optarg, NULL, 16); break; + case 'V': TargetVendor = strtol(optarg, NULL, 16); break; + case 'P': TargetProduct = strtol(optarg, NULL, 16); break; + case 'C': TargetClass = strtol(optarg, NULL, 16); break; + case 'm': MessageEndpoint = strtol(optarg, NULL, 16); break; + case 'M': strncpy(MessageContent, optarg, LINE_DIM); break; + case '2': strncpy(MessageContent2, optarg, LINE_DIM); break; + case '3': strncpy(MessageContent3, optarg, LINE_DIM); break; + case 'w': ReleaseDelay = strtol(optarg, NULL, 10); break; + case 'n': break; + case 'r': ResponseEndpoint = strtol(optarg, NULL, 16); break; + case 'K': StandardEject = 1; break; + case 'd': ModeMap = ModeMap + DETACHONLY_MODE; break; + case 'H': ModeMap = ModeMap + HUAWEI_MODE; break; + case 'J': ModeMap = ModeMap + HUAWEINEW_MODE; break; + case 'X': ModeMap = ModeMap + HUAWEIALT_MODE; break; + case 'S': ModeMap = ModeMap + SIERRA_MODE; break; + case 'O': ModeMap = ModeMap + SONY_MODE; break;; break; + case 'B': ModeMap = ModeMap + QISDA_MODE; break; + case 'E': ModeMap = ModeMap + QUANTA_MODE; break; + case 'G': ModeMap = ModeMap + GCT_MODE; break; + case 'T': ModeMap = ModeMap + KOBIL_MODE; break; + case 'N': ModeMap = ModeMap + SEQUANS_MODE; break; + case 'A': ModeMap = ModeMap + MOBILEACTION_MODE; break; + case 'L': ModeMap = ModeMap + CISCO_MODE; break; + case 'Z': ModeMap = ModeMap + BLACKBERRY_MODE; break; + case 'U': ModeMap = ModeMap + OPTION_MODE; break; + case 'F': ModeMap = ModeMap + PANTECH_MODE; + PantechMode = strtol(optarg, NULL, 10); break; + case 'c': readConfigFile(optarg); break; + case 't': readConfigFile("stdin"); break; + case 'W': verbose = 1; show_progress = 1; count--; break; + case 'Q': show_progress = 0; verbose = 0; count--; break; + case 'D': sysmode = 1; count--; break; + case 's': CheckSuccess = strtol(optarg, NULL, 10); count--; break; + case 'I': break; + case 'b': busnum = strtol(optarg, NULL, 10); break; + case 'g': devnum = strtol(optarg, NULL, 10); break; + + case 'i': Interface = strtol(optarg, NULL, 16); break; + case 'u': Configuration = strtol(optarg, NULL, 16); break; + case 'a': AltSetting = strtol(optarg, NULL, 16); break; + case 'j': mbim = 1; break; + + case 'f': + longConfig = malloc(strlen(optarg)+5); + strcpy(longConfig,"##\n"); + strcat(longConfig,optarg); + strcat(longConfig,"\n"); + readConfigFile(longConfig); + free(longConfig); + break; + + case 'e': + printVersion(); + exit(0); + break; + case 'h': + printVersion(); + printHelp(); + exit(0); + break; + + default: /* Unsupported - error message has already been printed */ + fprintf (output,"\n"); + printHelp(); + exit(1); + } + } + return count; +} + + +int main(int argc, char **argv) +{ + int ret=0, numDefaults=0, sonySuccess=0, i; + int defaultClass=0, interfaceClass=0, currentConfigVal=0; + struct libusb_device_descriptor descriptor; + enum libusb_error libusbError; + + /* Make sure we have empty strings even if not set by config */ + TargetProductList[0] = '\0'; + MessageContent[0] = '\0'; + MessageContent2[0] = '\0'; + MessageContent3[0] = '\0'; + DefaultProductList[0] = '\0'; + + /* Useful for debugging during boot */ +// output=fopen("/dev/console", "w"); + output=stdout; + + signal(SIGTERM, release_usb_device); + + /* + * Parameter parsing, USB preparation/diagnosis, plausibility checks + */ + + /* Check command arguments, use params instead of config file when given */ + switch (readArguments(argc, argv)) { + case 0: /* no argument or -W, -q or -s */ + break; + default: /* one or more arguments except -W, -q or -s */ + if (!config_read) /* if arguments contain -c, the config file was already processed */ + if (verbose) fprintf(output,"Take all parameters from the command line\n\n"); + } + + if (verbose) { + printVersion(); + printConfig(); + fprintf(output,"\n"); + } + + /* Some sanity checks. The default IDs are mandatory */ + if (!(DefaultVendor && DefaultProduct)) { + SHOW_PROGRESS(output,"No default vendor/product ID given. Abort\n\n"); + exit(1); + } + + if (strlen(MessageContent)) { + if (strlen(MessageContent) % 2 != 0) { + fprintf(stderr, "MessageContent hex string has uneven length. Abort\n\n"); + exit(1); + } + if ( hexstr2bin(MessageContent, ByteString, strlen(MessageContent)/2) == -1) { + fprintf(stderr, "MessageContent %s\n is not a hex string. Abort\n\n", + MessageContent); + + exit(1); + } + } + + if (devnum == -1) { + searchMode = SEARCH_DEFAULT; + } else { + SHOW_PROGRESS(output,"Use given bus/device number: %03d/%03d ...\n", busnum, devnum); + searchMode = SEARCH_BUSDEV; + } + + if (show_progress) + if (CheckSuccess && !(TargetVendor || TargetProduct > -1 || TargetProductList[0] != '\0') + && !TargetClass) + + fprintf(output,"Note: No target parameter given; success check limited\n"); + + if (TargetProduct > -1 && TargetProductList[0] == '\0') { + sprintf(TargetProductList,"%04x",TargetProduct); + TargetProduct = -1; + } + + /* libusb initialization */ + if ((libusbError = libusb_init(&ctx)) != LIBUSB_SUCCESS) { + fprintf(stderr, "Error: Failed to initialize libusb. %s (%d)\n\n", + libusb_error_name(libusbError), libusbError); + exit(1); + } + + if (verbose) + libusb_set_debug(ctx, 3); + + if (mbim) { + printf("%d\n", findMBIMConfig(DefaultVendor, DefaultProduct, searchMode) ); + exit(0); + } + + /* Count existing target devices, remember for success check */ + if (searchMode != SEARCH_BUSDEV && (TargetVendor || TargetClass)) { + SHOW_PROGRESS(output,"Look for target devices ...\n"); + search_devices(&targetDeviceCount, TargetVendor, TargetProductList, TargetClass, 0, + SEARCH_TARGET); + + if (targetDeviceCount) { + SHOW_PROGRESS(output," Found devices in target mode or class (%d)\n", targetDeviceCount); + } else + SHOW_PROGRESS(output," No devices in target mode or class found\n"); + } + + /* Count default devices, get the last one found */ + SHOW_PROGRESS(output,"Look for default devices ...\n"); + + sprintf(DefaultProductList,"%04x",DefaultProduct); + dev = search_devices(&numDefaults, DefaultVendor, DefaultProductList, TargetClass, + Configuration, searchMode); + + if (numDefaults) { + SHOW_PROGRESS(output," Found devices in default mode (%d)\n", numDefaults); + } else { + SHOW_PROGRESS(output," No devices in default mode found. Nothing to do. Bye!\n\n"); + close_all(); + exit(0); + } + + if (dev == NULL) { + SHOW_PROGRESS(output," No bus/device match. Is device connected? Abort\n\n"); + close_all(); + exit(0); + } else { + if (devnum == -1) { + devnum = libusb_get_device_address(dev); + busnum = libusb_get_bus_number(dev); + SHOW_PROGRESS(output,"Access device %03d on bus %03d\n", devnum, busnum); + } + libusb_open(dev, &devh); + if (devh == NULL) { + SHOW_PROGRESS(output,"Error opening the device. Abort\n\n"); + abortExit(); + } + } + + /* Get current configuration of default device, note value if Configuration + * parameter is set. Also sets active_config + */ + currentConfigVal = get_current_config_value(dev); + if (Configuration > -1) { + SHOW_PROGRESS(output,"Current configuration number is %d\n", currentConfigVal); + } else + currentConfigVal = 0; + + libusb_get_device_descriptor(dev, &descriptor); + defaultClass = descriptor.bDeviceClass; + if (Interface == -1) + Interface = active_config->interface[0].altsetting[0].bInterfaceNumber; + SHOW_PROGRESS(output,"Use interface number %d\n", Interface); + + /* Get class of default device/interface */ + interfaceClass = get_interface_class(); + + if (interfaceClass == -1) { + fprintf(stderr, "Error: Could not get class of interface %d. Does it exist? Abort\n\n",Interface); + abortExit(); + } else { + SHOW_PROGRESS(output," with class %d\n", interfaceClass); + } + + if (defaultClass == 0 || defaultClass == 0xef) + defaultClass = interfaceClass; + else + if (interfaceClass == LIBUSB_CLASS_MASS_STORAGE && defaultClass != LIBUSB_CLASS_MASS_STORAGE + && defaultClass != LIBUSB_CLASS_VENDOR_SPEC) { + + /* Unexpected default class combined with differing interface class */ + SHOW_PROGRESS(output,"Bogus Class/InterfaceClass: 0x%02x/0x08\n", defaultClass); + defaultClass = 8; + } + + if ((strlen(MessageContent) && strncmp("55534243",MessageContent,8) == 0) + || StandardEject || ModeMap & HUAWEINEW_MODE || ModeMap & HUAWEIALT_MODE + || ModeMap & CISCO_MODE || ModeMap & OPTION_MODE) + if (defaultClass != 8) { + fprintf(stderr, "Error: can't use storage command in MessageContent with interface %d; " + "interface class is %d, expected 8. Abort\n\n", Interface, defaultClass); + abortExit(); + } + + /* Check or get endpoints and alloc message list if needed*/ + if (strlen(MessageContent) || StandardEject || ModeMap & CISCO_MODE + || ModeMap & HUAWEINEW_MODE || ModeMap & HUAWEIALT_MODE + || ModeMap & OPTION_MODE) { + + Messages = (char**) calloc(MSG_DIM, sizeof(char*)); + for (i = 0; i < MSG_DIM; i++) { + Messages[i] = (char*) calloc(LINE_DIM, sizeof(char)); + Messages[i][0] = '\0'; + } + + if (!MessageEndpoint) + MessageEndpoint = find_first_bulk_endpoint(LIBUSB_ENDPOINT_OUT); + if (!ResponseEndpoint) + ResponseEndpoint = find_first_bulk_endpoint(LIBUSB_ENDPOINT_IN); + if (!MessageEndpoint) { + fprintf(stderr,"Error: message endpoint not given or found. Abort\n\n"); + abortExit(); + } + if (!ResponseEndpoint) { + fprintf(stderr,"Error: response endpoint not given or found. Abort\n\n"); + abortExit(); + } + SHOW_PROGRESS(output,"Use endpoints 0x%02x (out) and 0x%02x (in)\n", MessageEndpoint, + ResponseEndpoint); + + } + + if (verbose) { + fprintf(output,"\nUSB description data (for identification)\n"); + deviceDescription(); + } + + /* Special modes are exclusive, so check for illegal combinations. + * More than one bit set? + */ + if ( ModeMap & (ModeMap-1) ) { + fprintf(output,"Multiple special modes selected; check configuration. Abort\n\n"); + abortExit(); + } + + if ((strlen(MessageContent) || StandardEject) && ModeMap ) { + MessageContent[0] = '\0'; + StandardEject = 0; + fprintf(output,"Warning: MessageContent/StandardEject ignored; can't combine with special mode\n"); + } + + if (StandardEject && (strlen(MessageContent2) || strlen(MessageContent3))) { + fprintf(output,"Warning: MessageContent2/3 ignored; only one allowed with StandardEject\n"); + } + + if ( !ModeMap && !strlen(MessageContent) && AltSetting == -1 && !Configuration && !StandardEject ) + SHOW_PROGRESS(output,"Warning: no switching method given. See documentation\n"); + + /* + * The switching actions + */ + + if (sysmode) { + openlog("usb_modeswitch", 0, LOG_SYSLOG); + syslog(LOG_NOTICE, "switch device %04x:%04x on %03d/%03d", DefaultVendor, DefaultProduct, + busnum, devnum); + + } + + if (ModeMap & DETACHONLY_MODE) { + SHOW_PROGRESS(output,"Detach storage driver as switching method ...\n"); + ret = detachDrivers(); + if (ret == 2) + SHOW_PROGRESS(output," You may want to remove the storage driver manually\n"); + } + + if(ModeMap & HUAWEI_MODE) { + switchHuaweiMode(); + } + if(ModeMap & SIERRA_MODE) { + switchSierraMode(); + } + if(ModeMap & GCT_MODE) { + detachDrivers(); + switchGCTMode(); + } + if(ModeMap & QISDA_MODE) { + switchQisdaMode(); + } + if(ModeMap & KOBIL_MODE) { + detachDrivers(); + switchKobilMode(); + } + if(ModeMap & QUANTA_MODE) { + switchQuantaMode(); + } + if(ModeMap & SEQUANS_MODE) { + switchSequansMode(); + } + if(ModeMap & MOBILEACTION_MODE) { + switchActionMode(); + } + if(ModeMap & CISCO_MODE) { + detachDrivers(); + switchCiscoMode(); + } + if(ModeMap & BLACKBERRY_MODE) { + detachDrivers(); + switchBlackberryMode(); + } + if(ModeMap & PANTECH_MODE) { + detachDrivers(); + if (PantechMode > 1) + switchPantechMode(); + else + SHOW_PROGRESS(output,"Waiting for auto-switch of Pantech modem ...\n"); + } + if(ModeMap & SONY_MODE) { + if (CheckSuccess) + SHOW_PROGRESS(output,"Note: CheckSuccess ignored; Sony mode does separate checks\n"); + CheckSuccess = 0; /* separate and implied success control */ + sonySuccess = switchSonyMode(); + } + + if (StandardEject) { + SHOW_PROGRESS(output,"Sending standard EJECT sequence\n"); + detachDrivers(); + + strcpy(Messages[0],"5553424387654321000000000000061e000000000000000000000000000000"); + strcpy(Messages[1],"5553424397654321000000000000061b000000020000000000000000000000"); + strcpy(Messages[2],"5553424387654321000000000001061e000000000000000000000000000000"); + strcpy(Messages[3],"5553424397654321000000000001061b000000020000000000000000000000"); + if (MessageContent[0] != '\0') + strcpy(Messages[4], MessageContent); + + switchSendMessage(); + } else if (ModeMap & HUAWEINEW_MODE) { + SHOW_PROGRESS(output,"Using standard Huawei switching message\n"); + detachDrivers(); + strcpy(Messages[0],"55534243123456780000000000000011062000000101000100000000000000"); + switchSendMessage(); + } else if (ModeMap & HUAWEIALT_MODE) { + SHOW_PROGRESS(output,"Using alternative Huawei switching message\n"); + detachDrivers(); + strcpy(Messages[0],"55534243123456780000000000000011063000000000010000000000000000"); + switchSendMessage(); + } else if (ModeMap & OPTION_MODE) { + SHOW_PROGRESS(output,"Using standard Option switching message\n"); + detachDrivers(); + strcpy(Messages[0],"55534243123456780000000000000601000000000000000000000000000000"); + switchSendMessage(); + } else if (strlen(MessageContent)) { + detachDrivers(); + strcpy(Messages[0],MessageContent); + if (MessageContent2[0] != '\0') + strcpy(Messages[1], MessageContent2); + if (MessageContent3[0] != '\0') + strcpy(Messages[2], MessageContent3); + switchSendMessage(); + } + + if (Configuration > 0) { + if (currentConfigVal != Configuration) { + if (switchConfiguration()) { + currentConfigVal = get_current_config_value(dev); + if (currentConfigVal == Configuration) { + SHOW_PROGRESS(output,"The configuration was set successfully\n"); + } else { + SHOW_PROGRESS(output,"Changing the configuration has failed\n"); + } + } + } else { + SHOW_PROGRESS(output,"Target configuration %d already active. Nothing to do. Bye!\n\n", currentConfigVal); + close_all(); + exit(0); + } + } + + if (AltSetting != -1) { + switchAltSetting(); + } + + /* No "removal" check if these are set */ + if ((Configuration > 0 || AltSetting > -1) && !ResetUSB) { + libusb_close(devh); + devh = NULL; + } + + if (ResetUSB) { + resetUSB(); + devh = NULL; + } + + if (searchMode == SEARCH_BUSDEV && sysmode) { + printf("ok:busdev\n"); + close_all(); + exit(0); + } + + if (CheckSuccess) { + if (checkSuccess()) { + if (sysmode) { + if (NoDriverLoading) + printf("ok:\n"); + else + if (TargetProduct < 1) + printf("ok:no_data\n"); + else + printf("ok:%04x:%04x\n", TargetVendor, TargetProduct); + } + } else + if (sysmode) + printf("fail:\n"); + } else { + if (ModeMap & SONY_MODE) + if (sonySuccess) { + if (sysmode) { + syslog(LOG_NOTICE, "switched S.E. MD400 to modem mode"); + printf("ok:\n"); /* ACM device, no driver action */ + } + SHOW_PROGRESS(output,"-> device should be stable now. Bye!\n\n"); + } else { + if (sysmode) + printf("fail:\n"); + SHOW_PROGRESS(output,"-> switching was probably not completed. Bye!\n\n"); + } + else + SHOW_PROGRESS(output,"-> Run lsusb to note any changes. Bye!\n\n"); + } + close_all(); + exit(0); +} + + +/* Get descriptor strings if available (identification details) */ +void deviceDescription () +{ + char imanufact[DESCR_MAX], iproduct[DESCR_MAX], iserial[DESCR_MAX]; + int ret=0; + char* c; + memset (imanufact, ' ', DESCR_MAX); + memset (iproduct, ' ', DESCR_MAX); + memset (iserial, ' ', DESCR_MAX); + + struct libusb_device_descriptor descriptor; + libusb_get_device_descriptor(dev, &descriptor); + + int iManufacturer = descriptor.iManufacturer; + int iProduct = descriptor.iProduct; + int iSerialNumber = descriptor.iSerialNumber; + + if (iManufacturer) { + ret = libusb_get_string_descriptor_ascii(devh, iManufacturer, (unsigned char *)imanufact, DESCR_MAX); + if (ret < 0) { + fprintf(stderr, "Error: could not get description string \"manufacturer\"\n"); + strcpy(imanufact, "read error"); + } + } else + strcpy(imanufact, "not provided"); + c = strstr(imanufact, " "); + if (c) + memset((void*)c, '\0', 1); + + if (iProduct) { + ret = libusb_get_string_descriptor_ascii(devh, iProduct, (unsigned char *)iproduct, DESCR_MAX); + if (ret < 0) { + fprintf(stderr, "Error: could not get description string \"product\"\n"); + strcpy(iproduct, "read error"); + } + } else + strcpy(iproduct, "not provided"); + c = strstr(iproduct, " "); + if (c) + memset((void*)c, '\0', 1); + + if (iSerialNumber) { + ret = libusb_get_string_descriptor_ascii(devh, iSerialNumber, (unsigned char *)iserial, DESCR_MAX); + if (ret < 0) { + fprintf(stderr, "Error: could not get description string \"serial number\"\n"); + strcpy(iserial, "read error"); + } + } else + strcpy(iserial, "not provided"); + c = strstr(iserial, " "); + if (c) + memset((void*)c, '\0', 1); + fprintf(output,"-------------------------\n"); + fprintf(output,"Manufacturer: %s\n", imanufact); + fprintf(output," Product: %s\n", iproduct); + fprintf(output," Serial No.: %s\n", iserial); + fprintf(output,"-------------------------\n"); +} + + +/* Auxiliary function used by the wrapper */ +int findMBIMConfig(int vendor, int product, int mode) +{ + struct libusb_device **devs; + int resultConfig=0; + int i=0, j; + + if (libusb_get_device_list(ctx, &devs) < 0) { + perror("Libusb could not access USB. Abort"); + return 0; + } + + SHOW_PROGRESS(output,"Search USB devices ...\n"); + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor descriptor; + libusb_get_device_descriptor(dev, &descriptor); + + if (mode == SEARCH_BUSDEV) { + if ((libusb_get_bus_number(dev) != busnum) || + (libusb_get_device_address(dev) != devnum)) { + continue; + } else { + if (descriptor.idVendor != vendor) + continue; + if (product != descriptor.idProduct) + continue; + } + } + SHOW_PROGRESS(output,"Found device, search for MBIM configuration...\n"); + + // No check if there is only one configuration + if (descriptor.bNumConfigurations < 2) + return -1; + + // Checking all interfaces of all configurations + for (j=0; j<descriptor.bNumConfigurations; j++) { + struct libusb_config_descriptor *config; + + libusb_get_config_descriptor(dev, j, &config); + resultConfig = config->bConfigurationValue; + for (i=0; i<config->bNumInterfaces; i++) { + if ( config->interface[i].altsetting[0].bInterfaceClass == 2 ) + if ( config->interface[i].altsetting[0].bInterfaceSubClass == 0x0e ) { + // found MBIM interface in this configuration + libusb_free_config_descriptor(config); + return resultConfig; + } + } + libusb_free_config_descriptor(config); + } + return -1; + } + return 0; +} + + +void resetUSB () +{ + int success; + int bpoint = 0; + + if (!devh) { + fprintf(output,"Device handle empty, skip USB reset\n"); + return; + } + if (show_progress) { + fprintf(output,"Reset USB device "); + fflush(output); + } + sleep( 1 ); + do { + success = libusb_reset_device(devh); + if ( ((bpoint % 10) == 0) && show_progress ) { + fprintf(output,"."); + fflush(output); + } + bpoint++; + if (bpoint > 100) + success = 1; + } while (success < 0); + + if ( success ) { + SHOW_PROGRESS(output,"\n Device reset failed.\n"); + } else + SHOW_PROGRESS(output,"\n Device was reset\n"); +} + + +int switchSendMessage () +{ + const char* cmdHead = "55534243"; + int ret, i; + int retries = 1; +/* char* msg[3]; + msg[0] = MessageContent; + msg[1] = MessageContent2; + msg[2] = MessageContent3; +*/ + SHOW_PROGRESS(output,"Set up interface %d\n", Interface); + ret = libusb_claim_interface(devh, Interface); + if (ret != 0) { + SHOW_PROGRESS(output," Could not claim interface (error %d). Skip message sending\n", ret); + return 0; + } + + SHOW_PROGRESS(output,"Use endpoint 0x%02x for message sending ...\n", MessageEndpoint); + if (show_progress) + fflush(stdout); + +retry: + for (i=0; i<MSG_DIM; i++) { + if ( strlen(Messages[i]) == 0) + break; + + if ( sendMessage(Messages[i], i+1) ) + goto skip; + + if ( strstr(Messages[i],cmdHead) != NULL ) { + // UFI command + SHOW_PROGRESS(output,"Read the response to message %d (CSW) ...\n", i+1); + ret = read_bulk(ResponseEndpoint, ByteString, 13); + if (ret >= 0) { + SHOW_PROGRESS(output,", status %d",ByteString[12]); + } + } /* else { + // Other bulk transfer + SHOW_PROGRESS(output,"Read the response to message %d ...\n", i+1); + ret = read_bulk(ResponseEndpoint, ByteString, strlen(Messages[i])/2 ); + }*/ + SHOW_PROGRESS(output,"\n"); + if (ret == LIBUSB_TRANSFER_STALL && retries--) { + SHOW_PROGRESS(output,"Endpoint stalled. Resetting ...\n"); + libusb_clear_halt(devh, MessageEndpoint); + goto retry; + } + if (ret < 0) + goto skip; + } + + SHOW_PROGRESS(output,"Reset response endpoint 0x%02x\n", ResponseEndpoint); + ret = libusb_clear_halt(devh, ResponseEndpoint); + if (ret) + SHOW_PROGRESS(output," Could not reset endpoint (probably harmless): %d\n", ret); + SHOW_PROGRESS(output,"Reset message endpoint 0x%02x\n", MessageEndpoint); + ret = libusb_clear_halt(devh, MessageEndpoint); + if (ret) + SHOW_PROGRESS(output," Could not reset endpoint (probably harmless): %d\n", ret); + usleep(50000); + + if (ReleaseDelay) { + SHOW_PROGRESS(output,"Wait for %d ms before releasing interface ...\n", ReleaseDelay); + usleep(ReleaseDelay*1000); + } + ret = libusb_release_interface(devh, Interface); + if (ret) + goto skip; + return 1; + +skip: + SHOW_PROGRESS(output," Device is gone, skip any further commands\n"); + libusb_close(devh); + devh = NULL; + return 2; +} + + +int switchConfiguration () +{ + int ret; + + SHOW_PROGRESS(output,"Change configuration to %i ...\n", Configuration); + detachDrivers(); + ret = libusb_set_configuration(devh, -1); + if (ret < 0) { + SHOW_PROGRESS(output," Resetting the configuration failed (error %d). Try to continue\n", ret); + } + /* Empirically tested wait period, improves reliability of configuration change */ + usleep(100000); + ret = libusb_set_configuration(devh, Configuration); + if (ret < 0) { + SHOW_PROGRESS(output," Changing the configuration failed (error %d). Try to continue\n", ret); + return 0; + } else { + SHOW_PROGRESS(output," OK, configuration set\n"); + return 1; + } +} + + +int switchAltSetting () +{ + int ret; + SHOW_PROGRESS(output,"Change to alt setting %i ...\n", AltSetting); + ret = libusb_claim_interface(devh, Interface); + if (ret < 0) { + SHOW_PROGRESS(output," Could not claim interface (error %d). Skip AltSetting\n", ret); + return 0; + } + ret = libusb_set_interface_alt_setting(devh, Interface, AltSetting); + libusb_release_interface(devh, Interface); + if (ret < 0) { + SHOW_PROGRESS(output," Change to alt setting returned error %d. Try to continue\n", ret); + return 0; + } else + return 1; +} + + +void switchHuaweiMode () +{ + int ret; + SHOW_PROGRESS(output,"Send old Huawei control message ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_FEATURE, 00000001, 0, buffer, 0, 1000); + + if (ret != 0) { + fprintf(stderr, "Error: Huawei control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchSierraMode () +{ + int ret; + SHOW_PROGRESS(output,"Send Sierra control message\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_INTERFACE, 00000001, 0, buffer, 0, 1000); + if (ret == LIBUSB_ERROR_PIPE) { + SHOW_PROGRESS(output," communication with device stopped. May have switched modes anyway\n"); + return; + } + if (ret < 0) { + fprintf(stderr, "Error: Sierra control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchGCTMode () +{ + int ret; + ret = libusb_claim_interface(devh, Interface); + if (ret != 0) { + SHOW_PROGRESS(output," Could not claim interface (error %d). Skip GCT sequence\n", ret); + return; + } + SHOW_PROGRESS(output,"Send GCT control message 1 ...\n type (should be 161/0xA1): %d", + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN); + + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, + 0xa0, 0, Interface, buffer, 1, 1000); + + if (ret < 0) { + SHOW_PROGRESS(output," GCT control message 1 failed (error %d), continue anyway ...\n", ret); + } + SHOW_PROGRESS(output,"Send GCT control message 2 ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, + 0xfe, 0, Interface, buffer, 1, 1000); + + if (ret < 0) { + SHOW_PROGRESS(output," GCT control message 2 failed (error %d). Abort\n\n", ret); + } + libusb_release_interface(devh, Interface); + if (ret < 0) + exit(0); +} + + +void switchKobilMode() { + int ret; + SHOW_PROGRESS(output,"Send Kobil control message ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN, + 0x88, 0, 0, buffer, 8, 1000); + + if (ret < 0) { + fprintf(stderr, "Error: Kobil control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchQisdaMode () { + int ret; + SHOW_PROGRESS(output,"Sending Qisda control message ...\n"); + memcpy(buffer, "\x05\x8c\x04\x08\xa0\xee\x20\x00\x5c\x01\x04\x08\x98\xcd\xea\xbf", 16); + ret = libusb_control_transfer(devh, 0x40, 0x04, 0, 0, buffer, 16, 1000); + if (ret < 0) { + fprintf(stderr, "Error: Qisda control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchQuantaMode() { + int ret; + SHOW_PROGRESS(output,"Send Quanta control message ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN, + 0xff, 0, 0, buffer, 0, 1000); + + if (ret < 0) { + SHOW_PROGRESS(output,"Error: Quanta control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchBlackberryMode () +{ + int ret; + SHOW_PROGRESS(output,"Send Blackberry control message 1 ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN, + 0xb1, 0x0000, 0, buffer, 8, 1000); + + if (ret != 8) { + fprintf(stderr, "Error: Blackberry control message 1 failed (result %d)\n", ret); + } + SHOW_PROGRESS(output,"Send Blackberry control message 2 ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN, + 0xa9, 0x000e, 0, buffer, 2, 1000); + + if (ret != 2) { + fprintf(stderr, "Error: Blackberry control message 2 failed (result %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchPantechMode() +{ + int ret; + SHOW_PROGRESS(output,"Send Pantech control message, wValue %d ...\n", PantechMode); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT, + 0x70, PantechMode, 0, buffer, 0, 1000); + + if (ret < 0) { + SHOW_PROGRESS(output," Error: Pantech control message failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +#define EP_OUT 0x02 +#define EP_IN 0x81 +#define SIZE 0x08 + +#define MOBILE_ACTION_READLOOP1 63 +#define MOBILE_ACTION_READLOOP2 73 + +/* The code here is statically derived from sniffing (and confirmed working). + * However, I bet it could be simplified significantly. + */ + +void switchActionMode () +{ + int ret, i; + SHOW_PROGRESS(output,"Send MobileAction control sequence ...\n"); + memcpy(buffer, "\xb0\x04\x00\x00\x02\x90\x26\x86", SIZE); + libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + 0x09, 0x0300, 0, buffer, SIZE, 1000); + + memcpy(buffer, "\xb0\x04\x00\x00\x02\x90\x26\x86", SIZE); + libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + 0x09, 0x0300, 0, buffer, SIZE, 1000); + + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x37\x01\xfe\xdb\xc1\x33\x1f\x83", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x37\x0e\xb5\x9d\x3b\x8a\x91\x51", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x34\x87\xba\x0d\xfc\x8a\x91\x51", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + for (i=0; i < MOBILE_ACTION_READLOOP1; i++) { + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + } + memcpy(buffer, "\x37\x01\xfe\xdb\xc1\x33\x1f\x83", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x37\x0e\xb5\x9d\x3b\x8a\x91\x51", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x34\x87\xba\x0d\xfc\x8a\x91\x51", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + for (i=0; i < MOBILE_ACTION_READLOOP2; i++) { + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + } + memcpy(buffer, "\x33\x04\xfe\x00\xf4\x6c\x1f\xf0", SIZE); + usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + memcpy(buffer, "\x32\x07\xfe\xf0\x29\xb9\x3a\xf0", SIZE); + ret = usb_interrupt_io(devh, EP_OUT, buffer, SIZE, 1000); + usb_interrupt_io(devh, EP_IN, buffer, SIZE, 1000); + if (ret < 0) { + SHOW_PROGRESS(output," MobileAction control sequence did not complete\n" + " Last error was %d\n",ret); + } else { + SHOW_PROGRESS(output," MobileAction control sequence complete\n"); + } +} + + +#define SQN_SET_DEVICE_MODE_REQUEST 0x0b +#define SQN_GET_DEVICE_MODE_REQUEST 0x0a + +#define SQN_DEFAULT_DEVICE_MODE 0x00 +#define SQN_MASS_STORAGE_MODE 0x01 +#define SQN_CUSTOM_DEVICE_MODE 0x02 + +void switchSequansMode() +{ + + int ret; + SHOW_PROGRESS(output,"Send Sequans control message\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE + | LIBUSB_ENDPOINT_OUT, SQN_SET_DEVICE_MODE_REQUEST, SQN_CUSTOM_DEVICE_MODE, + 0, buffer, 0, 1000); + + if (ret < 0) { + fprintf(stderr, "Error: Sequans request failed (error %d). Abort\n\n", ret); + exit(0); + } +} + + +void switchCiscoMode() +{ + int ret, i, j; + + strcpy(Messages[0],"55534243f83bcd810002000080000afd000000030000000100000000000000"); + strcpy(Messages[1],"55534243984300820002000080000afd000000070000000100000000000000"); + strcpy(Messages[2],"55534243984300820000000000000afd000100071000000000000000000000"); + strcpy(Messages[3],"55534243984300820002000080000afd000200230000000100000000000000"); + strcpy(Messages[4],"55534243984300820000000000000afd000300238200000000000000000000"); + strcpy(Messages[5],"55534243984300820002000080000afd000200260000000100000000000000"); + strcpy(Messages[6],"55534243984300820000000000000afd00030026c800000000000000000000"); + strcpy(Messages[7],"55534243d84c04820002000080000afd000010730000000100000000000000"); + strcpy(Messages[8],"55534243d84c04820002000080000afd000200240000000100000000000000"); + strcpy(Messages[9],"55534243d84c04820000000000000afd000300241300000000000000000000"); + strcpy(Messages[10],"55534243d84c04820000000000000afd000110732400000000000000000000"); + + SHOW_PROGRESS(output,"Set up Cisco interface %d\n", Interface); + ret = libusb_claim_interface(devh, Interface); + if (ret < 0) { + SHOW_PROGRESS(output," Could not claim interface (error %d). Abort\n", ret); + abortExit(); + } + if (show_progress) + fflush(output); + +// ret = read_bulk(ResponseEndpoint, ByteString, 13); +// SHOW_PROGRESS(output," Extra response (CSW) read, result %d\n",ret); + + for (i=0; i<11; i++) { + if ( sendMessage(Messages[i], i+1) ) + goto skip; + + for (j=1; j<4; j++) { + + SHOW_PROGRESS(output," Read the CSW for bulk message %d (attempt %d) ...\n",i+1,j); + ret = read_bulk(ResponseEndpoint, ByteString, 13); + SHOW_PROGRESS(output,"\n"); + + if (ret < 0) + goto skip; + if (ret == 13) + break; + } + } + libusb_clear_halt(devh, MessageEndpoint); + libusb_clear_halt(devh, ResponseEndpoint); + + ReleaseDelay = 2000; + SHOW_PROGRESS(output,"Wait for %d ms before releasing interface ...\n", ReleaseDelay); + usleep(ReleaseDelay*1000); + + ret = libusb_release_interface(devh, Interface); + if (ret < 0) + goto skip; + return; + +skip: + SHOW_PROGRESS(output,"Device returned error %d, skip further commands\n", ret); + libusb_close(devh); + devh = NULL; +} + + +int switchSonyMode () +{ + int ret, i, found; + detachDrivers(); + + if (CheckSuccess) { + CheckSuccess = 0; + } + + SHOW_PROGRESS(output,"Send Sony control message\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE + | LIBUSB_ENDPOINT_IN, 0x11, 2, 0, buffer, 3, 100); + + if (ret < 0) { + fprintf(stderr, "Error: Sony control message failed (error %d). Abort\n\n", ret); + exit(0); + } else + SHOW_PROGRESS(output," OK, control message sent, wait for device to return ...\n"); + + libusb_close(devh); + devh = NULL; + + /* Now waiting for the device to reappear */ + devnum=-1; + busnum=-1; + i=0; + dev = 0; + while ( dev == 0 && i < 30 ) { + if ( i > 5 ) { + dev = search_devices(&found, DefaultVendor, DefaultProductList, TargetClass, + 0, SEARCH_TARGET); + } + if ( dev != 0 ) + break; + sleep(1); + if (show_progress) { + fprintf(output,"#"); + fflush(stdout); + } + i++; + } + SHOW_PROGRESS(output,"\n After %d seconds:",i); + if ( dev ) { + SHOW_PROGRESS(output," device came back, proceed\n"); + libusb_open(dev, &devh); + if (devh == 0) { + fprintf(stderr, "Error: could not get handle on device\n"); + return 0; + } + } else { + SHOW_PROGRESS(output," device still gone, abort\n"); + return 0; + } + sleep(1); + + SHOW_PROGRESS(output,"Send Sony control message again ...\n"); + ret = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE + | LIBUSB_ENDPOINT_IN, 0x11, 2, 0, buffer, 3, 100); + + if (ret < 0) { + fprintf(stderr, "Error: Sony control message (2) failed (error %d)\n", ret); + return 0; + } + SHOW_PROGRESS(output," OK, control message sent\n"); + return 1; +} + + +/* Detach driver + */ +int detachDrivers() +{ + int i, ret; + SHOW_PROGRESS(output,"Looking for active drivers ...\n"); + ret = libusb_kernel_driver_active(devh, 0); + if (ret == LIBUSB_ERROR_NOT_SUPPORTED) { + fprintf(output," Can't do driver detection on this platform.\n"); + return 2; + } + + struct libusb_config_descriptor *config; + libusb_get_active_config_descriptor(dev, &config); + + for (i=0; i<config->bNumInterfaces; i++) { + ret = libusb_kernel_driver_active(devh, i); + if (ret < 0) { + SHOW_PROGRESS(output," Failed to check driver status for interface %d (error %d)\n Try to continue\n",i,ret); + continue; + } + if (ret) { + ret = libusb_detach_kernel_driver(devh, i); + if (ret == LIBUSB_ERROR_NOT_SUPPORTED) { + fprintf(output," Can't do driver detaching on this platform.\n"); + return 2; + } + if (ret == 0) { + SHOW_PROGRESS(output," OK, driver detached\n"); + } else { + SHOW_PROGRESS(output," Driver detach failed for interface %d (error %d).\n Try to continue\n",i,ret); + continue; + } + } + } + libusb_free_config_descriptor(config); + return 1; +} + + +int sendMessage(char* message, int count) +{ + int ret, message_length; + + if (strlen(message) % 2 != 0) { + fprintf(stderr, "Error: MessageContent %d hex string has uneven length. Skipping ...\n", count); + return 1; + } + message_length = strlen(message) / 2; + if ( hexstr2bin(message, ByteString, message_length) == -1) { + fprintf(stderr, "Error: MessageContent %d %s\n is not a hex string. Skipping ...\n", + count, MessageContent); + + return 1; + } + SHOW_PROGRESS(output,"Trying to send message %d to endpoint 0x%02x ...\n", count, MessageEndpoint); + fflush(output); + ret = write_bulk(MessageEndpoint, ByteString, message_length); + if (ret == LIBUSB_ERROR_NO_DEVICE) + return 1; + + return 0; +} + + +int checkSuccess() +{ + int ret, i; + int newTargetCount, success=0; + + SHOW_PROGRESS(output,"\nCheck for mode switch (max. %d times, once per second) ...\n", CheckSuccess); + sleep(1); + + /* If target parameters are given, don't check for vanished device + * Changed for Cisco AM10 where a new device is added while the install + * storage device stays active + */ + if ((TargetVendor || TargetClass) && devh) { + libusb_close(devh); + devh = NULL; + } + + /* if target ID is not given but target class is, assign default as target; + * it will be needed for sysmode output + */ + if (!TargetVendor && TargetClass) { + TargetVendor = DefaultVendor; + TargetProduct = DefaultProduct; + } + + /* devh is 0 if device vanished during command transmission or if target params were given + */ + if (devh) + for (i=0; i < CheckSuccess; i++) { + + /* Test if default device still can be accessed; positive result does + * not necessarily mean failure + */ + SHOW_PROGRESS(output," Wait for original device to vanish ...\n"); + + ret = libusb_claim_interface(devh, Interface); + libusb_release_interface(devh, Interface); + if (ret < 0) { + SHOW_PROGRESS(output," Original device can't be accessed anymore. Good.\n"); + libusb_close(devh); + devh = NULL; + break; + } + if (i == CheckSuccess-1) { + SHOW_PROGRESS(output," Original device still present after the timeout\n\n" + "Mode switch most likely failed. Bye!\n\n"); + } else + sleep(1); + } + + if ( TargetVendor && (TargetProduct > -1 || TargetProductList[0] != '\0') ) { + + /* Recount target devices (compare with previous count) if target data is given. + * Target device on the same bus with higher device number is returned, + * description is read for syslog message + */ + // Wait counter passed on from previous loop + for (i=i; i < CheckSuccess; i++) { + SHOW_PROGRESS(output," Search for target devices ...\n"); + dev = search_devices(&newTargetCount, TargetVendor, TargetProductList, + TargetClass, 0, SEARCH_TARGET); + + if (dev && (newTargetCount > targetDeviceCount)) { + if (verbose) { + libusb_open(dev, &devh); + fprintf(output,"\nFound target device %03d on bus %03d\n", + libusb_get_device_address(dev), libusb_get_bus_number(dev)); + + fprintf(output,"\nTarget device description data\n"); + deviceDescription(); + libusb_close(devh); + devh = NULL; + } + SHOW_PROGRESS(output," Found correct target device\n\n" + "Mode switch succeeded. Bye!\n\n"); + + success = 2; + break; + } + if (i == CheckSuccess-1) { + SHOW_PROGRESS(output," No new devices in target mode or class found\n\n" + "Mode switch has failed. Bye!\n\n"); + } else + sleep(1); + } + } else + /* No target data given, rely on the vanished device */ + if (!devh) { + SHOW_PROGRESS(output," (For a better success check provide target IDs or class)\n"); + SHOW_PROGRESS(output," Original device vanished after switching\n\n" + "Mode switch most likely succeeded. Bye!\n\n"); + success = 1; + } + + switch (success) { + case 2: + if (sysmode) + syslog(LOG_NOTICE, "switched to %04x:%04x on %03d/%03d", TargetVendor, + TargetProduct, busnum, devnum); + + success = 1; + break; + case 1: + if (sysmode) + syslog(LOG_NOTICE, "device seems to have switched"); + default: + ; + } + if (sysmode) + closelog(); + + return success; + +} + + +int write_bulk(int endpoint, unsigned char *message, int length) +{ + int ret = usb_bulk_io(devh, endpoint, message, length, 3000); + if (ret >= 0 ) { + SHOW_PROGRESS(output," OK, message successfully sent\n"); + } else + if (ret == LIBUSB_ERROR_NO_DEVICE) { + SHOW_PROGRESS(output," Device seems to have vanished right after sending. Good.\n"); + } else + SHOW_PROGRESS(output," Sending the message returned error %d. Try to continue\n", ret); + return ret; +} + + +int read_bulk(int endpoint, unsigned char *buffer, int length) +{ + int ret = usb_bulk_io(devh, endpoint, buffer, length, 3000); + if (ret >= 0 ) { + SHOW_PROGRESS(output," Response successfully read (%d bytes)", ret); + } else + if (ret == LIBUSB_ERROR_NO_DEVICE) { + SHOW_PROGRESS(output," Device seems to have vanished after reading. Good."); + } else + SHOW_PROGRESS(output," Response reading failed (error %d)", ret); + return ret; +} + + +void release_usb_device(int __attribute__((unused)) dummy) +{ + SHOW_PROGRESS(output,"Program cancelled by system. Bye!\n\n"); + if (devh) + libusb_release_interface(devh, Interface); + close_all(); + exit(0); +} + + +/* Iterates over busses and devices, counts the ones which match the given + * parameters and returns the last one of them +*/ +struct libusb_device* search_devices( int *numFound, int vendor, char* productList, + int targetClass, int configuration, int mode) +{ + char *listcopy=NULL, *token; + unsigned char buffer[2]; + int devClass, product; + struct libusb_device* right_dev = NULL; + struct libusb_device **devs; + int i=0; + + /* only target class given, target vendor and product assumed unchanged */ + if ( targetClass && !(vendor || strlen(productList)) ) { + vendor = DefaultVendor; + productList = DefaultProductList; + } + *numFound = 0; + + /* Sanity check */ + if (!vendor || *productList == '\0') + return NULL; + + listcopy = malloc(strlen(productList)+1); + + if (libusb_get_device_list(ctx, &devs) < 0) { + perror("Libusb failed to get USB access!"); + return 0; + } + + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor descriptor; + libusb_get_device_descriptor(dev, &descriptor); + + if (mode == SEARCH_BUSDEV) { + if ((libusb_get_bus_number(dev) != busnum) || + (libusb_get_device_address(dev) != devnum)) + continue; + else + SHOW_PROGRESS(output," bus/device number matched\n"); + } + + if (verbose) + fprintf (output," found USB ID %04x:%04x\n", + descriptor.idVendor, descriptor.idProduct); + if (descriptor.idVendor != vendor) + continue; + if (verbose) + fprintf (output," vendor ID matched\n"); + + strcpy(listcopy, productList); + token = strtok(listcopy, ","); + while (token != NULL) { + if (strlen(token) != 4) { + SHOW_PROGRESS(output,"Error: entry in product ID list has wrong length: %s. " + "Ignored\n", token); + + goto NextToken; + } + if ( hexstr2bin(token, buffer, strlen(token)/2) == -1) { + SHOW_PROGRESS(output,"Error: entry in product ID list is not a hex string: %s. " + "Ignored\n", token); + + goto NextToken; + } + product = 0; + product += (unsigned char)buffer[0]; + product <<= 8; + product += (unsigned char)buffer[1]; + if (product == descriptor.idProduct) { + if (verbose) + fprintf(output," product ID matched\n"); + + if (targetClass != 0) { + /* TargetClass is set, check class of first interface */ + struct libusb_device_descriptor descriptor; + libusb_get_device_descriptor(dev, &descriptor); + devClass = descriptor.bDeviceClass; + struct libusb_config_descriptor *config; + libusb_get_config_descriptor(dev, 0, &config); + int ifaceClass = config->interface[0].altsetting[0].bInterfaceClass; + libusb_free_config_descriptor(config); + if (devClass == 0) + devClass = ifaceClass; + else + /* Check for some quirky devices */ + if (devClass != ifaceClass) + devClass = ifaceClass; + if (devClass == targetClass) { + if (verbose) + fprintf (output," target class %02x matches\n", targetClass); + if (mode == SEARCH_TARGET) { + (*numFound)++; + right_dev = dev; + if (verbose) + fprintf (output," count device\n"); + } else + if (verbose) + fprintf (output," device not counted, target class reached\n"); + } else { + if (verbose) + fprintf (output," device class %02x not matching target\n", devClass); + if (mode == SEARCH_DEFAULT || mode == SEARCH_BUSDEV) { + (*numFound)++; + right_dev = dev; + if (verbose) + fprintf (output," count device\n"); + } + } + } else { + /* Neither TargetClass nor Configuration are set */ + (*numFound)++; + right_dev = dev; + if (mode == SEARCH_BUSDEV) + break; + } + } + + NextToken: + token = strtok(NULL, ","); + } + } + if (listcopy != NULL) + free(listcopy); + return right_dev; +} + + +/* Autodetect bulk endpoints (ab) */ + +int find_first_bulk_endpoint(int direction) +{ + int i, j; + const struct libusb_interface_descriptor *alt; + const struct libusb_endpoint_descriptor *ep; + + for (j=0; j < active_config->bNumInterfaces; j++) { + alt = &(active_config->interface[j].altsetting[0]); + if (alt->bInterfaceNumber == Interface) { + for (i=0; i < alt->bNumEndpoints; i++) { + ep = &(alt->endpoint[i]); + if ( ( (ep->bmAttributes & LIBUSB_ENDPOINT_ADDRESS_MASK) == LIBUSB_TRANSFER_TYPE_BULK) + && ( (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == direction ) ) { + + return ep->bEndpointAddress; + } + } + } + } + return 0; +} + + +int get_current_config_value() +{ + SHOW_PROGRESS(output,"Get the current device configuration ...\n"); + if (active_config != NULL) { + libusb_free_config_descriptor(active_config); + active_config = NULL; + } + int ret = libusb_get_active_config_descriptor(dev, &active_config); + if (ret < 0) { + SHOW_PROGRESS(output," Determining the active configuration failed (error %d). Abort\n", ret); + abortExit(); + } + return active_config->bConfigurationValue; +} + + +int get_interface_class() +{ + int i; + for (i=0; i < active_config->bNumInterfaces; i++) { + if (active_config->interface[i].altsetting[0].bInterfaceNumber == Interface) + return active_config->interface[i].altsetting[0].bInterfaceClass; + } + return -1; +} + + +/* Parameter parsing */ + +char* ReadParseParam(const char* FileName, char *VariableName) +{ + static int numLines = 0; + static char* ConfigBuffer[MAXLINES]; + char *VarName, *Comment=NULL, *Equal=NULL; + char *FirstQuote, *LastQuote, *P1, *P2; + int Line=0; + unsigned Len=0, Pos=0; + static char Str[LINE_DIM]; + char *token, *configPos; + FILE *file = NULL; + + // Reading and storing input during the first call + if (numLines==0) { + if (strncmp(FileName,"##",2) == 0) { + if (verbose) fprintf(output,"\nRead long config from command line\n"); + // "Embedded" configuration data + configPos = (char*)FileName; + token = strtok(configPos, "\n"); + strncpy(Str,token,LINE_DIM-1); + } else { + if (strcmp(FileName, "stdin")==0) { + if (verbose) fprintf(output,"\nRead long config from stdin\n"); + file = stdin; + } else { + if (verbose) fprintf(output,"\nRead config file: %s\n", FileName); + file=fopen(FileName, "r"); + } + if (file==NULL) { + fprintf(stderr, "Error: Could not find file %s. Abort\n\n", FileName); + abortExit(); + } else { + token = fgets(Str, LINE_DIM-1, file); + } + } + while (token != NULL && numLines < MAXLINES) { + Len=strlen(Str); + if (Len==0) + goto NextLine; + if (Str[Len-1]=='\n' or Str[Len-1]=='\r') + Str[--Len]='\0'; + Equal = strchr (Str, '='); // search for equal sign + Pos = strcspn (Str, ";#!"); // search for comment + Comment = (Pos==Len) ? NULL : Str+Pos; + if (Equal==NULL or ( Comment!=NULL and Comment<=Equal)) + goto NextLine; // Comment or irrelevant, don't save + Len=strlen(Str)+1; + ConfigBuffer[numLines] = malloc(Len*sizeof(char)); + strcpy(ConfigBuffer[numLines],Str); + numLines++; + NextLine: + if (file == NULL) { + token = strtok(NULL, "\n"); + if (token != NULL) + strncpy(Str,token,LINE_DIM-1); + } else + token = fgets(Str, LINE_DIM-1, file); + } + if (file != NULL) + fclose(file); + } + + // Now checking for parameters + Line=0; + while (Line < numLines) { + strcpy(Str,ConfigBuffer[Line]); + Equal = strchr (Str, '='); // search for equal sign + *Equal++ = '\0'; + + // String + FirstQuote=strchr (Equal, '"'); // search for double quote char + LastQuote=strrchr (Equal, '"'); + if (FirstQuote!=NULL) { + if (LastQuote==NULL) { + fprintf(stderr, "Error reading parameters from file %s - " + "Missing end quote:\n%s\n", FileName, Str); + + goto Next; + } + *FirstQuote=*LastQuote='\0'; + Equal=FirstQuote+1; + } + + // removes leading/trailing spaces + Pos=strspn (Str, " \t"); + if (Pos==strlen(Str)) { + fprintf(stderr, "Error reading parameters from file %s - " + "Missing variable name:\n%s\n", FileName, Str); + + goto Next; + } + while ((P1=strrchr(Str, ' '))!=NULL or (P2=strrchr(Str, '\t'))!=NULL) + if (P1!=NULL) *P1='\0'; + else if (P2!=NULL) *P2='\0'; + VarName=Str+Pos; + + Pos=strspn (Equal, " \t"); + if (Pos==strlen(Equal)) { + fprintf(stderr, "Error reading parameter from file %s - " + "Missing value:\n%s\n", FileName, Str); + + goto Next; + } + Equal+=Pos; + + if (strcmp(VarName, VariableName)==0) { // Found it + return Equal; + } + Next: + Line++; + } + + return NULL; +} + + +int hex2num(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + + +int hex2byte(const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) + return -1; + b = hex2num(*hex++); + if (b < 0) + return -1; + return (a << 4) | b; +} + + +int hexstr2bin(const char *hex, unsigned char *buffer, int len) +{ + int i; + int a; + const char *ipos = hex; + unsigned char *opos = buffer; + + for (i = 0; i < len; i++) { + a = hex2byte(ipos); + if (a < 0) + return -1; + *opos++ = (unsigned char) a; + ipos += 2; + } + return 0; +} + + +void close_all() +{ + int i; + if (Messages) { + for ( i = 0; i < MSG_DIM; i++ ) { + free(Messages[i]); + } + free(Messages); + } + if (active_config) + libusb_free_config_descriptor(active_config); + if (devh) + libusb_close(devh); + // libusb_exit will crash on Raspbian 7, crude protection +#ifndef __ARMEL__ + if (ctx) + libusb_exit(NULL); +#endif + if (sysmode) + closelog(); +} + + +void abortExit() +{ + fflush(output); + fflush(stderr); + close_all(); + exit(1); +} + + +void printVersion() +{ + char* version = VERSION; + fprintf(output,"\n * usb_modeswitch: handle USB devices with multiple modes\n" + " * Version %s (C) Josua Dietze 2017\n" + " * Based on libusb1/libusbx\n\n" + " ! PLEASE REPORT NEW CONFIGURATIONS !\n\n", version); +} + + +void printHelp() +{ + fprintf(output,"\nUsage: usb_modeswitch [<params>] [-c filename]\n\n" + " -h, --help this help\n" + " -e, --version print version information and exit\n" + " -j, --find-mbim return config no. with MBIM interface, exit\n\n" + " -v, --default-vendor NUM vendor ID of original mode (mandatory)\n" + " -p, --default-product NUM product ID of original mode (mandatory)\n" + " -V, --target-vendor NUM target mode vendor ID (optional)\n" + " -P, --target-product NUM target mode product ID (optional)\n" + " -C, --target-class NUM target mode device class (optional)\n" + " -b, --bus-num NUM system bus number of device (for hard ID)\n" + " -g, --device-num NUM system device number (for hard ID)\n" + " -m, --message-endpoint NUM direct the message transfer there (optional)\n" + " -M, --message-content <msg> message to send (hex number as string)\n" + " -2, --message-content2 <msg> additional messages to send (-n recommended)\n" + " -3, --message-content3 <msg> additional messages to send (-n recommended)\n" + " -w, --release-delay NUM wait NUM ms before releasing the interface\n" + " -n, --need-response obsolete, no effect (always on)\n" + " -r, --response-endpoint NUM read response from there (optional)\n" + " -K, --std-eject send standard EJECT sequence\n" + " -d, --detach-only detach the active driver, no further action\n" + " -H, --huawei-mode apply a special procedure\n" + " -J, --huawei-new-mode apply a special procedure\n" + " -X, --huawei-alt-mode apply a special procedure\n" + " -S, --sierra-mode apply a special procedure\n" + " -O, --sony-mode apply a special procedure\n" + " -G, --gct-mode apply a special procedure\n" + " -N, --sequans-mode apply a special procedure\n" + " -A, --mobileaction-mode apply a special procedure\n" + " -T, --kobil-mode apply a special procedure\n" + " -L, --cisco-mode apply a special procedure\n" + " -B, --qisda-mode apply a special procedure\n" + " -E, --quanta-mode apply a special procedure\n" + " -F, --pantech-mode NUM apply a special procedure, pass NUM through\n" + " -Z, --blackberry-mode apply a special procedure\n" + " -U, --option-mode apply a special procedure\n" + " -R, --reset-usb reset the device after all other actions\n" + " -Q, --quiet don't show progress or error messages\n" + " -W, --verbose print all settings and debug output\n" + " -D, --sysmode specific result and syslog message\n" + " -s, --check-success <seconds> switching result check with timeout\n" + " -I, --inquire obsolete, no effect\n\n" + " -c, --config-file <filename> load long configuration from file\n\n" + " -t, --stdinput read long configuration from stdin\n\n" + " -f, --long-config <text> get long configuration from string\n\n" + " -i, --interface NUM select initial USB interface (default 0)\n" + " -u, --configuration NUM select USB configuration\n" + " -a, --altsetting NUM select alternative USB interface setting\n\n"); +} diff --git a/usb_modeswitch.conf b/usb_modeswitch.conf new file mode 100644 index 0000000..e1643b6 --- /dev/null +++ b/usb_modeswitch.conf @@ -0,0 +1,40 @@ +# Configuration for the usb_modeswitch package, a mode switching tool for +# USB devices providing multiple states or modes +# +# Evaluated by the wrapper script /usr/sbin/usb_modeswitch_dispatcher +# +# To enable an option, set it to "1", "yes" or "true" (case doesn't matter) +# Everything else counts as "disable" + + +# Disable automatic mode switching globally (e.g. to access the original +# install storage) + +DisableSwitching=0 + +# Disable check for MBIM module presence and configuration globally (to aid +# special embedded environments). Available as per-device parameter +# 'NoMBIMCheck' + +DisableMBIMGlobal=0 + +# Enable logging (results in a extensive report file in /var/log, named +# "usb_modeswitch_<interface-name>" and probably others + +EnableLogging=0 + +# Optional increase of "delay_use" for the usb-storage driver; there are hints +# that a recent kernel default change to 1 sec. may lead to problems, particu- +# larly with USB 3.0 ports. Set this to at least 3 (seconds) in that case. +# Does nothing if the current system value is same or higher + +#SetStorageDelay=4 + +# If the configuration for your Huawei modem uses the standard switching method, +# you can globally set an alternative method here which provides a different mode: +# either plain serial PPP (for older modems) or NCM which integrates nicely +# with recent Linux distributions, possibly avoiding configuration through a +# modem built-in web page. All other modems are not affected. +# Available as per-device parameter 'AltHuaweiMode' + +HuaweiAltModeGlobal=0 diff --git a/usb_modeswitch.h b/usb_modeswitch.h new file mode 100644 index 0000000..018c0e9 --- /dev/null +++ b/usb_modeswitch.h @@ -0,0 +1,116 @@ +/* + This file is part of usb_modeswitch, a mode switching tool for controlling + the mode of 'multi-state' USB devices + + Version 2.5.2, 2017/12/31 + Copyright (C) 2007 - 2017 Josua Dietze + + Config file parsing stuff borrowed from Guillaume Dargaud + (http://www.gdargaud.net/Hack/SourceCode.html) + + 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: + + http://www.gnu.org/licenses/gpl.txt + +*/ + +#include <stdlib.h> +#include <libusb.h> + +void readConfigFile(const char *configFilename); +void printConfig(); +int switchSendMessage(); +int switchConfiguration(); +int switchAltSetting(); +void switchHuaweiMode(); + +void switchSierraMode(); +void switchGCTMode(); +void switchKobilMode(); +void switchQisdaMode(); +void switchQuantaMode(); +void switchSequansMode(); +void switchActionMode(); +void switchBlackberryMode(); +void switchPantechMode(); +void switchCiscoMode(); +int switchSonyMode(); +int detachDrivers(); +int checkSuccess(); +int sendMessage(char* message, int count); +int write_bulk(int endpoint, unsigned char *message, int length); +int read_bulk(int endpoint, unsigned char *buffer, int length); +void release_usb_device(int dummy); +struct libusb_device* search_devices( int *numFound, int vendor, char* productList, + int targetClass, int configuration, int mode); +int find_first_bulk_endpoint(int direction); +int get_current_config_value(); +int get_interface_class(); +char* ReadParseParam(const char* FileName, char *VariableName); +int hex2num(char c); +int hex2byte(const char *hex); +int hexstr2bin(const char *hex, unsigned char *buffer, int len); +void printVersion(); +void printHelp(); +void close_all(); +void abortExit(); +int readArguments(int argc, char **argv); +void deviceDescription(); +void resetUSB(); +void release_usb_device(int dummy); +int findMBIMConfig(int vendor, int product, int mode); + + +// Boolean +#define and && +#define or || +#define not ! + +// Bitwise +#define bitand & +#define bitor | +#define compl ~ +#define xor ^ + +// Equals +#define and_eq &= +#define not_eq != +#define or_eq |= +#define xor_eq ^= + +extern char* ReadParseParam(const char* FileName, char *VariableName); + +extern char *TempPP; + +#define ParseParamString(ParamFileName, Str) \ + if ((TempPP=ReadParseParam((ParamFileName), #Str))!=NULL) \ + strcpy(Str, TempPP); else Str[0]='\0' + +#define ParseParamInt(ParamFileName, Int) \ + if ((TempPP=ReadParseParam((ParamFileName), #Int))!=NULL) \ + Int=atoi(TempPP) + +#define ParseParamHex(ParamFileName, Int) \ + if ((TempPP=ReadParseParam((ParamFileName), #Int))!=NULL) \ + Int=strtol(TempPP, NULL, 16) + +#define ParseParamFloat(ParamFileName, Flt) \ + if ((TempPP=ReadParseParam((ParamFileName), #Flt))!=NULL) \ + Flt=atof(TempPP) + +#define ParseParamBool(ParamFileName, B) \ + if ((TempPP=ReadParseParam((ParamFileName), #B))!=NULL) \ + B=(toupper(TempPP[0])=='Y' || toupper(TempPP[0])=='T'|| TempPP[0]=='1'); else B=0 + +#define ParseParamBoolMap(ParamFileName, B, M, Const) \ + if ((TempPP=ReadParseParam((ParamFileName), #B))!=NULL) \ + if (toupper(TempPP[0])=='Y' || toupper(TempPP[0])=='T'|| TempPP[0]=='1') \ + M=M+Const diff --git a/usb_modeswitch.sh b/usb_modeswitch.sh new file mode 100755 index 0000000..eb3fa3e --- /dev/null +++ b/usb_modeswitch.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# part of usb_modeswitch 2.5.2 +device_in() +{ + if [ ! -e /var/lib/usb_modeswitch/$1 ]; then + return 0 + fi + while read line + do + if [ $(expr "$line" : "$2:$3") != 0 ]; then + return 1 + fi + done </var/lib/usb_modeswitch/$1 + if [ $(expr "$line" : "$2:$3") != 0 ]; then + return 1 + fi + return 0 +} + +if [ $(expr "$1" : "--.*") ]; then + p_id=$4 + if [ -z $p_id ]; then + prod=$5 + if [ -z $prod ]; then + prod=$3 + fi + prod=${prod%/*} + v_id=0x${prod%/*} + p_id=0x${prod#*/} + if [ "$v_id" = "0x" ]; then + v_id="0" + p_id="0" + fi + v_id="$(printf %04x $(($v_id)))" + p_id="$(printf %04x $(($p_id)))" + else + v_id=$3 + fi +fi +PATH=/sbin:/usr/sbin:$PATH +case "$1" in + --driver-bind) + # driver binding code removed + exit 0 + ;; + --symlink-name) + device_in "link_list" $v_id $p_id + if [ "$?" = "1" ]; then + if [ -e "/usr/sbin/usb_modeswitch_dispatcher" ]; then + exec usb_modeswitch_dispatcher $1 $2 2>>/dev/null + fi + fi + exit 0 + ;; +esac + +IFS='/' read -r p1 p2 <<EOF +$1 +EOF +if [ "$p2" = "" -a "$p1" != "" ]; then + p2=$p1 +fi + +PATH=/bin:/sbin:/usr/bin:/usr/sbin +init_path=`readlink -f /sbin/init` +if [ `basename $init_path` = "systemd" ]; then + systemctl --no-block start usb_modeswitch@$p2.service +elif [ -e "/etc/init/usb-modeswitch-upstart.conf" ]; then + initctl emit --no-wait usb-modeswitch-upstart UMS_PARAM=$p2 +else + # only old distros, new udev will kill all subprocesses + exec 1<&- 2<&- 5<&- 7<&- + exec usb_modeswitch_dispatcher --switch-mode $p2 & +fi +exit 0 diff --git a/usb_modeswitch.tcl b/usb_modeswitch.tcl new file mode 100755 index 0000000..d2ee50c --- /dev/null +++ b/usb_modeswitch.tcl @@ -0,0 +1,1005 @@ +#!/usr/bin/tclsh + +# Wrapper (tcl) for usb_modeswitch, called from +# /lib/udev/rules.d/40-usb_modeswitch.rules +# (part of data pack "usb-modeswitch-data") via +# /lib/udev/usb_modeswitch +# +# Does ID check on newly discovered USB devices and calls +# the mode switching program with the matching parameter +# file from /usr/share/usb_modeswitch +# +# Part of usb-modeswitch-2.5.2 package +# (C) Josua Dietze 2009-2017 + +set arg0 [lindex $argv 0] +if [regexp {\.tcl$} $arg0] { + if [file exists $arg0] { + set argv [lrange $argv 1 end] + source $arg0 + exit + } +} + +# Setting of these switches is done in the global config +# file (/etc/usb_modeswitch.conf) if available + +set flags(logging) 1 +set flags(noswitching) 0 +set flags(stordelay) 0 +set flags(nombim) 0 + +set flags(logwrite) 0 +# also settable in device config files +set flags(nombim) 0 +set flags(althuawei) 0 + +# Execution starts at file bottom + +proc {Main} {argv argc} { + +global scsi usb config match device flags setup devdir loginit + +set flags(config) "" +Log "[ParseGlobalConfig]" + +if {$flags(stordelay) > 0} { + SetStorageDelay $flags(stordelay) +} + + +# The facility to add a symbolic link pointing to the +# ttyUSB port which provides interrupt transfer, i.e. +# the port to connect through. +# Will check for interrupt endpoint in ttyUSB port (lowest if +# there is more than one); if found, return "gsmmodem[n]" name +# to udev for symlink creation + +# This is run once for every port of LISTED devices by +# a udev rule + +if {[lindex $argv 0] == "--symlink-name"} { + puts -nonewline [SymLinkName [lindex $argv 1]] + SafeExit +} + +# arg0: the bus id for the device (udev: %b), now deprecated +# arg1: the "kernel name" for the device (udev: %k) +# +# From version 2.5.0 upward %b is removed by udev sh script +# which can handle old and new udev params ('%b/%k' and '%k') + +Log "Raw parameters: $argv" +set device "noname" +if {[lindex $argv 0] == "--switch-mode"} { + if [string length [lindex $argv 1]] { + set arg1 [lindex $argv 1] + } else { + Log "\nNo data from udev. Exit" + SafeExit + } +} else { + Log "\nNo command given. Exit" + SafeExit +} + +if {![regexp {(.*?):.*$} $arg1 d device]} { + if {![regexp {([0-9]+-[0-9]+\.?[0-9]*.*)} $arg1 d device]} { + Log "Could not determine device dir from udev values! Exit" + SafeExit + } +} +set flags(logwrite) 1 + +set setup(dbdir) /usr/share/usb_modeswitch +set setup(dbdir_etc) /etc/usb_modeswitch.d +if {![file exists $setup(dbdir)] && ![file exists $setup(dbdir_etc)]} { + Log "\nError: no config database found in /usr/share or /etc. Exit" + SafeExit +} + +set bindir /usr/sbin +set devList1 {} +set devList2 {} +set ifChk 0 + +set devdir /sys/bus/usb/devices/$device +if {![file isdirectory $devdir]} { + Log "Top device directory not found ($devdir)! Exit" + SafeExit +} +Log "Use top device dir $devdir" + +set iface 0 +Log "Check class of first interface ..." +set config(class) [IfClass 0 $devdir] +if {$config(class) < 0} { + Log " No access to interface 0. Exit" + SafeExit +} +Log " Interface 0 class is $config(class)." + +set ifdir [file tail [IfDir $iface $devdir]] +regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface + +# Mapping of the short string identifiers (in the config +# file names) to the long name used here +# +# If we need them it's a snap to add new attributes here! + +set match(sVe) scsi(vendor) +set match(sMo) scsi(model) +set match(sRe) scsi(rev) +set match(uMa) usb(manufacturer) +set match(uPr) usb(product) +set match(uSe) usb(serial) + + +# Now reading the USB attributes +if {![ReadUSBAttrs $devdir]} { + Log "USB attributes not found in sysfs tree. Exit" + SafeExit +} +set config(vendor) $usb(idVendor) +set config(product) $usb(idProduct) + + +if $flags(logging) { + Log "\n----------------\nUSB values from sysfs:" + foreach attr {manufacturer product serial} { + Log " $attr\t$usb($attr)" + } + Log "----------------" +} + +if $flags(noswitching) { + SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)" + Log "\nSwitching globally disabled. Exit" + SafeExit +} + +if {$usb(bNumConfigurations) == "1"} { + set configParam "-u -1" + Log "bNumConfigurations is 1 - don't check for active configuration" +} else { + set configParam "" +} + +# Check (and switch) for operating system if Huawei device present + +set flags(os) "linux" +if {$usb(idVendor) == "12d1" && [regexp -nocase {android} [exec cat /proc/version]]} { + set flags(os) "android" +} +if {$flags(os) == "android"} { + set configList [ConfigGet conflist $usb(idVendor):#android] +} else { + set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)] +} + +if {[llength $configList] == 0} { + Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exit" + SafeExit +} +Log "ConfigList: $configList" + +# Check if there is more than one config file for this USB ID, +# which would make an attribute test necessary. If so, check if +# SCSI values are needed + +set scsiNeeded 0 +if {[llength $configList] > 1} { + if [regexp {:s} $configList] { + set scsiNeeded 1 + } +} +if $scsiNeeded { + if [ReadSCSIAttrs $devdir:$iface] { + Log "----------------\nSCSI values from sysfs:" + foreach attr {vendor model rev} { + Log " $attr\t$scsi($attr)" + } + Log "----------------" + } else { + Log "Could not get SCSI attributes, exclude devices with SCSI match" + } +} else { + Log "SCSI attributes not needed, move on" +} + +# Now check for a matching config file. Matching is done +# by MatchDevice + +set report "" +foreach mconfig $configList { + + # skipping installer leftovers like "*.rpmnew" + if [regexp {\.(dpkg|rpm)} $mconfig] {continue} + + Log "Check config: $mconfig" + if [MatchDevice $mconfig] { + Log "! matched. Read config data" + set flags(config) [ConfigGet conffile $mconfig] + break + } else { + Log "* no match, don't use this config" + } +} +if {$flags(config) == ""} { + Log "No matching config file found. Exit" + SafeExit +} + +ParseDeviceConfig $flags(config) + +if [regexp -nocase {0x([0-9a-f]+)} $config(TargetClass) d tc] { + if {$tc == $config(class)} { + Log "Class of interface 0 matches target. Do nothing" + set report "ok:busdev" + } +} + +if [string length $usb(busnum)] { + set busParam "-b [string trimleft $usb(busnum) 0]" + set devParam "-g [string trimleft $usb(devnum) 0]" +} else { + set busParam "" + set devParam "" +} +if [regexp -nocase $flags(os) $flags(config)] { + Log "Note: Using generic manufacturer configuration for \"$flags(os)\"" +} +if $flags(althuawei) { + regsub {HuaweiNewMode} $flags(config) {HuaweiAltMode} flags(config) + Log "Alternative Huawei mode set globally, modify config" +} +if $flags(nombim) { + set config(NoMBIMCheck) 1 +} +if {$config(NoMBIMCheck)==0 && $usb(bNumConfigurations) > 1} { + Log "Device may have an MBIM configuration, check driver ..." + if [CheckMBIM] { + Log " driver for MBIM devices is available" + Log "Find MBIM configuration number ..." + if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] { + Log "Error when trying to find MBIM configuration, switch to legacy modem mode" + } else { + set cfgno [string trim $cfgno] + if {$cfgno > 0} { + set config(Configuration) $cfgno + set flags(config) "Configuration=$cfgno" + } else { + Log " No MBIM configuration found, switch to legacy modem mode" + } + } + } else { + Log " no MBIM driver found, switch to legacy modem mode" + } +} +if [PantechAutoSwitch] { + Log "Waiting for Pantech auto-modeswitch" + set report "ok:busdev" +} +if {$config(Configuration) == 0} { + Log "Config file contains dummy method, do nothing. Exit" + SafeExit +} +# General wait - some devices need this +after 500 + +if {$config(WaitBefore) != ""} { + Log "Delay time of $config(WaitBefore) seconds" + append config(WaitBefore) "000" + after $config(WaitBefore) +} + +if {$report == ""} { + # Now we are actually switching + if $flags(logging) { + Log "Command line:\nusb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f \$flags(config)" + catch {set report [exec /usr/sbin/usb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]} report + Log "\nVerbose debug output of usb_modeswitch and libusb follows" + Log "(Note that some USB errors are to be expected in the process)" + Log "--------------------------------" + Log $report + Log "--------------------------------" + Log "(end of usb_modeswitch output)\n" + } else { + catch {set report [exec /usr/sbin/usb_modeswitch -Q -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]} report + } +} + +# Switching is complete; success checking was either +# done by usb_modeswitch and logged via syslog OR bus/dev +# parameter were used; then we do check for success HERE + +if {$config(Configuration) != ""} { + set ifdir [regsub {(\d):\d+\.0} $ifdir "\\1:$config(Configuration).0"] +} + +if [regexp {ok:busdev} $report] { + if [CheckSuccess $devdir] { + Log "Mode switching was successful, found $usb(idVendor):$usb(idProduct) ($usb(manufacturer): $usb(product))" + SysLog "usb_modeswitch: switched to $usb(idVendor):$usb(idProduct) on [format %03d $usb(busnum)]/[format %03d $usb(devnum)]" + } else { + Log "\nTarget config not matching - current values are" + LogAttributes + Log "\nMode switching may have failed. Exit" + SafeExit + } +} else { + if {![file isdirectory $devdir]} { + Log "Device directory in sysfs is gone! Something went wrong, abort" + SafeExit + } + if {![regexp {ok:} $report]} { + Log "\nCore program reported switching failure. Exit" + SafeExit + } + # Give the device another second if it's not fully back yet + if {![file exists $devdir/idProduct]} { + after 1000 + } + ReadUSBAttrs $devdir $ifdir +} + +# driver binding removed !! + +if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} { + if {![regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)]} { + Log "No target vendor/product ID found or given, can't continue. Abort" + SafeExit + } +} +# wait for drivers to bind +after 500 +if {[llength [glob -nocomplain $devdir/$ifdir/ttyUSB*]] > 0} { + Log "Serial USB driver bound to interface 0\n will try to guess and symlink modem port on next connect" + AddToList link_list $usb(idVendor):$usb(idProduct) +} + +# In newer kernels there is a switch to avoid the use of a device +# reset (e.g. from usb-storage) which would possibly switch back +# a mode-switching device to initial mode +if [regexp {ok:} $report] { + Log "Check for AVOID_RESET_QUIRK kernel attribute" + if [file exists $devdir/avoid_reset_quirk] { + if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] { + Log " Error setting the attribute: $err" + } else { + Log " AVOID_RESET_QUIRK activated" + } + } else { + Log " not present in this kernel" + } +} + +Log "\nAll done, exit\n" +SafeExit + +} +# end of proc {Main} + + +proc {ReadSCSIAttrs} {topdir} { + +global scsi +set counter 0 +set sysdir $topdir +Log "Check storage tree in sysfs ..." +while {$counter < 20} { + Log " loop $counter/20" + if {![file isdirectory $sysdir]} { + # Device is gone. Unplugged? Switched by kernel? + Log " sysfs device tree is gone; abort SCSI value check" + return 0 + } + # Searching the storage/SCSI tree; might take a while + if {[set dirList [glob -nocomplain $topdir/host*]] != ""} { + set sysdir [lindex $dirList 0] + if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} { + set sysdir [lindex $dirList 0] + regexp {.*target(.*)} $sysdir d subdir + if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} { + set sysdir [lindex $dirList 0] + if [file exists $sysdir/vendor] { + Log " Storage tree is ready" + break + } + } + } + } + after 500 + incr counter +} +if {$counter == 20} { + Log "SCSI tree not found; you may want to check if this path/file exists:" + Log "$sysdir/vendor\n" + return 0 +} + +Log "Read SCSI values ..." +foreach attr {vendor model rev} { + if [file exists $sysdir/$attr] { + set rc [open $sysdir/$attr r] + set scsi($attr) [read -nonewline $rc] + close $rc + } else { + set scsi($attr) "" + Log "Warning: SCSI attribute \"$attr\" not found." + } +} +return 1 + +} +# end of proc {ReadSCSIAttrs} + + +proc {ReadUSBAttrs} {dir args} { + +global usb + +set attrList {idVendor idProduct bConfigurationValue manufacturer product serial devnum busnum bNumConfigurations} +set mandatoryList {idVendor idProduct bNumConfigurations} +set result 1 +if {$args != ""} { + lappend attrList "$args/bInterfaceClass" + lappend mandatoryList "$args/bInterfaceClass" +} +foreach attr $attrList { + if [file exists $dir/$attr] { + set rc [open $dir/$attr r] + set usb($attr) [string trim [read -nonewline $rc]] + close $rc + } else { + set usb($attr) "" + if {[lsearch $mandatoryList $attr] > -1} { + set result 0 + } + if {$attr == "serial"} {continue} + Log " Warning: USB attribute \"$attr\" not found" + } +} +return $result + +} +# end of proc {ReadUSBAttrs} + + +proc {MatchDevice} {config} { + +global scsi usb match + +set devinfo [file tail $config] +set infoList [split $devinfo :] +set stringList [lrange $infoList 2 end] +if {[llength $stringList] == 0} {return 1} + +foreach teststring $stringList { + if {$teststring == "?"} {return 0} + set tokenList [split $teststring =] + set id [lindex $tokenList 0] + set matchstring [lindex $tokenList 1] + set blankstring "" + regsub -all {_} $matchstring { } blankstring + Log "match $match($id)" + Log " string1 (exact): $matchstring" + Log " string2 (blanks): $blankstring" + Log " device string: [set $match($id)]" + if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} { + return 0 + } +} +return 1 + +} +# end of proc {MatchDevice} + + +proc {ParseGlobalConfig} {} { + +global flags +set configFile "" +set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch] +foreach cfg $places { + if [file exists $cfg] { + set configFile $cfg + break + } +} +if {$configFile == ""} {return} + +set rc [open $configFile r] +while {![eof $rc]} { + gets $rc line + if [regexp {^#} [string trim $line]] {continue} + if [regexp {DisableMBIMGlobal\s*=\s*([^\s]+)} $line d val] { + if [regexp -nocase {1|yes|true} $val] { + set flags(nombim) 1 + } + } + if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] { + if [regexp -nocase {1|yes|true} $val] { + set flags(noswitching) 1 + } + } + if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] { + if [regexp -nocase {0|no|false} $val] { + set flags(logging) 0 + } + } + if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] { + if [regexp {\d+} $val] { + set flags(stordelay) $val + } + } + if [regexp {HuaweiAltModeGlobal\s*=\s*([^\s]+)} $line d val] { + if [regexp -nocase {1|yes|true} $val] { + set flags(althuawei) 1 + } + } + +} +return "Use global config file: $configFile" + +} +# end of proc {ParseGlobalConfig} + + +proc ParseDeviceConfig {cfg} { + +global config +set config(WaitBefore) "" +set config(TargetVendor) "" +set config(TargetProduct) "" +set config(TargetClass) "" +set config(Configuration) "" +set config(NoMBIMCheck) 0 +set config(PantechMode) 0 +set config(CheckSuccess) 20 + +foreach pname [lsort [array names config]] { + if [regexp -line "^\[^# \]*?$pname.*?= *(0x(\\w+)|\"(\[0-9a-fA-F,\]+)\"|(\[0-9\]+)) *\$" $cfg d config($pname)] { +# Log "config: $pname set to $config($pname)" + } +} + +set config(WaitBefore) [string trimleft $config(WaitBefore) 0] + +} +# end of proc {ParseDeviceConfig} + + +proc ConfigGet {command config} { + +global setup usb flags + +switch $command { + + conflist { + # Unpackaged configs first; sorting is essential for priority + set configList [lsort -decreasing [glob -nocomplain $setup(dbdir_etc)/$config*]] + set configList [concat $configList [lsort -decreasing [glob -nocomplain $setup(dbdir)/$config*]]] + eval lappend configList [glob -nocomplain $setup(dbdir)/$usb(idVendor):#$flags(os)] + if [file exists $setup(dbdir)/configPack.tar.gz] { + Log "Found packed config collection $setup(dbdir)/configPack.tar.gz" + if [catch {set packedList [exec tar -tzf $setup(dbdir)/configPack.tar.gz 2>/dev/null]} err] { + Log "Error: problem opening config package; tar returned\n $err" + return {} + } + set packedList [split $packedList \n] + set packedConfigList [lsort -decreasing [lsearch -glob -all -inline $packedList $config*]] + lappend packedConfigList [lsearch -inline $packedList $usb(idVendor):#$flags(os)] + # Now add packaged configs with a mark, again sorted for priority + foreach packedConfig $packedConfigList { + lappend configList "pack/$packedConfig" + } + } + return $configList + } + conffile { + if [regexp {^pack/} $config] { + set config [regsub {pack/} $config {}] + Log "Extract config $config from collection $setup(dbdir)/configPack.tar.gz" + set configContent [exec tar -xzOf $setup(dbdir)/configPack.tar.gz $config 2>/dev/null] + } else { + if [regexp [list $setup(dbdir_etc)] $config] { + Log "Use config file from override folder $setup(dbdir_etc)" + SysLog "usb_modeswitch: use overriding config file $config; make sure this is intended" + SysLog "usb_modeswitch: please report any new or corrected settings; otherwise, check for outdated files" + } + set rc [open $config r] + set configContent [read $rc] + close $rc + } + return $configContent + } +} + +} +# end of proc {ConfigGet} + +proc {Log} {msg} { + +global flags device loginit + +if {$flags(logging) == 0} {return} + +if $flags(logwrite) { + if [string length $loginit] { + exec echo "\nUSB_ModeSwitch log from [clock format [clock seconds]]" >/var/log/usb_modeswitch_$device + exec echo "$loginit" >>/var/log/usb_modeswitch_$device + set loginit "" + } + exec echo $msg >>/var/log/usb_modeswitch_$device +} else { + append loginit "\n$msg" +} + +} +# end of proc {Log} + + +# Writing the log file and exit +proc {SafeExit} {} { + +global flags +set flags(logwrite) 1 +Log "" +exit + +} +# end of proc {SafeExit} + + +proc {SymLinkName} {path} { +global device flags + +proc {hasInterrupt} {ifDir} { + if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} { + Log " no ttyUSB interface - skip endpoint check" + return 0 + } + foreach epDir [glob -nocomplain $ifDir/ep_*] { + set e [file tail $epDir] + Log " check $e ..." + if [file exists $epDir/type] { + set rc [open $epDir/type r] + set type [read $rc] + close $rc + if [regexp {Interrupt} $type] { + Log " $e has interrupt transfer type" + return 1 + } + } + } + return 0 +} + +set loginit "usb_modeswitch called with --symlink-name\n parameter: $path\n" + +# In case the device path is returned as /class/tty/ttyUSB, +# get the USB device path from linked tree "device" +set linkpath /sys$path/device +if [file exists $linkpath] { + if {[file type $linkpath] == "link"} { + set rawpath [file readlink $linkpath] + set trimpath [regsub -all {\.\./} $rawpath {}] + if [file isdirectory /sys/$trimpath] { + append loginit "\n Use path $path\n" + set path /$trimpath + } + } +} +if {![regexp {([0-9]+-[0-9]+[\.0-9]*:[^/]*).*(ttyUSB[0-9]+)} $path d myDev myPort]} { + if $flags(logging) { + set device [clock clicks] + set flags(logwrite) 1 + Log "$loginit\nThis is not a ttyUSB port. Abort" + } + return "" +} + +set device ttyUSB_$myDev +set flags(logwrite) 1 +Log "$loginit\nMy name is $myPort\n" + +if {![regexp {(.*?[0-9]+)\.([0-9]+)/ttyUSB} /sys$path d ifRoot ifNum]} { + Log "Could not find interface in path\n $path. Abort" + return "" +} + +set ifDir $ifRoot.$ifNum + +Log "Check my endpoints ...\n in $ifDir" +if [hasInterrupt $ifDir] { + Log "\n--> I am an interrupt port" + set rightPort 1 +} else { + Log "\n--> I am not an interrupt port\n" + set rightPort 0 +} + +# There are devices with more than one interrupt interface. +# Assume that the lowest of these is usable. Check all +# possible lower interfaces + +if { $rightPort && ($ifNum > 0) } { + Log "\nLook for lower ports with interrupt endpoints" + for {set i 0} {$i < $ifNum} {incr i} { + set ifDir $ifRoot.$i + Log " in ifDir $ifDir ..." + if [hasInterrupt $ifDir] { + Log "\n--> found an interrupt interface below me\n" + set rightPort 0 + break + } + } +} +if {$rightPort == 0} { + Log "Return empty name and exit" + return "" +} + +Log "\n--> No interrupt interface below me\n" + +cd /dev +set idx 2 +set symlinkName "gsmmodem" +while {$idx < 256} { + if {![file exists $symlinkName]} { + set placeholder [open /dev/$symlinkName w] + close $placeholder + break + } + set symlinkName gsmmodem$idx + incr idx +} +if {$idx == 256} {return ""} + +Log "Return symlink name \"$symlinkName\" and exit" +return $symlinkName + +} +# end of proc {SymLinkName} + + +# Add USB ID to list of devices needing later treatment +proc {AddToList} {name id} { + +set listfile /var/lib/usb_modeswitch/$name +if [file exists $listfile] { + set rc [open $listfile r] + set buffer [read $rc] + close $rc + if [string match *$id* $buffer] { + return + } + set idList [split [string trim $buffer] \n] +} +lappend idList $id +set buffer [join $idList "\n"] +if [catch {set lc [open $listfile w]}] {return} +puts $lc $buffer +close $lc + +} +# end of proc {AddToList} + + +proc {CheckSuccess} {devdir} { + +global config usb flags + +# For Cisco AM10, target device not on same port +if {$usb(idVendor) == "1307" && $usb(idProduct) == "1169"} { + set devdir [string range $devdir 0 end-1]2 +} +set ifdir [file tail [IfDir 0 $devdir]] +if {[string length $config(TargetClass)] || [string length $config(Configuration)]} { + set config(TargetVendor) $usb(idVendor) + set config(TargetProduct) $usb(idProduct) +} +Log "Check success of mode switch for max. $config(CheckSuccess) seconds ..." + +set expected 1 +for {set i 1} {$i <= $config(CheckSuccess)} {incr i} { + after 1000 + if {![file isdirectory $devdir]} { + Log " Wait for device file system ($i sec.) ..." + continue + } else { + Log " Read attributes ..." + } + set ifdir [IfDir 0 $devdir] + if {$ifdir == ""} {continue} + set ifdir [file tail $ifdir] + if {![ReadUSBAttrs $devdir $ifdir]} { + Log " Essential attributes are missing, continue wait ..." + continue + } + if [string length $config(Configuration)] { + if {$usb(bConfigurationValue) != $config(Configuration)} {continue} + } + if [string length $config(TargetClass)] { + if {![regexp -nocase $usb($ifdir/bInterfaceClass) $config(TargetClass)]} { + if {$config(class) != $usb($ifdir/bInterfaceClass} { + set expected 0 + } else {continue} + } + } + if {![regexp -nocase $usb(idVendor) $config(TargetVendor)]} { + if {![regexp -nocase $usb(idVendor) $config(vendor)]} { + set expected 0 + } else {continue} + } + if {![regexp -nocase $usb(idProduct) $config(TargetProduct)]} { + if {![regexp -nocase $usb(idProduct) $config(product)]} { + set expected 0 + } else {continue} + } + # Arriving here means that device attributes have changed + if $expected { + Log " All attributes matched" + } else { + if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] { + Log " idProduct has changed after generic mode-switch, assume success" + } else { + if [regexp {HuaweiAltMode} $flags(config)] { + Log " Alternative target attributes found, assume success" + } else { + Log " Attributes are different but target values are unexpected:" + LogAttributes + } + } + } + break +} +if {$i > 20} {return 0} else {return 1} + +} +# end of proc {CheckSuccess} + + +proc {IfDir} {iface devdir} { + +set allfiles [glob -nocomplain $devdir/*] +set files [glob -nocomplain $devdir/*.$iface] +if {[llength $files] == 0} { + return "" +} +set ifdir [lindex $files 0] +if {![file isdirectory $ifdir]} { + return "" +} +return $ifdir + +} +# end of proc {IfDir} + +proc {IfClass} {iface devdir} { + +set ifdir [IfDir $iface $devdir] + +if {![file exists $ifdir/bInterfaceClass]} { + return -1 +} +set rc [open $ifdir/bInterfaceClass r] +set c [read $rc] +close $rc +return [string trim $c] + +} +# end of proc {IfClass} + + +proc {SysLog} {msg} { + +global flags +if {![info exists flags(logger)]} { + set flags(logger) "" + foreach fn {/bin/logger /usr/bin/logger} { + if [file exists $fn] { + set flags(logger) $fn + } + } + Log "Logger is $flags(logger)" +} +if {$flags(logger) == ""} { + Log "Can't add system message, no syslog helper found" + return +} +catch {exec $flags(logger) -p syslog.notice "$msg" 2>/dev/null} + +} +# end of proc {SysLog} + +proc {SetStorageDelay} {secs} { + +Log "Adjust delay for USB storage devices ..." +set attrib /sys/module/usb_storage/parameters/delay_use +if {![file exists $attrib]} { + Log "Error: could not find delay_use attribute" + return +} +if [catch {set ch [open $attrib r+]} err] { + Log "Error: could not access delay_use attribute: $err" + return +} +if {[read $ch] < $secs} { + seek $ch 0 start + puts -nonewline $ch $secs + Log " Delay set to $secs seconds\n" +} else { + Log " Current value is higher than $secs. Leave it alone\n" +} +close $ch + +} +# end of proc {SetStorageDelay} + +proc {CheckMBIM} {} { + +set kversion [exec uname -r] +if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/cdc_mbim*]] {return 1} +if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1} +return 0 + +} + +proc {CheckQMI} {} { + +set kversion [exec uname -r] +if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/qmi_wwan*]] {return 1} +if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1} +return 0 + +} + +proc {PantechAutoSwitch} {} { + +global config flags +if {$config(PantechMode) == 3} {return 1} +if {$config(PantechMode) == 1} { + if {"$config(vendor):$config(product)" == "10a9:6080"} { + set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=2"] + Log " PantechMode changed to 2" + return 0 + } elseif [CheckQMI] { + set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=4"] + Log " PantechMode changed to 4" + return 0 + } else { + return 1 + } +} else {return 0} + +} + +proc {LogAttributes} {} { + +global flags usb +if $flags(logging) { + set attrList {idVendor idProduct bConfigurationValue manufacturer product serial} + foreach attr [lsort [array names usb]] { + Log " [format %-26s $attr:] $usb($attr)" + } +} + +} + +proc {HasFF} {devdir} { + +set i 0 +while {[set dir [IfDir $i $devdir]] != ""} { + set c [exec cat $dir/bInterfaceClass] + if {$c == "ff"} {return 1} + incr i +} +return 0 + +} + + +# The actual entry point +Main $argv $argc diff --git a/usb_modeswitch@.service b/usb_modeswitch@.service new file mode 100644 index 0000000..f74a8bf --- /dev/null +++ b/usb_modeswitch@.service @@ -0,0 +1,8 @@ +[Unit] +Description=USB_ModeSwitch_%i + +[Service] +Type=oneshot +ExecStart=/usr/sbin/usb_modeswitch_dispatcher --switch-mode %i +#ExecStart=/bin/echo %i + diff --git a/usb_modeswitch_dispatcher.1 b/usb_modeswitch_dispatcher.1 new file mode 100644 index 0000000..5b826d4 --- /dev/null +++ b/usb_modeswitch_dispatcher.1 @@ -0,0 +1,21 @@ +.TH "USB_MODESWITCH_DISPATCHER" "1" +.SH "NAME" +usb_modeswitch_dispatcher - Linux wrapper for usb_modeswitch (not intended for direct invocation) +.SH "SYNOPSIS" +.PP +\fBusb_modeswitch_dispatcher\fR +.SH "DESCRIPTION" +.PP +usb_modeswitch_dispatcher will do detailed device checking and will subsequently +use the Linux-independent usb_modeswitch binary together with the selected device +config file to switch the mode of certain USB devices. +.PP +If no drivers are taking care of the device after the mode switch, the dispatcher +will try to load and bind the "option" serial driver to any USB interfaces with +class 0xff, in order to make the device useable in case it is not recognized by that +driver yet. This may or may not work. +.PP +This program is called by udev and is not supposed to be called directly +by the user. +.SH SEE ALSO +.BR usb_modeswitch(1). |