usb_modeswitch: ver. 2.2.1 with data package 2015-01-15
[tomato.git] / release / src-rt-6.x.4708 / router / usbmodeswitch / usb_modeswitch.tcl
blobb1151819081d655dd558aff2f7391461b7beaa57
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.2.1 package
13 # (C) Josua Dietze 2009-2015
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) 0
28 set flags(noswitching) 0
29 set flags(stordelay) 0
31 # Execution starts at file bottom
33 proc {Main} {argv argc} {
35 global scsi usb config match device flags setup devdir loginit
37 set flags(config) ""
38 set flags(logwrite) 0
39 Log "[ParseGlobalConfig]"
41 # The facility to add a symbolic link pointing to the
42 # ttyUSB port which provides interrupt transfer, i.e.
43 # the port to connect through.
44 # Will check for interrupt endpoint in ttyUSB port (lowest if
45 # there is more than one); if found, return "gsmmodem[n]" name
46 # to udev for symlink creation
48 # This is run once for every port of LISTED devices by
49 # an udev rule
51 if {[lindex $argv 0] == "--symlink-name"} {
52 puts -nonewline [SymLinkName [lindex $argv 1]]
53 SafeExit
56 if {[lindex $argv 0] == "--switch-systemd"} {
57 set device [string trim [lindex $argv 1] "/-"]
58 set device [regsub {/} $device "-"]
59 set argList [list "" $device]
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] /]
66 if [string length [lindex $argList 1]] {
67 set device [lindex $argList 1]
68 } else {
69 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 # new udev rules file, got to check class of first interface
116 set ifChk 1
117 } else {
118 Log "Could not determine device dir from udev values! Exit"
119 SafeExit
123 } else {
124 set dev_top [lindex $argList 0]
125 regexp {(.*?):} $dev_top d dev_top
128 set devdir /sys/bus/usb/devices/$dev_top
129 if {![file isdirectory $devdir]} {
130 Log "Top device directory not found ($devdir)! Exit"
131 SafeExit
133 Log "Use top device dir $devdir"
135 set iface 0
136 if $ifChk {
137 Log "Check class of first interface ..."
138 set config(class) [IfClass 0]
139 if {$iface < 0} {
140 Log " No access to interface 0. Exit"
141 SafeExit
143 Log " Interface class is $config(class)."
144 if {$config(class) == "08" || $config(class) == "03"} {
145 } else {
146 Log "No install mode found. Aborting"
147 exit
150 set ifdir [file tail [IfDir $iface]]
151 regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface
153 set flags(logwrite) 1
155 # Mapping of the short string identifiers (in the config
156 # file names) to the long name used here
158 # If we need them it's a snap to add new attributes here!
160 set match(sVe) scsi(vendor)
161 set match(sMo) scsi(model)
162 set match(sRe) scsi(rev)
163 set match(uMa) usb(manufacturer)
164 set match(uPr) usb(product)
165 set match(uSe) usb(serial)
168 # Now reading the USB attributes
169 if {![ReadUSBAttrs $devdir]} {
170 Log "USB attributes not found in sysfs tree. Exit"
171 SafeExit
173 set config(vendor) $usb(idVendor)
174 set config(product) $usb(idProduct)
177 if $flags(logging) {
178 Log "\n----------------\nUSB values from sysfs:"
179 foreach attr {manufacturer product serial} {
180 Log " $attr\t$usb($attr)"
182 Log "----------------"
185 if $flags(noswitching) {
186 SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"
187 Log "\nSwitching globally disabled. Exit"
188 SafeExit
191 if {$usb(bNumConfigurations) == "1"} {
192 set configParam "-u -1"
193 Log "bNumConfigurations is 1 - don't check for active configuration"
194 } else {
195 set configParam ""
198 # Check (and switch) for operating system if Huawei device present
200 set flags(os) "linux"
201 if {$usb(idVendor) == "12d1" && [regexp -nocase {android} [exec cat /proc/version]]} {
202 set flags(os) "android"
204 if {$flags(os) == "android"} {
205 set configList [ConfigGet conflist $usb(idVendor):#android]
206 } else {
207 set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)]
210 if {[llength $configList] == 0} {
211 Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exit"
212 SafeExit
214 Log "ConfigList: $configList"
216 # Check if there is more than one config file for this USB ID,
217 # which would make an attribute test necessary. If so, check if
218 # SCSI values are needed
220 set scsiNeeded 0
221 if {[llength $configList] > 1} {
222 if [regexp {:s} $configList] {
223 set scsiNeeded 1
226 if $scsiNeeded {
227 if [ReadSCSIAttrs $devdir:$iface] {
228 Log "----------------\nSCSI values from sysfs:"
229 foreach attr {vendor model rev} {
230 Log " $attr\t$scsi($attr)"
232 Log "----------------"
233 } else {
234 Log "Could not get SCSI attributes, exclude devices with SCSI match"
236 } else {
237 Log "SCSI attributes not needed, move on"
240 # General wait - some devices need this
241 after 500
243 # Now check for a matching config file. Matching is done
244 # by MatchDevice
246 set report {}
247 foreach mconfig $configList {
249 # skipping installer leftovers like "*.rpmnew"
250 if [regexp {\.(dpkg|rpm)} $mconfig] {continue}
252 Log "Check config: $mconfig"
253 if [MatchDevice $mconfig] {
254 Log "! matched. Read config data"
255 set flags(config) $mconfig
256 if [string length $usb(busnum)] {
257 set busParam "-b [string trimleft $usb(busnum) 0]"
258 set devParam "-g [string trimleft $usb(devnum) 0]"
259 } else {
260 set busParam ""
261 set devParam ""
263 set configBuffer [ConfigGet conffile $mconfig]
264 ParseDeviceConfig $configBuffer
265 if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
266 Log "Note: Using generic manufacturer configuration for \"$flags(os)\""
268 if {$config(waitBefore) != ""} {
269 Log "Delay time of $config(waitBefore) seconds"
270 append config(waitBefore) "000"
271 after $config(waitBefore)
272 Log " wait is over, start mode switch"
274 if {$config(noMBIMCheck)==0 && $usb(bNumConfigurations) > 1} {
275 Log "Device may have an MBIM configuration, check driver ..."
276 if [CheckMBIM] {
277 Log " driver for MBIM devices is available"
278 Log "Find MBIM configuration number ..."
279 if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] {
280 Log "Error when trying to find MBIM configuration, switch to legacy modem mode"
281 } else {
282 set cfgno [string trim $cfgno]
283 if {$cfgno > 0} {
284 set config(Configuration) $cfgno
285 set config(driverModule) ""
286 set configBuffer "Configuration=$cfgno"
287 } else {
288 Log " No MBIM configuration found, switch to legacy modem mode"
291 } else {
292 Log " no MBIM driver found, switch to legacy modem mode"
296 # Now we are actually switching
297 if $flags(logging) {
298 Log "Command to be run:\nusb_modeswitch -W -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f \$configBuffer"
299 set report [exec /usr/sbin/usb_modeswitch -W -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$configBuffer" 2>@1]
300 Log "\nVerbose debug output of usb_modeswitch and libusb follows"
301 Log "(Note that some USB errors are to be expected in the process)"
302 Log "--------------------------------"
303 Log $report
304 Log "--------------------------------"
305 Log "(end of usb_modeswitch output)\n"
306 } else {
307 set report [exec /usr/sbin/usb_modeswitch -Q -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$configBuffer" 2>@1]
309 break
310 } else {
311 Log "* no match, don't use this config"
315 # Switching is complete; success checking was either
316 # done by usb_modeswitch and logged via syslog OR bus/dev
317 # parameter were used; then we do check for success HERE
319 if [regexp {ok:busdev} $report] {
320 if [CheckSuccess $devdir] {
321 Log "Mode switching was successful, found $usb(idVendor):$usb(idProduct) ($usb(manufacturer): $usb(product))"
322 SysLog "usb_modeswitch: switched to $usb(idVendor):$usb(idProduct) on [format %03d $usb(busnum)]/[format %03d $usb(devnum)]"
323 } else {
324 Log "\nTarget config not matching - current values are"
325 LogAttributes
326 Log "\nMode switching may have failed. Exit"
327 SafeExit
329 } else {
330 if {![file isdirectory $devdir]} {
331 Log "Device directory in sysfs is gone! Something went wrong, abort"
332 SafeExit
334 if {![regexp {ok:} $report]} {
335 Log "\nCore program reported switching failure. Exit"
336 SafeExit
338 # Give the device another second if it's not fully back yet
339 if {![file exists $devdir/idProduct]} {
340 after 1000
342 ReadUSBAttrs $devdir $ifdir
345 # Now checking for bound drivers (only for class 0xff)
347 if {$config(driverModule) != "" && $usb($ifdir/bInterfaceClass) != "" && [regexp {ok:} $report]} {
348 if {$usb($ifdir/bInterfaceClass) != "ff"} {
349 set config(driverModule) ""
350 Log " No vendor-specific class found, skip driver check"
354 # If module is set (it is by default), driver shall be loaded.
355 # If not, then NoDriverLoading is active
357 if {$config(driverModule) != ""} {
358 if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
359 if {![regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)]} {
360 Log "No target vendor/product ID found or given, can't continue. Abort"
361 SafeExit
364 # wait for any drivers to bind automatically
365 after 1000
366 Log "Now check for bound driver ..."
367 if {![file exists $devdir/$ifdir/driver]} {
368 Log " no driver has bound to interface 0 yet"
369 AddToList link_list $usb(idVendor):$usb(idProduct)
371 # If device is known, the sh wrapper will take care, else:
372 if {[InBindList $usb(idVendor):$usb(idProduct)] == 0} {
373 Log "Device is not in \"bind_list\" yet, bind it now"
375 # Load driver
376 CheckDriverBind $usb(idVendor) $usb(idProduct)
378 # Old/slow systems may take a while to create the devices
379 set counter 0
380 while {![file exists $devdir/$ifdir/driver]} {
381 if {$counter == 14} {break}
382 after 500
383 incr counter
385 if {$counter == 14} {
386 Log " driver binding failed"
387 } else {
388 Log " driver was bound to the device"
389 AddToList bind_list $usb(idVendor):$usb(idProduct)
392 } else {
393 Log " driver has bound, device is known"
394 if {[llength [glob -nocomplain $devdir/$ifdir/ttyUSB*]] > 0} {
395 AddToList link_list $usb(idVendor):$usb(idProduct)
398 } else {
399 # Just in case "NoDriverLoading" was added after the first bind
400 RemoveFromBindList $usb(idVendor):$usb(idProduct)
403 if [regexp {ok:$} $report] {
404 # "NoDriverLoading" was set
405 Log "No driver check or bind for this device"
408 # In newer kernels there is a switch to avoid the use of a device
409 # reset (e.g. from usb-storage) which would possibly switch back
410 # a mode-switching device to initial mode
411 if [regexp {ok:} $report] {
412 Log "Check for AVOID_RESET_QUIRK kernel attribute"
413 if [file exists $devdir/avoid_reset_quirk] {
414 if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] {
415 Log " Error setting the attribute: $err"
416 } else {
417 Log " AVOID_RESET_QUIRK activated"
419 } else {
420 Log " not present in this kernel"
424 Log "\nAll done, exit\n"
425 SafeExit
428 # end of proc {Main}
431 proc {ReadSCSIAttrs} {topdir} {
433 global scsi
434 set counter 0
435 set sysdir $topdir
436 Log "Check storage tree in sysfs ..."
437 while {$counter < 20} {
438 Log " loop $counter/20"
439 if {![file isdirectory $sysdir]} {
440 # Device is gone. Unplugged? Switched by kernel?
441 Log " sysfs device tree is gone; abort SCSI value check"
442 return 0
444 # Searching the storage/SCSI tree; might take a while
445 if {[set dirList [glob -nocomplain $topdir/host*]] != ""} {
446 set sysdir [lindex $dirList 0]
447 if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
448 set sysdir [lindex $dirList 0]
449 regexp {.*target(.*)} $sysdir d subdir
450 if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
451 set sysdir [lindex $dirList 0]
452 if [file exists $sysdir/vendor] {
453 Log " Storage tree is ready"
454 break
459 after 500
460 incr counter
462 if {$counter == 20} {
463 Log "SCSI tree not found; you may want to check if this path/file exists:"
464 Log "$sysdir/vendor\n"
465 return 0
468 Log "Read SCSI values ..."
469 foreach attr {vendor model rev} {
470 if [file exists $sysdir/$attr] {
471 set rc [open $sysdir/$attr r]
472 set scsi($attr) [read -nonewline $rc]
473 close $rc
474 } else {
475 set scsi($attr) ""
476 Log "Warning: SCSI attribute \"$attr\" not found."
479 return 1
482 # end of proc {ReadSCSIAttrs}
485 proc {ReadUSBAttrs} {dir args} {
487 global usb
489 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial devnum busnum bNumConfigurations}
490 set mandatoryList {idVendor idProduct bNumConfigurations}
491 set result 1
492 if {$args != ""} {
493 lappend attrList "$args/bInterfaceClass"
494 lappend mandatoryList "$args/bInterfaceClass"
496 foreach attr $attrList {
497 if [file exists $dir/$attr] {
498 set rc [open $dir/$attr r]
499 set usb($attr) [string trim [read -nonewline $rc]]
500 close $rc
501 } else {
502 set usb($attr) ""
503 if {[lsearch $mandatoryList $attr] > -1} {
504 set result 0
506 if {$attr == "serial"} {continue}
507 Log " Warning: USB attribute \"$attr\" not found"
510 return $result
513 # end of proc {ReadUSBAttrs}
516 proc {MatchDevice} {config} {
518 global scsi usb match
520 set devinfo [file tail $config]
521 set infoList [split $devinfo :]
522 set stringList [lrange $infoList 2 end]
523 if {[llength $stringList] == 0} {return 1}
525 foreach teststring $stringList {
526 if {$teststring == "?"} {return 0}
527 set tokenList [split $teststring =]
528 set id [lindex $tokenList 0]
529 set matchstring [lindex $tokenList 1]
530 set blankstring ""
531 regsub -all {_} $matchstring { } blankstring
532 Log "match $match($id)"
533 Log " string1 (exact): $matchstring"
534 Log " string2 (blanks): $blankstring"
535 Log " device string: [set $match($id)]"
536 if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} {
537 return 0
540 return 1
543 # end of proc {MatchDevice}
546 proc {ParseGlobalConfig} {} {
548 global flags
549 set configFile ""
550 set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
551 foreach cfg $places {
552 if [file exists $cfg] {
553 set configFile $cfg
554 break
557 if {$configFile == ""} {return}
559 set rc [open $configFile r]
560 while {![eof $rc]} {
561 gets $rc line
562 if [regexp {^#} [string trim $line]] {continue}
563 if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
564 if [regexp -nocase {1|yes|true} $val] {
565 set flags(noswitching) 1
568 if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
569 if [regexp -nocase {1|yes|true} $val] {
570 set flags(logging) 1
573 if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] {
574 if [regexp {\d+} $val] {
575 set flags(stordelay) $val
580 return "Use global config file: $configFile"
583 # end of proc {ParseGlobalConfig}
586 proc ParseDeviceConfig {configContent} {
588 global config
589 set config(driverModule) ""
590 set config(driverIDPath) ""
591 set config(waitBefore) ""
592 set config(targetVendor) ""
593 set config(targetProduct) ""
594 set config(targetClass) ""
595 set config(Configuration) ""
596 set config(noMBIMCheck) 0
597 set config(checkSuccess) 20
598 set loadDriver 1
600 if [regexp -line {^[^#]*?TargetVendor.*?=.*?0x(\w+).*?$} $configContent d config(targetVendor)] {
601 Log "config: TargetVendor set to $config(targetVendor)"
603 if [regexp -line {^[^#]*?TargetProduct.*?=.*?0x(\w+).*?$} $configContent d config(targetProduct)] {
604 Log "config: TargetProduct set to $config(targetProduct)"
606 if [regexp -line {^[^#]*?TargetProductList.*?=.*?"([0-9a-fA-F,]+).*?$} $configContent d config(targetProduct)] {
607 Log "config: TargetProductList set to $config(targetProduct)"
609 if [regexp -line {^[^#]*?TargetClass.*?=.*?0x(\w+).*?$} $configContent d config(targetClass)] {
610 Log "config: TargetClass set to $config(targetClass)"
612 if [regexp -line {^[^#]*?Configuration.*?=.*?([0-9]+).*?$} $configContent d config(Configuration)] {
613 Log "config: Configuration (target) set to $config(Configuration)"
615 if [regexp -line {^[^#]*?DriverModule.*?=.*?(\w+).*?$} $configContent d config(driverModule)] {
616 Log "config: DriverModule set to $config(driverModule)"
618 if [regexp -line {^[^#]*?DriverIDPath.*?=.*?"?([/\-\w]+).*?$} $configContent d config(driverIDPath)] {
619 Log "config: DriverIDPath set to $config(driverIDPath)"
621 if [regexp -line {^[^#]*?CheckSuccess.*?=.*?([0-9]+).*?$} $configContent d config(checkSuccess)] {
622 Log "config: CheckSuccess set to $config(checkSuccess)"
624 if [regexp -line {^[^#]*?WaitBefore.*?=.*?([0-9]+).*?$} $configContent d config(waitBefore)] {
625 Log "config: WaitBefore set to $config(waitBefore)"
627 if [regexp -line {^[^#]*?NoMBIMCheck.*?=.*?([0-9]+).*?$} $configContent d config(noMBIMCheck)] {
628 Log "config: noMBIMCheck set to $config(noMBIMCheck)"
630 if [regexp -line {^[^#]*?NoDriverLoading.*?=.*?(1|yes|true).*?$} $configContent] {
631 set loadDriver 0
632 Log "config: NoDriverLoading is set to active"
635 # For general driver loading; TODO: add respective device names.
636 # Presently only useful for HSO devices (which are recounted now)
637 if $loadDriver {
638 if {$config(driverModule) == ""} {
639 set config(driverModule) "option"
640 set config(driverIDPath) "/sys/bus/usb-serial/drivers/option1"
641 } else {
642 if {$config(driverIDPath) == ""} {
643 set config(driverIDPath) "/sys/bus/usb/drivers/$config(driverModule)"
646 Log "Driver module is \"$config(driverModule)\", ID path is $config(driverIDPath)\n"
647 } else {
648 Log "Driver will not be handled by usb_modeswitch"
650 set config(waitBefore) [string trimleft $config(waitBefore) 0]
653 # end of proc {ParseDeviceConfig}
656 proc ConfigGet {command config} {
658 global setup usb flags
660 switch $command {
662 conflist {
663 # Unpackaged configs first; sorting is essential for priority
664 set configList [lsort -decreasing [glob -nocomplain $setup(dbdir_etc)/$config*]]
665 set configList [concat $configList [lsort -decreasing [glob -nocomplain $setup(dbdir)/$config*]]]
666 eval lappend configList [glob -nocomplain $setup(dbdir)/$usb(idVendor):#$flags(os)]
667 if [file exists $setup(dbdir)/configPack.tar.gz] {
668 Log "Found packed config collection $setup(dbdir)/configPack.tar.gz"
669 if [catch {set packedList [exec tar -tzf $setup(dbdir)/configPack.tar.gz 2>/dev/null]} err] {
670 Log "Error: problem opening config package; tar returned\n $err"
671 return {}
673 set packedList [split $packedList \n]
674 set packedConfigList [lsort -decreasing [lsearch -glob -all -inline $packedList $config*]]
675 lappend packedConfigList [lsearch -inline $packedList $usb(idVendor):#$flags(os)]
676 # Now add packaged configs with a mark, again sorted for priority
677 foreach packedConfig $packedConfigList {
678 lappend configList "pack/$packedConfig"
681 return $configList
683 conffile {
684 if [regexp {^pack/} $config] {
685 set config [regsub {pack/} $config {}]
686 Log "Extract config $config from collection $setup(dbdir)/configPack.tar.gz"
687 set configContent [exec tar -xzOf $setup(dbdir)/configPack.tar.gz $config 2>/dev/null]
688 } else {
689 if [regexp [list $setup(dbdir_etc)] $config] {
690 Log "Use config file from override folder $setup(dbdir_etc)"
691 SysLog "usb_modeswitch: use overriding config file $config; make sure this is intended"
692 SysLog "usb_modeswitch: please report any new or corrected settings; otherwise, check for outdated files"
694 set rc [open $config r]
695 set configContent [read $rc]
696 close $rc
698 return $configContent
703 # end of proc {ConfigGet}
705 proc {Log} {msg} {
707 global flags device loginit
709 if {$flags(logging) == 0} {return}
711 if $flags(logwrite) {
712 if [string length $loginit] {
713 exec echo "\nUSB_ModeSwitch log from [clock format [clock seconds]]" >/var/log/usb_modeswitch_$device
714 exec echo "$loginit" >>/var/log/usb_modeswitch_$device
715 set loginit ""
717 exec echo $msg >>/var/log/usb_modeswitch_$device
718 } else {
719 append loginit "\n$msg"
723 # end of proc {Log}
726 # Writing the log file and exit
727 proc {SafeExit} {} {
729 global flags
730 set $flags(logwrite) 1
731 Log ""
732 exit
735 # end of proc {SafeExit}
738 proc {SymLinkName} {path} {
739 global device
741 proc {hasInterrupt} {ifDir} {
742 if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} {
743 Log " no ttyUSB interface - skip endpoint check"
744 return 0
746 foreach epDir [glob -nocomplain $ifDir/ep_*] {
747 set e [file tail $epDir]
748 Log " check $e ..."
749 if [file exists $epDir/type] {
750 set rc [open $epDir/type r]
751 set type [read $rc]
752 close $rc
753 if [regexp {Interrupt} $type] {
754 Log " $e has interrupt transfer type"
755 return 1
759 return 0
762 set loginit "usb_modeswitch called with --symlink-name\n parameter: $path\n"
764 # In case the device path is returned as /class/tty/ttyUSB,
765 # get the USB device path from linked tree "device"
766 set linkpath /sys$path/device
767 if [file exists $linkpath] {
768 if {[file type $linkpath] == "link"} {
769 set rawpath [file readlink $linkpath]
770 set trimpath [regsub -all {\.\./} $rawpath {}]
771 if [file isdirectory /sys/$trimpath] {
772 append loginit "\n Use path $path\n"
773 set path /$trimpath
778 if {![regexp {ttyUSB[0-9]+} $path myPort]} {
779 if $flags(logging) {
780 set device [clock clicks]
781 Log "$loginit\nThis is not a ttyUSB port. Abort"
783 return ""
786 set device $myPort
787 Log "$loginit\nMy name is $myPort\n"
789 if {![regexp {(.*?[0-9]+)\.([0-9]+)/ttyUSB} /sys$path d ifRoot ifNum]} {
790 Log "Could not find interface in path\n $path. Abort"
791 return ""
794 set ifDir $ifRoot.$ifNum
796 Log "Check my endpoints ...\n in $ifDir"
797 if [hasInterrupt $ifDir] {
798 Log "\n--> I am an interrupt port"
799 set rightPort 1
800 } else {
801 Log "\n--> I am not an interrupt port\n"
802 set rightPort 0
805 # There are devices with more than one interrupt interface.
806 # Assume that the lowest of these is usable. Check all
807 # possible lower interfaces
809 if { $rightPort && ($ifNum > 0) } {
810 Log "\nLook for lower ports with interrupt endpoints"
811 for {set i 0} {$i < $ifNum} {incr i} {
812 set ifDir $ifRoot.$i
813 Log " in ifDir $ifDir ..."
814 if [hasInterrupt $ifDir] {
815 Log "\n--> found an interrupt interface below me\n"
816 set rightPort 0
817 break
821 if {$rightPort == 0} {
822 Log "Return empty name and exit"
823 return ""
826 Log "\n--> No interrupt interface below me\n"
828 cd /dev
829 set idx 2
830 set symlinkName "gsmmodem"
831 while {$idx < 256} {
832 if {![file exists $symlinkName]} {
833 set placeholder [open /dev/$symlinkName w]
834 close $placeholder
835 break
837 set symlinkName gsmmodem$idx
838 incr idx
840 if {$idx == 256} {return ""}
842 Log "Return symlink name \"$symlinkName\" and exit"
843 return $symlinkName
846 # end of proc {SymLinkName}
849 # Load and bind driver (default "option")
851 proc {CheckDriverBind} {vid pid} {
852 global config
854 foreach fn {/sbin/modprobe /usr/sbin/modprobe} {
855 if [file exists $fn] {
856 set loader $fn
859 Log "Module loader is $loader"
861 set idfile $config(driverIDPath)/new_id
862 if {![file exists $idfile]} {
863 if {$loader == ""} {
864 Log "Can't do anymore without module loader; get \"modtools\"!"
865 return
867 Log "\nTry to load module \"$config(driverModule)\""
868 if [catch {set result [exec $loader -v $config(driverModule)]} err] {
869 Log " Running \"$loader $config(driverModule)\" gave an error:\n $err"
870 } else {
871 Log " Module was loaded successfully:\n$result"
873 } else {
874 Log "Module is active already"
876 set i 0
877 while {$i < 50} {
878 if [file exists $idfile] {
879 break
881 after 20
882 incr i
884 if {$i < 50} {
885 Log "Try to add ID to driver \"$config(driverModule)\""
886 SysLog "usb_modeswitch: add device ID $vid:$pid to driver \"$config(driverModule)\""
887 SysLog "usb_modeswitch: please report the device ID to the Linux USB developers!"
888 if [catch {exec echo "$vid $pid ff" >$idfile} err] {
889 Log " Error adding ID to driver:\n $err"
890 } else {
891 Log " ID added to driver; check for new devices in /dev"
893 } else {
894 Log " \"$idfile\" not found, check if kernel version is at least 2.6.27"
895 Log "Fall back to \"usbserial\""
896 set config(driverModule) usbserial
897 Log "\nTry to unload driver \"usbserial\""
898 if [catch {exec $loader -r usbserial} err] {
899 Log " Running \"$loader -r usbserial\" gave an error:\n $err"
900 Log "No more fallbacks"
901 return
903 after 50
904 Log "\nTry to load driver \"usbserial\" with device IDs"
905 if [catch {set result [exec $loader -v usbserial vendor=0x$vid product=0x$pid]} err] {
906 Log " Running \"$loader usbserial\" gave an error:\n $err"
907 } else {
908 Log " Driver was loaded successfully:\n$result"
913 # end of proc {CheckDriverBind}
916 # Check if USB ID is listed as needing driver binding
917 proc {InBindList} {id} {
919 set listfile /var/lib/usb_modeswitch/bind_list
920 if {![file exists $listfile]} {return 0}
921 set rc [open $listfile r]
922 set buffer [read $rc]
923 close $rc
924 if [string match *$id* $buffer] {
925 Log "Found $id in bind_list"
926 return 1
927 } else {
928 Log "No $id in bind_list"
929 return 0
933 # end of proc {InBindList}
935 # Add USB ID to list of devices needing later treatment
936 proc {AddToList} {name id} {
938 set listfile /var/lib/usb_modeswitch/$name
939 set oldlistfile /etc/usb_modeswitch.d/bind_list
941 if {($name == "bind_list") && [file exists $oldlistfile] && ![file exists $listfile]} {
942 if [catch {file rename $oldlistfile $listfile} err] {
943 Log "Error renaming the old bind list file ($err)"
944 return
948 if [file exists $listfile] {
949 set rc [open $listfile r]
950 set buffer [read $rc]
951 close $rc
952 if [string match *$id* $buffer] {
953 return
955 set idList [split [string trim $buffer] \n]
957 lappend idList $id
958 set buffer [join $idList "\n"]
959 if [catch {set lc [open $listfile w]}] {return}
960 puts $lc $buffer
961 close $lc
964 # end of proc {AddToList}
967 # Remove USB ID from bind list (NoDriverLoading is set)
968 proc {RemoveFromBindList} {id} {
970 set listfile /var/lib/usb_modeswitch/bind_list
971 if [file exists $listfile] {
972 set rc [open $listfile r]
973 set buffer [read $rc]
974 close $rc
975 set idList [split [string trim $buffer] \n]
976 } else {
977 return
979 set idx [lsearch $idList $id]
980 if {$idx > -1} {
981 set idList [lreplace $idList $idx $idx]
982 } else {
983 return
985 if {[llength $idList] == 0} {
986 file delete $listfile
987 return
989 set buffer [join $idList "\n"]
990 if [catch {set lc [open $listfile w]}] {return}
991 puts $lc $buffer
992 close $lc
995 # end of proc {RemoveFromBindList}
998 proc {CheckSuccess} {devdir} {
1000 global config usb flags
1001 set ifdir [file tail [IfDir 0]]
1003 if {[string length $config(targetClass)] || [string length $config(Configuration)]} {
1004 set config(targetVendor) $usb(idVendor)
1005 set config(targetProduct) $usb(idProduct)
1007 Log "Check success of mode switch for max. $config(checkSuccess) seconds ..."
1009 set expected 1
1010 for {set i 1} {$i <= $config(checkSuccess)} {incr i} {
1011 after 1000
1012 if {![file isdirectory $devdir]} {
1013 Log " Wait for device file system ($i sec.) ..."
1014 continue
1015 } else {
1016 Log " Read attributes ..."
1018 set ifdir [IfDir 0]
1019 if {$ifdir == ""} {continue}
1020 set ifdir [file tail $ifdir]
1021 if {![ReadUSBAttrs $devdir $ifdir]} {
1022 Log " Essential attributes are missing, continue wait ..."
1023 continue
1025 if [string length $config(Configuration)] {
1026 if {$usb(bConfigurationValue) != $config(Configuration)} {continue}
1028 if [string length $config(targetClass)] {
1029 if {![regexp $usb($ifdir/bInterfaceClass) $config(targetClass)]} {
1030 if {$config(class) != $usb($ifdir/bInterfaceClass} {
1031 set expected 0
1032 } else {continue}
1035 if {![regexp $usb(idVendor) $config(targetVendor)]} {
1036 if {![regexp $usb(idVendor) $config(vendor)]} {
1037 set expected 0
1038 } else {continue}
1040 if {![regexp $usb(idProduct) $config(targetProduct)]} {
1041 if {![regexp $usb(idProduct) $config(product)]} {
1042 set expected 0
1043 } else {continue}
1045 if $expected {
1046 Log " All attributes matched"
1047 } else {
1048 if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
1049 Log " idProduct has changed after generic mode-switch, assume success"
1050 } else {
1051 Log " Attributes are different but target values are unexpected:"
1052 LogAttributes
1055 break
1057 if {$i > 20} {
1058 return 0
1060 return 1
1063 # end of proc {CheckSuccess}
1066 proc {IfDir} {iface} {
1068 global devdir
1069 set allfiles [glob -nocomplain $devdir/*]
1070 set files [glob -nocomplain $devdir/*.$iface]
1071 if {[llength $files] == 0} {
1072 return ""
1074 set ifdir [lindex $files 0]
1075 if {![file isdirectory $ifdir]} {
1076 return ""
1078 return $ifdir
1081 # end of proc {IfDir}
1083 proc {IfClass} {iface} {
1085 set ifdir [IfDir $iface]
1087 if {![file exists $ifdir/bInterfaceClass]} {
1088 return -1
1090 set rc [open $ifdir/bInterfaceClass r]
1091 set c [read $rc]
1092 close $rc
1093 return [string trim $c]
1096 # end of proc {IfClass}
1099 proc {SysLog} {msg} {
1101 global flags
1102 if {![info exists flags(logger)]} {
1103 set flags(logger) ""
1104 foreach fn {/bin/logger /usr/bin/logger} {
1105 if [file exists $fn] {
1106 set flags(logger) $fn
1109 Log "Logger is $flags(logger)"
1111 if {$flags(logger) == ""} {
1112 Log "Can't add system message, no syslog helper found"
1113 return
1115 catch {exec $flags(logger) -p syslog.notice "$msg" 2>/dev/null}
1118 # end of proc {SysLog}
1120 proc {SetStorageDelay} {secs} {
1122 Log "Adjust delay for USB storage devices ..."
1123 set attrib /sys/module/usb_storage/parameters/delay_use
1124 if {![file exists $attrib]} {
1125 Log "Error: could not find delay_use attribute"
1126 return
1128 if [catch {set ch [open $attrib r+]} err] {
1129 Log "Error: could not access delay_use attribute: $err"
1130 return
1132 if {[read $ch] < $secs} {
1133 seek $ch 0 start
1134 puts -nonewline $ch $secs
1135 Log " Delay set to $secs seconds\n"
1136 } else {
1137 Log " Current value is higher than $secs. Leave it alone\n"
1139 close $ch
1142 # end of proc {SetStorageDelay}
1144 proc {CheckMBIM} {} {
1146 set kversion [exec uname -r]
1147 if [file exists /lib/modules/$kversion/kernel/drivers/net/usb/cdc_mbim.ko] {return 1}
1148 if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1149 return 0
1153 proc {LogAttributes} {} {
1155 global flags usb
1156 if $flags(logging) {
1157 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial}
1158 foreach attr [lsort [array names usb]] {
1159 Log " [format %-26s $attr:] $usb($attr)"
1165 # The actual entry point
1166 Main $argv $argc