2
# next lines for bash, ignored by tclsh, restarting in background\
3
export PATH=/bin:/usr/bin; \
4
if [ ! -e "/usr/bin/tclsh" ]; then \
5
logger -p syslog.error "usb_modeswitch: tcl shell not found, install tcl package!"; \
7
/usr/bin/tclsh "$0" "$@" &>/dev/null & \
12
# Wrapper (tcl) for usb_modeswitch, called from
13
# /lib/udev/rules.d/40-usb_modeswitch.rules
14
# (part of data pack "usb-modeswitch-data")
16
# Does ID check on hotplugged USB devices and calls the
17
# mode switching program with the matching parameter file
18
# from /etc/usb_modeswitch.d
20
# Part of usb-modeswitch-1.1.2 package
21
# (C) Josua Dietze 2009, 2010
24
# Setting of the following switches is done in an external config
25
# file (/etc/usb_modeswitch.conf)
32
set env(PATH) "/bin:/usr/bin"
34
# Execution starts at file bottom
36
proc {Main} {argc argv} {
38
global scsi usb match wc device logging noswitching
40
# The facility to add a symbolic link pointing to the
41
# ttyUSB port which provides interrupt transfer, i.e.
42
# the port to connect through; returns a symlink name
45
if {[lindex $argv 0] == "symlink"} {
46
puts [SymLinkName [lindex $argv 2]]
49
eval file delete d [glob -nocomplain /tmp/gsmmodem_*]
51
set dbdir /etc/usb_modeswitch.d
58
# argv contains the values provided from the udev rule
61
set argList [split [lindex $argv 0] /]
63
if [string length [lindex $argList 1]] {
64
set device [lindex $argList 1]
71
Log "raw args from udev: $argv"
73
if {$device == "noname"} {
74
Log "No data from udev. Exiting"
78
if {![string match *0 $device]} {
79
Log "Interface is not 0. Not the install storage. Exiting"
84
# arg 0: the bus id for the device (udev: %b)
85
# arg 1: the "kernel name" for the device (udev: %k)
87
# Both together give the top directory where the path
88
# to the SCSI attributes can be determined (further down)
89
# Addendum: older kernel/udev version seem to differ in
90
# providing these attributes - or not. So more probing
93
if {[string length [lindex $argList 0]] == 0} {
94
if {[string length [lindex $argList 1]] == 0} {
95
Log "No device number values given from udev! Exiting"
98
Log "Bus ID for device not given by udev."
99
Log " Trying to determine it from kernel name ([lindex $argList 1]) ..."
100
if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
101
Log "Could not determine top device dir from udev values! Exiting"
106
set dev_top [lindex $argList 0]
107
regexp {(.*?):} $dev_top d dev_top
111
set devdir /sys/bus/usb/devices/$dev_top
112
if {![file isdirectory $devdir]} {
113
Log "Top sysfs directory not found ($devdir)! Exiting"
118
# Mapping of the short string identifiers (in the config
119
# file names) to the long name used here
121
# If we need them it's a snap to add new attributes here!
123
set match(sVe) scsi(vendor)
124
set match(sMo) scsi(model)
125
set match(sRe) scsi(rev)
126
set match(uMa) usb(manufacturer)
127
set match(uPr) usb(product)
128
set match(uSe) usb(serial)
131
# Now reading the USB attributes
135
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
136
Log "USB IDs not found in sysfs tree. Exiting"
140
Log "----------------\nUSB values from sysfs:"
141
foreach attr {manufacturer product serial} {
142
Log " $attr\t$usb($attr)"
144
Log "----------------"
147
Log "\nSwitching globally disabled. Exiting\n"
148
catch {exec logger -p syslog.notice "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"}
153
if {"$usb(idVendor)$usb(idProduct)" == "19d22000"} {
154
foreach dir {/etc/udev/rules.d /lib/udev/rules.d} {
155
catch {eval exec grep {"19d2.*2000.*eject"} [glob -nocomplain $dir/*]} result
156
if [regexp {(.*?):.*19d2} $result d ruleFile] {
157
Log "\nExisting ZTE rule found in $ruleFile. Exiting\n"
163
# Check if there is more than one config file for this USB ID,
164
# which would point to a possible ambiguity. If so, check if
165
# SCSI values are needed
167
# The glob matches $idVendor:$idProduct without postfix and with :spec=value postfixes
168
# This allows to filter out .dpkg-old files
169
set configList [glob -nocomplain $dbdir/$usb(idVendor):$usb(idProduct){,\[:a-Z0-9=\]*}]
170
if {[llength $configList] == 0} {
171
Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exiting"
176
if {[llength $configList] > 1} {
177
if [regexp {:s} $configList] {
182
Log "SCSI attributes not needed, moving on"
186
# Getting the SCSI values via libusb results in a detached
187
# usb-storage driver. Not good for devices that want to be
188
# left alone. Fortunately, the sysfs tree provides the values
189
# too without need for direct access
191
# First we wait until the SCSI data is ready - or timeout.
192
# Timeout means: no storage driver was bound to the device.
193
# We run 20 times max, every half second (max. 10 seconds
196
# We also check if the device itself changes, probably
197
# because it was switched by the kernel (or even unplugged).
198
# Then we do simply nothing and exit quietly ...
201
while {$scsiNeeded && $counter < 20} {
204
Log "waiting for storage tree in sysfs"
206
set sysdir $devdir/[lindex $argList 1]
208
if {![file isdirectory $sysdir]} {
209
# Device is gone. Unplugged? Switched by kernel?
210
Log "sysfs device tree is gone; exiting"
213
set rc [open $devdir/product r]
214
set newproduct [read -nonewline $rc]
216
if {![string match $newproduct $usb(product)]} {
217
# Device has just changed. Switched by someone else?
218
Log "device has changed; exiting"
222
# Searching the storage/SCSI tree; might take a while
223
if {[set dirList [glob -nocomplain $sysdir/host*]] != ""} {
224
set sysdir [lindex $dirList 0]
225
if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
226
set sysdir [lindex $dirList 0]
227
regexp {.*target(.*)} $sysdir d subdir
228
if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
229
set sysdir [lindex $dirList 0]
230
if [file exists $sysdir/vendor] {
231
# Finally SCSI structure is ready, get the values
232
ReadSCSIAttrs $sysdir
233
Log "SCSI values read"
241
if {$counter == 20 && [string length $scsi(vendor)] == 0} {
242
Log "SCSI tree not found; you may want to check if this path/file exists:"
243
Log "$sysdir/vendor\n"
245
Log "----------------\nSCSI values from sysfs:"
246
foreach attr {vendor model rev} {
247
Log " $attr\t$scsi($attr)"
249
Log "----------------"
251
Log "Waiting 3 secs. after SCSI device was added"
257
# If SCSI tree in sysfs was not identified, try and get the values
258
# from a (nonswitching) call of usb_modeswitch; this detaches the
259
# storage driver, so it's just the last resort
261
if {$scsiNeeded && $scsi(vendor)==""} {
262
set testSCSI [exec $bindir/usb_modeswitch -v 0x$usb(idVendor) -p 0x$usb(idProduct)]
263
regexp { Vendor String: (.*?)\n} $testSCSI d scsi(vendor)
264
regexp { Model String: (.*?)\n} $testSCSI d scsi(model)
265
regexp {Revision String: (.*?)\n} $testSCSI d scsi(rev)
266
Log "SCSI values from usb_modeswitch:"
267
foreach attr {vendor model rev} {
268
Log " $attr\t$scsi($attr)"
272
# If we don't have the SCSI values by now, we just
273
# leave the variables empty; they won't match anything
275
# Time to check for a matching config file.
276
# Matching itself is done by MatchDevice
278
# Sorting the configuration file names reverse so that
279
# the ones with matching additions are tried first; the
280
# common configs without match attributes are used at the
281
# end and provide a fallback
284
# The glob matches $idVendor:$idProduct without postfix and with :spec=value postfixes
285
# This allows to filter out .dpkg-old files
286
set configList [glob -nocomplain $dbdir/$usb(idVendor):$usb(idProduct){,\[:a-Z0-9=\]*}]
287
foreach configuration [lsort -decreasing $configList] {
288
Log "checking config: $configuration"
289
if [MatchDevice $configuration] {
290
set switch_config $configuration
291
set devList1 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
292
Log "! matched, now switching"
293
set tc [open /tmp/gsmmodem_$dev_top w]
296
Log " (running command: $bindir/usb_modeswitch -I -W -c $configuration)"
297
set report [exec $bindir/usb_modeswitch -I -W -D -c $configuration 2>@ stdout]
299
set report [exec $bindir/usb_modeswitch -I -Q -D -c $configuration]
301
Log "\nverbose output of usb_modeswitch:"
302
Log "--------------------------------"
304
Log "--------------------------------"
305
Log "(end of usb_modeswitch output)\n"
308
Log "* no match, not switching with this config"
312
# We're finished with switching; success checking
313
# was done by usb_modeswitch and logged via syslog.
315
# If switching was OK we now check for drivers by
316
# simply recounting serial devices under /dev
318
# If target ID given, driver shall be loaded
319
if [regexp -nocase {ok:[0-9a-f]{4}:[0-9a-f]{4}} $report] {
321
# For general driver loading; TODO: add respective device names.
322
# Presently only useful for HSO devices (which are recounted now)
325
set rc [open $configuration r]
326
set lineList [split [read $rc] \n]
328
foreach line $lineList {
329
regexp {DriverModule[[:blank:]]*=[[:blank:]]*"?(\w+)"?} $line d driverModule
330
regexp {DriverIDPath[[:blank:]]*=[[:blank:]]*?"?([/\-\w]+)"?} $line d driverIDPath
332
if {$driverModule == ""} {
333
set driverModule "option"
334
set driverIDPath "/sys/bus/usb-serial/drivers/option1"
336
if {$driverIDPath == ""} {
337
set driverIDPath "/sys/bus/usb/drivers/$driverModule"
340
Log "Driver module is \"$driverModule\", ID path is $driverIDPath\n"
342
# some settle time in ms
345
Log "Now checking for newly created serial devices ..."
346
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
348
if {[llength $devList1] >= [llength $devList2]} {
349
Log " no new serial devices found"
351
if {![file isdirectory $devdir]} {
352
Log "Device directory in sysfs is gone! Something went wrong, aborting"
356
# Give the device annother second if it's not fully back yet
357
if {![file exists $devdir/idProduct]} {
362
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
363
regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)
365
set t "$usb(idVendor)$usb(idProduct)"
366
if {[string length $t] == 8 && [string trim $t 0] != ""} {
367
set idfile $driverIDPath/new_id
368
if {![file exists $idfile]} {
369
Log "\nTrying to load driver \"$driverModule\""
370
set loader /sbin/modprobe
371
Log " loader is: $loader"
372
if [file exists $loader] {
373
if [catch {set result [exec $loader -v $driverModule]} err] {
374
Log " Running \"$loader $driverModule\" gave an error:\n $err"
377
Log " /sbin/modprobe not found"
380
if [file exists $idfile] {
381
Log "Trying to add ID to driver \"$driverModule\""
382
catch {exec logger -p syslog.notice "usb_modeswitch: adding device ID $usb(idVendor):$usb(idProduct) to driver \"$driverModule\""}
383
catch {exec echo "$usb(idVendor) $usb(idProduct)" >$idfile}
385
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
386
if {[llength $devList1] >= [llength $devList2]} {
387
Log " still no new serial devices found"
389
Log " driver successfully bound"
392
Log " \"$idfile\" not found, can't add ID"
396
Log " new serial devices found, driver has bound"
402
if [regexp {ok:$} $report] {
403
Log "Doing no driver checking or binding for this device"
406
# In newer kernels there is a switch to avoid the use of a device
407
# reset (e.g. from usb-storage) which would likely switch back
408
# a mode-switching device
409
if [regexp {ok:} $report] {
410
Log "Checking for AVOID_RESET_QUIRK attribute"
411
if [file exists $devdir/avoid_reset_quirk] {
412
if [catch {exec echo "1" >$devdir/avoid_reset_quirk} err] {
413
Log " Error setting the attribute: $err"
415
Log " AVOID_RESET_QUIRK activated"
418
Log " AVOID_RESET_QUIRK not present"
422
Log "\nAll done, exiting\n"
429
proc {ReadSCSIAttrs} {dir} {
432
Log "SCSI dir exists: $dir"
434
foreach attr {vendor model rev} {
435
if [file exists $dir/$attr] {
436
set rc [open $dir/$attr r]
437
set scsi($attr) [read -nonewline $rc]
441
Log "Warning: SCSI attribute \"$attr\" not found."
446
# end of proc {ReadSCSIAttrs}
449
proc {ReadUSBAttrs} {dir} {
452
Log "USB dir exists: $dir"
454
foreach attr {idVendor idProduct manufacturer product serial} {
455
if [file exists $dir/$attr] {
456
set rc [open $dir/$attr r]
457
set usb($attr) [read -nonewline $rc]
461
Log "Warning: USB attribute \"$attr\" not found."
466
# end of proc {ReadUSBAttrs}
469
proc {MatchDevice} {config} {
471
global scsi usb match
473
set devinfo [file tail $config]
474
set infoList [split $devinfo :]
475
set stringList [lrange $infoList 2 end]
476
if {[llength $stringList] == 0} {return 1}
478
foreach teststring $stringList {
479
if {$teststring == "?"} {return 0}
480
set tokenList [split $teststring =]
481
set id [lindex $tokenList 0]
482
set matchstring [lindex $tokenList 1]
484
regsub -all {_} $matchstring { } blankstring
485
Log "matching $match($id)"
486
Log " match string1: $matchstring"
487
Log " match string2: $blankstring"
488
Log " device string: [set $match($id)]"
489
if {!([string match $matchstring* [set $match($id)]] || [string match $blankstring* [set $match($id)]])} {
496
# end of proc {MatchDevice}
499
proc {ParseConfigFile} {} {
501
global logging noswitching
504
set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
505
foreach cfg $places {
506
if [file exists $cfg] {
512
if {$configFile == ""} {return}
514
set rc [open $configFile r]
517
if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
518
if [regexp -nocase {1|yes|true} $val] {
522
if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
523
if [regexp -nocase {1|yes|true} $val] {
529
Log "Using global config file: $configFile"
532
# end of proc {ParseConfigFile}
537
global wc logging device
538
if {$logging == 0} {return}
539
if {![info exists wc]} {
540
set wc [open /var/log/usb_modeswitch_$device a]
541
puts $wc "\n\nUSB_ModeSwitch log from [clock format [clock seconds]]\n"
550
# Checking for interrupt endpoint in ttyUSB port; if found,
551
# check for unused "gsmmodem[n]" name.
552
# First link will be "gsmmodem", then "gsmmodem2" and up
554
proc {SymLinkName} {path} {
556
# HACK ... /tmp/gsmmodem_* was generated by a switching run before;
557
# no way found to signal annother instance in the udev environment
558
set tmpname [lindex [glob -nocomplain /tmp/gsmmodem_*] 0]
559
set dev_top [lindex [split $tmpname _] 1]
561
set dirList [split $path /]
563
set idx [lsearch -regexp $dirList {\d+-\d+:\d+\.\d+}]
567
set ifDir /sys[join [lrange $dirList 0 $idx] /]
568
set port [lindex $dirList end]
570
if {![regexp "/$dev_top/" $path]} {
575
foreach epDir [glob -nocomplain $ifDir/ep_*] {
576
if [file exists $epDir/type] {
577
set rc [open $epDir/type r]
580
if [regexp {Interrupt} $type] {
581
set symlinkName "gsmmodem"
588
set trunkName $symlinkName
590
if {![file exists $symlinkName]} {
593
set symlinkName $trunkName$idx
600
# end of proc {SymLinkName}
605
if [info exists wc] {
611
# end of proc {SafeExit}
614
# The actual entry point