summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/nppp/modem.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/nppp/modem.b')
-rw-r--r--appl/cmd/ip/nppp/modem.b469
1 files changed, 469 insertions, 0 deletions
diff --git a/appl/cmd/ip/nppp/modem.b b/appl/cmd/ip/nppp/modem.b
new file mode 100644
index 00000000..f8c81396
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.b
@@ -0,0 +1,469 @@
+implement Modem;
+
+#
+# Copyright © 1998-2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+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
+
+# 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("/prog/"+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): string
+{
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil)
+ return sys->sprint("can't open %s: %r", d.local);
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil)
+ return sys->sprint("can't open %s: %r", d.local+"ctl");
+
+ d.speed = maxspeed;
+ d.avail = nil;
+ return nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+Device.close(m: self ref Device): ref Sys->Connection
+{
+ 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
+#
+
+Device.send(d: self ref Device, x: string): string
+{
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if(f != len a) {
+ # let's attempt to close & reopen the modem
+ d.close();
+ err := openserial(d);
+ if(err != nil)
+ return err;
+ f = sys->write(d.data,a, len a);
+ if(f < 0)
+ return sys->sprint("%r");
+ if(f != len a)
+ return "short write";
+ }
+ if(d.trace)
+ sys->print("->%s\n",x);
+ return nil;
+}
+
+#
+# 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(d.send(buf) != nil)
+ 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(d.send("+++") != nil)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil && d.trace)
+ 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
+#
+Device.onhook(d: self ref Device)
+{
+ # 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");
+ }
+
+ d.close();
+}
+
+#
+# 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)
+{
+ found := 0;
+ msecs := secs*1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+msecs;){
+ a := d.getinput(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");
+ }
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc, nil);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int, errc: chan of string)
+{
+ err := openserial(d);
+ pidc <-= sys->pctl(0, nil);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ 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;
+ err = openserial(d);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+Device.getinput(d: self 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;
+}
+
+Device.getc(d: self ref Device, msec: int): int
+{
+ start := sys->millisec();
+ while((b := d.getinput(1)) == nil) {
+ if (msec && sys->millisec() > start+msec)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if(lock == nil)
+ return sys->sprint("can't load %s: %r", Lock->PATH);
+ lock->init();
+ return nil;
+}
+
+Device.new(modeminfo: ref ModemInfo, trace: int): ref Device
+{
+ d := ref Device;
+ d.lock = Semaphore.new();
+ d.local = modeminfo.path;
+ d.pid = 0;
+ d.speed = 0;
+ d.t = *modeminfo;
+ if(d.t.hangup == nil)
+ d.t.hangup = hangupcmd;
+ d.trace = trace | 1; # always trace for now
+ return d;
+}
+
+#
+# dial a number
+#
+Device.dial(d: self ref Device, number: string): string
+{
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ if(d.trace)
+ sys->print("modem: attention\n");
+ x := attention(d);
+ if (x != Ok && d.trace)
+ return "bad response from modem";
+ #
+ # extended Hayes commands, meaning depends on modem
+ #
+ sys->print("modem: 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
+ if(d.trace)
+ sys->print("modem: dial\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ err := d.send(sys->sprint("%s%s\r", dt, number));
+ if(err != nil){
+ if(d.trace)
+ sys->print("modem: can't dial %s: %s\n", number, err);
+ return err;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success){
+ if(d.trace)
+ sys->print("modem: modem error reply: %s\n", msg);
+ return msg;
+ }
+
+ 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
+ }
+
+ return nil;
+}
+
+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;
+}