diff options
Diffstat (limited to 'appl/cmd/ip/ppp/modem.b')
| -rw-r--r-- | appl/cmd/ip/ppp/modem.b | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/appl/cmd/ip/ppp/modem.b b/appl/cmd/ip/ppp/modem.b new file mode 100644 index 00000000..6085524a --- /dev/null +++ b/appl/cmd/ip/ppp/modem.b @@ -0,0 +1,468 @@ +implement Modem; + +include "sys.m"; + sys: Sys; + +include "lock.m"; + lock: Lock; + Semaphore: import lock; + +include "draw.m"; + +include "modem.m"; + +hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ (BUG: should be in modeminfo) + +# modem return codes +Ok, Success, Failure, Abort, Noise, Found: con iota; + +maxspeed: con 115200; + +# +# modem return messages +# +Msg: adt { + text: string; + code: int; +}; + +msgs: array of Msg = array [] of { + ("OK", Ok), + ("NO CARRIER", Failure), + ("ERROR", Failure), + ("NO DIALTONE", Failure), + ("BUSY", Failure), + ("NO ANSWER", Failure), + ("CONNECT", Success), +}; + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "kill") < 0) + sys->print("modem: can't kill %d: %r\n", pid); +} + +# +# prepare a modem port +# +openserial(d: ref Device) +{ + if (d==nil) { + raise "fail: device not initialized"; + return; + } + + d.data = nil; + d.ctl = nil; + + d.data = sys->open(d.local, Sys->ORDWR); + if(d.data == nil) { + raise "fail: can't open "+d.local; + return; + } + + d.ctl = sys->open(d.local+"ctl", Sys->ORDWR); + if(d.ctl == nil) { + raise "can't open "+d.local+"ctl"; + return; + } + + d.speed = maxspeed; + d.avail = nil; +} + +# +# shut down the monitor (if any) and return the connection +# + +close(m: ref Device): ref Sys->Connection +{ + if(m == nil) + return nil; + if(m.pid != 0){ + kill(m.pid); + m.pid = 0; + } + if(m.data == nil) + return nil; + mc := ref sys->Connection(m.data, m.ctl, nil); + m.ctl = nil; + m.data = nil; + return mc; +} + +# +# Send a string to the modem +# + +send(d: ref Device, x: string): int +{ + if (d == nil) + return -1; + + a := array of byte x; + f := sys->write(d.data, a, len a); + if (f < 0) { + # let's attempt to close & reopen the modem + close(d); + openserial(d); + f = sys->write(d.data,a, len a); + } + sys->print("->%s\n",x); + return f; +} + +# +# apply a string of commands to modem & look for a response +# + +apply(d: ref Device, s: string, substr: string, secs: int): int +{ + m := Ok; + buf := ""; + for(i := 0; i < len s; i++){ + c := s[i]; + buf[len buf] = c; # assume no Unicode + if(c == '\r' || i == (len s -1)){ + if(c != '\r') + buf[len buf] = '\r'; + if(send(d, buf) < 0) + return Abort; + (m, nil) = readmsg(d, secs, substr); + buf = ""; + } + } + return m; +} + +# +# get modem into command mode if it isn't already +# +GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz + +attention(d: ref Device): int +{ + for(i := 0; i < 3; i++){ + if(apply(d, hangupcmd, nil, 2) == Ok) + return Ok; + sys->sleep(GUARDTIME); + if(send(d, "+++") < 0) + return Abort; + sys->sleep(GUARDTIME); + (nil, msg) := readmsg(d, 0, nil); + if(msg != nil) + sys->print("status: %s\n", msg); + } + return Failure; +} + +# +# apply a command type +# + +applyspecial(d: ref Device, cmd: string): int +{ + if(cmd == nil) + return Failure; + return apply(d, cmd, nil, 2); +} + +# +# hang up any connections in progress and close the device +# +onhook(d: ref Device) +{ + if(d == nil) + return; + + # hang up the modem + monitoring(d); + if(attention(d) != Ok) + sys->print("modem: no attention\n"); + + # hangup the stream (eg, for ppp) and toggle the lines to the modem + if(d.ctl != nil) { + sys->fprint(d.ctl,"d0\n"); + sys->fprint(d.ctl,"r0\n"); + sys->fprint(d.ctl, "h\n"); # hangup on native serial + sys->sleep(250); + sys->fprint(d.ctl,"r1\n"); + sys->fprint(d.ctl,"d1\n"); + } + + close(d); +} + +# +# does string s contain t anywhere? +# + +contains(s, t: string): int +{ + if(t == nil) + return 1; + if(s == nil) + return 0; + n := len t; + for(i := 0; i+n <= len s; i++) + if(s[i:i+n] == t) + return 1; + return 0; +} + +# +# read till we see a message or we time out +# +readmsg(d: ref Device, secs: int, substr: string): (int, string) +{ + if (d == nil) + return (Abort, "device not initialized"); + found := 0; + secs *= 1000; + limit := 1000; # pretty arbitrary + s := ""; + + for(start := sys->millisec(); sys->millisec() <= start+secs;){ + a := getinput(d,1); + if(len a == 0){ + if(limit){ + sys->sleep(1); + continue; + } + break; + } + if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){ + if (len s) { + if (s[(len s)-1] == '\r') + s[(len s)-1] = '\n'; + sys->print("<-%s\n",s); + } + if(substr != nil && contains(s, substr)) + found = 1; + for(k := 0; k < len msgs; k++) + if(len s >= len msgs[k].text && + s[0:len msgs[k].text] == msgs[k].text){ + if(found) + return (Found, s); + return (msgs[k].code, s); + } + start = sys->millisec(); + s = ""; + continue; + } + s[len s] = int a[0]; + limit--; + } + s = "No response from modem"; + if(found) + return (Found, s); + + return (Noise, s); +} + +# +# get baud rate from a connect message +# + +getspeed(msg: string, speed: int): int +{ + p := msg[7:]; # skip "CONNECT" + while(p[0] == ' ' || p[0] == '\t') + p = p[1:]; + s := int p; + if(s <= 0) + return speed; + else + return s; +} + +# +# set speed and RTS/CTS modem flow control +# + +setspeed(d: ref Device, baud: int) +{ + if(d != nil && d.ctl != nil){ + sys->fprint(d.ctl, "b%d", baud); + sys->fprint(d.ctl, "m1"); + } +} + +dumpa(a: array of byte): string +{ + s := ""; + for(i:=0; i<len a; i++){ + b := int a[i]; + if(b >= ' ' && b < 16r7f) + s[len s] = b; + else + s += sys->sprint("\\%.2x", b); + } + return s; +} + +monitoring(d: ref Device) +{ + # if no monitor then spawn one + if(d.pid == 0) { + pidc := chan of int; + spawn monitor(d, pidc); + d.pid = <-pidc; + } +} + +# +# a process to read input from a modem. +# +monitor(d: ref Device, pidc: chan of int) +{ + openserial(d); + pidc <-= sys->pctl(0, nil); # pidc can be written once only. + a := array[Sys->ATOMICIO] of byte; + for(;;) { + d.lock.obtain(); + d.status = "Idle"; + d.remote = ""; + setspeed(d, d.speed); + d.lock.release(); + # shuttle bytes + while((n := sys->read(d.data, a, len a)) > 0){ + d.lock.obtain(); + if (len d.avail < Sys->ATOMICIO) { + na := array[len d.avail + n] of byte; + na[0:] = d.avail[0:]; + na[len d.avail:] = a[0:n]; + d.avail = na; + } + d.lock.release(); + } + # on an error, try reopening the device + d.data = nil; + d.ctl = nil; + openserial(d); + } +} + +# +# return up to n bytes read from the modem by monitor() +# +getinput(d: ref Device, n: int): array of byte +{ + if(d==nil || n <= 0) + return nil; + a: array of byte; + d.lock.obtain(); + if(len d.avail != 0){ + if(n > len d.avail) + n = len d.avail; + a = d.avail[0:n]; + d.avail = d.avail[n:]; + } + d.lock.release(); + return a; +} + +getc(m: ref Device, timo: int): int +{ + start := sys->millisec(); + while((b := getinput(m, 1)) == nil) { + if (timo && sys->millisec() > start+timo) + return 0; + sys->sleep(1); + } + return int b[0]; +} + +init(modeminfo: ref ModemInfo): ref Device +{ + if (sys == nil) { + sys = load Sys Sys->PATH; + lock = load Lock Lock->PATH; + if (lock == nil) { + raise "fail: Couldn't load lock module"; + return nil; + } + lock->init(); + } + + newdev := ref Device; + newdev.lock = Semaphore.new(); + newdev.local = modeminfo.path; + newdev.pid = 0; + newdev.t = modeminfo; + + return newdev; +} + + +# +# dial a number +# +dial(d: ref Device, number: string) +{ + if (d==nil) { + raise "fail: Device not initialized"; + return; + } + + monitoring(d); + + # modem type should already be established, but just in case + sys->print("Attention\n"); + x := attention(d); + if (x != Ok) + sys->print("Attention failed\n"); + # + # extended Hayes commands, meaning depends on modem (VGA all over again) + # + sys->print("Init\n"); + if(d.t.country != nil) + applyspecial(d, d.t.country); + + if(d.t.init != nil) + applyspecial(d, d.t.init); + + if(d.t.other != nil) + applyspecial(d, d.t.other); + + applyspecial(d, d.t.errorcorrection); + + compress := Abort; + if(d.t.mnponly != nil) + compress = applyspecial(d, d.t.mnponly); + if(d.t.compression != nil) + compress = applyspecial(d, d.t.compression); + + rateadjust := Abort; + if(compress != Ok) + rateadjust = applyspecial(d, d.t.rateadjust); + applyspecial(d, d.t.flowctl); + + # finally, dialout + sys->print("Dialing\n"); + if((dt := d.t.dialtype) == nil) + dt = "ATDT"; + if(send(d, sys->sprint("%s%s\r", dt, number)) < 0) { + raise "can't dial "+number; + return; + } + + (i, msg) := readmsg(d, 120, nil); + if(i != Success) { + raise "fail: "+msg; + return; + } + + connectspeed := getspeed(msg, d.speed); + + # change line rate if not compressing + if(rateadjust == Ok) + setspeed(d, connectspeed); + + if(d.ctl != nil){ + if(d != nil) + sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it) + sys->fprint(d.ctl, "c1"); # enable CD monitoring + } +} |
