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