usbmodeswitch: Updated to v.1.2.6 from shibby's branch.
[tomato.git] / release / src / router / usbmodeswitch / usb_modeswitch_dispatcher
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
21         }
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
109                         }
110                 }
111         }
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]
154         }
155         if {$iface < 0} {
156                 if {$usb(idVendor)=="19d2" && $usb(idProduct)=="2000"} {
157                         set iface [ChkIface 3]
158                 }
159 # Corrected, was wrongly reported
160 #               if {$usb(idVendor)=="16d8" && $usb(idProduct)=="6803"} {
161 #                       set iface [ChkIface 3]
162 #               }
163         }
164         if {$iface < 0} {
165                 Log " Device is not in install mode. Exiting"
166                 SafeExit
167         } else {
168                 Log " Device is in install mode."
169         }
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)"
181         }
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
213         }
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)"
220                 }
221                 Log "----------------"
222         } else {
223                 Log "Could not get SCSI attributes, exclude devices with SCSI match"
224         }
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 ""
250                 }
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"
259                 }
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"
275                                         }
276                                 }
277                         } else {
278                                 Log " no MBIM driver found, switch to legacy modem mode"
279                         }
280                 }
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]
294                 }
295                 break
296         } else {
297                 Log "* no match, not switching with this config"
298         }
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)"
314                 }
315                 Log "\nMode switching may have failed. Exiting\n"
316                 SafeExit
317         }
318 } else {
319         if {![file isdirectory $devdir]} {
320                 Log "Device directory in sysfs is gone! Something went wrong, aborting"
321                 SafeExit
322         }
323         if {![regexp {ok:} $report]} {
324                 Log "\nCore program reported switching failure. Exiting\n"
325                 SafeExit
326         }
327         # Give the device another second if it's not fully back yet
328         if {![file exists $devdir/idProduct]} {
329                 after 1000
330         }
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"
340         }
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
351                 }
352         }
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
373                         }
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)
379                         }
380                 }
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)
385                 }
386         }
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"
407                 }
408         } else {
409                 Log " not present in this kernel"
410         }
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
432         }
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
444                                 }
445                         }
446                 }
447         }
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."
466         }
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
494                 }
495                 if {$attr == "serial"} {continue}
496                 Log "   Warning: USB attribute \"$attr\" not found"
497         }
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
527         }
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
544         }
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
555                 }
556         }
557         if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
558                 if [regexp -nocase {1|yes|true} $val] {
559                         set flags(logging) 1
560                 }
561         }
562         if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] {
563                 if [regexp {\d+} $val] {
564                         set flags(stordelay) $val
565                 }
566         }
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)"
633                 }
634         }
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 {}
660                         }
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"
666                         }
667                 }
669                 return $configList
670         }
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"
681                         }
682                         set rc [open $config r]
683                         set configContent [read $rc]
684                         close $rc
685                 }
686                 return $configContent
687         }
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"
705                 }
706         }
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
736         }
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
747                         }
748                 }
749         }
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
765                 }
766         }
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"
773         }
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
809                 }
810         }
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
827         }
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
848         }
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
857         }
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"
863         }
864 } else {
865         Log "Module is active already"
867 set i 0
868 while {$i < 50} {
869         if [file exists $idfile] {
870                 break
871         }
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"
883         }
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
893         }
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"
900         }
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
936         }
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
945         }
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 ..."
1007         }
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
1014         }
1015         if [string length $config(targetClass)] {
1016                 if {![regexp $usb($ifdir/bInterfaceClass) $config(targetClass)]} {continue}
1017         }
1018         if [string length $config(Configuration)] {
1019                 if {$usb(bConfigurationValue) != $config(Configuration)} {continue}
1020         }
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
1087                 }
1088         }
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