2
# next lines for bash, ignored by tclsh, restarting in background \
5
while [ $count != 0 ]; do \
6
if [ ! -e "/usr/bin/tclsh" ]; then \
8
count=$(($count - 1)); \
10
exec /usr/bin/tclsh "$0" "$@" 2>/dev/null & \
18
# Wrapper (tcl) for usb_modeswitch, called from
19
# /lib/udev/rules.d/40-usb_modeswitch.rules
20
# (part of data pack "usb-modeswitch-data") via
21
# /lib/udev/usb_modeswitch
23
# Does ID check on hotplugged USB devices and calls the
24
# mode switching program with the matching parameter file
25
# from /etc/usb_modeswitch.d
27
# Part of usb-modeswitch-1.1.4 package
28
# (C) Josua Dietze 2009, 2010
31
# Setting of the these switches is done in the global config
32
# file (/etc/usb_modeswitch.conf)
39
set env(PATH) "/bin:/usr/bin"
41
# Execution starts at file bottom
43
proc {Main} {argc argv} {
45
global scsi usb config match wc device logging noswitching settings
47
# The facility to add a symbolic link pointing to the
48
# ttyUSB port which provides interrupt transfer, i.e.
49
# the port to connect through; returns a symlink name
51
# This is run once for every device interface by an
54
if {[lindex $argv 0] == "--symlink-name"} {
55
# set device [clock clicks]
56
puts [SymLinkName [lindex $argv 1]]
60
# The facility to bind the driver on-the-fly after a warm
61
# boot; the device is still in modem mode but if the
62
# driver was bound by the switching script before (ID
63
# not yet added to the driver), the device needs to be
66
if {[lindex $argv 0] == "--driver-bind"} {
67
CheckDriverBind [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
71
set settings(dbdir) /etc/usb_modeswitch.d
77
# argv contains the values provided from the udev rule
80
set argList [split [lindex $argv 0] /]
82
if [string length [lindex $argList 1]] {
83
set device [lindex $argList 1]
90
Log "raw args from udev: $argv"
92
if {$device == "noname"} {
93
Log "No data from udev. Exiting"
97
# arg 0: the bus id for the device (udev: %b)
98
# arg 1: the "kernel name" for the device (udev: %k)
100
# Both together give the top directory where the path
101
# to the SCSI attributes can be determined (further down)
102
# Addendum: older kernel/udev version seem to differ in
103
# providing these attributes - or not. So more probing
106
if {[string length [lindex $argList 0]] == 0} {
107
if {[string length [lindex $argList 1]] == 0} {
108
Log "No device number values given from udev! Exiting"
111
Log "Bus ID for device not given by udev."
112
Log " Trying to determine it from kernel name ([lindex $argList 1]) ..."
113
if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
114
Log "Could not determine top device dir from udev values! Exiting"
119
set dev_top [lindex $argList 0]
120
regexp {(.*?):} $dev_top d dev_top
124
set devdir /sys/bus/usb/devices/$dev_top
125
if {![file isdirectory $devdir]} {
126
Log "Top sysfs directory not found ($devdir)! Exiting"
131
# Mapping of the short string identifiers (in the config
132
# file names) to the long name used here
134
# If we need them it's a snap to add new attributes here!
136
set match(sVe) scsi(vendor)
137
set match(sMo) scsi(model)
138
set match(sRe) scsi(rev)
139
set match(uMa) usb(manufacturer)
140
set match(uPr) usb(product)
141
set match(uSe) usb(serial)
144
# Now reading the USB attributes
148
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
149
Log "USB IDs not found in sysfs tree. Exiting"
153
Log "----------------\nUSB values from sysfs:"
154
foreach attr {manufacturer product serial} {
155
Log " $attr\t$usb($attr)"
157
Log "----------------"
160
Log "\nSwitching globally disabled. Exiting\n"
161
catch {exec logger -p syslog.notice "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)" 2>/dev/null}
165
# Check if there is more than one config file for this USB ID,
166
# which would point to a possible ambiguity. If so, check if
167
# SCSI values are needed
169
set configList [ConfigGet list $usb(idVendor):$usb(idProduct)]
171
if {[llength $configList] == 0} {
172
Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exiting"
177
if {[llength $configList] > 1} {
178
if [regexp {:s} $configList] {
183
Log "SCSI attributes not needed, moving on"
187
# Getting the SCSI values via libusb results in a detached
188
# usb-storage driver. Not good for devices that want to be
189
# left alone. Fortunately, the sysfs tree provides the values
190
# too without need for direct access
192
# First we wait until the SCSI data is ready - or timeout.
193
# Timeout means: no storage driver was bound to the device.
194
# We run 20 times max, every half second (max. 10 seconds
197
# We also check if the device itself changes, probably
198
# because it was switched by the kernel (or even unplugged).
199
# Then we do simply nothing and exit quietly ...
202
while {$scsiNeeded && $counter < 20} {
205
Log "waiting for storage tree in sysfs"
207
set sysdir $devdir/[lindex $argList 1]
209
if {![file isdirectory $sysdir]} {
210
# Device is gone. Unplugged? Switched by kernel?
211
Log "sysfs device tree is gone; exiting"
214
set rc [open $devdir/product r]
215
set newproduct [read -nonewline $rc]
217
if {![string match $newproduct $usb(product)]} {
218
# Device has just changed. Switched by someone else?
219
Log "device has changed; exiting"
223
# Searching the storage/SCSI tree; might take a while
224
if {[set dirList [glob -nocomplain $sysdir/host*]] != ""} {
225
set sysdir [lindex $dirList 0]
226
if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
227
set sysdir [lindex $dirList 0]
228
regexp {.*target(.*)} $sysdir d subdir
229
if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
230
set sysdir [lindex $dirList 0]
231
if [file exists $sysdir/vendor] {
232
# Finally SCSI structure is ready, get the values
233
ReadSCSIAttrs $sysdir
234
Log "SCSI values read"
242
if {$counter == 20 && [string length $scsi(vendor)] == 0} {
243
Log "SCSI tree not found; you may want to check if this path/file exists:"
244
Log "$sysdir/vendor\n"
246
Log "----------------\nSCSI values from sysfs:"
247
foreach attr {vendor model rev} {
248
Log " $attr\t$scsi($attr)"
250
Log "----------------"
252
Log "Waiting 3 secs. after SCSI device was added"
258
# If SCSI tree in sysfs was not identified, try and get the values
259
# from a (nonswitching) call of usb_modeswitch; this detaches the
260
# storage driver, so it's just the last resort
262
if {$scsiNeeded && $scsi(vendor)==""} {
263
set testSCSI [exec $bindir/usb_modeswitch -v 0x$usb(idVendor) -p 0x$usb(idProduct) 2>/dev/null]
264
regexp { Vendor String: (.*?)\n} $testSCSI d scsi(vendor)
265
regexp { Model String: (.*?)\n} $testSCSI d scsi(model)
266
regexp {Revision String: (.*?)\n} $testSCSI d scsi(rev)
267
Log "SCSI values from usb_modeswitch:"
268
foreach attr {vendor model rev} {
269
Log " $attr\t$scsi($attr)"
273
# If we don't have the SCSI values by now, we just
274
# leave the variables empty; they won't match anything
276
# Time to check for a matching config file.
277
# Matching itself is done by MatchDevice
279
# Sorting the configuration file names reverse so that
280
# the ones with matching additions are tried first; the
281
# common configs without match attributes are used at the
282
# end and provide a fallback
285
#set configList [glob -nocomplain $settings(dbdir)/$usb(idVendor):$usb(idProduct)*]
286
foreach configuration [lsort -decreasing $configList] {
288
# skipping installer leftovers
289
if [regexp {\.(dpkg|rpm)} $configuration] {continue}
291
Log "checking config: $configuration"
292
if [MatchDevice $configuration] {
293
ParseDeviceConfig [ConfigGet config $configuration]
294
set devList1 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
295
if {$config(waitBefore) == ""} {
296
Log "! matched, now switching"
298
Log "! matched, waiting time set to $config(waitBefore) seconds"
299
after [expr $config(waitBefore) * 1000]
300
Log " waiting is over, switching starts now"
303
# Now we are actually switching
305
Log " (running command: $bindir/usb_modeswitch -I -W -c $settings(tmpConfig))"
306
set report [exec $bindir/usb_modeswitch -I -W -D -c $settings(tmpConfig) 2>@ stdout]
308
set report [exec $bindir/usb_modeswitch -I -Q -D -c $settings(tmpConfig) 2>/dev/null]
310
Log "\nverbose output of usb_modeswitch:"
311
Log "--------------------------------"
313
Log "--------------------------------"
314
Log "(end of usb_modeswitch output)\n"
315
if [regexp {/tmp/} $settings(tmpConfig)] {
316
file delete $settings(tmpConfig)
320
Log "* no match, not switching with this config"
324
# We're finished with switching; success checking
325
# was done by usb_modeswitch and logged via syslog.
327
# If switching was OK we now check for drivers by
328
# simply recounting serial devices under /dev
330
# If target ID given, driver shall be loaded
331
if [regexp -nocase {ok:[0-9a-f]{4}:[0-9a-f]{4}} $report] {
333
# For general driver loading; TODO: add respective device names.
334
# Presently only useful for HSO devices (which are recounted now)
335
if {$config(driverModule) == ""} {
336
set config(driverModule) "option"
337
set config(driverIDPath) "/sys/bus/usb-serial/drivers/option1"
339
if {$config(driverIDPath) == ""} {
340
set config(driverIDPath) "/sys/bus/usb/drivers/$config(driverModule)"
343
Log "Driver module is \"$config(driverModule)\", ID path is $config(driverIDPath)\n"
345
# some settling time in ms
348
Log "Now checking for newly created serial devices ..."
349
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
351
if {[llength $devList1] >= [llength $devList2]} {
352
Log " no new serial devices found"
354
if {![file isdirectory $devdir]} {
355
Log "Device directory in sysfs is gone! Something went wrong, aborting"
359
# Give the device annother second if it's not fully back yet
360
if {![file exists $devdir/idProduct]} {
365
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
366
regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)
368
set t "$usb(idVendor)$usb(idProduct)"
369
if {[string length $t] == 8 && [string trim $t 0] != ""} {
370
set idfile $config(driverIDPath)/new_id
371
if {![file exists $idfile]} {
372
Log "\nTrying to load driver \"$config(driverModule)\""
373
set loader /sbin/modprobe
374
Log " loader is: $loader"
375
if [file exists $loader] {
376
if [catch {set result [exec $loader -v $config(driverModule) 2>/dev/null]} err] {
377
Log " Running \"$loader $config(driverModule)\" gave an error:\n $err"
380
Log " /sbin/modprobe not found"
383
if [file exists $idfile] {
384
Log "Trying to add ID to driver \"$config(driverModule)\""
385
catch {exec logger -p syslog.notice "usb_modeswitch: adding device ID $usb(idVendor):$usb(idProduct) to driver \"$config(driverModule)\"" 2>/dev/null}
386
catch {exec echo "$usb(idVendor) $usb(idProduct)" >$idfile 2>/dev/null}
388
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
389
if {[llength $devList1] >= [llength $devList2]} {
390
Log " still no new serial devices found"
392
AddLastSeen "$usb(idVendor):$usb(idProduct)"
393
Log " driver successfully bound"
396
Log " \"$idfile\" not found, can't add ID"
400
Log " new serial devices found, driver has bound"
406
if [regexp {ok:$} $report] {
407
Log "Doing no driver checking or binding for this device"
410
# In newer kernels there is a switch to avoid the use of a device
411
# reset (e.g. from usb-storage) which would likely switch back
412
# a mode-switching device
413
if [regexp {ok:} $report] {
414
Log "Checking for AVOID_RESET_QUIRK attribute"
415
if [file exists $devdir/avoid_reset_quirk] {
416
if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] {
417
Log " Error setting the attribute: $err"
419
Log " AVOID_RESET_QUIRK activated"
422
Log " AVOID_RESET_QUIRK not present"
426
Log "\nAll done, exiting\n"
433
proc {ReadSCSIAttrs} {dir} {
436
Log "SCSI dir exists: $dir"
438
foreach attr {vendor model rev} {
439
if [file exists $dir/$attr] {
440
set rc [open $dir/$attr r]
441
set scsi($attr) [read -nonewline $rc]
445
Log "Warning: SCSI attribute \"$attr\" not found."
450
# end of proc {ReadSCSIAttrs}
453
proc {ReadUSBAttrs} {dir} {
456
Log "USB dir exists: $dir"
458
foreach attr {idVendor idProduct manufacturer product serial} {
459
if [file exists $dir/$attr] {
460
set rc [open $dir/$attr r]
461
set usb($attr) [read -nonewline $rc]
465
Log "Warning: USB attribute \"$attr\" not found."
470
# end of proc {ReadUSBAttrs}
473
proc {MatchDevice} {config} {
475
global scsi usb match
477
set devinfo [file tail $config]
478
set infoList [split $devinfo :]
479
set stringList [lrange $infoList 2 end]
480
if {[llength $stringList] == 0} {return 1}
482
foreach teststring $stringList {
483
if {$teststring == "?"} {return 0}
484
set tokenList [split $teststring =]
485
set id [lindex $tokenList 0]
486
set matchstring [lindex $tokenList 1]
488
regsub -all {_} $matchstring { } blankstring
489
Log "matching $match($id)"
490
Log " match string1: $matchstring"
491
Log " match string2: $blankstring"
492
Log " device string: [set $match($id)]"
493
if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} {
500
# end of proc {MatchDevice}
503
proc {ParseGlobalConfig} {} {
505
global logging noswitching
508
set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
509
foreach cfg $places {
510
if [file exists $cfg] {
516
if {$configFile == ""} {return}
518
set rc [open $configFile r]
521
if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
522
if [regexp -nocase {1|yes|true} $val] {
526
if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
527
if [regexp -nocase {1|yes|true} $val] {
533
Log "Using global config file: $configFile"
536
# end of proc {ParseGlobalConfig}
539
proc ParseDeviceConfig {configFile} {
542
set config(driverModule) ""
543
set config(driverIDPath) ""
544
set config(waitBefore) ""
545
set rc [open $configFile r]
546
set lineList [split [read $rc] \n]
548
foreach line $lineList {
549
regexp {DriverModule[[:blank:]]*=[[:blank:]]*"?(\w+)"?} $line d config(driverModule)
550
regexp {DriverIDPath[[:blank:]]*=[[:blank:]]*?"?([/\-\w]+)"?} $line d config(driverIDPath)
551
regexp {WaitBefore[[:blank:]]*=[[:blank:]]*?(\d+)} $line d config(waitBefore)
553
set config(waitBefore) [string trimleft $config(waitBefore) 0]
556
# end of proc {ParseDeviceConfig}
559
proc {ConfigGet} {command config} {
566
if [file exists $settings(dbdir)/configPack.tar.gz] {
567
Log "Found packed config collection $settings(dbdir)/configPack.tar.gz"
568
if [catch {set configList [exec tar -tzf $settings(dbdir)/configPack.tar.gz 2>/dev/null]} err] {
569
Log "Error: problem opening config package; tar returned\n $err"
572
set configList [split $configList \n]
573
set configList [lsearch -all -inline $configList $config*]
575
set configList [glob -nocomplain $settings(dbdir)/$config*]
581
if [file exists $settings(dbdir)/configPack.tar.gz] {
582
set settings(tmpConfig) /tmp/usb_modeswitch.current_cfg
583
Log "Extracting config $config from collection $settings(dbdir)/configPack.tar.gz"
584
set wc [open $settings(tmpConfig) w]
585
puts -nonewline $wc [exec tar -xzOf $settings(dbdir)/configPack.tar.gz $config 2>/dev/null]
588
set settings(tmpConfig) $config
590
return $settings(tmpConfig)
595
# end of proc {ConfigGet}
599
global wc logging device
600
if {$logging == 0} {return}
601
if {![info exists wc]} {
602
if [catch {set wc [open /var/log/usb_modeswitch_$device a]} err] {
604
puts "Error: Can't write to log file, $err"
607
puts $wc "\n\nUSB_ModeSwitch log from [clock format [clock seconds]]\n"
609
if {$wc == "error"} {return}
616
# Closing the log file if open and exit
620
if [info exists wc] {
626
# end of proc {SafeExit}
629
# Checking for interrupt endpoint in ttyUSB port (lowest if there is
630
# more than one); if found, check for unused "gsmmodem[n]" name.
631
# Link for first modem will be "gsmmodem", then "gsmmodem2" and up
633
proc {SymLinkName} {path} {
635
# Internal proc, used only here
636
proc {hasInterrupt} {ifDir} {
637
if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} {return 0}
638
foreach epDir [glob -nocomplain $ifDir/ep_*] {
639
if [file exists $epDir/type] {
640
set rc [open $epDir/type r]
643
if [regexp {Interrupt} $type] {
651
# In case the device path is returned as /class/tty/ttyUSB,
652
# we need to extract the USB device path from symlink "device"
653
set linkpath /sys$path/device
654
if [file exists $linkpath] {
655
if {[file type $linkpath] == "link"} {
656
set rawpath [file link $linkpath]
657
set trimpath [regsub -all {\.\./} $rawpath {}]
658
if [file isdirectory /sys/$trimpath] {
664
if {![regexp {ttyUSB\d+?} $path myPort]} {
667
if {![regexp "\\d+\\.(\\d+)/$myPort" $path d myIf]} {
670
if {![regexp {usb\d*/(\d+-\d+)/} $path d dev_top]} {
674
set dirList [split $path /]
675
set idx [lsearch $dirList $dev_top]
676
set devDir /sys[join [lrange $dirList 0 $idx] /]
678
if {![regexp "$devDir/$dev_top:\[0-9\]" /sys$path ifRoot]} {
681
set ifDir $ifRoot.$myIf
684
if [hasInterrupt $ifDir] {
688
# Unfortunately, there are devices with more than one interrupt
689
# port. The assumption so far is that the lowest of these is
690
# right. Check all lower interfaces for annother one (if interface)
691
# is bigger than 0). If found, don't return any name.
692
if { $rightPort && ($myIf > 0) } {
693
for {set i 0} {$i < $myIf} {incr i} {
695
if [hasInterrupt $ifDir] {
701
if {$rightPort == 0} {
705
# Use first free "gsmmodem[n]" name
708
set symlinkName "gsmmodem"
710
if {![file exists $symlinkName]} {
713
set symlinkName gsmmodem$idx
719
# end of proc {SymLinkName}
722
# Add serial driver after warm boot
723
proc {CheckDriverBind} {path vid pid prod} {
725
if {$vid == ""} {set vid $prod}
726
if [regexp {/} $vid] {
727
set id_list [split $vid /]
728
set vid [format %04s [lindex $id_list 0]]
729
set pid [format %04s [lindex $id_list 1]]
732
set dirList [glob -nocomplain /sys$path/*]
733
if [string match *ttyUSB* $dirList] {return}
735
if {[WasLastSeen $vid:$pid] == 0} {return}
737
set config(driverModule) "option"
738
set config(driverIDPath) "/sys/bus/usb-serial/drivers/option1"
739
set idfile $config(driverIDPath)/new_id
740
if {![file exists $idfile]} {
741
set loader /sbin/modprobe
742
if {![file exists $loader]} {return}
743
if [catch {exec $loader $config(driverModule) 2>/dev/null}] {return}
746
if [file exists $idfile] {
752
if {$i == 50} {return}
754
catch {exec echo "$vid $pid" >$idfile 2>/dev/null}
757
# end of proc {CheckDriverBind}
760
# Add USB ID to list of devices needing driver binding
761
proc {AddLastSeen} {id} {
763
set lastseen /etc/usb_modeswitch.d/last_seen
764
if [file exists $lastseen] {
765
set rc [open $lastseen r]
766
set buffer [read $rc]
768
if [string match *$id* $buffer] {
771
set idList [split [string trim $buffer] \n]
774
set buffer [join $idList "\n"]
775
if [catch {set wc [open $lastseen w]}] {return}
776
puts -nonewline $wc $buffer
780
# end of proc {AddLastSeen}
783
# Check if USB ID is listed as needing driver binding
784
proc {WasLastSeen} {id} {
786
set lastseen /etc/usb_modeswitch.d/last_seen
787
if {![file exists $lastseen]} {return 0}
788
set rc [open $lastseen r]
789
set buffer [read $rc]
791
if [string match *$id* $buffer] {
798
# end of proc {WasLastSeen}
801
# The actual entry point