4 # Copyright 2016,2020 Ian Jackson
5 # SPDX-License-Identifier: GPL-3.0-or-later
6 # There is NO WARRANTY.
10 proc debug {m} { puts "DEBUG $m" }
12 proc log {m} { puts "LOG $m" }
18 proc find-devices {} {
19 global errorCode errorInfo devices expected_devs
20 set base /sys/class/tty
21 foreach candidate [glob -nocomplain -directory $base -tails ttyACM*] {
22 debug "candidate $candidate"
23 if {[catch { file link $base/$candidate/device } ltarget]} {
24 debug " readlink failed [lrange $errorCode 0 1]"
25 switch -glob $errorCode {
26 {POSIX EINVAL *} continue
27 {POSIX ENOENT *} continue
28 default { error "$ltarget \[$errorCode] $errorInfo $errorCode" }
31 if {![regexp {^(.*)\.(\d+)$} $ltarget dummy dbase interf]} {
32 debug " readlink bad target $ltarget"
35 debug " approved $dbase $interf $candidate"
36 lappend devs($dbase) [list $interf $candidate]
38 set howmany [array size devs]
40 experror "no appropriate device(s) found"
43 experror "several appropriate device(s) found [array names $devs]"
46 foreach dev [lsort -index 0 -integer $devs([lindex [array names devs] 0])] {
47 lappend devices [lindex $dev 1]
49 if {[llength $devices] != $expected_devs} {
50 experror "wrong # devices ($devices), expected $expected"
54 proc create-dev-nodes {} {
55 global devices expected_devs
56 set ourdevs /dev/atmux
58 for {set i 0} {$i < $expected_devs-1} {incr i} {
60 file link -symbolic ../$device $ourdevs/.new.$new
61 file rename -force $ourdevs/.new.$new $ourdevs/$new
64 foreach candidate [glob -nocomplain -directory $ourdevs -tails ttyAT*] {
65 if {![info exists wanted($candidate)]} {
66 file remove $ourdevs/$candidate
71 proc reopen-our-device {} {
73 set dchan [open /dev/[lindex $devices 1] r+]
74 fconfigure $dchan -blocking no -buffering line -translation {crlf cr}
75 read $dchan; # flush input
79 set result [read $dchan]
80 if {![regexp -line {^OK$} $result]} { experror "got [logquote $result]" }
81 fileevent $dchan readable chan-readable $dchan dchan "modem device"
90 proc dchan-failure {dummy} {
92 if {[info exists dchan]} {
93 catch { close $dchan }
98 proc try-open-our-device {} {
106 sendout-async *TTYATMUX "*TTYATMUXDEVS [join $devices ,]"
107 sendout-async *TTYATMUX "*TTYATMUXOPEN"
110 proc trap-log {what body var} {
111 global errorCode errorInfo
118 switch -glob $errorCode {
119 {POSIX *} - EXPECTED { log "$what: $result" }
121 log "unexpected: $what: $result"
122 foreach l [split $errorInfo "\n"] { log " $l" }
131 return -code $rc -errorinfo $errorInfo \
132 -errorcode $errorCode $result
137 proc chan-readable {chan how what args} {
139 if {[trap-log "$what failure" {
146 if {![eof $chan]} return
151 trap-log "error processing $what data" {
157 proc async-notif fixme this has wrong arguments
159 proc async-notif-or-resp-fixed {asid l} {
160 global current_command_asid
161 if {![string compare $asid $current_command_asid]} {
168 proc async-notif-creg {asid l} {
169 set ll [llength [split $l ,]]
173 async-notif {+CREG} 2 2 $l
174 async-notif {+CREG} 1 1 [lindex [split $l ,] 0]
178 async-notif {+CREG} 1 2 $l
181 bad-data $l "async-notif-creg $ll"
186 proc async-control-max0 {c l allows} {
187 async-control-core $c $l $allows {
192 if {$tw} { set wanted 1 }
195 foreach allow $allows { lappend $send [lindex $allow 0] }
196 sync-subcommand $c "$cmd=[join $send ,]" async-updated-ok $c
200 proc async-control-cmer {c l allows} {
201 async-control-core $c $l $allows {
204 set mode 0; set ind 0
205 manyset $ca($c) mode keyp disp ind bfr
206 if {$mode==3 && $ind} { set send 3,0,0,1 }
208 sync-subcommand $c "$cmd=$send" async-updated-ok $c
212 proc async-updated-ok
214 proc async-control-core {c l allows ubody_init ubody_perclient ubody_finish} {
217 if {[regexp {^(AT[^=?])\?$} $l dummy cmd]} {
218 sync-subcommand $c $l async-massage-result-subs $c $cmd
219 } elseif {[regexp {^(AT[^=?])=\?$} $l dummy cmd]} {
220 sync-subcommand $c $l async-massage-result-support $c $cmd $allows
221 } elseif {[regexp {^(AT[^=?])=([0-9,]+)$} $l dummy cmd values]} {
222 set values [split $values ,]
223 if {[llength $values] > [llength $allows]} {
224 bad-command "too many values"
227 while {[llength $values] < [llength $allows]} {
230 foreach val $values allow $allows {
231 if {[lsearch -exact $allow $val]<0} {
232 bad-command "$val not in allowed $allow ($allows)"
236 uplevel 1 [list upvar #0 client_async/$cmd ca]
237 upvar #0 client_async/$cmd ca
239 uplevel 1 $ubody_init
241 foreach uc [array names clients] {
242 uplevel 1 $ubody_perclient
244 uplevel 1 $ubody_finish
246 bad-command "unknown async control syntax"
250 proc set-client-echo {c yn} {
252 set client_echo($c) 0
253 client-command-complete $c OK
256 proc simple-command {c l} {
257 sync-subcommand $c $l simple-command-complete
260 proc client-command-complete
262 proc process-client-command {c nl} {
264 {^AT\+CREG\b} { async-control-max0 $c $l {{0 1 2}} }
265 {^AT\+CGREG\b} { async-control-max0 $c $l {{0 1 2}} }
266 {^AT\*ERINFO\b} { async-control-max0 $c $l {{0 1}} }
267 {^AT\+CGEREP\b} { async-control-max0 $c $l {{0 1 2} 0} }
268 {^AT\+CMER\b} { async-control-cmer $c $l {{0 3} 0 0 {0 1} 0} }
269 {^ATE0$} { set-client-echo $c 0 }
270 {^ATE1$} { set-client-echo $c 1 }
271 {^AT\+CFUN\b} { simple-command $c $l }
272 default { bad-command "unknown command" }
276 proc dchan-line {l} {
279 {\+CREG:} { async-notif-creg +CREG $l }
280 {\+CGREG:} { async-notif-creg +CGREG $l }
281 {\*ERINFO:} { async-notif-or-resp-fixed *ERINFO $l }
282 {\+CGEV:} { async-notif +CGEREP $l }
283 {\+CIEV:} { async-notif +CMER $l }
284 default { syync-reply $l }
288 proc cchan-line {c l} {
289 lappend queue [list $c $l]
293 proc sync-subcommand {c l args} {
295 if {[info exists busy]} { error "already busy $busy; want $c $l $args" }
296 if {[trap-log "write device" { puts $dchan $l } dummy]} {
299 proc sync-reply {l} {
301 if {![info exists busy]} {
302 bad-data $l "unexpected sync reply"
309 while {![info exists busy] && [llength $queue]} {
310 manyset [lindex $queue 0] c l
311 set queue [lrange $queue 1 end]
312 if {[trap-log "process for $c [logquote $l]"] {
313 process-client-command $c $l
315 client-command-complete $c ERROR
320 proc client-command-complete {c l} {
321 if {[trap-log "write to $c" { puts $c $l } dummy]} {
326 proc cchan-readable {