1#!/usr/bin/tclsh
2
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
7#
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
11#
12# Part of usb-modeswitch-2.2.3 package
13# (C) Josua Dietze 2009-2015
14
15set arg0 [lindex $argv 0]
16if [regexp {\.tcl$} $arg0] {
17	if [file exists $arg0] {
18		set argv [lrange $argv 1 end]
19		source $arg0
20		exit
21	}
22}
23
24# Setting of these switches is done in the global config
25# file (/etc/usb_modeswitch.conf) if available
26
27set flags(logging) 1
28set flags(noswitching) 0
29set flags(stordelay) 0
30set flags(logwrite) 0
31
32# Execution starts at file bottom
33
34proc {Main} {argv argc} {
35
36global scsi usb config match device flags setup devdir loginit
37
38set flags(config) ""
39Log "[ParseGlobalConfig]"
40
41# The facility to add a symbolic link pointing to the
42# ttyUSB port which provides interrupt transfer, i.e.
43# the port to connect through.
44# Will check for interrupt endpoint in ttyUSB port (lowest if
45# there is more than one); if found, return "gsmmodem[n]" name
46# to udev for symlink creation
47
48# This is run once for every port of LISTED devices by
49# a udev rule
50
51if {[lindex $argv 0] == "--symlink-name"} {
52	puts -nonewline [SymLinkName [lindex $argv 1]]
53	SafeExit
54}
55
56if {[lindex $argv 0] == "--switch-systemd"} {
57	set argList [split [lindex $argv 1] _]
58	Log "\nStarted via systemd"
59} else {
60	if {[lindex $argv 0] == "--switch-upstart"} {
61		Log "\nStarted via upstart"
62	}
63	set argList [split [lindex $argv 1] /]
64}
65if [string length [lindex $argList 1]] {
66	set device [lindex $argList 1]
67} else {
68	set device "noname"
69}
70if {$flags(stordelay) > 0} {
71	SetStorageDelay $flags(stordelay)
72}
73
74Log "Raw args from udev: [lindex $argv 1]\n"
75
76if {$device == "noname"} {
77	Log "\nNo data from udev. Exit"
78	SafeExit
79}
80
81if {![regexp -- {--switch-} [lindex $argv 0]]} {
82	Log "\nNo command given. Exit"
83	SafeExit
84}
85
86set setup(dbdir) /usr/share/usb_modeswitch
87set setup(dbdir_etc) /etc/usb_modeswitch.d
88
89
90if {![file exists $setup(dbdir)] && ![file exists $setup(dbdir_etc)]} {
91	Log "\nError: no config database found in /usr/share or /etc. Exit"
92	SafeExit
93}
94set bindir /usr/sbin
95
96set devList1 {}
97set devList2 {}
98
99
100# arg 0: the bus id for the device (udev: %b), often ommitted
101# arg 1: the "kernel name" for the device (udev: %k)
102#
103# Used to determine the top directory for the device in sysfs
104
105set ifChk 0
106if {[string length [lindex $argList 0]] == 0} {
107	if {[string length [lindex $argList 1]] == 0} {
108		Log "No device number values given from udev! Exit"
109		SafeExit
110	} else {
111		if {![regexp {(.*?):} [lindex $argList 1] d dev_top]} {
112			if [regexp {([0-9]+-[0-9]+\.?[0-9]*.*)} [lindex $argList 1] d dev_top] {
113				# new udev rules file, got to check class of first interface
114				set ifChk 1
115			} else {
116				Log "Could not determine device dir from udev values! Exit"
117				SafeExit
118			}
119		}
120	}
121} else {
122	set dev_top [lindex $argList 0]
123	regexp {(.*?):} $dev_top d dev_top
124}
125
126set devdir /sys/bus/usb/devices/$dev_top
127if {![file isdirectory $devdir]} {
128	Log "Top device directory not found ($devdir)! Exit"
129	SafeExit
130}
131Log "Use top device dir $devdir"
132
133set iface 0
134if $ifChk {
135	Log "Check class of first interface ..."
136	set config(class) [IfClass 0]
137	if {$iface < 0} {
138		Log " No access to interface 0. Exit"
139		SafeExit
140	}
141	Log " Interface class is $config(class)."
142	if {$config(class) == "08" || $config(class) == "03"} {
143	} else {
144		Log "No install mode found. Aborting"
145		exit
146	}
147}
148set ifdir [file tail [IfDir $iface]]
149regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface
150
151set flags(logwrite) 1
152
153# Mapping of the short string identifiers (in the config
154# file names) to the long name used here
155#
156# If we need them it's a snap to add new attributes here!
157
158set match(sVe) scsi(vendor)
159set match(sMo) scsi(model)
160set match(sRe) scsi(rev)
161set match(uMa) usb(manufacturer)
162set match(uPr) usb(product)
163set match(uSe) usb(serial)
164
165
166# Now reading the USB attributes
167if {![ReadUSBAttrs $devdir]} {
168	Log "USB attributes not found in sysfs tree. Exit"
169	SafeExit
170}
171set config(vendor) $usb(idVendor)
172set config(product) $usb(idProduct)
173
174
175if $flags(logging) {
176	Log "\n----------------\nUSB values from sysfs:"
177	foreach attr {manufacturer product serial} {
178		Log "  $attr\t$usb($attr)"
179	}
180	Log "----------------"
181}
182
183if $flags(noswitching) {
184	SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"
185	Log "\nSwitching globally disabled. Exit"
186	SafeExit
187}
188
189if {$usb(bNumConfigurations) == "1"} {
190	set configParam "-u -1"
191	Log "bNumConfigurations is 1 - don't check for active configuration"
192} else {
193	set configParam ""
194}
195
196# Check (and switch) for operating system if Huawei device present
197
198set flags(os) "linux"
199if {$usb(idVendor) == "12d1" && [regexp -nocase {android} [exec cat /proc/version]]} {
200	set flags(os) "android"
201}
202if {$flags(os) == "android"} {
203	set configList [ConfigGet conflist $usb(idVendor):#android]
204} else {
205	set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)]
206}
207
208if {[llength $configList] == 0} {
209	Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exit"
210	SafeExit
211}
212Log "ConfigList: $configList"
213
214# Check if there is more than one config file for this USB ID,
215# which would make an attribute test necessary. If so, check if
216# SCSI values are needed
217
218set scsiNeeded 0
219if {[llength $configList] > 1} {
220	if [regexp {:s} $configList] {
221		set scsiNeeded 1
222	}
223}
224if $scsiNeeded {
225	if [ReadSCSIAttrs $devdir:$iface] {
226		Log "----------------\nSCSI values from sysfs:"
227		foreach attr {vendor model rev} {
228			Log " $attr\t$scsi($attr)"
229		}
230		Log "----------------"
231	} else {
232		Log "Could not get SCSI attributes, exclude devices with SCSI match"
233	}
234} else {
235	Log "SCSI attributes not needed, move on"
236}
237
238# General wait - some devices need this
239after 500
240
241# Now check for a matching config file. Matching is done
242# by MatchDevice
243
244set report {}
245foreach mconfig $configList {
246
247	# skipping installer leftovers like "*.rpmnew"
248	if [regexp {\.(dpkg|rpm)} $mconfig] {continue}
249
250	Log "Check config: $mconfig"
251	if [MatchDevice $mconfig] {
252		Log "! matched. Read config data"
253		set flags(config) $mconfig
254		if [string length $usb(busnum)] {
255			set busParam "-b [string trimleft $usb(busnum) 0]"
256			set devParam "-g [string trimleft $usb(devnum) 0]"
257		} else {
258			set busParam ""
259			set devParam ""
260		}
261		set flags(config) [ConfigGet conffile $mconfig]
262		ParseDeviceConfig $flags(config)
263		if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
264			Log "Note: Using generic manufacturer configuration for \"$flags(os)\""
265		}
266		if $flags(nombim) {
267			set config(NoMBIMCheck) 1
268		}
269		if {$config(WaitBefore) != ""} {
270			Log "Delay time of $config(WaitBefore) seconds"
271			append config(WaitBefore) "000"
272			after $config(WaitBefore)
273			Log " wait is over, start mode switch"
274		}
275		if {$config(NoMBIMCheck)==0 && $usb(bNumConfigurations) > 1} {
276			Log "Device may have an MBIM configuration, check driver ..."
277			if [CheckMBIM] {
278				Log " driver for MBIM devices is available"
279				Log "Find MBIM configuration number ..."
280				if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] {
281					Log "Error when trying to find MBIM configuration, switch to legacy modem mode"
282				} else {
283					set cfgno [string trim $cfgno]
284					if {$cfgno > 0} {
285						set config(Configuration) $cfgno
286						set config(DriverModule) ""
287						set flags(config) "Configuration=$cfgno"
288					} else {
289						Log " No MBIM configuration found, switch to legacy modem mode"
290					}
291				}
292			} else {
293				Log " no MBIM driver found, switch to legacy modem mode"
294			}
295		}
296		if [PantechAutoSwitch] {
297			Log "Waiting for Pantech auto-modeswitch"
298			set report "ok:busdev"
299			break
300		}
301		# Now we are actually switching
302		if $flags(logging) {
303			Log "Command to be run:\nusb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f \$flags(config)"
304			set report [exec /usr/sbin/usb_modeswitch -W -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]
305			Log "\nVerbose debug output of usb_modeswitch and libusb follows"
306			Log "(Note that some USB errors are to be expected in the process)"
307			Log "--------------------------------"
308			Log $report
309			Log "--------------------------------"
310			Log "(end of usb_modeswitch output)\n"
311		} else {
312			set report [exec /usr/sbin/usb_modeswitch -Q -D $configParam $busParam $devParam -v $usb(idVendor) -p $usb(idProduct) -f "$flags(config)" 2>@1]
313		}
314		break
315	} else {
316		Log "* no match, don't use this config"
317	}
318}
319
320# Switching is complete; success checking was either
321# done by usb_modeswitch and logged via syslog OR bus/dev
322# parameter were used; then we do check for success HERE
323
324#Log "Result from core tool: $report"
325if [regexp {ok:busdev} $report] {
326	if [CheckSuccess $devdir] {
327		Log "Mode switching was successful, found $usb(idVendor):$usb(idProduct) ($usb(manufacturer): $usb(product))"
328		SysLog "usb_modeswitch: switched to $usb(idVendor):$usb(idProduct) on [format %03d $usb(busnum)]/[format %03d $usb(devnum)]"
329	} else {
330		Log "\nTarget config not matching - current values are"
331		LogAttributes
332		Log "\nMode switching may have failed. Exit"
333		SafeExit
334	}
335} else {
336	if {![file isdirectory $devdir]} {
337		Log "Device directory in sysfs is gone! Something went wrong, abort"
338		SafeExit
339	}
340	if {![regexp {ok:} $report]} {
341		Log "\nCore program reported switching failure. Exit"
342		SafeExit
343	}
344	# Give the device another second if it's not fully back yet
345	if {![file exists $devdir/idProduct]} {
346		after 1000
347	}
348	ReadUSBAttrs $devdir $ifdir
349}
350
351# Checking for bound drivers if there is an interface with class 0xff
352
353if {$config(DriverModule) != "" && [regexp {ok:} $report]} {
354	if [HasFF] {
355		AddToList link_list $usb(idVendor):$usb(idProduct)
356	} else {
357		set config(DriverModule) ""
358		Log " No vendor-specific class found, skip driver check"
359	}
360}
361
362# If module is set (it is by default), driver shall be loaded.
363# If not, then NoDriverLoading is active
364
365if {$config(DriverModule) != ""} {
366	if {[string length "$usb(idVendor)$usb(idProduct)"] < 8} {
367		if {![regexp {ok:(\w{4}):(\w{4})} $report d usb(idVendor) usb(idProduct)]} {
368			Log "No target vendor/product ID found or given, can't continue. Abort"
369			SafeExit
370		}
371	}
372	# wait for any drivers to bind automatically
373	after 1500
374	Log "Now check for bound driver ..."
375	if {![file exists $devdir/$ifdir/driver]} {
376		Log " no driver has bound to interface 0 yet"
377
378		# If device is known, the sh wrapper will take care, else:
379		if {[InBindList $usb(idVendor):$usb(idProduct)] == 0} {
380			Log "Device is not in \"bind_list\" yet, bind it now"
381
382			# Load driver
383			CheckDriverBind $usb(idVendor) $usb(idProduct)
384
385			# Old/slow systems may take a while to create the devices
386			set counter 0
387			while {![file exists $devdir/$ifdir/driver]} {
388				if {$counter == 14} {break}
389				after 500
390				incr counter
391			}
392			if {$counter == 14} {
393				Log " driver binding failed"
394			} else {
395				Log " driver was bound to the device"
396				AddToList bind_list $usb(idVendor):$usb(idProduct)
397			}
398		}
399	} else {
400		Log " driver has bound, device is known"
401		if {[llength [glob -nocomplain $devdir/$ifdir/ttyUSB*]] > 0} {
402			AddToList link_list $usb(idVendor):$usb(idProduct)
403		}
404	}
405} else {
406	# Just in case "NoDriverLoading" was added after the first bind
407	RemoveFromBindList $usb(idVendor):$usb(idProduct)
408}
409
410if [regexp {ok:$} $report] {
411	# "NoDriverLoading" was set
412	Log "No driver check or bind for this device"
413}
414
415# In newer kernels there is a switch to avoid the use of a device
416# reset (e.g. from usb-storage) which would possibly switch back
417# a mode-switching device to initial mode
418if [regexp {ok:} $report] {
419	Log "Check for AVOID_RESET_QUIRK kernel attribute"
420	if [file exists $devdir/avoid_reset_quirk] {
421		if [catch {exec echo "1" >$devdir/avoid_reset_quirk 2>/dev/null} err] {
422			Log " Error setting the attribute: $err"
423		} else {
424			Log " AVOID_RESET_QUIRK activated"
425		}
426	} else {
427		Log " not present in this kernel"
428	}
429}
430
431Log "\nAll done, exit\n"
432SafeExit
433
434}
435# end of proc {Main}
436
437
438proc {ReadSCSIAttrs} {topdir} {
439
440global scsi
441set counter 0
442set sysdir $topdir
443Log "Check storage tree in sysfs ..."
444while {$counter < 20} {
445	Log " loop $counter/20"
446	if {![file isdirectory $sysdir]} {
447		# Device is gone. Unplugged? Switched by kernel?
448		Log " sysfs device tree is gone; abort SCSI value check"
449		return 0
450	}
451	# Searching the storage/SCSI tree; might take a while
452	if {[set dirList [glob -nocomplain $topdir/host*]] != ""} {
453		set sysdir [lindex $dirList 0]
454		if {[set dirList [glob -nocomplain $sysdir/target*]] != ""} {
455			set sysdir [lindex $dirList 0]
456			regexp {.*target(.*)} $sysdir d subdir
457			if {[set dirList [glob -nocomplain $sysdir/$subdir*]] != ""} {
458				set sysdir [lindex $dirList 0]
459				if [file exists $sysdir/vendor] {
460					Log " Storage tree is ready"
461					break
462				}
463			}
464		}
465	}
466	after 500
467	incr counter
468}
469if {$counter == 20} {
470	Log "SCSI tree not found; you may want to check if this path/file exists:"
471	Log "$sysdir/vendor\n"
472	return 0
473}
474
475Log "Read SCSI values ..."
476foreach attr {vendor model rev} {
477	if [file exists $sysdir/$attr] {
478		set rc [open $sysdir/$attr r]
479		set scsi($attr) [read -nonewline $rc]
480		close $rc
481	} else {
482		set scsi($attr) ""
483		Log "Warning: SCSI attribute \"$attr\" not found."
484	}
485}
486return 1
487
488}
489# end of proc {ReadSCSIAttrs}
490
491
492proc {ReadUSBAttrs} {dir args} {
493
494global usb
495
496set attrList {idVendor idProduct bConfigurationValue manufacturer product serial devnum busnum bNumConfigurations}
497set mandatoryList {idVendor idProduct bNumConfigurations}
498set result 1
499if {$args != ""} {
500	lappend attrList "$args/bInterfaceClass"
501	lappend mandatoryList "$args/bInterfaceClass"
502}
503foreach attr $attrList {
504	if [file exists $dir/$attr] {
505		set rc [open $dir/$attr r]
506		set usb($attr) [string trim [read -nonewline $rc]]
507		close $rc
508	} else {
509		set usb($attr) ""
510		if {[lsearch $mandatoryList $attr] > -1} {
511			set result 0
512		}
513		if {$attr == "serial"} {continue}
514		Log "   Warning: USB attribute \"$attr\" not found"
515	}
516}
517return $result
518
519}
520# end of proc {ReadUSBAttrs}
521
522
523proc {MatchDevice} {config} {
524
525global scsi usb match
526
527set devinfo [file tail $config]
528set infoList [split $devinfo :]
529set stringList [lrange $infoList 2 end]
530if {[llength $stringList] == 0} {return 1}
531
532foreach teststring $stringList {
533	if {$teststring == "?"} {return 0}
534	set tokenList [split $teststring =]
535	set id [lindex $tokenList 0]
536	set matchstring [lindex $tokenList 1]
537	set blankstring ""
538	regsub -all {_} $matchstring { } blankstring
539	Log "match $match($id)"
540	Log "  string1 (exact):  $matchstring"
541	Log "  string2 (blanks): $blankstring"
542	Log " device string: [set $match($id)]"
543	if {!([string match *$matchstring* [set $match($id)]] || [string match *$blankstring* [set $match($id)]])} {
544		return 0
545	}
546}
547return 1
548
549}
550# end of proc {MatchDevice}
551
552
553proc {ParseGlobalConfig} {} {
554
555global flags
556set configFile ""
557set places [list /etc/usb_modeswitch.conf /etc/sysconfig/usb_modeswitch /etc/default/usb_modeswitch]
558foreach cfg $places {
559	if [file exists $cfg] {
560		set configFile $cfg
561		break
562	}
563}
564if {$configFile == ""} {return}
565
566set rc [open $configFile r]
567while {![eof $rc]} {
568	gets $rc line
569	if [regexp {^#} [string trim $line]] {continue}
570	if [regexp {DisableMBIMGlobal\s*=\s*([^\s]+)} $line d val] {
571		if [regexp -nocase {1|yes|true} $val] {
572			set flags(nombim) 1
573		} else {
574			set flags(nombim) 0
575		}
576	}
577	if [regexp {DisableSwitching\s*=\s*([^\s]+)} $line d val] {
578		if [regexp -nocase {1|yes|true} $val] {
579			set flags(noswitching) 1
580		}
581	}
582	if [regexp {EnableLogging\s*=\s*([^\s]+)} $line d val] {
583		if [regexp -nocase {1|yes|true} $val] {
584			set flags(logging) 1
585		} else {
586			set flags(logging) 0
587		}
588	}
589	if [regexp {SetStorageDelay\s*=\s*([^\s]+)} $line d val] {
590		if [regexp {\d+} $val] {
591			set flags(stordelay) $val
592		}
593	}
594
595}
596return "Use global config file: $configFile"
597
598}
599# end of proc {ParseGlobalConfig}
600
601
602proc ParseDeviceConfig {cfg} {
603
604global config
605set config(DriverModule) ""
606set config(DriverIDPath) ""
607set config(WaitBefore) ""
608set config(TargetVendor) ""
609set config(TargetProduct) ""
610set config(TargetClass) ""
611set config(Configuration) ""
612set config(NoMBIMCheck) 0
613set config(PantechMode) 0
614set config(CheckSuccess) 20
615set loadDriver 1
616
617foreach pname [lsort [array names config]] {
618	if [regexp -line "^\[^# \]*?$pname.*?= *(0x(\\w+)|\"(\[0-9a-fA-F,\]+)\"|(\[0-9\]+)) *\$" $cfg d config($pname)] {
619#		Log "config: $pname set to $config($pname)"
620	}
621}
622
623if [regexp -line {^[^#]*?NoDriverLoading.*?=.*?(1|yes|true).*?$} $cfg] {
624	set loadDriver 0
625	Log "config: NoDriverLoading is set to active"
626}
627
628# For general driver loading; TODO: add respective device names.
629# Presently only useful for HSO devices (which are recounted now)
630if $loadDriver {
631	if {$config(DriverModule) == ""} {
632		set config(DriverModule) "option"
633		set config(DriverIDPath) "/sys/bus/usb-serial/drivers/option1"
634	} else {
635		if {$config(DriverIDPath) == ""} {
636			set config(DriverIDPath) "/sys/bus/usb/drivers/$config(DriverModule)"
637		}
638	}
639	Log "Driver module is \"$config(DriverModule)\", ID path is $config(DriverIDPath)\n"
640} else {
641	Log "Driver will not be handled by usb_modeswitch"
642}
643set config(WaitBefore) [string trimleft $config(WaitBefore) 0]
644
645}
646# end of proc {ParseDeviceConfig}
647
648
649proc ConfigGet {command config} {
650
651global setup usb flags
652
653switch $command {
654
655	conflist {
656		# Unpackaged configs first; sorting is essential for priority
657		set configList [lsort -decreasing [glob -nocomplain $setup(dbdir_etc)/$config*]]
658		set configList [concat $configList [lsort -decreasing [glob -nocomplain $setup(dbdir)/$config*]]]
659		eval lappend configList [glob -nocomplain $setup(dbdir)/$usb(idVendor):#$flags(os)]
660		if [file exists $setup(dbdir)/configPack.tar.gz] {
661			Log "Found packed config collection $setup(dbdir)/configPack.tar.gz"
662			if [catch {set packedList [exec tar -tzf $setup(dbdir)/configPack.tar.gz 2>/dev/null]} err] {
663				Log "Error: problem opening config package; tar returned\n $err"
664				return {}
665			}
666			set packedList [split $packedList \n]
667			set packedConfigList [lsort -decreasing [lsearch -glob -all -inline $packedList $config*]]
668			lappend packedConfigList [lsearch -inline $packedList $usb(idVendor):#$flags(os)]
669			# Now add packaged configs with a mark, again sorted for priority
670			foreach packedConfig $packedConfigList {
671				lappend configList "pack/$packedConfig"
672			}
673		}
674		return $configList
675	}
676	conffile {
677		if [regexp {^pack/} $config] {
678			set config [regsub {pack/} $config {}]
679			Log "Extract config $config from collection $setup(dbdir)/configPack.tar.gz"
680			set configContent [exec tar -xzOf $setup(dbdir)/configPack.tar.gz $config 2>/dev/null]
681		} else {
682			if [regexp [list $setup(dbdir_etc)] $config] {
683				Log "Use config file from override folder $setup(dbdir_etc)"
684				SysLog "usb_modeswitch: use overriding config file $config; make sure this is intended"
685				SysLog "usb_modeswitch: please report any new or corrected settings; otherwise, check for outdated files"
686			}
687			set rc [open $config r]
688			set configContent [read $rc]
689			close $rc
690		}
691		return $configContent
692	}
693}
694
695}
696# end of proc {ConfigGet}
697
698proc {Log} {msg} {
699
700global flags device loginit
701
702if {$flags(logging) == 0} {return}
703
704if $flags(logwrite) {
705	if [string length $loginit] {
706		exec echo "\nUSB_ModeSwitch log from [clock format [clock seconds]]" >/var/log/usb_modeswitch_$device
707		exec echo "$loginit" >>/var/log/usb_modeswitch_$device
708		set loginit ""
709	}
710	exec echo $msg >>/var/log/usb_modeswitch_$device
711} else {
712	append loginit "\n$msg"
713}
714
715}
716# end of proc {Log}
717
718
719# Writing the log file and exit
720proc {SafeExit} {} {
721
722global flags
723set $flags(logwrite) 1
724Log ""
725exit
726
727}
728# end of proc {SafeExit}
729
730
731proc {SymLinkName} {path} {
732global device flags
733
734proc {hasInterrupt} {ifDir} {
735	if {[llength [glob -nocomplain $ifDir/ttyUSB*]] == 0} {
736		Log "  no ttyUSB interface - skip endpoint check"
737		return 0
738	}
739	foreach epDir [glob -nocomplain $ifDir/ep_*] {
740		set e [file tail $epDir]
741		Log "  check $e ..."
742		if [file exists $epDir/type] {
743			set rc [open $epDir/type r]
744			set type [read $rc]
745			close $rc
746			if [regexp {Interrupt} $type] {
747				Log "  $e has interrupt transfer type"
748				return 1
749			}
750		}
751	}
752	return 0
753}
754
755set loginit "usb_modeswitch called with --symlink-name\n parameter: $path\n"
756
757# In case the device path is returned as /class/tty/ttyUSB,
758# get the USB device path from linked tree "device"
759set linkpath /sys$path/device
760if [file exists $linkpath] {
761	if {[file type $linkpath] == "link"} {
762		set rawpath [file readlink $linkpath]
763		set trimpath [regsub -all {\.\./} $rawpath {}]
764 		if [file isdirectory /sys/$trimpath] {
765			append loginit "\n Use path $path\n"
766			set path /$trimpath
767		}
768	}
769}
770if {![regexp {([0-9]+-[0-9]+[\.0-9]*:[^/]*).*(ttyUSB[0-9]+)} $path d myDev myPort]} {
771	if $flags(logging) {
772		set device [clock clicks]
773		set flags(logwrite) 1
774		Log "$loginit\nThis is not a ttyUSB port. Abort"
775	}
776	return ""
777}
778
779set device ttyUSB_$myDev
780set flags(logwrite) 1
781Log "$loginit\nMy name is $myPort\n"
782
783if {![regexp {(.*?[0-9]+)\.([0-9]+)/ttyUSB} /sys$path d ifRoot ifNum]} {
784	Log "Could not find interface in path\n $path. Abort"
785	return ""
786}
787
788set ifDir $ifRoot.$ifNum
789
790Log "Check my endpoints ...\n in $ifDir"
791if [hasInterrupt $ifDir] {
792	Log "\n--> I am an interrupt port"
793	set rightPort 1
794} else {
795	Log "\n--> I am not an interrupt port\n"
796	set rightPort 0
797}
798
799# There are devices with more than one interrupt interface.
800# Assume that the lowest of these is usable. Check all
801# possible lower interfaces
802
803if { $rightPort && ($ifNum > 0) } {
804	Log "\nLook for lower ports with interrupt endpoints"
805	for {set i 0} {$i < $ifNum} {incr i} {
806		set ifDir $ifRoot.$i
807		Log " in ifDir $ifDir ..."
808		if [hasInterrupt $ifDir] {
809			Log "\n--> found an interrupt interface below me\n"
810			set rightPort 0
811			break
812		}
813	}
814}
815if {$rightPort == 0} {
816	Log "Return empty name and exit"
817	return ""
818}
819
820Log "\n--> No interrupt interface below me\n"
821
822cd /dev
823set idx 2
824set symlinkName "gsmmodem"
825while {$idx < 256} {
826	if {![file exists $symlinkName]} {
827		set placeholder [open /dev/$symlinkName w]
828		close $placeholder
829		break
830	}
831	set symlinkName gsmmodem$idx
832	incr idx
833}
834if {$idx == 256} {return ""}
835
836Log "Return symlink name \"$symlinkName\" and exit"
837return $symlinkName
838
839}
840# end of proc {SymLinkName}
841
842
843# Load and bind driver (default "option")
844#
845proc {CheckDriverBind} {vid pid} {
846global config
847
848foreach fn {/sbin/modprobe /usr/sbin/modprobe} {
849	if [file exists $fn] {
850		set loader $fn
851	}
852}
853Log "Module loader is $loader"
854
855set idfile $config(DriverIDPath)/new_id
856if {![file exists $idfile]} {
857	if {$loader == ""} {
858		Log "Can't do anymore without module loader; get \"modtools\"!"
859		return
860	}
861	Log "\nTry to load module \"$config(DriverModule)\""
862	if [catch {set result [exec $loader -v $config(DriverModule)]} err] {
863		Log " Running \"$loader $config(DriverModule)\" gave an error:\n  $err"
864	} else {
865		Log " Module was loaded successfully:\n$result"
866	}
867} else {
868	Log "Module is active already"
869}
870set i 0
871while {$i < 50} {
872	if [file exists $idfile] {
873		break
874	}
875	after 20
876	incr i
877}
878if {$i < 50} {
879	Log "Try to add ID to driver \"$config(DriverModule)\""
880	SysLog "usb_modeswitch: add device ID $vid:$pid to driver \"$config(DriverModule)\""
881	SysLog "usb_modeswitch: please report the device ID to the Linux USB developers!"
882	if [catch {exec echo "$vid $pid ff" >$idfile} err] {
883		Log " Error adding ID to driver:\n  $err"
884	} else {
885		Log " ID added to driver; check for new devices in /dev"
886	}
887} else {
888	Log " \"$idfile\" not found, check if kernel version is at least 2.6.27"
889	Log "Fall back to \"usbserial\""
890	set config(DriverModule) usbserial
891	Log "\nTry to unload driver \"usbserial\""
892	if [catch {exec $loader -r usbserial} err] {
893		Log " Running \"$loader -r usbserial\" gave an error:\n  $err"
894		Log "No more fallbacks"
895		return
896	}
897	after 50
898	Log "\nTry to load driver \"usbserial\" with device IDs"
899	if [catch {set result [exec $loader -v usbserial vendor=0x$vid product=0x$pid]} err] {
900		Log " Running \"$loader usbserial\" gave an error:\n  $err"
901	} else {
902		Log " Driver was loaded successfully:\n$result"
903	}
904}
905
906}
907# end of proc {CheckDriverBind}
908
909
910# Check if USB ID is listed as needing driver binding
911proc {InBindList} {id} {
912
913set listfile /var/lib/usb_modeswitch/bind_list
914if {![file exists $listfile]} {return 0}
915set rc [open $listfile r]
916set buffer [read $rc]
917close $rc
918if [string match *$id* $buffer] {
919Log "Found $id in bind_list"
920	return 1
921} else {
922Log "No $id in bind_list"
923	return 0
924}
925
926}
927# end of proc {InBindList}
928
929# Add USB ID to list of devices needing later treatment
930proc {AddToList} {name id} {
931
932set listfile /var/lib/usb_modeswitch/$name
933if [file exists $listfile] {
934	set rc [open $listfile r]
935	set buffer [read $rc]
936	close $rc
937	if [string match *$id* $buffer] {
938		return
939	}
940	set idList [split [string trim $buffer] \n]
941}
942lappend idList $id
943set buffer [join $idList "\n"]
944if [catch {set lc [open $listfile w]}] {return}
945puts $lc $buffer
946close $lc
947
948}
949# end of proc {AddToList}
950
951
952# Remove USB ID from bind list (NoDriverLoading is set)
953proc {RemoveFromBindList} {id} {
954
955set listfile /var/lib/usb_modeswitch/bind_list
956if [file exists $listfile] {
957	set rc [open $listfile r]
958	set buffer [read $rc]
959	close $rc
960	set idList [split [string trim $buffer] \n]
961} else {
962	return
963}
964set idx [lsearch $idList $id]
965if {$idx > -1} {
966	set idList [lreplace $idList $idx $idx]
967} else {
968	return
969}
970if {[llength $idList] == 0} {
971	file delete $listfile
972	return
973}
974set buffer [join $idList "\n"]
975if [catch {set lc [open $listfile w]}] {return}
976puts $lc $buffer
977close $lc
978
979}
980# end of proc {RemoveFromBindList}
981
982
983proc {CheckSuccess} {devdir} {
984
985global config usb flags
986set ifdir [file tail [IfDir 0]]
987
988if {[string length $config(TargetClass)] || [string length $config(Configuration)]} {
989	set config(TargetVendor) $usb(idVendor)
990	set config(TargetProduct) $usb(idProduct)
991}
992Log "Check success of mode switch for max. $config(CheckSuccess) seconds ..."
993
994set expected 1
995for {set i 1} {$i <= $config(CheckSuccess)} {incr i} {
996	after 1000
997	if {![file isdirectory $devdir]} {
998		Log " Wait for device file system ($i sec.) ..."
999		continue
1000	} else {
1001		Log " Read attributes ..."
1002	}
1003	set ifdir [IfDir 0]
1004	if {$ifdir == ""} {continue}
1005	set ifdir [file tail $ifdir]
1006	if {![ReadUSBAttrs $devdir $ifdir]} {
1007		Log " Essential attributes are missing, continue wait ..."
1008		continue
1009	}
1010	if [string length $config(Configuration)] {
1011		if {$usb(bConfigurationValue) != $config(Configuration)} {continue}
1012	}
1013	if [string length $config(TargetClass)] {
1014		if {![regexp $usb($ifdir/bInterfaceClass) $config(TargetClass)]} {
1015			if {$config(class) != $usb($ifdir/bInterfaceClass} {
1016				set expected 0
1017			} else {continue}
1018		}
1019	}
1020	if {![regexp $usb(idVendor) $config(TargetVendor)]} {
1021		if {![regexp $usb(idVendor) $config(vendor)]} {
1022			set expected 0
1023		} else {continue}
1024	}
1025	if {![regexp $usb(idProduct) $config(TargetProduct)]} {
1026		if {![regexp $usb(idProduct) $config(product)]} {
1027			set expected 0
1028		} else {continue}
1029	}
1030	# Arriving here means that device attributes have changed
1031	if $expected {
1032		Log " All attributes matched"
1033	} else {
1034		if [regexp -nocase {/[0-9a-f]+:#} $flags(config)] {
1035			Log " idProduct has changed after generic mode-switch, assume success"
1036		} else {
1037			Log " Attributes are different but target values are unexpected:"
1038			LogAttributes
1039		}
1040	}
1041	break
1042}
1043if {$i > 20} {return 0} else {return 1}
1044
1045}
1046# end of proc {CheckSuccess}
1047
1048
1049proc {IfDir} {iface} {
1050
1051global devdir
1052set allfiles [glob -nocomplain $devdir/*]
1053set files [glob -nocomplain $devdir/*.$iface]
1054if {[llength $files] == 0} {
1055	return ""
1056}
1057set ifdir [lindex $files 0]
1058if {![file isdirectory $ifdir]} {
1059	return ""
1060}
1061return $ifdir
1062
1063}
1064# end of proc {IfDir}
1065
1066proc {IfClass} {iface} {
1067
1068set ifdir [IfDir $iface]
1069
1070if {![file exists $ifdir/bInterfaceClass]} {
1071	return -1
1072}
1073set rc [open $ifdir/bInterfaceClass r]
1074set c [read $rc]
1075close $rc
1076return [string trim $c]
1077
1078}
1079# end of proc {IfClass}
1080
1081
1082proc {SysLog} {msg} {
1083
1084global flags
1085if {![info exists flags(logger)]} {
1086	set flags(logger) ""
1087	foreach fn {/bin/logger /usr/bin/logger} {
1088		if [file exists $fn] {
1089			set flags(logger) $fn
1090		}
1091	}
1092	Log "Logger is $flags(logger)"
1093}
1094if {$flags(logger) == ""} {
1095	Log "Can't add system message, no syslog helper found"
1096	return
1097}
1098catch {exec $flags(logger) -p syslog.notice "$msg" 2>/dev/null}
1099
1100}
1101# end of proc {SysLog}
1102
1103proc {SetStorageDelay} {secs} {
1104
1105Log "Adjust delay for USB storage devices ..."
1106set attrib /sys/module/usb_storage/parameters/delay_use
1107if {![file exists $attrib]} {
1108	Log "Error: could not find delay_use attribute"
1109	return
1110}
1111if [catch {set ch [open $attrib r+]} err] {
1112	Log "Error: could not access delay_use attribute: $err"
1113	return
1114}
1115if {[read $ch] < $secs} {
1116	seek $ch 0 start
1117	puts -nonewline $ch $secs
1118	Log " Delay set to $secs seconds\n"
1119} else {
1120	Log " Current value is higher than $secs. Leave it alone\n"
1121}
1122close $ch
1123
1124}
1125# end of proc {SetStorageDelay}
1126
1127proc {CheckMBIM} {} {
1128
1129set kversion [exec uname -r]
1130if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/cdc_mbim*]] {return 1}
1131if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1132return 0
1133
1134}
1135
1136proc {CheckQMI} {} {
1137
1138set kversion [exec uname -r]
1139if [llength [glob -nocomplain /lib/modules/$kversion/kernel/drivers/net/usb/qmi_wwan*]] {return 1}
1140if [file exists /sys/bus/usb/drivers/cdc_mbim] {return 1}
1141return 0
1142
1143}
1144
1145proc {PantechAutoSwitch} {} {
1146
1147global config flags
1148if {$config(PantechMode) == 3} {return 1}
1149if {$config(PantechMode) == 1} {
1150	if {"$config(vendor):$config(product)" == "10a9:6080"} {
1151		set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=2"]
1152		Log " PantechMode changed to 2"
1153		return 0
1154	} elseif [CheckQMI] {
1155		set flags(config) [regsub {PantechMode *= *1} $flags(config) "PantechMode=4"]
1156		Log " PantechMode changed to 4"
1157		return 0
1158	} else {
1159		return 1
1160	}
1161} else {return 0}
1162
1163}
1164
1165proc {LogAttributes} {} {
1166
1167global flags usb
1168if $flags(logging) {
1169	set attrList {idVendor idProduct bConfigurationValue manufacturer product serial}
1170	foreach attr [lsort [array names usb]] {
1171		Log "    [format %-26s $attr:] $usb($attr)"
1172	}
1173}
1174
1175}
1176
1177proc {HasFF} {} {
1178
1179set i 0
1180while {[set dir [IfDir $i]] != ""} {
1181	set c [exec cat $dir/bInterfaceClass]
1182	if {$c == "ff"} {return 1}
1183	incr i
1184}
1185return 0
1186
1187}
1188
1189# The actual entry point
1190Main $argv $argc
1191