ref: fa9f4c5c6852ce467644f610e77504664cb86af4
dir: /appl/cmd/palm/palmsrv.b/
implement Palmsrv;
#
# serve up a Palm using SLP and PADP
#
# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
#
# forsyth@vitanuova.com
#
# TO DO
# USB and possibly other transports
# tickle
include "sys.m";
sys: Sys;
include "draw.m";
include "timers.m";
timers: Timers;
Timer, Sec: import timers;
include "palm.m";
include "arg.m";
Palmsrv: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
debug := 0;
usage()
{
sys->fprint(sys->fildes(2), "usage: palm/palmsrv [-d /dev/eia0] [-s 57600]\n");
raise "fail:usage";
}
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
device, speed: string;
arg := load Arg Arg->PATH;
if(arg == nil)
error(sys->sprint("can't load %s: %r", Arg->PATH));
arg->init(args);
while((c := arg->opt()) != 0)
case c {
'D' =>
debug++;
'd' =>
device = arg->arg();
's' =>
speed = arg->arg();
* =>
usage();
}
args = arg->argv();
arg = nil;
if(device == nil)
device = "/dev/eia0";
if(speed == nil)
speed = "57600";
dfd := sys->open(device, Sys->ORDWR);
if(dfd == nil)
error(sys->sprint("can't open %s: %r", device));
cfd := sys->open(device+"ctl", Sys->OWRITE);
timers = load Timers Timers->PATH;
if(timers == nil)
error(sys->sprint("can't load %s: %r", Timers->PATH));
srvio := sys->file2chan("/chan", "palmsrv");
if(srvio == nil)
error(sys->sprint("can't create channel /chan/palmsrv: %r"));
timers->init(Sec/100);
p := Pchan.init(dfd, cfd);
spawn server(srvio, p);
}
error(s: string)
{
sys->fprint(sys->fildes(2), "palmsrv: %s\n", s);
raise "fail:error";
}
Xact: adt
{
fid: int;
reply: array of byte;
error: string;
};
server(srv: ref Sys->FileIO, p: ref Pchan)
{
actions: list of ref Xact;
nuser := 0;
for(;;)alt{
(nil, nbytes, fid, rc) := <-srv.read =>
if(rc == nil){
actions = delact(actions, fid);
break;
}
act := findact(actions, fid);
if(act == nil){
rc <-= (nil, "no transaction in progress");
break;
}
actions = delact(actions, fid);
if(p.shutdown)
rc <-= (nil, "link shut down");
else if(act.error != nil)
rc <-= (nil, act.error);
else if(act.reply != nil)
rc <-= (act.reply, nil);
else
rc <-= (nil, "no reply"); # probably shouldn't happen
(nil, data, fid, wc) := <-srv.write =>
actions = delact(actions, fid); # discard result of any previous transaction
if(wc == nil){
if(--nuser <= 0){
nuser = 0;
p.stop();
}
break;
}
if(len data == 4 && string data == "exit"){
p.close();
wc <-= (len data, nil);
exit;
}
if(p.shutdown){
wc <-= (0, "link shut down"); # must close then reopen
break;
}
if(!p.started){
err := p.start();
if(err != nil){
wc <-= (0, sys->sprint("can't start protocol: %s", err));
break;
}
nuser++;
}
(result, err) := p.padp_xchg(data, 20*1000);
if(err != nil){
wc <-= (0, err);
break;
}
actions = ref Xact(fid, result, err) :: actions;
wc <-= (len data, nil);
}
}
findact(l: list of ref Xact, fid: int): ref Xact
{
for(; l != nil; l = tl l)
if((a := hd l).fid == fid)
return a;
return nil;
}
delact(l: list of ref Xact, fid: int): list of ref Xact
{
ol := l;
l = nil;
for(; ol != nil; ol = tl ol)
if((a := hd ol).fid != fid)
l = a :: l;
return l;
}
killpid(pid: int)
{
if(pid != 0){
fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE);
if(fd != nil)
sys->fprint(fd, "kill");
}
}
#
# protocol implementation
# Serial Link Protocol (framing)
# Connection Management Protocol (wakeup, negotiation)
# Packet Assembly/Disassembly Protocol (reliable delivery fragmented datagram)
#
DATALIM: con 1024;
# SLP packet types
SLP_System, SLP_Unused, SLP_PAD, SLP_Loop: con iota;
# SLP block content, without framing
Sblock: adt {
src: int; # socket ID
dst: int; # socket ID
proto: int; # packet type
xid: int; # transaction ID
data: array of byte;
new: fn(): ref Sblock;
print: fn(sb: self ref Sblock, dir: string);
};
#
# Palm channel
#
Pchan: adt {
started: int;
shutdown: int;
protocol: int;
lport: byte;
rport: byte;
fd: ref Sys->FD;
cfd: ref Sys->FD;
baud: int;
rpid: int;
lastid: int;
rd: chan of ref Sblock;
reply: ref Sblock; # data replacing lost ack
init: fn(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan;
start: fn(p: self ref Pchan): string;
stop: fn(p: self ref Pchan);
close: fn(p: self ref Pchan): int;
slp_read: fn(p: self ref Pchan, nil: int): (ref Sblock, string);
slp_write: fn(p: self ref Pchan, xid: int, nil: array of byte): string;
setbaud: fn(p: self ref Pchan, nil: int);
padp_read: fn(p: self ref Pchan, xid: int, timeout: int): (array of byte, string);
padp_write: fn(p: self ref Pchan, msg: array of byte, xid: int): string;
padp_xchg: fn(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string);
tickle: fn(p: self ref Pchan);
connect: fn(p: self ref Pchan): string;
accept: fn(p: self ref Pchan, baud: int): string;
nextseq: fn(p: self ref Pchan): int;
};
Pchan.init(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan
{
p := ref Pchan;
p.fd = dfd;
p.cfd = cfd;
p.baud = InitBaud;
p.protocol = SLP_PAD;
p.rport = byte 3;
p.lport = byte 3;
p.rd = chan of ref Sblock;
p.lastid = 0;
p.rpid = 0;
p.started = 0;
p.shutdown = 0;
return p;
}
Pchan.start(p: self ref Pchan): string
{
if(p.started)
return nil;
p.shutdown = 0;
p.baud = InitBaud;
p.reply = nil;
ctl(p, "f");
ctl(p, "d1");
ctl(p, "r1");
ctl(p, "i8");
ctl(p, "q8192");
ctl(p, sys->sprint("b%d", InitBaud));
pidc := chan of int;
spawn slp_recv(p, pidc);
p.started = 1;
p.rpid = <-pidc;
err := p.accept(57600);
if(err != nil)
p.stop();
return err;
}
ctl(p: ref Pchan, s: string)
{
if(p.cfd != nil)
sys->fprint(p.cfd, "%s", s);
}
Pchan.setbaud(p: self ref Pchan, baud: int)
{
if(p.baud != baud){
p.baud = baud;
ctl(p, sys->sprint("b%d", baud));
sys->sleep(200);
}
}
Pchan.stop(p: self ref Pchan)
{
p.shutdown = 0;
if(!p.started)
return;
killpid(p.rpid);
p.rpid = 0;
p.reply = nil;
# ctl(p, "f");
# ctl(p, "d0");
# ctl(p, "r0");
# ctl(p, sys->sprint("b%d", InitBaud));
p.started = 0;
}
Pchan.close(p: self ref Pchan): int
{
if(p.started)
p.stop();
p.reply = nil;
p.cfd = nil;
p.fd = nil;
timers->shutdown();
return 0;
}
# CMP protocol for connection management
# See include/Core/System/CMCommon.h, Palm SDK
# There are two major versions: the original V1, still always used in wakeup messsages;
# and V2, which is completely different (similar structure to Desklink) and used by newer devices, but the headers
# are the same length. Start off in V1 announcing version 2.x, then switch to that.
# My device supports only V1, so I use that.
CMPHDRLEN: con 10; # V1: type[1] flags[1] vermajor[1] verminor[1] mbz[2] baud[4]
# V2: type[1] cmd[1] error[2] argc[1] mbz[1] mbz[4]
# CMP V1
Cmajor: con 1;
Cminor: con 2;
InitBaud: con 9600;
# type
Cwake, Cinit, Cabort, Cextended: con 1+iota;
# Cinit flags
ChangeBaud: con 16r80;
RcvTimeout1: con 16r40; # tell Palm to set receive timeout to 1 minute (CMP v1.1)
RcvTimeout2: con 16r20; # tell Palm to set receive timeout to 2 minutes (v1.1)
# Cinit and Cwake flag
LongPacketEnable: con 16r10; # enable long packet support (v1.2)
# Cabort flags
WrongVersion: con 16r80; # incompatible com versions
# CMP V2
Carg1: con Palm->ArgIDbase;
Cresponse: con 16r80;
Cxchgprefs, Chandshake: con 16r10+iota;
Pchan.connect(p: self ref Pchan): string
{
(nil, e1) := cmp_write(p, Cwake, 0, Cmajor, Cminor, 57600);
if(e1 != nil)
return e1;
(op, flag, nil, nil, baud, e2) := cmp_read(p, 0);
if(e2 != nil)
return e2;
case op {
Cinit=>
if(flag & ChangeBaud)
p.setbaud(baud);
return nil;
Cabort=>
return "Palm rejected connect";
* =>
return sys->sprint("Palm connect: reply %d", op);
}
return nil;
}
Pchan.accept(p: self ref Pchan, maxbaud: int): string
{
(op, nil, major, minor, baud, err) := cmp_read(p, 0);
if(err != nil)
return err;
if(major != 1){
sys->fprint(sys->fildes(2), "palmsrv: comm version mismatch: %d.%d\n", major, minor);
cmp_write(p, Cabort, WrongVersion, Cmajor, 0, 0);
return sys->sprint("comm version mismatch: %d.%d", major, minor);
}
if(baud > maxbaud)
baud = maxbaud;
flag := 0;
if(baud != InitBaud)
flag = ChangeBaud;
(nil, err) = cmp_write(p, Cinit, flag, Cmajor, Cminor, baud);
if(err != nil)
return err;
p.setbaud(baud);
return nil;
}
cmp_write(p: ref Pchan, op: int, flag: int, major: int, minor: int, baud: int): (int, string)
{
cmpbuf := array[CMPHDRLEN] of byte;
cmpbuf[0] = byte op;
cmpbuf[1] = byte flag;
cmpbuf[2] = byte major;
cmpbuf[3] = byte minor;
cmpbuf[4] = byte 0;
cmpbuf[5] = byte 0;
put4(cmpbuf[6:], baud);
if(op == Cwake)
return (16rFF, p.padp_write(cmpbuf, 16rFF));
xid := p.nextseq();
return (xid, p.padp_write(cmpbuf, xid));
}
cmp_read(p: ref Pchan, xid: int): (int, int, int, int, int, string)
{
(c, err) := p.padp_read(xid, 20*Sec);
if(err != nil)
return (0, 0, 0, 0, 0, err);
if(len c != CMPHDRLEN)
return (0, 0, 0, 0, 0, "CMP: bad response");
return (int c[0], int c[1], int c[2], int c[3], get4(c[6:]), nil);
}
#
# Palm PADP protocol
# ``The Packet Assembly/Disassembly Protocol'' in
# Developing Palm OS Communications, US Robotics, 1996, pp. 53-68.
#
# forsyth@caldo.demon.co.uk, 1997
#
FIRST: con 16r80;
LAST: con 16r40;
MEMERROR: con 16r20;
# packet types
Pdata: con 1;
Pack: con 2;
Ptickle: con 4;
Pabort: con 8;
PADPHDRLEN: con 4; # type[1] flags[1] size[2]
RetryInterval: con 4*Sec;
MaxRetries: con 14; # they say 14 `seconds', but later state they might need 20 for heap mgmt, so i'll assume 14 attempts (at 4sec ea)
Pchan.padp_xchg(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string)
{
xid := p.nextseq();
err := p.padp_write(msg, xid);
if(err != nil)
return (nil, err);
return p.padp_read(xid, timeout);
}
#
# PADP header
# type[1] flags[2] size[2], high byte first for size
#
# max block size is 2^16-1
# must ack within 2 seconds
# wait at most 10 seconds for next chunk
# 10 retries
#
Pchan.padp_write(p: self ref Pchan, buf: array of byte, xid: int): string
{
count := len buf;
if(count >= 1<<16)
return "padp: write too big";
p.reply = nil;
flags := FIRST;
mem := buf[0:];
offset := 0;
while(count > 0){
n := count;
if(n > DATALIM)
n = DATALIM;
else
flags |= LAST;
ob := array[PADPHDRLEN+n] of byte;
ob[0] = byte Pdata;
ob[1] = byte flags;
l: int;
if(flags & FIRST)
l = count; # total size in first segment
else
l = offset; # offset in rest
put2(ob[2:], l);
ob[PADPHDRLEN:] = mem[0:n];
if(debug)
padp_dump(ob, "Tx");
p.slp_write(xid, ob);
retries := 0;
for(;;){
(ib, nil) := p.slp_read(RetryInterval);
if(ib == nil){
sys->print("padp write: ack timeout\n");
retries++;
if(retries > MaxRetries){
# USR says not to give up if (flags&LAST)!=0; giving up seems safer
sys->print("padp write: give up\n");
return "PADP: no response";
}
p.slp_write(xid, ob);
continue;
}
if(ib.proto != SLP_PAD || len ib.data < PADPHDRLEN || ib.xid != xid && ib.xid != 16rFF){
sys->print("padp write: ack wrong type(%d) or xid(%d,%d), or len %d\n", ib.proto, ib.xid, xid, len ib.data);
continue;
}
if(ib.xid == 16rFF){ # connection management
if(int ib.data[0] == Ptickle)
continue;
if(int ib.data[0] == Pabort){
sys->print("padp write: device abort\n");
p.shutdown = 1;
return "device cancelled operation";
}
}
if(int ib.data[0] != Pack){
if(int ib.data[0] == Ptickle)
continue;
# right transaction ... if it's acceptable data, USR says to save it & treat as ack
sys->print("padp write: type %d, not ack\n", int ib.data[0]);
if(int ib.data[0] == Pdata && flags & LAST && int ib.data[1] & FIRST){
p.reply = ib;
break;
}
continue;
}
if(int ib.data[1] & MEMERROR)
return "padp: pilot out of memory";
if((flags&(FIRST|LAST)) != (int ib.data[1]&(FIRST|LAST)) ||
get2(ib.data[2:]) != get2(ob[2:])){
sys->print("padp write: ack, wrong flags (#%x,#%x) or offset (%d,%d)\n", int ib.data[1], flags, get2(ib.data[2:]), get2(ob[2:]));
continue;
}
if(debug)
sys->print("padp write: ack %d %d\n", xid, get2(ob[2:]));
break;
}
mem = mem[n:];
count -= n;
offset += n;
flags &= ~FIRST;
}
return nil;
}
Pchan.padp_read(p: self ref Pchan, xid, timeout: int): (array of byte, string)
{
buf, mem: array of byte;
offset := 0;
ready := 0;
retries := 0;
ack := array[PADPHDRLEN] of byte;
for(;;){
b := p.reply;
if(b == nil){
err: string;
(b, err) = p.slp_read(timeout);
if(b == nil){
sys->print("padp read: timeout %d\n", retries);
if(++retries <= 5)
continue;
sys->print("padp read: gave up\n");
return (nil, err);
}
retries = 0;
} else
p.reply = nil;
if(debug)
padp_dump(b.data, "Rx");
if(len b.data < PADPHDRLEN){
sys->print("padp read: length\n");
continue;
}
if(b.proto != SLP_PAD){
sys->print("padp read: bad proto (%d)\n", b.proto);
continue;
}
if(int b.data[0] == Pabort && b.xid == 16rFF){
p.shutdown = 1;
return (nil, "device cancelled transaction");
}
if(int b.data[0] != Pdata || xid != 0 && b.xid != xid){
sys->print("padp read mismatch: type (%d) or xid(%d::%d)\n", int b.data[0], b.xid, xid);
continue;
}
f := int b.data[1];
o := get2(b.data[2:]);
if(f & FIRST){
buf = array[o] of byte;
ready = 1;
offset = 0;
o = 0;
mem = buf;
timeout = 4*Sec;
}
if(!ready || o != offset){
sys->print("padp read: offset %d, expected %d\n", o, offset);
continue;
}
n := len b.data - PADPHDRLEN;
if(n > len mem){
sys->print("padp read: record too long (%d/%d)\n", n, len mem);
# it's probably fatal, but retrying does no harm
continue;
}
mem[0:] = b.data[PADPHDRLEN:PADPHDRLEN+n];
mem = mem[n:];
offset += n;
ack[0:] = b.data[0:PADPHDRLEN];
ack[0] = byte Pack;
p.slp_write(xid, ack);
if(f & LAST)
break;
}
if(offset != len buf)
return (buf[0:offset], nil);
return (buf, nil);
}
Pchan.nextseq(p: self ref Pchan): int
{
n := p.lastid + 1;
if(n >= 16rFF)
n = 1;
p.lastid = n;
return n;
}
Pchan.tickle(p: self ref Pchan)
{
xid := p.nextseq();
data := array[PADPHDRLEN] of byte;
data[0] = byte Ptickle;
data[1] = byte (FIRST|LAST);
put2(data[2:], 0);
if(debug)
sys->print("PADP: tickle\n");
p.slp_write(xid, data);
}
padp_dump(data: array of byte, dir: string)
{
stype: string;
case int data[0] {
Pdata => stype = "Data";
Pack => stype = "Ack";
Ptickle => stype = "Tickle";
Pabort => stype = "Abort";
* => stype = sys->sprint("#%x", int data[0]);
}
sys->print("PADP %s %s flags=#%x len=%d\n", stype, dir, int data[1], get2(data[2:]));
if(debug > 1 && (data[0] != byte Pack || len data > 4)){
data = data[4:];
for(i := 0; i < len data;){
sys->print(" %.2x", int data[i]);
if(++i%16 == 0)
sys->print("\n");
}
sys->print("\n");
}
}
#
# Palm's Serial Link Protocol
# See include/Core/System/SerialLinkMgr.h in Palm SDK
# and the description in the USR document mentioned above.
#
SLPHDRLEN: con 10; # BE[1] EF[1] ED[1] dest[1] src[1] type[1] size[2] xid[1] check[1] body[size] crc[2]
SLP_MTU: con SLPHDRLEN+PADPHDRLEN+DATALIM;
Sblock.new(): ref Sblock
{
return ref Sblock(0, 0, 0, 16rFF, nil);
}
#
# format and write an SLP frame
#
Pchan.slp_write(p: self ref Pchan, xid: int, b: array of byte): string
{
d := array[SLPHDRLEN] of byte;
cb := array[2] of byte;
nb := len b;
d[0] = byte 16rBE;
d[1] = byte 16rEF;
d[2] = byte 16rED;
d[3] = byte p.rport;
d[4] = byte p.lport;
d[5] = byte p.protocol;
d[6] = byte (nb >> 8);
d[7] = byte (nb & 16rFF);
d[8] = byte xid;
d[9] = byte 0;
n := 0;
for(i:=0; i<len d; i++)
n += int d[i];
d[9] = byte (n & 16rFF);
if(debug)
printbytes(d, "SLP Tx hdr");
crc := crc16(d, 0);
put2(cb, crc16(b, crc));
if(sys->write(p.fd, d, SLPHDRLEN) != SLPHDRLEN ||
sys->write(p.fd, b, nb) != len b ||
sys->write(p.fd, cb, 2) != 2)
return sys->sprint("%r");
return nil;
}
Pchan.slp_read(p: self ref Pchan, timeout: int): (ref Sblock, string)
{
clock := Timer.start(timeout);
alt {
<-clock.timeout =>
if(debug)
sys->print("SLP: timeout\n");
return (nil, "SLP: timeout");
b := <-p.rd =>
clock.stop();
return (b, nil);
}
}
slp_recv(p: ref Pchan, pidc: chan of int)
{
n: int;
pidc <-= sys->pctl(0, nil);
buf := array[2*SLP_MTU] of byte;
sb := Sblock.new();
rd := wr := 0;
Work:
for(;;){
if(wr != rd){
# data already in buffer might start a new frame
if(rd != 0){
buf[0:] = buf[rd:wr];
wr -= rd;
rd = 0;
}
}else
rd = wr = 0;
# header
while(wr < SLPHDRLEN){
n = sys->read(p.fd, buf[wr:], SLPHDRLEN-wr);
if(n <= 0)
break Work;
wr += n;
}
# {for(i:=0; i<wr;i++)sys->print("%.2x", int buf[i]);sys->print("\n");}
if(buf[0] != byte 16rBE || buf[1] != byte 16rEF || buf[2] != byte 16rED){
rd++;
continue;
}
if(debug)
printbytes(buf[0:wr], "SLP Rx hdr");
n = 0;
for(i:=0; i<SLPHDRLEN-1; i++)
n += int buf[i];
if((n & 16rFF) != int buf[9]){
rd += 3;
continue;
}
hdr := buf[0:SLPHDRLEN];
sb.dst = int hdr[3];
sb.src = int hdr[4];
sb.proto = int hdr[5];
size := (int hdr[6]<<8) | int hdr[7];
sb.xid = int hdr[8];
sb.data = array[size] of byte;
crc := crc16(hdr, 0);
rd += SLPHDRLEN;
if(rd == wr)
rd = wr = 0;
# data and CRC
while(wr-rd < size+2){
n = sys->read(p.fd, buf[wr:], size+2-(wr-rd));
if(n <= 0)
break Work;
wr += n;
}
crc = crc16(buf[rd:rd+size], crc);
if(crc != get2(buf[rd+size:])){
if(debug)
sys->print("CRC error: local=#%.4ux pilot=#%.4ux\n", crc, get2(buf[rd+size:]));
for(; rd < wr && buf[rd] != byte 16rBE; rd++)
; # hunt for next header
continue;
}
if(sb.proto != SLP_Loop){
sb.data[0:] = buf[rd:rd+size];
if(debug)
sb.print("Rx");
rd += size+2;
p.rd <-= sb;
sb = Sblock.new();
} else {
# should we reflect these?
if(debug)
sb.print("Loop");
rd += size+2;
}
}
p.rd <-= nil;
}
Sblock.print(b: self ref Sblock, dir: string)
{
sys->print("SLP %s %d->%d len=%d proto=%d xid=#%.2x\n",
dir, int b.src, int b.dst, len b.data, int b.proto, int b.xid);
}
printbytes(d: array of byte, what: string)
{
buf := sys->sprint("%s[", what);
for(i:=0; i<len d; i++)
buf += sys->sprint(" #%.2x", int d[i]);
buf += "]";
sys->print("%s\n", buf);
}
get4(p: array of byte): int
{
return (int p[0]<<24) | (int p[1]<<16) | (int p[2]<<8) | int p[3];
}
get3(p: array of byte): int
{
return (int p[1]<<16) | (int p[2]<<8) | int p[3];
}
get2(p: array of byte): int
{
return (int p[0]<<8) | int p[1];
}
put4(p: array of byte, v: int)
{
p[0] = byte (v>>24);
p[1] = byte (v>>16);
p[2] = byte (v>>8);
p[3] = byte (v & 16rFF);
}
put3(p: array of byte, v: int)
{
p[0] = byte (v>>16);
p[1] = byte (v>>8);
p[2] = byte (v & 16rFF);
}
put2(p: array of byte, v: int)
{
p[0] = byte (v>>8);
p[1] = byte (v & 16rFF);
}
# this will be done by table look up;
# polynomial is xⁱ⁶+xⁱ+x⁵+1
crc16(buf: array of byte, crc: int): int
{
for(j := 0; j < len buf; j++){
crc = crc ^ (int buf[j]) << 8;
for(i := 0; i < 8; i++)
if(crc & 16r8000)
crc = (crc << 1) ^ 16r1021;
else
crc = crc << 1;
}
return crc & 16rffff;
}