usbmodeswitch: Updated to v.1.2.6 from shibby's branch.
[tomato.git] / release / src / router / usbmodeswitch / usb_modeswitch.tcl
blob7f8c37d2d6f8e5ea8e6715536caf35d57d9ffd75
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-1.2.6 package
13 # (C) Josua Dietze 2009-2013
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
37 set loginit [ParseGlobalConfig]
39 # The facility to add a symbolic link pointing to the
40 # ttyUSB port which provides interrupt transfer, i.e.
41 # the port to connect through.
42 # Will check for interrupt endpoint in ttyUSB port (lowest if
43 # there is more than one); if found, return "gsmmodem[n]" name
44 # to udev for symlink creation
46 # This is run once for every port of LISTED devices by
47 # an udev rule
49 if {[lindex $argv 0] == "--symlink-name"} {
50 puts -nonewline [SymLinkName [lindex $argv 1]]
51 SafeExit
54 set argList [split [lindex $argv 1] /]
55 if [string length [lindex $argList 1]] {
56 set device [lindex $argList 1]
57 } else {
58 set device "noname"
61 if {$flags(stordelay) > 0} {
62 SetStorageDelay $flags(stordelay)
65 Log "Raw args from udev: [lindex $argv 1]\n\n$loginit"
66 if {$device == "noname"} {
67 Log "No data from udev. Exiting"
68 SafeExit
71 if {[lindex $argv 0] != "--switch-mode"} {
72 Log "No command given. Exiting"
73 SafeExit
76 set setup(dbdir) /usr/share/usb_modeswitch
77 set setup(dbdir_etc) /etc/usb_modeswitch.d
80 if {![file exists $setup(dbdir)] && ![file exists $setup(dbdir_etc)]} {
81 Log "Error: no config database found in /usr/share or /etc. Exiting"
82 SafeExit
84 set bindir /usr/sbin
86 set devList1 {}
87 set devList2 {}
90 # arg 0: the bus id for the device (udev: %b)
91 # arg 1: the "kernel name" for the device (udev: %k)
93 # Used to determine the top directory for the device in sysfs
95 set ifChk 0
96 if {[string length [lindex $argList 0]] == 0} {
97 if {[string length [lindex $argList 1]] == 0} {
98 Log "No device number values given from udev! Exiting"
99 SafeExit
100 } else {
101 if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
102 if [regexp {([0-9]+-[0-9]+\.?[0-9]*.*)} [lindex $argList 1] d dev_top] {
103 # new udev rules file, got to check class of first interface
104 Log "Called by new rules file - remember to check class of first interface ..."
105 set ifChk 1
106 } else {
107 Log "Could not determine device dir from udev values! Exiting"
108 SafeExit
112 } else {
113 set dev_top [lindex $argList 0]
114 regexp {(.*?):} $dev_top d dev_top
117 # NC
118 #set dev_top [lindex $argList 1]
121 set devdir /sys/bus/usb/devices/$dev_top
122 if {![file isdirectory $devdir]} {
123 Log "Top device directory not found ($devdir)! Exiting"
124 SafeExit
126 Log "Using top device dir $devdir"
129 # Mapping of the short string identifiers (in the config
130 # file names) to the long name used here
132 # If we need them it's a snap to add new attributes here!
134 set match(sVe) scsi(vendor)
135 set match(sMo) scsi(model)
136 set match(sRe) scsi(rev)
137 set match(uMa) usb(manufacturer)
138 set match(uPr) usb(product)
139 set match(uSe) usb(serial)
142 # Now reading the USB attributes
143 if {![ReadUSBAttrs $devdir]} {
144 Log "USB attributes not found in sysfs tree. Exiting"
145 SafeExit
148 set iface 0
149 if $ifChk {
150 Log "Check class of first interface ..."
151 set iface [ChkIface 0]
152 if {$iface < 0} {
153 set iface [ChkIface 9]
155 if {$iface < 0} {
156 if {$usb(idVendor)=="19d2" && $usb(idProduct)=="2000"} {
157 set iface [ChkIface 3]
159 # Corrected, was wrongly reported
160 # if {$usb(idVendor)=="16d8" && $usb(idProduct)=="6803"} {
161 # set iface [ChkIface 3]
164 if {$iface < 0} {
165 Log " Device is not in install mode. Exiting"
166 SafeExit
167 } else {
168 Log " Device is in install mode."
171 set ifdir [file tail [IfDir $iface]]
172 regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface
174 Log "Using interface $iface"
177 if $flags(logging) {
178 Log "----------------\nUSB values from sysfs:"
179 foreach attr {manufacturer product serial} {
180 Log " $attr\t$usb($attr)"
182 Log "----------------"
185 if $flags(noswitching) {
186 Log "\nSwitching globally disabled. Exiting\n"
187 SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"
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 if there is more than one config file for this USB ID,
199 # which would make an attribute test necessary. If so, check if
200 # SCSI values are needed
202 set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)]
204 if {[llength $configList] == 0} {
205 Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exiting"
206 SafeExit
209 set scsiNeeded 0
210 if {[llength $configList] > 1} {
211 if [regexp {:s} $configList] {
212 set scsiNeeded 1
215 if $scsiNeeded {
216 if [ReadSCSIAttrs $devdir:$iface] {
217 Log "----------------\nSCSI values from sysfs:"
218 foreach attr {vendor model rev} {
219 Log " $attr\t$scsi($attr)"
221 Log "----------------"
222 } else {
223 Log "Could not get SCSI attributes, exclude devices with SCSI match"
225 } else {
226 Log "SCSI attributes not needed, moving on"
229 # General wait - this is important
230 after 500
232 # Now check for a matching config file. Matching is done
233 # by MatchDevice
235 set report {}
236 foreach configuration $configList {
238 # skipping installer leftovers
239 if [regexp {\.(dpkg|rpm)} $configuration] {continue}
241 Log "checking config: $configuration"
242 if [MatchDevice $configuration] {
243 Log "! matched. Reading config data"
244 if [string length $usb(busnum)] {
245 set busParam "-b [string trimleft $usb(busnum) 0]"
246 set devParam "-g [string trimleft $usb(devnum) 0]"
247 } else {
248 set busParam ""
249 set devParam ""
251 set configBuffer [ConfigGet conffile $configuration]
252 ParseDeviceConfig $configBuffer
253 if {$config(waitBefore) == ""} {
254 } else {
255 Log " waiting time set to $config(waitBefore) seconds"
256 append config(waitBefore) "000"
257 after $config(waitBefore)
258 Log " waiting is over, switching starts now"
260 if {$config(noMBIMCheck)==0 && $usb(bNumConfigurations) > 1} {
261 Log "Device may have an MBIM configuration, checking driver ..."
262 if [CheckMBIM] {
263 Log " driver for MBIM devices is available"
264 Log "Finding MBIM configuration number ..."
265 if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] {
266 Log "Error when trying to find MBIM configuration, switch to legacy modem mode"
267 } else {
268 set cfgno [string trim $cfgno]
269 if {$cfgno > 0} {
270 set config(Configuration) $cfgno
271 set config(driverModule) ""
272 set configBuffer "Configuration=$cfgno"
273 } else {
274 Log " No MBIM configuration found, switch to legacy modem mode"
277 } else {
278 Log " no MBIM driver found, switch to legacy modem mode"
282 # Now we are actually switching
283 if $flags(logging) {
284 Log "Command to be run:\nusb_modeswitch -W -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f \$configBuffer"
285 set report [exec /usr/sbin/usb_modeswitch -W -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$configBuffer" 2>@ stdout]
286 Log "\nVerbose debug output of usb_modeswitch and libusb follows"
287 Log "(Note that some USB errors are to be expected in the process)"
288 Log "--------------------------------"
289 Log $report
290 Log "--------------------------------"
291 Log "(end of usb_modeswitch output)\n"
292 } else {
293 set report [exec /usr/sbin/usb_modeswitch -Q -D -s 20 $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$configBuffer" 2>@ stdout]
295 break
296 } else {
297 Log "* no match, not switching with this config"
301 # Switching is complete; success checking was either
302 # done by usb_modeswitch and logged via syslog OR bus/dev
303 # parameter were used; then we do check for success HERE
305 if [regexp {ok:busdev} $report] {
306 if [CheckSuccess $devdir] {
307 Log "Mode switching was successful, found $usb(idVendor):$usb(idProduct) ($usb(manufacturer): $usb(product))"
308 SysLog "usb_modeswitch: switched to $usb(idVendor):$usb(idProduct) on [format %03d $usb(busnum)]/[format %03d $usb(devnum)]"
309 } else {
310 Log "\nTarget config not matching - current values are"
311 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial}
312 foreach attr [lsort [array names usb]] {
313 Log " [format %-26s $attr:] $usb($attr)"
315 Log "\nMode switching may have failed. Exiting\n"
316 SafeExit
318 } else {
319 if {![file isdirectory $devdir]} {
320 Log "Device directory in sysfs is gone! Something went wrong, aborting"
321 SafeExit
323 if {![regexp {ok:} $report]} {
324 Log "\nCore program reported switching failure. Exiting\n"
325 SafeExit
327 # Give the device another second if it's not fully back yet
328 if {![file exists $devdir/idProduct]} {
329 after 1000
331 ReadUSBAttrs $devdir $ifdir
334 # Now checking for bound drivers (only for class 0xff)
336 if {$config(driverModule) != "" && $usb($ifdir/bInterfaceClass) != "" && [regexp {ok:} $report]} {
337 if {$usb($ifdir/bInterfaceClass) != "ff"} {
338 set config(driverModule) ""
339 Log " No vendor-specific class found, skip driver checking"
343 # If module is set (it is by default), driver shall be loaded.
344 # If not, then NoDriverLoading is active
346 if {$config(driverModule) != ""} {
347 if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
348 if {![regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)]} {
349 Log "No target vendor/product ID found or given, can't continue. Aborting"
350 SafeExit
353 # wait for any drivers to bind automatically
354 after 1000
355 Log "Now checking for bound driver ..."
356 if {![file exists $devdir/$ifdir/driver]} {
357 Log " no driver has bound to interface 0 yet"
358 AddToList link_list $usb(idVendor):$usb(idProduct)
360 # If device is known, the sh wrapper will take care, else:
361 if {[InBindList $usb(idVendor):$usb(idProduct)] == 0} {
362 Log "Device is not in \"bind_list\" yet, bind it now"
364 # Load driver
365 CheckDriverBind $usb(idVendor) $usb(idProduct)
367 # Old/slow systems may take a while to create the devices
368 set counter 0
369 while {![file exists $devdir/$ifdir/driver]} {
370 if {$counter == 14} {break}
371 after 500
372 incr counter
374 if {$counter == 14} {
375 Log " driver binding failed"
376 } else {
377 Log " driver was bound to the device"
378 AddToList bind_list $usb(idVendor):$usb(idProduct)
381 } else {
382 Log " driver has bound, device is known"
383 if {[llength [glob -nocomplain $devdir/$ifdir/ttyUSB*]] > 0} {
384 AddToList link_list $usb(idVendor):$usb(idProduct)
387 } else {
388 # Just in case "NoDriverLoading" was added after the first bind
389 RemoveFromBindList $usb(idVendor):$usb(idProduct)
392 if [regexp {ok:$} $report] {
393 # "NoDriverLoading" was set
394 Log "Doing no driver checking or binding for this device"
397 # In newer kernels there is a switch to avoid the use of a device
398 # reset (e.g. from usb-storage) which would possibly switch back
399 # a mode-switching device to initial mode
400 if [regexp {ok:} $report] {
401 Log "Checking for AVOID_RESET_QUIRK kernel attribute"
402 if [file exists $devdir/avoid_reset_quirk] {
403 if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] {
404 Log " Error setting the attribute: $err"
405 } else {
406 Log " AVOID_RESET_QUIRK activated"
408 } else {
409 Log " not present in this kernel"
413 Log "\nAll done, exiting\n"
414 SafeExit
417 # end of proc {Main}
420 proc {ReadSCSIAttrs} {topdir} {
422 global scsi
423 set counter 0
424 set sysdir $topdir
425 Log "Checking storage tree in sysfs ..."
426 while {$counter < 20} {
427 Log " loop $counter/20"
428 if {![file isdirectory $sysdir]} {
429 # Device is gone. Unplugged? Switched by kernel?
430 Log " sysfs device tree is gone; abort SCSI value check"
431 return 0
433 # Searching the storage/SCSI tree; might take a while
434 if {[set dirList [glob -nocomplain $topdir/host*]] != ""} {
435 set sysdir [lindex $dirList 0]
436 if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
437 set sysdir [lindex $dirList 0]
438 regexp {.*target(.*)} $sysdir d subdir
439 if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
440 set sysdir [lindex $dirList 0]
441 if [file exists $sysdir/vendor] {
442 Log " Storage tree is ready"
443 break
448 after 500
449 incr counter
451 if {$counter == 20} {
452 Log "SCSI tree not found; you may want to check if this path/file exists:"
453 Log "$sysdir/vendor\n"
454 return 0
457 Log "Reading SCSI values ..."
458 foreach attr {vendor model rev} {
459 if [file exists $sysdir/$attr] {
460 set rc [open $sysdir/$attr r]
461 set scsi($attr) [read -nonewline $rc]
462 close $rc
463 } else {
464 set scsi($attr) ""
465 Log "Warning: SCSI attribute \"$attr\" not found."
468 return 1
471 # end of proc {ReadSCSIAttrs}
474 proc {ReadUSBAttrs} {dir args} {
476 global usb
478 set attrList {idVendor idProduct bConfigurationValue manufacturer product serial devnum busnum bNumConfigurations}
479 set mandatoryList {idVendor idProduct bNumConfigurations}
480 set result 1
481 if {$args != ""} {
482 lappend attrList "$args/bInterfaceClass"
483 lappend mandatoryList "$args/bInterfaceClass"
485 foreach attr $attrList {
486 if [file exists $dir/$attr] {
487 set rc [open $dir/$attr r]
488 set usb($attr) [string trim [read -nonewline $rc]]
489 close $rc
490 } else {
491 set usb($attr) ""
492 if {[lsearch $mandatoryList $attr] > -1} {
493 set result 0
495 if {$attr == "serial"} {continue}
496 Log " Warning: USB attribute \"$attr\" not found"
499 return $result
502 # end of proc {ReadUSBAttrs}
505 proc {MatchDevice} {config} {
507 global scsi usb match
509 set devinfo [file tail $config]
510 set infoList [split $devinfo :]
511 set stringList [lrange $infoList 2 end]
512 if {[llength $stringList] == 0} {return 1}
514 foreach teststring $stringList {
515 if {$teststring == "?"} {return 0}
516 set tokenList [split $teststring =]
517 set id [lindex $tokenList 0]
518 set matchstring [lindex $tokenList 1]
519 set blankstring ""
520 regsub -all {_} $matchstring { } blankstring
521 Log "matching $match($id)"
522 Log " match string1 (exact): $matchstring"
523 Log " match string2 (blanks): $blankstring"
524 Log " device string: [set $match($id)]"
525 if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} {
526 return 0
529 return 1
532 # end of proc {MatchDevice}
535 proc {ParseGlobalConfig} {} {
537 global flags
538 set configFile ""
539 set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
540 foreach cfg $places {
541 if [file exists $cfg] {
542 set configFile $cfg
543 break
546 if {$configFile == ""} {return}
548 set rc [open $configFile r]
549 while {![eof $rc]} {
550 gets $rc line
551 if [regexp {^#} [string trim $line]] {continue}
552 if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
553 if [regexp -nocase {1|yes|true} $val] {
554 set flags(noswitching) 1
557 if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
558 if [regexp -nocase {1|yes|true} $val] {
559 set flags(logging) 1
562 if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] {
563 if [regexp {\d+} $val] {
564 set flags(stordelay) $val
569 return "Using global config file: $configFile"
572 # end of proc {ParseGlobalConfig}
575 proc ParseDeviceConfig {configContent} {
577 global config
578 set config(driverModule) ""
579 set config(driverIDPath) ""
580 set config(waitBefore) ""
581 set config(targetVendor) ""
582 set config(targetProduct) ""
583 set config(targetClass) ""
584 set config(Configuration) ""
585 set config(noMBIMCheck) 0
586 set config(checkSuccess) 20
587 set loadDriver 1
589 if [regexp -line {^[^#]*?TargetVendor.*?=.*?0x(\w+).*?$} $configContent d config(targetVendor)] {
590 Log "config: TargetVendor set to $config(targetVendor)"
592 if [regexp -line {^[^#]*?TargetProduct.*?=.*?0x(\w+).*?$} $configContent d config(targetProduct)] {
593 Log "config: TargetProduct set to $config(targetProduct)"
595 if [regexp -line {^[^#]*?TargetProductList.*?=.*?"([0-9a-fA-F,]+).*?$} $configContent d config(targetProduct)] {
596 Log "config: TargetProductList set to $config(targetProduct)"
598 if [regexp -line {^[^#]*?TargetClass.*?=.*?0x(\w+).*?$} $configContent d config(targetClass)] {
599 Log "config: TargetClass set to $config(targetClass)"
601 if [regexp -line {^[^#]*?Configuration.*?=.*?([0-9]+).*?$} $configContent d config(Configuration)] {
602 Log "config: Configuration (target) set to $config(Configuration)"
604 if [regexp -line {^[^#]*?DriverModule.*?=.*?(\w+).*?$} $configContent d config(driverModule)] {
605 Log "config: DriverModule set to $config(driverModule)"
607 if [regexp -line {^[^#]*?DriverIDPath.*?=.*?"?([/\-\w]+).*?$} $configContent d config(driverIDPath)] {
608 Log "config: DriverIDPath set to $config(driverIDPath)"
610 if [regexp -line {^[^#]*?CheckSuccess.*?=.*?([0-9]+).*?$} $configContent d config(checkSuccess)] {
611 Log "config: CheckSuccess set to $config(checkSuccess)"
613 if [regexp -line {^[^#]*?WaitBefore.*?=.*?([0-9]+).*?$} $configContent d config(waitBefore)] {
614 Log "config: WaitBefore set to $config(waitBefore)"
616 if [regexp -line {^[^#]*?NoMBIMCheck.*?=.*?([0-9]+).*?$} $configContent d config(noMBIMCheck)] {
617 Log "config: noMBIMCheck set to $config(noMBIMCheck)"
619 if [regexp -line {^[^#]*?NoDriverLoading.*?=.*?(1|yes|true).*?$} $configContent] {
620 set loadDriver 0
621 Log "config: NoDriverLoading is set to active"
624 # For general driver loading; TODO: add respective device names.
625 # Presently only useful for HSO devices (which are recounted now)
626 if $loadDriver {
627 if {$config(driverModule) == ""} {
628 set config(driverModule) "option"
629 set config(driverIDPath) "/sys/bus/usb-serial/drivers/option1"
630 } else {
631 if {$config(driverIDPath) == ""} {
632 set config(driverIDPath) "/sys/bus/usb/drivers/$config(driverModule)"
635 Log "Driver module is \"$config(driverModule)\", ID path is $config(driverIDPath)\n"
636 } else {
637 Log "Driver will not be handled by usb_modeswitch"
639 set config(waitBefore) [string trimleft $config(waitBefore) 0]
642 # end of proc {ParseDeviceConfig}
645 proc ConfigGet {command config} {
647 global setup
649 switch $command {
651 conflist {
652 # Unpackaged configs first; sorting is essential for priority
653 set configList [lsort -decreasing [glob -nocomplain $setup(dbdir_etc)/$config*]]
654 set configList [concat $configList [lsort -decreasing [glob -nocomplain $setup(dbdir)/$config*]]]
655 if [file exists $setup(dbdir)/configPack.tar.gz] {
656 Log "Found packed config collection $setup(dbdir)/configPack.tar.gz"
657 if [catch {set packedList [exec tar -tzf $setup(dbdir)/configPack.tar.gz 2>/dev/null]} err] {
658 Log "Error: problem opening config package; tar returned\n $err"
659 return {}
661 set packedList [split $packedList \n]
662 set packedConfigList [lsort -decreasing [lsearch -glob -all -inline $packedList $config*]]
663 # Now add packaged configs with a mark, again sorted for priority
664 foreach packedConfig $packedConfigList {
665 lappend configList "pack/$packedConfig"
669 return $configList
671 conffile {
672 if [regexp {^pack/} $config] {
673 set config [regsub {pack/} $config {}]
674 Log "Extracting config $config from collection $setup(dbdir)/configPack.tar.gz"
675 set configContent [exec tar -xzOf $setup(dbdir)/configPack.tar.gz $config 2>/dev/null]
676 } else {
677 if [regexp [list $setup(dbdir_etc)] $config] {
678 Log "Using config file from override folder $setup(dbdir_etc)"
679 SysLog "usb_modeswitch: using overriding config file $config; make sure this is intended"
680 SysLog "usb_modeswitch: please report any new or corrected settings; otherwise, check for outdated files"
682 set rc [open $config r]
683 set configContent [read $rc]
684 close $rc
686 return $configContent
691 # end of proc {ConfigGet}
693 proc {Log} {msg} {
695 global flags device
697 if {$flags(logging) == 0} {return}
698 if {![info exists flags(wc)]} {
699 if [catch {set flags(wc) [open /var/log/usb_modeswitch_$device w]} err] {
700 if [catch {set flags(wc) [open /dev/console w]} err] {
701 set flags(wc) "error"
702 return
703 } else {
704 puts $flags(wc) "Error opening log file ($err), redirect to console"
707 puts $flags(wc) "\n\nUSB_ModeSwitch log from [clock format [clock seconds]]\n"
709 if {$flags(wc) == "error"} {return}
710 puts $flags(wc) $msg
713 # end of proc {Log}
716 # Closing the log file if open and exit
717 proc {SafeExit} {} {
719 global flags
720 if [info exists flags(wc)] {
721 catch {close $flags(wc)}
723 exit
726 # end of proc {SafeExit}
729 proc {SymLinkName} {path} {
730 global device
732 proc {hasInterrupt} {ifDir} {
733 if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} {
734 Log " no ttyUSB interface - skip checking endpoints"
735 return 0
737 foreach epDir [glob -nocomplain $ifDir/ep_*] {
738 set e [file tail $epDir]
739 Log " checking $e ..."
740 if [file exists $epDir/type] {
741 set rc [open $epDir/type r]
742 set type [read $rc]
743 close $rc
744 if [regexp {Interrupt} $type] {
745 Log " $e has interrupt transfer type"
746 return 1
750 return 0
753 set loginit "usb_modeswitch called with --symlink-name\n parameter: $path\n"
755 # In case the device path is returned as /class/tty/ttyUSB,
756 # get the USB device path from linked tree "device"
757 set linkpath /sys$path/device
758 if [file exists $linkpath] {
759 if {[file type $linkpath] == "link"} {
760 set rawpath [file readlink $linkpath]
761 set trimpath [regsub -all {\.\./} $rawpath {}]
762 if [file isdirectory /sys/$trimpath] {
763 append loginit "\n Using path $path\n"
764 set path /$trimpath
769 if {![regexp {ttyUSB[0-9]+} $path myPort]} {
770 if $flags(logging) {
771 set device [clock clicks]
772 Log "$loginit\nThis is not a ttyUSB port. Aborting"
774 return ""
777 set device $myPort
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. Aborting"
782 return ""
785 set ifDir $ifRoot.$ifNum
787 Log "Checking 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 "\nLooking 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 "\nTrying 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 "Trying to add ID to driver \"$config(driverModule)\""
877 SysLog "usb_modeswitch: adding 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 "Falling back to \"usbserial\""
887 set config(driverModule) usbserial
888 Log "\nTrying 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 "\nTrying 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 set oldlistfile /etc/usb_modeswitch.d/bind_list
932 if {($name == "bind_list") && [file exists $oldlistfile] && ![file exists $listfile]} {
933 if [catch {file rename $oldlistfile $listfile} err] {
934 Log "Error renaming the old bind list file ($err)"
935 return
939 if [file exists $listfile] {
940 set rc [open $listfile r]
941 set buffer [read $rc]
942 close $rc
943 if [string match *$id* $buffer] {
944 return
946 set idList [split [string trim $buffer] \n]
948 lappend idList $id
949 set buffer [join $idList "\n"]
950 if [catch {set lc [open $listfile w]}] {return}
951 puts $lc $buffer
952 close $lc
955 # end of proc {AddToList}
958 # Remove USB ID from bind list (NoDriverLoading is set)
959 proc {RemoveFromBindList} {id} {
961 set listfile /var/lib/usb_modeswitch/bind_list
962 if [file exists $listfile] {
963 set rc [open $listfile r]
964 set buffer [read $rc]
965 close $rc
966 set idList [split [string trim $buffer] \n]
967 } else {
968 return
970 set idx [lsearch $idList $id]
971 if {$idx > -1} {
972 set idList [lreplace $idList $idx $idx]
973 } else {
974 return
976 if {[llength $idList] == 0} {
977 file delete $listfile
978 return
980 set buffer [join $idList "\n"]
981 if [catch {set lc [open $listfile w]}] {return}
982 puts $lc $buffer
983 close $lc
986 # end of proc {RemoveFromBindList}
989 proc {CheckSuccess} {devdir} {
991 global config usb
992 set ifdir [file tail [IfDir 0]]
994 if {[string length $config(targetClass)] || [string length $config(Configuration)]} {
995 set config(targetVendor) $usb(idVendor)
996 set config(targetProduct) $usb(idProduct)
998 Log "Checking success of mode switch for max. $config(checkSuccess) seconds ..."
1000 for {set i 1} {$i <= $config(checkSuccess)} {incr i} {
1001 after 1000
1002 if {![file isdirectory $devdir]} {
1003 Log " Waiting for device file system ($i sec.) ..."
1004 continue
1005 } else {
1006 Log " Reading attributes ..."
1008 set ifdir [IfDir 0]
1009 if {$ifdir == ""} {continue}
1010 set ifdir [file tail $ifdir]
1011 if {![ReadUSBAttrs $devdir $ifdir]} {
1012 Log " Essential attributes are missing, continue wait ..."
1013 continue
1015 if [string length $config(targetClass)] {
1016 if {![regexp $usb($ifdir/bInterfaceClass) $config(targetClass)]} {continue}
1018 if [string length $config(Configuration)] {
1019 if {$usb(bConfigurationValue) != $config(Configuration)} {continue}
1021 if {![regexp $usb(idVendor) $config(targetVendor)]} {continue}
1022 if {![regexp $usb(idProduct) $config(targetProduct)]} {continue}
1023 Log " All attributes matched"
1024 break
1026 if {$i > 20} {
1027 return 0
1029 return 1
1032 # end of proc {CheckSuccess}
1035 proc {ChkIface} {iface} {
1037 if {[IfClass $iface] == 8} {
1038 return $iface
1039 } else {
1040 return -1
1044 # end of proc {ChkIface}
1046 proc {IfDir} {iface} {
1048 global devdir
1049 set allfiles [glob -nocomplain $devdir/*]
1050 set files [glob -nocomplain $devdir/*.$iface]
1051 if {[llength $files] == 0} {
1052 return ""
1054 set ifdir [lindex $files 0]
1055 if {![file isdirectory $ifdir]} {
1056 return ""
1058 return $ifdir
1061 # end of proc {IfDir}
1063 proc {IfClass} {iface} {
1065 set ifdir [IfDir $iface]
1067 if {![file exists $ifdir/bInterfaceClass]} {
1068 return -1
1070 set rc [open $ifdir/bInterfaceClass r]
1071 set c [read $rc]
1072 close $rc
1073 return [string trimleft [string trim $c] 0]
1076 # end of proc {IfClass}
1079 proc {SysLog} {msg} {
1081 global flags
1082 if {![info exists flags(logger)]} {
1083 set flags(logger) ""
1084 foreach fn {/bin/logger /usr/bin/logger} {
1085 if [file exists $fn] {
1086 set flags(logger) $fn
1089 Log "Logger is $flags(logger)"
1091 if {$flags(logger) == ""} {
1092 Log "Can't add system message, no syslog helper found"
1093 return
1095 catch {exec $flags(logger) -p syslog.notice "$msg" 2>/dev/null}
1098 # end of proc {SysLog}
1100 proc {SetStorageDelay} {secs} {
1102 Log "Adjusting delay for USB storage devices ..."
1103 set attrib /sys/module/usb_storage/parameters/delay_use
1104 if {![file exists $attrib]} {
1105 Log "Error: could not find delay_use attribute"
1106 return
1108 if [catch {set ch [open $attrib r+]} err] {
1109 Log "Error: could not access delay_use attribute: $err"
1110 return
1112 if {[read $ch] < $secs} {
1113 seek $ch 0 start
1114 puts -nonewline $ch $secs
1115 Log " Delay set to $secs seconds\n"
1116 } else {
1117 Log " Current value is higher than $secs. Leave it alone\n"
1119 close $ch
1122 # end of proc {SetStorageDelay}
1124 proc {CheckMBIM} {} {
1126 set kversion [exec uname -r]
1127 if [file exists /lib/modules/$kversion/kernel/drivers/net/usb/cdc_mbim.ko] {return 1}
1128 if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1129 return 0
1134 # The actual entry point
1135 Main $argv $argc