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