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 by
13
# /lib/udev/rules.d/40-usb_modeswitch.rules
15
# Does ID check on hotplugged USB devices and calls the
16
# mode switching program with the matching parameter file
17
# from /etc/usb_modeswitch.d
19
# Part of usb-modeswitch-1.1.0 package
20
# (C) Josua Dietze 2009, 2010
23
# Change this to 1 if you want verbose logging
24
# to /var/log/usb_modeswitch_<device-interface>
29
set env(PATH) "/bin:/usr/bin"
31
# Execution starts at file bottom
33
proc {Main} {argc argv} {
35
global scsi usb match wc logging device
37
set dbdir /etc/usb_modeswitch.d
40
# argv contains the values provided from the udev rule
43
set argList [split [lindex $argv 0] /]
45
if [string length [lindex $argList 1]] {
46
set device [lindex $argList 1]
51
Log "raw args from udev: $argv"
53
if {$device == "noname"} {
54
Log "No data from udev. Exiting"
58
if {![string match *0 $device]} {
59
Log "Interface is not 0. Not the install storage. Exiting"
64
# arg 0: the bus id for the device (udev: %b)
65
# arg 1: the "kernel name" for the device (udev: %k)
67
# Both together give the top directory where the path
68
# to the SCSI attributes can be determined (further down)
69
# Addendum: older kernel/udev version seem to differ in
70
# providing these attributes - or not. So more probing
73
if {[string length [lindex $argList 0]] == 0} {
74
if {[string length [lindex $argList 1]] == 0} {
75
Log "No device number values given from udev! Exiting"
78
Log "Bus ID for device not given by udev."
79
Log " Trying to determine it from kernel name ([lindex $argList 1]) ..."
80
if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
81
Log "Could not determine top device dir from udev values! Exiting"
86
set dev_top [lindex $argList 0]
87
regexp {(.*?):} $dev_top d dev_top
91
set devdir /sys/bus/usb/devices/$dev_top
92
if {![file isdirectory $devdir]} {
93
Log "Top sysfs directory not found ($devdir)! Exiting"
98
# Mapping of the short string identifiers (in the config
99
# file names) to the long name used here
101
# If we need them it's a snap to add new attributes here!
103
set match(sVe) scsi(vendor)
104
set match(sMo) scsi(model)
105
set match(sRe) scsi(rev)
106
set match(uMa) usb(manufacturer)
107
set match(uPr) usb(product)
108
set match(uSe) usb(serial)
111
# Now reading the USB attributes
115
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
116
Log "USB IDs not found in sysfs tree. Exiting"
120
Log "----------------\nUSB values from sysfs:"
121
foreach attr {manufacturer product serial} {
122
Log " $attr\t$usb($attr)"
124
Log "----------------"
126
# Check if there is more than one config file for this USB ID,
127
# which would point to a possible ambiguity. If so, check if
128
# SCSI values are needed
130
set configList [glob -nocomplain $dbdir/$usb(idVendor):$usb(idProduct)*]
131
if {[llength $configList] == 0} {
132
Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exiting"
137
if {[llength $configList] > 1} {
138
if [regexp {:s} $configList] {
143
Log "SCSI attributes not needed, moving on"
147
# Getting the SCSI values via libusb results in a detached
148
# usb-storage driver. Not good for devices that want to be
149
# left alone. Fortunately, the sysfs tree provides the values
150
# too without need for direct access
152
# First we wait until the SCSI data is ready - or timeout.
153
# Timeout means: no storage driver was bound to the device.
154
# We run 20 times max, every half second (max. 10 seconds
157
# We also check if the device itself changes, probably
158
# because it was switched by the kernel (or even unplugged).
159
# Then we do simply nothing and exit quietly ...
162
while {$scsiNeeded && $counter < 20} {
165
Log "waiting for storage tree in sysfs"
167
set sysdir $devdir/[lindex $argList 1]
169
if {![file isdirectory $sysdir]} {
170
# Device is gone. Unplugged? Switched by kernel?
171
Log "sysfs device tree is gone; exiting"
174
set rc [open $devdir/product r]
175
set newproduct [read -nonewline $rc]
177
if {![string match $newproduct $usb(product)]} {
178
# Device has just changed. Switched by someone else?
179
Log "device has changed; exiting"
183
# Searching the storage/SCSI tree; might take a while
184
if {[set dirList [glob -nocomplain $sysdir/host*]] != ""} {
185
set sysdir [lindex $dirList 0]
186
if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
187
set sysdir [lindex $dirList 0]
188
regexp {.*target(.*)} $sysdir d subdir
189
if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
190
set sysdir [lindex $dirList 0]
191
if [file exists $sysdir/vendor] {
192
# Finally SCSI structure is ready, get the values
193
ReadSCSIAttrs $sysdir
194
Log "SCSI values read"
202
if {$counter == 20 && [string length $scsi(vendor)] == 0} {
203
Log "SCSI tree not found; you may want to check if this path/file exists:"
204
Log "$sysdir/vendor\n"
206
Log "----------------\nSCSI values from sysfs:"
207
foreach attr {vendor model rev} {
208
Log " $attr\t$scsi($attr)"
210
Log "----------------"
212
Log "Waiting 3 secs. after SCSI device was added"
218
# If SCSI tree in sysfs was not identified, try and get the values
219
# from a (nonswitching) call of usb_modeswitch; this detaches the
220
# storage driver, so it's just the last resort
222
if {$scsiNeeded && $scsi(vendor)==""} {
223
set testSCSI [exec $bindir/usb_modeswitch -v 0x$usb(idVendor) -p 0x$usb(idProduct)]
224
regexp { Vendor String: (.*?)\n} $testSCSI d scsi(vendor)
225
regexp { Model String: (.*?)\n} $testSCSI d scsi(model)
226
regexp {Revision String: (.*?)\n} $testSCSI d scsi(rev)
227
Log "SCSI values from usb_modeswitch:"
228
foreach attr {vendor model rev} {
229
Log " $attr\t$scsi($attr)"
233
# If we don't have the SCSI values by now, we just
234
# leave the variables empty; they won't match anything
236
# Time to check for a matching config file.
237
# Matching itself is done by MatchDevice
239
# Sorting the configuration file names reverse so that
240
# the ones with matching additions are tried first; the
241
# common configs without matching are used at the end and
242
# provide a kind of fallback
245
set configList [glob -nocomplain $dbdir/$usb(idVendor):$usb(idProduct)*]
246
foreach configuration [lsort -decreasing $configList] {
247
Log "checking config: $configuration"
248
if [MatchDevice $configuration] {
249
set switch_config $configuration
250
set devList1 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
251
Log "! matched, now switching"
253
Log " (running command: $bindir/usb_modeswitch -I -W -c $configuration)"
254
set report [exec $bindir/usb_modeswitch -I -W -D -c $configuration 2>@ stdout]
256
set report [exec $bindir/usb_modeswitch -I -Q -D -c $configuration]
258
Log "\nverbose output of usb_modeswitch:"
259
Log "--------------------------------"
261
Log "--------------------------------"
262
Log "(end of usb_modeswitch output)\n"
265
Log "* no match, not switching with this config"
269
# We're finished with switching; success checking
270
# was done by usb_modeswitch and logged via syslog.
272
# If switching was OK we now check for drivers by
273
# simply recounting serial devices under /dev
275
if [regexp {ok:} $report] {
276
# some settle time in ms
279
Log "Now checking for newly created serial devices ..."
280
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS*]
282
if {[llength $devList1] >= [llength $devList2]} {
283
Log " no new serial devices found"
285
# Backup for unknown target IDs: check sysfs again
286
# as soon as device is back
287
if [regexp {ok:0000:0000} $report] {
288
for {set i 0} {$i < 19} {incr i} {
289
if {![file exists $devdir/idProduct]} {
296
if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
297
regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)
299
set t "$usb(idVendor)$usb(idProduct)"
300
if {[string length $t] == 8 && [string trim $t 0] != ""} {
301
set idfile /sys/bus/usb-serial/drivers/option1/new_id
302
if {![file exists $idfile]} {
303
Log "\nTrying to load the option driver"
304
set loader /sbin/modprobe
305
Log " loader is: $loader"
306
if [file exists $loader] {
307
set result [exec $loader -v option]
308
if {[regexp {not found} $result]} {
309
Log " option driver not present as module"
312
Log " /sbin/modprobe not found"
315
if [file exists $idfile] {
316
Log "Trying to add ID to option driver"
317
catch {exec logger -p syslog.notice "usb_modeswitch: adding device ID $usb(idVendor):$usb(idProduct)" to driver \"option\""}
319
exec echo "$usb(idVendor) $usb(idProduct)" >$idfile
321
set devList2 [glob -nocomplain /dev/ttyUSB* /dev/ttyACM*]
322
if {[llength $devList1] >= [llength $devList2]} {
323
Log " still no new serial devices found"
325
Log " driver successfully bound"
330
Log " new serial devices found, driver has bound"
334
Log "\nAll done, exiting\n"
341
proc {ReadSCSIAttrs} {dir} {
344
Log "SCSI dir exists: $dir"
346
foreach attr {vendor model rev} {
347
if [file exists $dir/$attr] {
348
set rc [open $dir/$attr r]
349
set scsi($attr) [read -nonewline $rc]
353
Log "Warning: SCSI attribute \"$attr\" not found."
358
# end of proc {ReadSCSIAttrs}
361
proc {ReadUSBAttrs} {dir} {
364
Log "USB dir exists: $dir"
366
foreach attr {idVendor idProduct manufacturer product serial} {
367
if [file exists $dir/$attr] {
368
set rc [open $dir/$attr r]
369
set usb($attr) [read -nonewline $rc]
373
Log "Warning: USB attribute \"$attr\" not found."
378
# end of proc {ReadUSBAttrs}
381
proc {MatchDevice} {config} {
383
global scsi usb match
385
set devinfo [file tail $config]
386
set infoList [split $devinfo :]
387
set stringList [lrange $infoList 2 end]
388
if {[llength $stringList] == 0} {return 1}
390
foreach teststring $stringList {
391
if {$teststring == "?"} {return 0}
392
set tokenList [split $teststring =]
393
set id [lindex $tokenList 0]
394
set matchstring [lindex $tokenList 1]
395
regsub -all {_} $matchstring { } matchstring
396
Log "matching $match($id)"
397
Log " match string: $matchstring"
398
Log " device string: [set $match($id)]"
399
if {![string match $matchstring* [set $match($id)]] } {
406
# end of proc {MatchDevice}
411
global wc logging device
412
if {$logging == 0} {return}
413
if {![info exists wc]} {
414
set wc [open /var/log/usb_modeswitch_$device a+]
415
puts $wc "\n\nUSB_ModeSwitch log from [clock format [clock seconds]]\n"
417
# set wc [open /var/log/usb_modeswitch_$device a+]
428
if [info exists wc] {
434
# end of proc {SafeExit}
437
# The actual entry point