summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/ppp/modem.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/ppp/modem.b')
-rw-r--r--appl/cmd/ip/ppp/modem.b468
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
+ }
+}