usb_modeswitch ver. 2.3.0 with data package 2016-01-12
[tomato.git] / release / src / router / usbmodeswitch / usb_modeswitch.tcl
blob0a71565c68a2708d4def320da9c8386572d248fe
1 #!/usr/bin/tclsh
3 # Wrapper (tcl) for usb_modeswitch, called from
4 # /lib/udev/rules.d/40-usb_modeswitch.rules
5 # (part of data pack "usb-modeswitch-data") via
6 # /lib/udev/usb_modeswitch
8 # Does ID check on newly discovered USB devices and calls
9 # the mode switching program with the matching parameter
10 # file from /usr/share/usb_modeswitch
12 # Part of usb-modeswitch-2.3.0 package
13 # (C) Josua Dietze 2009-2016
15 set arg0 [lindex $argv 0]
16 if [regexp {\.tcl$} $arg0] {
17 if [file exists $arg0] {
18 set argv [lrange $argv 1 end]
19 source $arg0
20 exit
24 # Setting of these switches is done in the global config
25 # file (/etc/usb_modeswitch.conf) if available
27 set flags(logging) 1
28 set flags(noswitching) 0
29 set flags(stordelay) 0
30 set flags(logwrite) 0
31 # also settable in device config files
32 set flags(nombim) 0
34 # Execution starts at file bottom
36 proc {Main} {argv argc} {
38 global scsi usb config match device flags setup devdir loginit
40 set flags(config) ""
41 Log "[ParseGlobalConfig]"
43 # The facility to add a symbolic link pointing to the
44 # ttyUSB port which provides interrupt transfer, i.e.
45 # the port to connect through.
46 # Will check for interrupt endpoint in ttyUSB port (lowest if
47 # there is more than one); if found, return "gsmmodem[n]" name
48 # to udev for symlink creation
50 # This is run once for every port of LISTED devices by
51 # a udev rule
53 if {[lindex $argv 0] == "--symlink-name"} {
54 puts -nonewline [SymLinkName [lindex $argv 1]]
55 SafeExit
58 if {[lindex $argv 0] == "--switch-systemd"} {
59 set argList [split [lindex $argv 1] _]
60 Log "\nStarted via systemd"
61 } else {
62 if {[lindex $argv 0] == "--switch-upstart"} {
63 Log "\nStarted via upstart"
65 set argList [split [lindex $argv 1] /]
67 if [string length [lindex $argList 1]] {
68 set device [lindex $argList 1]
69 } else {
70 set device "noname"
72 if {$flags(stordelay) > 0} {
73 SetStorageDelay $flags(stordelay)
76 Log "Raw args from udev: [lindex $argv 1]\n"
78 if {$device == "noname"} {
79 Log "\nNo data from udev. Exit"
80 SafeExit
83 if {![regexp -- {--switch-} [lindex $argv 0]]} {
84 Log "\nNo command given. Exit"
85 SafeExit
88 set setup(dbdir) /usr/share/usb_modeswitch
89 set setup(dbdir_etc) /etc/usb_modeswitch.d
92 if {![file exists $setup(dbdir)] && ![file exists $setup(dbdir_etc)]} {
93 Log "\nError: no config database found in /usr/share or /etc. Exit"
94 SafeExit
96 set bindir /usr/sbin
98 set devList1 {}
99 set devList2 {}
102 # arg 0: the bus id for the device (udev: %b), often ommitted
103 # arg 1: the "kernel name" for the device (udev: %k)
105 # Used to determine the top directory for the device in sysfs
107 set ifChk 0
108 if {[string length [lindex $argList 0]] == 0} {
109 if {[string length [lindex $argList 1]] == 0} {
110 Log "No device number values given from udev! Exit"
111 SafeExit
112 } else {
113 if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
114 if {![regexp {([0-9]+-[0-9]+\.?[0-9]*.*)} [lindex $argList 1] d dev_top]} {
115 Log "Could not determine device dir from udev values! Exit"
116 SafeExit
120 } else {
121 set dev_top [lindex $argList 0]
122 regexp {(.*?):} $dev_top d dev_top
125 set devdir /sys/bus/usb/devices/$dev_top
126 if {![file isdirectory $devdir]} {
127 Log "Top device directory not found ($devdir)! Exit"
128 SafeExit
130 Log "Use top device dir $devdir"
132 set iface 0
133 Log "Check class of first interface ..."
134 set config(class) [IfClass 0 $devdir]
135 if {$config(class) < 0} {
136 Log " No access to interface 0. Exit"
137 SafeExit
139 Log " Interface 0 class is $config(class)."
141 set ifdir [file tail [IfDir $iface $devdir]]
142 regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface
144 set flags(logwrite) 1
146 # Mapping of the short string identifiers (in the config
147 # file names) to the long name used here
149 # If we need them it's a snap to add new attributes here!
151 set match(sVe) scsi(vendor)
152 set match(sMo) scsi(model)
153 set match(sRe) scsi(rev)
154 set match(uMa) usb(manufacturer)
155 set match(uPr) usb(product)
156 set match(uSe) usb(serial)
159 # Now reading the USB attributes
160 if {![ReadUSBAttrs $devdir]} {
161 Log "USB attributes not found in sysfs tree. Exit"
162 SafeExit
164 set config(vendor) $usb(idVendor)
165 set config(product) $usb(idProduct)
168 if $flags(logging) {
169 Log "\n----------------\nUSB values from sysfs:"
170 foreach attr {manufacturer product serial} {
171 Log " $attr\t$usb($attr)"
173 Log "----------------"
176 if $flags(noswitching) {
177 SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"
178 Log "\nSwitching globally disabled. Exit"
179 SafeExit
182 if {$usb(bNumConfigurations) == "1"} {
183 set configParam "-u -1"
184 Log "bNumConfigurations is 1 - don't check for active configuration"
185 } else {
186 set configParam ""
189 # Check (and switch) for operating system if Huawei device present
191 set flags(os) "linux"
192 if {$usb(idVendor) == "12d1" && [regexp -nocase {android} [exec cat /proc/version]]} {
193 set flags(os) "android"
195 if {$flags(os) == "android"} {
196 set configList [ConfigGet conflist $usb(idVendor):#android]
197 } else {
198 set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)]
201 if {[llength $configList] == 0} {
202 Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exit"
203 SafeExit
205 Log "ConfigList: $configList"
207 # Check if there is more than one config file for this USB ID,
208 # which would make an attribute test necessary. If so, check if
209 # SCSI values are needed
211 set scsiNeeded 0
212 if {[llength $configList] > 1} {
213 if [regexp {:s} $configList] {
214 set scsiNeeded 1
217 if $scsiNeeded {
218 if [ReadSCSIAttrs $devdir:$iface] {
219 Log "----------------\nSCSI values from sysfs:"
220 foreach attr {vendor model rev} {
221 Log " $attr\t$scsi($attr)"
223 Log "----------------"
224 } else {
225 Log "Could not get SCSI attributes, exclude devices with SCSI match"
227 } else {
228 Log "SCSI attributes not needed, move on"
231 # General wait - some devices need this
232 after 500
234 # Now check for a matching config file. Matching is done
235 # by MatchDevice
237 set report {}
238 foreach mconfig $configList {
240 # skipping installer leftovers like "*.rpmnew"
241 if [regexp {\.(dpkg|rpm)} $mconfig] {continue}
243 Log "Check config: $mconfig"
244 if [MatchDevice $mconfig] {
245 Log "! matched. Read config data"
246 # set flags(config) $mconfig
247 if [string length $usb(busnum)] {
248 set busParam "-b [string trimleft $usb(busnum) 0]"
249 set devParam "-g [string trimleft $usb(devnum) 0]"
250 } else {
251 set busParam ""
252 set devParam ""
254 set flags(config) [ConfigGet conffile $mconfig]
255 ParseDeviceConfig $flags(config)
256 if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
257 Log "Note: Using generic manufacturer configuration for \"$flags(os)\""
259 if $flags(nombim) {
260 set config(NoMBIMCheck) 1
262 if {$config(WaitBefore) != ""} {
263 Log "Delay time of $config(WaitBefore) seconds"
264 append config(WaitBefore) "000"
265 after $config(WaitBefore)
266 Log " wait is over, start mode switch"
268 if {$config(NoMBIMCheck)==0 && $usb(bNumConfigurations) > 1} {
269 Log "Device may have an MBIM configuration, check driver ..."
270 if [CheckMBIM] {
271 Log " driver for MBIM devices is available"
272 Log "Find MBIM configuration number ..."
273 if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] {
274 Log "Error when trying to find MBIM configuration, switch to legacy modem mode"
275 } else {
276 set cfgno [string trim $cfgno]
277 if {$cfgno > 0} {
278 set config(Configuration) $cfgno
279 set config(DriverModule) ""
280 set flags(config) "Configuration=$cfgno"
281 } else {
282 Log " No MBIM configuration found, switch to legacy modem mode"
285 } else {
286 Log " no MBIM driver found, switch to legacy modem mode"
289 if [PantechAutoSwitch] {
290 Log "Waiting for Pantech auto-modeswitch"
291 set report "ok:busdev"
292 break
294 UnbindDriver $devdir $ifdir
295 # Now we are actually switching
296 if $flags(logging) {
297 Log "Command to be run:\nusb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f \$flags(config)"
298 set report [exec /usr/sbin/usb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]
299 Log "\nVerbose debug output of usb_modeswitch and libusb follows"
300 Log "(Note that some USB errors are to be expected in the process)"
301 Log "--------------------------------"
302 Log $report
303 Log "--------------------------------"
304 Log "(end of usb_modeswitch output)\n"
305 } else {
306 set report [exec /usr/sbin/usb_modeswitch -Q -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]
308 break
309 } else {
310 Log "* no match, don't use this config"
314 # Switching is complete; success checking was either
315 # done by usb_modeswitch and logged via syslog OR bus/dev
316 # parameter were used; then we do check for success HERE
318 if {$config(Configuration) != ""} {
319 set ifdir [regsub {(\d):\d+\.0} $ifdir "\\1:$config(Configuration).0"]
322 if [regexp {ok:busdev} $report] {
323 if [CheckSuccess $devdir] {
324 Log "Mode switching was successful, found $usb(idVendor):$usb(idProduct) ($usb(manufacturer): $usb(product))"
325 SysLog "usb_modeswitch: switched to $usb(idVendor):$usb(idProduct) on [format %03d $usb(busnum)]/[format %03d $usb(devnum)]"
326 } else {
327 Log "\nTarget config not matching - current values are"
328 LogAttributes
329 Log "\nMode switching may have failed. Exit"
330 SafeExit
332 } else {
333 if {![file isdirectory $devdir]} {
334 Log "Device directory in sysfs is gone! Something went wrong, abort"
335 SafeExit
337 if {![regexp {ok:} $report]} {
338 Log "\nCore program reported switching failure. Exit"
339 SafeExit
341 # Give the device another second if it's not fully back yet
342 if {![file exists $devdir/idProduct]} {
343 after 1000
345 ReadUSBAttrs $devdir $ifdir
348 # Checking for bound drivers if there is an interface with class 0xff
350 if {$config(DriverModule) != "" && [regexp {ok:} $report]} {
351 if [HasFF $devdir] {
352 AddToList link_list $usb(idVendor):$usb(idProduct)
353 } else {
354 set config(DriverModule) ""
355 Log " No vendor-specific class found, skip driver check"
359 # If module is set (it is by default), driver shall be loaded.
360 # If not, then NoDriverLoading is active
362 if {$config(DriverModule) != ""} {
363 if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
364 if {![regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)]} {
365 Log "No target vendor/product ID found or given, can't continue. Abort"
366 SafeExit
369 # wait for any drivers to bind automatically
370 after 1500
371 Log "Now check for bound driver ..."
372 if {![file exists $devdir/$ifdir/driver]} {
373 Log " no driver has bound to interface 0 yet"
375 # If device is known, the sh wrapper will take care, else:
376 if {[InBindList $usb(idVendor):$usb(idProduct)] == 0} {
377 Log "Device is not in \"bind_list\" yet, bind it now"
379 # Load driver
380 CheckDriverBind $usb(idVendor) $usb(idProduct)
382 # Old/slow systems may take a while to create the devices
383 set counter 0
384 while {![file exists $devdir/$ifdir/driver]} {
385 if {$counter == 14} {break}
386 after 500
387 incr counter
389 if {$counter == 14} {
390 Log " driver binding failed"
391 } else {
392 Log " driver was bound to the device"
393 AddToList bind_list $usb(idVendor):$usb(idProduct)
396 } else {
397 Log " driver has bound, device is known"
398 if {[llength [glob -nocomplain $devdir/$ifdir/ttyUSB*]] > 0} {
399 AddToList link_list $usb(idVendor):$usb(idProduct)
402 } else {
403 # Just in case "NoDriverLoading" was added after the first bind
404 RemoveFromBindList $usb(idVendor):$usb(idProduct)
407 if [regexp {ok:$} $report] {
408 # "NoDriverLoading" was set
409 Log "No driver check or bind for this device"
412 # In newer kernels there is a switch to avoid the use of a device
413 # reset (e.g. from usb-storage) which would possibly switch back
414 # a mode-switching device to initial mode
415 if [regexp {ok:} $report] {
416 Log "Check for AVOID_RESET_QUIRK kernel attribute"
417 if [file exists $devdir/avoid_reset_quirk] {
418 if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] {
419 Log " Error setting the attribute: $err"
420 } else {
421 Log " AVOID_RESET_QUIRK activated"
423 } else {
424 Log " not present in this kernel"
428 Log "\nAll done, exit\n"
429 SafeExit
432 # end of proc {Main}
435 proc {ReadSCSIAttrs} {topdir} {
437 global scsi
438 set counter 0
439 set sysdir $topdir
440 Log "Check storage tree in sysfs ..."
441 while {$counter < 20} {
442 Log " loop $counter/20"
443 if {![file isdirectory $sysdir]} {
444 # Device is gone. Unplugged? Switched by kernel?
445 Log " sysfs device tree is gone; abort SCSI value check"
446 return 0
448 # Searching the storage/SCSI tree; might take a while
449 if {[set dirList [glob -nocomplain $topdir/host*]] != ""} {
450 set sysdir [lindex $dirList 0]
451 if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
452 set sysdir [lindex $dirList 0]
453 regexp {.*target(.*)} $sysdir d subdir
454 if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
455 set sysdir [lindex $dirList 0]
456 if [file exists $sysdir/vendor] {
457 Log " Storage tree is ready"
458 break
463 after 500
464 incr counter
466 if {$counter == 20} {
467 Log "SCSI tree not found; you may want to check if this path/file exists:"
468 Log "$sysdir/vendor\n"
469 return 0
472 Log "Read SCSI values ..."
473 foreach attr {vendor model rev} {
474 if [file exists $sysdir/$attr] {
475 set rc [open $sysdir/$attr r]
476 set scsi($attr) [read -nonewline $rc]
477 close $rc
478 } else {
479 set scsi($attr) ""
480 Log "Warning: SCSI attribute \"$attr\" not found."
483 return 1
486 # end of proc {ReadSCSIAttrs}
489 proc {ReadUSBAttrs} {dir args} {
491 global usb
493 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial devnum busnum bNumConfigurations}
494 set mandatoryList {idVendor idProduct bNumConfigurations}
495 set result 1
496 if {$args != ""} {
497 lappend attrList "$args/bInterfaceClass"
498 lappend mandatoryList "$args/bInterfaceClass"
500 foreach attr $attrList {
501 if [file exists $dir/$attr] {
502 set rc [open $dir/$attr r]
503 set usb($attr) [string trim [read -nonewline $rc]]
504 close $rc
505 } else {
506 set usb($attr) ""
507 if {[lsearch $mandatoryList $attr] > -1} {
508 set result 0
510 if {$attr == "serial"} {continue}
511 Log " Warning: USB attribute \"$attr\" not found"
514 return $result
517 # end of proc {ReadUSBAttrs}
520 proc {MatchDevice} {config} {
522 global scsi usb match
524 set devinfo [file tail $config]
525 set infoList [split $devinfo :]
526 set stringList [lrange $infoList 2 end]
527 if {[llength $stringList] == 0} {return 1}
529 foreach teststring $stringList {
530 if {$teststring == "?"} {return 0}
531 set tokenList [split $teststring =]
532 set id [lindex $tokenList 0]
533 set matchstring [lindex $tokenList 1]
534 set blankstring ""
535 regsub -all {_} $matchstring { } blankstring
536 Log "match $match($id)"
537 Log " string1 (exact): $matchstring"
538 Log " string2 (blanks): $blankstring"
539 Log " device string: [set $match($id)]"
540 if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} {
541 return 0
544 return 1
547 # end of proc {MatchDevice}
550 proc {ParseGlobalConfig} {} {
552 global flags
553 set configFile ""
554 set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
555 foreach cfg $places {
556 if [file exists $cfg] {
557 set configFile $cfg
558 break
561 if {$configFile == ""} {return}
563 set rc [open $configFile r]
564 while {![eof $rc]} {
565 gets $rc line
566 if [regexp {^#} [string trim $line]] {continue}
567 if [regexp {DisableMBIMGlobal\s*=\s*([^\s]+)} $line d val] {
568 if [regexp -nocase {1|yes|true} $val] {
569 set flags(nombim) 1
570 } else {
571 set flags(nombim) 0
574 if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
575 if [regexp -nocase {1|yes|true} $val] {
576 set flags(noswitching) 1
579 if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
580 if [regexp -nocase {1|yes|true} $val] {
581 set flags(logging) 1
582 } else {
583 set flags(logging) 0
586 if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] {
587 if [regexp {\d+} $val] {
588 set flags(stordelay) $val
593 return "Use global config file: $configFile"
596 # end of proc {ParseGlobalConfig}
599 proc ParseDeviceConfig {cfg} {
601 global config
602 set config(DriverModule) ""
603 set config(DriverIDPath) ""
604 set config(WaitBefore) ""
605 set config(TargetVendor) ""
606 set config(TargetProduct) ""
607 set config(TargetClass) ""
608 set config(Configuration) ""
609 set config(NoMBIMCheck) 0
610 set config(PantechMode) 0
611 set config(CheckSuccess) 20
612 set loadDriver 1
614 foreach pname [lsort [array names config]] {
615 if [regexp -line "^\[^# \]*?$pname.*?= *(0x(\\w+)|\"(\[0-9a-fA-F,\]+)\"|(\[0-9\]+)) *\$" $cfg d config($pname)] {
616 # Log "config: $pname set to $config($pname)"
620 if [regexp -line {^[^#]*?NoDriverLoading.*?=.*?(1|yes|true).*?$} $cfg] {
621 set loadDriver 0
622 Log "config: NoDriverLoading is set to active"
625 # For general driver loading; TODO: add respective device names.
626 # Presently only useful for HSO devices (which are recounted now)
627 if $loadDriver {
628 if {$config(DriverModule) == ""} {
629 set config(DriverModule) "option"
630 set config(DriverIDPath) "/sys/bus/usb-serial/drivers/option1"
631 } else {
632 if {$config(DriverIDPath) == ""} {
633 set config(DriverIDPath) "/sys/bus/usb/drivers/$config(DriverModule)"
636 Log "Driver module is \"$config(DriverModule)\", ID path is $config(DriverIDPath)\n"
637 } else {
638 Log "Driver will not be handled by usb_modeswitch"
640 set config(WaitBefore) [string trimleft $config(WaitBefore) 0]
643 # end of proc {ParseDeviceConfig}
646 proc ConfigGet {command config} {
648 global setup usb flags
650 switch $command {
652 conflist {
653 # Unpackaged configs first; sorting is essential for priority
654 set configList [lsort -decreasing [glob -nocomplain $setup(dbdir_etc)/$config*]]
655 set configList [concat $configList [lsort -decreasing [glob -nocomplain $setup(dbdir)/$config*]]]
656 eval lappend configList [glob -nocomplain $setup(dbdir)/$usb(idVendor):#$flags(os)]
657 if [file exists $setup(dbdir)/configPack.tar.gz] {
658 Log "Found packed config collection $setup(dbdir)/configPack.tar.gz"
659 if [catch {set packedList [exec tar -tzf $setup(dbdir)/configPack.tar.gz 2>/dev/null]} err] {
660 Log "Error: problem opening config package; tar returned\n $err"
661 return {}
663 set packedList [split $packedList \n]
664 set packedConfigList [lsort -decreasing [lsearch -glob -all -inline $packedList $config*]]
665 lappend packedConfigList [lsearch -inline $packedList $usb(idVendor):#$flags(os)]
666 # Now add packaged configs with a mark, again sorted for priority
667 foreach packedConfig $packedConfigList {
668 lappend configList "pack/$packedConfig"
671 return $configList
673 conffile {
674 if [regexp {^pack/} $config] {
675 set config [regsub {pack/} $config {}]
676 Log "Extract config $config from collection $setup(dbdir)/configPack.tar.gz"
677 set configContent [exec tar -xzOf $setup(dbdir)/configPack.tar.gz $config 2>/dev/null]
678 } else {
679 if [regexp [list $setup(dbdir_etc)] $config] {
680 Log "Use config file from override folder $setup(dbdir_etc)"
681 SysLog "usb_modeswitch: use overriding config file $config; make sure this is intended"
682 SysLog "usb_modeswitch: please report any new or corrected settings; otherwise, check for outdated files"
684 set rc [open $config r]
685 set configContent [read $rc]
686 close $rc
688 return $configContent
693 # end of proc {ConfigGet}
695 proc {Log} {msg} {
697 global flags device loginit
699 if {$flags(logging) == 0} {return}
701 if $flags(logwrite) {
702 if [string length $loginit] {
703 exec echo "\nUSB_ModeSwitch log from [clock format [clock seconds]]" >/var/log/usb_modeswitch_$device
704 exec echo "$loginit" >>/var/log/usb_modeswitch_$device
705 set loginit ""
707 exec echo $msg >>/var/log/usb_modeswitch_$device
708 } else {
709 append loginit "\n$msg"
713 # end of proc {Log}
716 # Writing the log file and exit
717 proc {SafeExit} {} {
719 global flags
720 set flags(logwrite) 1
721 Log ""
722 exit
725 # end of proc {SafeExit}
728 proc {SymLinkName} {path} {
729 global device flags
731 proc {hasInterrupt} {ifDir} {
732 if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} {
733 Log " no ttyUSB interface - skip endpoint check"
734 return 0
736 foreach epDir [glob -nocomplain $ifDir/ep_*] {
737 set e [file tail $epDir]
738 Log " check $e ..."
739 if [file exists $epDir/type] {
740 set rc [open $epDir/type r]
741 set type [read $rc]
742 close $rc
743 if [regexp {Interrupt} $type] {
744 Log " $e has interrupt transfer type"
745 return 1
749 return 0
752 set loginit "usb_modeswitch called with --symlink-name\n parameter: $path\n"
754 # In case the device path is returned as /class/tty/ttyUSB,
755 # get the USB device path from linked tree "device"
756 set linkpath /sys$path/device
757 if [file exists $linkpath] {
758 if {[file type $linkpath] == "link"} {
759 set rawpath [file readlink $linkpath]
760 set trimpath [regsub -all {\.\./} $rawpath {}]
761 if [file isdirectory /sys/$trimpath] {
762 append loginit "\n Use path $path\n"
763 set path /$trimpath
767 if {![regexp {([0-9]+-[0-9]+[\.0-9]*:[^/]*).*(ttyUSB[0-9]+)} $path d myDev myPort]} {
768 if $flags(logging) {
769 set device [clock clicks]
770 set flags(logwrite) 1
771 Log "$loginit\nThis is not a ttyUSB port. Abort"
773 return ""
776 set device ttyUSB_$myDev
777 set flags(logwrite) 1
778 Log "$loginit\nMy name is $myPort\n"
780 if {![regexp {(.*?[0-9]+)\.([0-9]+)/ttyUSB} /sys$path d ifRoot ifNum]} {
781 Log "Could not find interface in path\n $path. Abort"
782 return ""
785 set ifDir $ifRoot.$ifNum
787 Log "Check my endpoints ...\n in $ifDir"
788 if [hasInterrupt $ifDir] {
789 Log "\n--> I am an interrupt port"
790 set rightPort 1
791 } else {
792 Log "\n--> I am not an interrupt port\n"
793 set rightPort 0
796 # There are devices with more than one interrupt interface.
797 # Assume that the lowest of these is usable. Check all
798 # possible lower interfaces
800 if { $rightPort && ($ifNum > 0) } {
801 Log "\nLook for lower ports with interrupt endpoints"
802 for {set i 0} {$i < $ifNum} {incr i} {
803 set ifDir $ifRoot.$i
804 Log " in ifDir $ifDir ..."
805 if [hasInterrupt $ifDir] {
806 Log "\n--> found an interrupt interface below me\n"
807 set rightPort 0
808 break
812 if {$rightPort == 0} {
813 Log "Return empty name and exit"
814 return ""
817 Log "\n--> No interrupt interface below me\n"
819 cd /dev
820 set idx 2
821 set symlinkName "gsmmodem"
822 while {$idx < 256} {
823 if {![file exists $symlinkName]} {
824 set placeholder [open /dev/$symlinkName w]
825 close $placeholder
826 break
828 set symlinkName gsmmodem$idx
829 incr idx
831 if {$idx == 256} {return ""}
833 Log "Return symlink name \"$symlinkName\" and exit"
834 return $symlinkName
837 # end of proc {SymLinkName}
840 # Load and bind driver (default "option")
842 proc {CheckDriverBind} {vid pid} {
843 global config
845 foreach fn {/sbin/modprobe /usr/sbin/modprobe} {
846 if [file exists $fn] {
847 set loader $fn
850 Log "Module loader is $loader"
852 set idfile $config(DriverIDPath)/new_id
853 if {![file exists $idfile]} {
854 if {$loader == ""} {
855 Log "Can't do anymore without module loader; get \"modtools\"!"
856 return
858 Log "\nTry to load module \"$config(DriverModule)\""
859 if [catch {set result [exec $loader -v $config(DriverModule)]} err] {
860 Log " Running \"$loader $config(DriverModule)\" gave an error:\n $err"
861 } else {
862 Log " Module was loaded successfully:\n$result"
864 } else {
865 Log "Module is active already"
867 set i 0
868 while {$i < 50} {
869 if [file exists $idfile] {
870 break
872 after 20
873 incr i
875 if {$i < 50} {
876 Log "Try to add ID to driver \"$config(DriverModule)\""
877 SysLog "usb_modeswitch: add device ID $vid:$pid to driver \"$config(DriverModule)\""
878 SysLog "usb_modeswitch: please report the device ID to the Linux USB developers!"
879 if [catch {exec echo "$vid $pid ff" >$idfile} err] {
880 Log " Error adding ID to driver:\n $err"
881 } else {
882 Log " ID added to driver; check for new devices in /dev"
884 } else {
885 Log " \"$idfile\" not found, check if kernel version is at least 2.6.27"
886 Log "Fall back to \"usbserial\""
887 set config(DriverModule) usbserial
888 Log "\nTry to unload driver \"usbserial\""
889 if [catch {exec $loader -r usbserial} err] {
890 Log " Running \"$loader -r usbserial\" gave an error:\n $err"
891 Log "No more fallbacks"
892 return
894 after 50
895 Log "\nTry to load driver \"usbserial\" with device IDs"
896 if [catch {set result [exec $loader -v usbserial vendor=0x$vid product=0x$pid]} err] {
897 Log " Running \"$loader usbserial\" gave an error:\n $err"
898 } else {
899 Log " Driver was loaded successfully:\n$result"
904 # end of proc {CheckDriverBind}
907 # Check if USB ID is listed as needing driver binding
908 proc {InBindList} {id} {
910 set listfile /var/lib/usb_modeswitch/bind_list
911 if {![file exists $listfile]} {return 0}
912 set rc [open $listfile r]
913 set buffer [read $rc]
914 close $rc
915 if [string match *$id* $buffer] {
916 Log "Found $id in bind_list"
917 return 1
918 } else {
919 Log "No $id in bind_list"
920 return 0
924 # end of proc {InBindList}
926 # Add USB ID to list of devices needing later treatment
927 proc {AddToList} {name id} {
929 set listfile /var/lib/usb_modeswitch/$name
930 if [file exists $listfile] {
931 set rc [open $listfile r]
932 set buffer [read $rc]
933 close $rc
934 if [string match *$id* $buffer] {
935 return
937 set idList [split [string trim $buffer] \n]
939 lappend idList $id
940 set buffer [join $idList "\n"]
941 if [catch {set lc [open $listfile w]}] {return}
942 puts $lc $buffer
943 close $lc
946 # end of proc {AddToList}
949 # Remove USB ID from bind list (NoDriverLoading is set)
950 proc {RemoveFromBindList} {id} {
952 set listfile /var/lib/usb_modeswitch/bind_list
953 if [file exists $listfile] {
954 set rc [open $listfile r]
955 set buffer [read $rc]
956 close $rc
957 set idList [split [string trim $buffer] \n]
958 } else {
959 return
961 set idx [lsearch $idList $id]
962 if {$idx > -1} {
963 set idList [lreplace $idList $idx $idx]
964 } else {
965 return
967 if {[llength $idList] == 0} {
968 file delete $listfile
969 return
971 set buffer [join $idList "\n"]
972 if [catch {set lc [open $listfile w]}] {return}
973 puts $lc $buffer
974 close $lc
977 # end of proc {RemoveFromBindList}
980 proc {CheckSuccess} {devdir} {
982 global config usb flags
984 # For Cisco AM10, target device not on same port
985 if {$usb(idVendor) == "1307" && $usb(idProduct) == "1169"} {
986 set devdir [string range $devdir 0 end-1]2
988 set ifdir [file tail [IfDir 0 $devdir]]
989 if {[string length $config(TargetClass)] || [string length $config(Configuration)]} {
990 set config(TargetVendor) $usb(idVendor)
991 set config(TargetProduct) $usb(idProduct)
993 Log "Check success of mode switch for max. $config(CheckSuccess) seconds ..."
995 set expected 1
996 for {set i 1} {$i <= $config(CheckSuccess)} {incr i} {
997 after 1000
998 if {![file isdirectory $devdir]} {
999 Log " Wait for device file system ($i sec.) ..."
1000 continue
1001 } else {
1002 Log " Read attributes ..."
1004 set ifdir [IfDir 0 $devdir]
1005 if {$ifdir == ""} {continue}
1006 set ifdir [file tail $ifdir]
1007 if {![ReadUSBAttrs $devdir $ifdir]} {
1008 Log " Essential attributes are missing, continue wait ..."
1009 continue
1011 if [string length $config(Configuration)] {
1012 if {$usb(bConfigurationValue) != $config(Configuration)} {continue}
1014 if [string length $config(TargetClass)] {
1015 if {![regexp $usb($ifdir/bInterfaceClass) $config(TargetClass)]} {
1016 if {$config(class) != $usb($ifdir/bInterfaceClass} {
1017 set expected 0
1018 } else {continue}
1021 if {![regexp $usb(idVendor) $config(TargetVendor)]} {
1022 if {![regexp $usb(idVendor) $config(vendor)]} {
1023 set expected 0
1024 } else {continue}
1026 if {![regexp $usb(idProduct) $config(TargetProduct)]} {
1027 if {![regexp $usb(idProduct) $config(product)]} {
1028 set expected 0
1029 } else {continue}
1031 # Arriving here means that device attributes have changed
1032 if $expected {
1033 Log " All attributes matched"
1034 } else {
1035 if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
1036 Log " idProduct has changed after generic mode-switch, assume success"
1037 } else {
1038 Log " Attributes are different but target values are unexpected:"
1039 LogAttributes
1042 break
1044 if {$i > 20} {return 0} else {return 1}
1047 # end of proc {CheckSuccess}
1050 proc {IfDir} {iface devdir} {
1052 set allfiles [glob -nocomplain $devdir/*]
1053 set files [glob -nocomplain $devdir/*.$iface]
1054 if {[llength $files] == 0} {
1055 return ""
1057 set ifdir [lindex $files 0]
1058 if {![file isdirectory $ifdir]} {
1059 return ""
1061 return $ifdir
1064 # end of proc {IfDir}
1066 proc {IfClass} {iface devdir} {
1068 set ifdir [IfDir $iface $devdir]
1070 if {![file exists $ifdir/bInterfaceClass]} {
1071 return -1
1073 set rc [open $ifdir/bInterfaceClass r]
1074 set c [read $rc]
1075 close $rc
1076 return [string trim $c]
1079 # end of proc {IfClass}
1082 proc {SysLog} {msg} {
1084 global flags
1085 if {![info exists flags(logger)]} {
1086 set flags(logger) ""
1087 foreach fn {/bin/logger /usr/bin/logger} {
1088 if [file exists $fn] {
1089 set flags(logger) $fn
1092 Log "Logger is $flags(logger)"
1094 if {$flags(logger) == ""} {
1095 Log "Can't add system message, no syslog helper found"
1096 return
1098 catch {exec $flags(logger) -p syslog.notice "$msg" 2>/dev/null}
1101 # end of proc {SysLog}
1103 proc {SetStorageDelay} {secs} {
1105 Log "Adjust delay for USB storage devices ..."
1106 set attrib /sys/module/usb_storage/parameters/delay_use
1107 if {![file exists $attrib]} {
1108 Log "Error: could not find delay_use attribute"
1109 return
1111 if [catch {set ch [open $attrib r+]} err] {
1112 Log "Error: could not access delay_use attribute: $err"
1113 return
1115 if {[read $ch] < $secs} {
1116 seek $ch 0 start
1117 puts -nonewline $ch $secs
1118 Log " Delay set to $secs seconds\n"
1119 } else {
1120 Log " Current value is higher than $secs. Leave it alone\n"
1122 close $ch
1125 # end of proc {SetStorageDelay}
1127 proc {CheckMBIM} {} {
1129 set kversion [exec uname -r]
1130 if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/cdc_mbim*]] {return 1}
1131 if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1132 return 0
1136 proc {CheckQMI} {} {
1138 set kversion [exec uname -r]
1139 if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/qmi_wwan*]] {return 1}
1140 if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1141 return 0
1145 proc {PantechAutoSwitch} {} {
1147 global config flags
1148 if {$config(PantechMode) == 3} {return 1}
1149 if {$config(PantechMode) == 1} {
1150 if {"$config(vendor):$config(product)" == "10a9:6080"} {
1151 set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=2"]
1152 Log " PantechMode changed to 2"
1153 return 0
1154 } elseif [CheckQMI] {
1155 set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=4"]
1156 Log " PantechMode changed to 4"
1157 return 0
1158 } else {
1159 return 1
1161 } else {return 0}
1165 proc UnbindDriver {devdir ifdir} {
1167 set att $devdir/$ifdir/driver/unbind
1168 if [file exists $att] {
1169 Log "Unbinding driver"
1170 exec echo -n "$ifdir" > $att
1175 proc {LogAttributes} {} {
1177 global flags usb
1178 if $flags(logging) {
1179 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial}
1180 foreach attr [lsort [array names usb]] {
1181 Log " [format %-26s $attr:] $usb($attr)"
1187 proc {HasFF} {devdir} {
1189 set i 0
1190 while {[set dir [IfDir $i $devdir]] != ""} {
1191 set c [exec cat $dir/bInterfaceClass]
1192 if {$c == "ff"} {return 1}
1193 incr i
1195 return 0
1200 # The actual entry point
1201 Main $argv $argc