diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/palm | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/palm')
| -rw-r--r-- | appl/cmd/palm/connex.b | 124 | ||||
| -rw-r--r-- | appl/cmd/palm/desklink.b | 843 | ||||
| -rw-r--r-- | appl/cmd/palm/desklink.m | 90 | ||||
| -rw-r--r-- | appl/cmd/palm/mkfile | 16 | ||||
| -rw-r--r-- | appl/cmd/palm/palmsrv.b | 901 |
5 files changed, 1974 insertions, 0 deletions
diff --git a/appl/cmd/palm/connex.b b/appl/cmd/palm/connex.b new file mode 100644 index 00000000..2cd66fd8 --- /dev/null +++ b/appl/cmd/palm/connex.b @@ -0,0 +1,124 @@ +implement Connex; + +# +# temporary test program for palmsrv development +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "palm.m"; + palm: Palm; + Record: import palm; + palmdb: Palmdb; + DB, PDB, PRC: import palmdb; + +include "desklink.m"; + desklink: Desklink; + SysInfo: import desklink; + +Connex: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + palm = load Palm Palm->PATH; + if(palm == nil) + error(sys->sprint("can't load %s: %r", palm->PATH)); + desklink = load Desklink Desklink->PATH1; + if(desklink == nil) + error(sys->sprint("can't load Desklink: %r")); + + palm->init(); + + err: string; + (palmdb, err) = desklink->connect("/chan/palmsrv"); + if(palmdb == nil) + error(sys->sprint("can't init Desklink: %s", err)); + desklink->init(palm); + sysinfo := desklink->ReadSysInfo(); + if(sysinfo == nil) + error(sys->sprint("can't read sys Info: %r")); + sys->print("ROM: %8.8ux locale: %8.8ux product: '%s'\n", sysinfo.romversion, sysinfo.locale, sysinfo.product); + user := desklink->ReadUserInfo(); + if(user == nil) + error(sys->sprint("can't read user info")); + sys->print("userid: %d viewerid: %d lastsyncpc: %d succsync: %8.8ux lastsync: %8.8ux uname: '%s' password: %s\n", + user.userid, user.viewerid, user.lastsyncpc, user.succsynctime, user.lastsynctime, user.username, ba(user.password)); + sys->print("Storage:\n"); + for(cno:=0;;){ + (cards, more, err) := desklink->ReadStorageInfo(cno); + for(i:=0; i<len cards; i++){ + sys->print("%2d v=%d c=%d romsize=%d ramsize=%d ramfree=%d name='%s' maker='%s'\n", + cards[i].cardno, cards[i].version, cards[i].creation, cards[i].romsize, cards[i].ramsize, + cards[i].ramfree, cards[i].name, cards[i].maker); + cno = cards[i].cardno+1; + } + if(!more) + break; + } + sys->print("ROM DBs:\n"); + listdbs(Desklink->DBListROM); + sys->print("RAM DBs:\n"); + listdbs(Desklink->DBListRAM); + + (db, ee) := DB.open("AddressDB", Palmdb->OREAD); + if(db == nil){ + sys->print("error: AddressDB: %s\n", ee); + exit; + } + pdb := db.records(); + if(pdb == nil){ + sys->print("error: AddressDB: %r\n"); + exit; + } + dumpfd := sys->create("dump", Sys->OWRITE, 8r600); + for(i:=0; (r := pdb.read(i)) != nil; i++) + sys->write(dumpfd, r.data, len r.data); +# desklink->EndOfSync(Desklink->SyncNormal); + desklink->hangup(); +} + +listdbs(sort: int) +{ + index := 0; + for(;;){ + (dbs, more, e) := desklink->ReadDBList(0, sort, index); + if(dbs == nil){ + if(e != nil) + sys->print("ReadDBList: %s\n", e); + break; + } + for(i := 0; i < len dbs; i++){ + sys->print("#%4.4ux '%s'\n", dbs[i].index, dbs[i].name); + index = dbs[i].index+1; + } + if(!more) + break; + } +} + +ba(a: array of byte): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += sys->sprint("%2.2ux", int a[i]); + return s; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "tconn: %s\n", s); + fd := sys->open("/prog/"+string sys->pctl(0,nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + raise "fail:error"; +} diff --git a/appl/cmd/palm/desklink.b b/appl/cmd/palm/desklink.b new file mode 100644 index 00000000..23dafaa7 --- /dev/null +++ b/appl/cmd/palm/desklink.b @@ -0,0 +1,843 @@ +implement Palmdb, Desklink; + +# +# Palm Desk Link Protocol (DLP) +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# +# Request and response formats were extracted from +# include/Core/System/DLCommon.h in the PalmOS SDK-5 +# + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime: Daytime; + Tm: import daytime; + +include "palm.m"; + palm: Palm; + DBInfo, Record, Resource, id2s, s2id, get2, put2, get4, put4, gets, argsize, packargs, unpackargs: import palm; + +include "timers.m"; + +include "desklink.m"; + +Maxrecbytes: con 16rFFFF; + +# operations defined by Palm + +T_ReadUserInfo, T_WriteUserInfo, T_ReadSysInfo, T_GetSysDateTime, +T_SetSysDateTime, T_ReadStorageInfo, T_ReadDBList, T_OpenDB, T_CreateDB, +T_CloseDB, T_DeleteDB, T_ReadAppBlock, T_WriteAppBlock, T_ReadSortBlock, +T_WriteSortBlock, T_ReadNextModifiedRec, T_ReadRecord, T_WriteRecord, +T_DeleteRecord, T_ReadResource, T_WriteResource, T_DeleteResource, +T_CleanUpDatabase, T_ResetSyncFlags, T_CallApplication, T_ResetSystem, +T_AddSyncLogEntry, T_ReadOpenDBInfo, T_MoveCategory, T_ProcessRPC, +T_OpenConduit, T_EndOfSync, T_ResetDBIndex, T_ReadRecordIDList, +# DLP 1.1 functions +T_ReadNextRecInCategory, T_ReadNextModifiedRecInCategory, +T_ReadAppPreference, T_WriteAppPreference, T_ReadNetSyncInfo, +T_WriteNetSyncInfo, T_ReadFeature, +# DLP 1.2 functions +T_FindDB, T_SetDBInfo, +# DLP 1.3 functions +T_LoopBackTest, T_ExpSlotEnumerate, T_ExpCardPresent, T_ExpCardInfo: con 16r10+iota; +# then there's a group of VFS requests that we don't currently use + +Response: con 16r80; + +Maxname: con 32; + +A1, A2: con Palm->ArgIDbase+iota; # argument IDs have request-specific interpretation (most have only one ID) + +Timeout: con 30; # seconds time out used by Palm's headers +srvfd: ref Sys->FD; +selfdb: Palmdb; + +errorlist := array [] of { + "no error", + "general Pilot system error", + "unknown request", + "out of dynamic memory on device", + "invalid parameter", + "not found", + "no open databases", + "database already open", + "too many open databases", + "database already exists", + "cannot open database", + "record previously deleted", + "record busy", + "operation not supported", + "unexpected error (ErrUnused1)", + "read only object", + "not enough space", + "size limit exceeded", + "sync cancelled", + "bad arg wrapper", + "argument missing", + "bad argument size", +}; + +Eshort: con "desklink protocol: response too short"; + +debug := 0; + +connect(srvfile: string): (Palmdb, string) +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return (nil, sys->sprint("can't load %s: %r", Daytime->PATH)); + srvfd = sys->open(srvfile, Sys->ORDWR); + if(srvfd == nil) + return (nil, sys->sprint("can't open %s: %r", srvfile)); + selfdb = load Palmdb "$self"; + if(selfdb == nil) + return (nil, sys->sprint("can't load self as Palmdb: %r")); + return (selfdb, nil); +} + +hangup(): int +{ + srvfd = nil; + return 0; +} + +# +# set the system error string +# +e(s: string): string +{ + if(s != nil){ + s = "palm: "+s; + sys->werrstr(s); + } + return s; +} + +# +# sent before each conduit is opened by the desktop, +# apparently to detect a pending cancel request (on the device) +# +OpenConduit(): int +{ + return nexec(T_OpenConduit, A1, nil); +} + +# +# end of sync on desktop +# +EndOfSync(status: int): int +{ + req := array[2] of byte; + put2(req, status); + return nexec(T_EndOfSync, A1, req); +} + +ReadSysInfo(): ref SysInfo +{ + if((reply := dexec(T_ReadSysInfo, A1, nil, 14)) == nil) + return nil; + s := ref SysInfo; + s.romversion = get4(reply); + s.locale = get4(reply[4:]); + l := int reply[9]; # should be at most 4 apparently? + s.product = gets(reply[10:10+l]); + return s; +} + +ReadSysInfoVer(): (int, int, int) +{ + req := array[4] of byte; + put2(req, 1); # major version + put2(req, 2); # minor version + if((reply := dexec(T_ReadSysInfo, A2, req, 12)) == nil) + return (0, 0, 0); + return (get4(reply), get4(reply[4:]), get4(reply[8:])); +} + +ReadUserInfo(): ref User +{ + if((reply := dexec(T_ReadUserInfo, 0, nil, 30)) == nil) + return nil; + u := ref User; + u.userid = get4(reply); + u.viewerid = get4(reply[4:]); + u.lastsyncpc = get4(reply[8:]); + u.succsynctime = getdate(reply[12:]); + u.lastsynctime = getdate(reply[20:]); + userlen := int reply[28]; + pwlen := int reply[29]; + u.username = gets(reply[30:30+userlen]); + u.password = array[pwlen] of byte; + u.password[0:] = reply[30+userlen:30+userlen+pwlen]; + return u; +} + +WriteUserInfo(u: ref User, flags: int): int +{ + req := array[22+Maxname] of byte; + put4(req, u.userid); + put4(req[4:], u.viewerid); + put4(req[8:], u.lastsyncpc); + putdate(req[12:], u.lastsynctime); + req[20] = byte flags; + l := puts(req[22:], u.username); + req[21] = byte l; + return nexec(T_WriteUserInfo, A1, req[0:22+l]); +} + +GetSysDateTime(): int +{ + if((reply := dexec(T_GetSysDateTime, A1, nil, 8)) == nil) + return -1; + return getdate(reply); +} + +SetSysDateTime(time: int): int +{ + return nexec(T_SetSysDateTime, A1, putdate(array[8] of byte, time)); +} + +ReadStorageInfo(cardno: int): (array of ref CardInfo, int, string) +{ + req := array[2] of byte; + req[0] = byte cardno; + req[1] = byte 0; + (reply, err) := rexec(T_ReadStorageInfo, A1, req, 30); + if(reply == nil) + return (nil, 0, err); + nc := int reply[3]; + if(nc <= 0) + return (nil, 0, nil); + more := int reply[1] != 0; + a := array[nc] of ref CardInfo; + p := 4; + for(i:=0; i<nc; i++){ + nb: int; + (a[i], nb) = unpackcard(reply[p:]); + p += nb; + } + return (a, more, nil); +} + +unpackcard(a: array of byte): (ref CardInfo, int) +{ + nb := int a[0]; # total size of this card's info + c := ref CardInfo; + c.cardno = int a[1]; + c.version = get2(a[2:]); + c.creation = getdate(a[4:]); + c.romsize = get4(a[12:]); + c.ramsize = get4(a[16:]); + c.ramfree = get4(a[20:]); + l1 := int a[24] + 26; + l2 := int a[25]; + c.name = gets(a[26:l1]); + c.maker = gets(a[l1:l1+l2]); + return (c, nb); +} + +ReadDBCount(cardno: int): (int, int) +{ + req := array[2] of byte; + req[0] = byte cardno; + req[1] = byte 0; + if((reply := dexec(T_ReadStorageInfo, A2, req, 20)) == nil) + return (-1, -1); + return (get2(req[0:]), get2(req[2:])); +} + +unpackdbinfo(a: array of byte): (ref DBInfo, int) +{ + size := int a[0]; + misc := int a[1]; + info := ref DBInfo; + info.attr = get2(a[2:]); + info.dtype = id2s(get4(a[4:])); + info.creator = id2s(get4(a[8:])); + info.version = get2(a[12:]); + info.modno = get4(a[14:]); + info.ctime = getdate(a[18:]); + info.mtime = getdate(a[26:]); + info.btime = getdate(a[34:]); + info.index = get2(a[42:]); + if(size > len a) + size = len a; + info.name = gets(a[44:size]); + return (info, size); +} + +ReadDBList(cardno: int, flags: int, start: int): (array of ref DBInfo, int, string) +{ + req := array[4] of byte; + req[0] = byte (flags | DBListMultiple); + req[1] = byte cardno; + put2(req[2:], start); + (reply, err) := rexec(T_ReadDBList, A1, req, 48); + if(reply == nil || int reply[3] == 0) + return (nil, 0, err); + # lastindex[2] flags[1] actcount[1] + # flags is 16r80 => more to list + more := (reply[2] & byte 16r80) != byte 0; + dbs := array[int reply[3]] of ref DBInfo; +#sys->print("ndb=%d more=%d lastindex=#%4.4ux\n", len dbs, more, get2(reply)); + a := reply[4:]; + for(i := 0; i < len dbs; i++){ + (db, n) := unpackdbinfo(a); + dbs[i] = db; + a = a[n:]; + } + return (dbs, more, nil); +} + +matchdb(cardno: int, flag: int, start: int, dbname: string, dtype: string, creator: string): (ref DBInfo, int) +{ + for(;;){ + (dbs, more, err) := ReadDBList(cardno, flag, start); + if(dbs == nil) + break; + for(i := 0; i < len dbs; i++){ + info := dbs[i]; + if((dbname == nil || info.name == dbname) && + (dtype == nil || info.dtype == dtype) && + (creator == nil || info.creator == creator)) + return (info, info.index); + start = info.index+1; + } + } + return (nil, 0); +} + + +FindDBInfo(cardno: int, start: int, dbname: string, dtype: string, creator: string): ref DBInfo +{ + if(start < 16r1000) { + (info, i) := matchdb(cardno, 16r80, start, dbname, dtype, creator); + if(info != nil) + return info; + } + (info, i) := matchdb(cardno, 16r40, start&~16r1000, dbname, dtype, creator); + if(info != nil) + info.index |= 16r1000; + return info; +} + +DeleteDB(name: string): int +{ + (cardno, dbname) := parsedb(name); + req := array[2+Maxname] of byte; + req[0] = byte cardno; + req[1] = byte 0; + n := puts(req[2:], dbname); + return nexec(T_DeleteDB, A1, req[0:2+n]); +} + +ResetSystem(): int +{ + return nexec(T_ResetSystem, 0, nil); +} + +CloseDB_All(): int +{ + return nexec(T_CloseDB, A2, nil); +} + +AddSyncLogEntry(entry: string): int +{ + req := array[256] of byte; + n := puts(req, entry); + return nexec(T_AddSyncLogEntry, A1, req[0:n]); +} + +# +# this implements a Palmdb->DB directly accessed using the desklink protocol +# + +init(m: Palm): string +{ + palm = m; + return nil; +} + +# +# syntax is [cardno/]dbname +# where cardno defaults to 0 +# +parsedb(name: string): (int, string) +{ + (nf, flds) := sys->tokenize(name, "/"); + if(nf > 1) + return (int hd flds, hd tl flds); + return (0, name); +} + +DB.open(name: string, mode: int): (ref DB, string) +{ + (cardno, dbname) := parsedb(name); + req := array[2+Maxname] of byte; + req[0] = byte cardno; + req[1] = byte mode; + n := puts(req[2:], dbname); + (reply, err) := rexec(T_OpenDB, A1, req[0:2+n], 1); + if(reply == nil) + return (nil, err); + db := ref DB; + db.x = int reply[0]; + inf := db.stat(); + if(inf == nil) + return (nil, sys->sprint("can't get DBInfo: %r")); + db.attr = inf.attr; # mainly need to know whether it's Fresource or not + return (db, nil); +} + +DB.create(name: string, nil: int, nil: int, inf: ref DBInfo): (ref DB, string) +{ + (cardno, dbname) := parsedb(name); + req := array[14+Maxname] of byte; + put4(req, s2id(inf.creator)); + put4(req[4:], s2id(inf.dtype)); + req[8] = byte cardno; + req[9] = byte 0; + put2(req[10:], inf.attr); + put2(req[12:], inf.version); + n := puts(req[14:], dbname); + (reply, err) := rexec(T_CreateDB, A1, req[0:14+n], 1); + if(reply == nil) + return (nil, err); + db := ref DB; + db.x = int reply[0]; + db.attr = inf.attr; + return (db, nil); +} + +DB.stat(db: self ref DB): ref DBInfo +{ + (reply, err) := rexec(T_FindDB, A2, array[] of {byte 16r80, byte db.x}, 54); + if(err != nil) + return nil; + return unpackdbinfo(reply[10:]).t0; +} + +DB.wstat(db: self ref DB, inf: ref DBInfo, flags: int) +{ + # TO DO +} + +DB.close(db: self ref DB): string +{ + return rexec(T_CloseDB, A1, array[] of {byte db.x}, 0).t1; +} + +DB.records(db: self ref DB): ref PDB +{ + if(db.attr & Palm->Fresource){ + sys->werrstr("not a database file"); + return nil; + } + return ref PDB(db); +} + +DB.resources(db: self ref DB): ref PRC +{ + if((db.attr & Palm->Fresource) == 0){ + sys->werrstr("not a resource file"); + return nil; + } + return ref PRC(db); +} + +DB.readidlist(db: self ref DB, sort: int): array of int +{ + req := array[6] of byte; + req[0] = byte db.x; + if(sort) + req[1] = byte 16r80; + else + req[1] = byte 0; + put2(req[2:], 0); + put2(req[4:], -1); + p := dexec(T_ReadRecordIDList, A1, req, 2); + if(p == nil) + return nil; + ret := get2(p); + ids := array[ret] of int; + p = p[8:]; + for (i := 0; i < ret; p = p[4:]) + ids[i++] = get4(p); + return ids; +} + +DB.nentries(db: self ref DB): int +{ + if((reply := dexec(T_ReadOpenDBInfo, A1, array[] of {byte db.x}, 2)) == nil) + return -1; + return get2(reply); +} + +DB.rdappinfo(db: self ref DB): (array of byte, string) +{ + req := array[6] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 0); # offset + put2(req[4:], -1); # to end + (reply, err) := rexec(T_ReadAppBlock, A1, req, 2); + if(reply == nil) + return (nil, err); + if(get2(reply) < len reply-2) + return (nil, "short reply"); + return (reply[2:], nil); +} + +DB.wrappinfo(db: self ref DB, data: array of byte): string +{ + req := array[4 + len data] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], len data); + req[4:] = data; + return rexec(T_WriteAppBlock, A1, req, 0).t1; +} + +DB.rdsortinfo(db: self ref DB): (array of int, string) +{ + req := array[6] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 0); + put2(req[4:], -1); + (reply, err) := rexec(T_ReadSortBlock, A1, req, 2); + if(reply == nil) + return (nil, err); + n := len reply; + a := reply[2:n]; + n = (n-2)/2; + s := array[n] of int; + for(i := 0; i < n; i++) + s[i] = get2(a[i*2:]); + return (s, nil); +} + +DB.wrsortinfo(db: self ref DB, s: array of int): string +{ + n := len s; + req := array[4+2*n] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 2*n); + for(i := 0; i < n; i++) + put2(req[2+i*2:], s[i]); + return rexec(T_WriteSortBlock, A1, req, 0).t1; +} + +PDB.purge(db: self ref PDB): string +{ + return rexec(T_CleanUpDatabase, A1, array[] of {byte db.db.x}, 0).t1; +} + +DB.resetsyncflags(db: self ref DB): string +{ + return rexec(T_ResetSyncFlags, A1, array[] of {byte db.x}, 0).t1; +} + +# +# .pdb and other data base files +# + +PDB.read(db: self ref PDB, index: int): ref Record +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put2(req[2:], index); + put2(req[4:], 0); # offset + put2(req[6:], Maxrecbytes); + return unpackrec(dexec(T_ReadRecord, A2, req, 10)).t0; +} + +PDB.readid(db: self ref PDB, id: int): (ref Record, int) +{ + req := array[10] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], id); + put2(req[6:], 0); # offset + put2(req[8:], Maxrecbytes); + return unpackrec(dexec(T_ReadRecord, A1, req, 10)); +} + +PDB.write(db: self ref PDB, r: ref Record): string +{ + req := array[8+len r.data] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], r.id); + req[6] = byte (r.attr & Palm->Rsecret); + req[7] = byte r.cat; + req[8:] = r.data; + (reply, err) := rexec(T_WriteRecord, A1, req, 4); + if(reply == nil) + return err; + if(r.id == 0) + r.id = get4(reply); + return nil; +} + +PDB.movecat(db: self ref PDB, from: int, tox: int): string +{ + req := array[4] of byte; + req[0] = byte db.db.x; + req[1] = byte from; + req[2] = byte tox; + req[3] = byte 0; + return rexec(T_MoveCategory, A1, req, 0).t1; +} + +PDB.resetnext(db: self ref PDB): int +{ + return nexec(T_ResetDBIndex, A1, array[] of {byte db.db.x}); +} + +PDB.readnextmod(db: self ref PDB): (ref Record, int) +{ + return unpackrec(dexec(T_ReadNextModifiedRec, A1, array[] of {byte db.db.x}, 10)); +} + +PDB.delete(db: self ref PDB, id: int): string +{ + req := array[6] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], id); + return rexec(T_DeleteRecord, A1, req, 0).t1; +} + +PDB.deletecat(db: self ref PDB, cat: int): string +{ + return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r40, 2 to 6 => byte 0, 7=>byte cat}, 0).t1; +} + +PDB.truncate(db: self ref PDB): string +{ + return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1; +} + +# +# .prc resource files +# + +PRC.write(db: self ref PRC, r: ref Resource): string +{ + req := array[8+len r.data] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], r.name); + put2(req[6:], r.id); + put2(req[8:], len r.data); + return rexec(T_WriteResource, A1, req, 0).t1; +} + +PRC.delete(db: self ref PRC, name: int, id: int): string +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], name); + put4(req[6:], id); + return rexec(T_DeleteResource, A1, req, 0).t1; +} + +PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int) +{ + req := array[12] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], name); + put2(req[6:], id); + put2(req[8:], 0); # Offset into record + put2(req[10:], Maxrecbytes); + return unpackresource(dexec(T_ReadResource, A2, req, 10)); +} + +PRC.truncate(db: self ref PRC): string +{ + return rexec(T_DeleteResource, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1; +} + +PRC.read(db: self ref PRC, index: int): ref Resource +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put2(req[2:], index); + put2(req[4:], 0); # offset + put2(req[6:], Maxrecbytes); + return unpackresource(dexec(T_ReadResource, A1, req, 12)).t0; +} + +# +# DL protocol +# +# request +# id: byte # operation +# argc: byte # arg count +# args: byte[] +# +# response +# id: byte # cmd|16r80 +# argc: byte # argc response arguments follow header +# error: byte[2] # error code +# args: byte[] +# +# args wrapped by Palm->packargs etc. +# + +# +# RPC exchange with device +# +rpc(req: array of byte): (array of (int, array of byte), string) +{ + if(sys->write(srvfd, req, len req) != len req) + return (nil, sys->sprint("link: %r")); + reply := array[65536] of byte; + nb := sys->read(srvfd, reply, len reply); + if(nb == 0) + return (nil, "link: hangup"); + if(nb < 0) + return (nil, sys->sprint("link: %r")); + r := int reply[0]; + if((r & Response) == 0) + return (nil, e(sys->sprint("received request #%2.2x not response", r))); + if(r != (Response|int req[0])) + return (nil, e(sys->sprint("wrong response #%x", r))); + if(nb < 4) + return (nil, e(Eshort)); + rc := get2(reply[2:]); + if(rc != 0){ + if(rc < 0 || rc >= len errorlist) + return (nil, e(sys->sprint("unknown error %d", rc))); + return (nil, e(errorlist[rc])); + } + argc := int reply[1]; # count of following arguments + if(argc == 0) + return (nil, nil); + return unpackargs(argc, reply[4:nb]); +} + +rexec(cmd: int, argid: int, arg: array of byte, minlen: int): (array of byte, string) +{ + args: array of (int, array of byte); + if(arg != nil) + args = array[] of {(argid, arg)}; + req := array[2+argsize(args)] of byte; + req[0] = byte cmd; + req[1] = byte len args; + packargs(req[2:], args); + (replies, err) := rpc(req); + if(replies == nil){ + if(err != nil) + return (nil, err); + if(minlen > 0) + return (nil, e(Eshort)); + return (nil, nil); + } + (nil, reply) := replies[0]; + if(len reply < minlen) + return (nil, e(Eshort)); + return (reply, nil); +} + +dexec(cmd: int, argid: int, msg: array of byte, minlen: int): array of byte +{ + (reply, nil) := rexec(cmd, argid, msg, minlen); + return reply; +} + +nexec(cmd: int, argid: int, msg: array of byte): int +{ + (nil, err) := rexec(cmd, argid, msg, 0); + if(err != nil) + return -1; + return 0; +} + +unpackresource(a: array of byte): (ref Resource, int) +{ + nb := len a; + if(nb < 10) + return (nil, -1); + size := get2(a[8:]); + if(nb-10 < size) + return (nil, -1); + r := Resource.new(get4(a), get2(a[4:]), size); + r.data[0:] = a[10:10+size]; + return (r, get2(a[6:])); +} + +unpackrec(a: array of byte): (ref Record, int) +{ + nb := len a; + if(nb < 10) + return (nil, -1); + size := get2(a[6:]); + if(nb-10 < size) + return (nil, -1); + r := Record.new(get4(a), int a[8], int a[9], size); + r.data[0:] = a[10:10+size]; + return (r, get2(a[4:])); +} + +# +# pack string (must be Latin1) as zero-terminated array of byte +# +puts(a: array of byte, s: string): int +{ + for(i := 0; i < len s && i < len a-1; i++) + a[i] = byte s[i]; + a[i++] = byte 0; + return i; +} + +# +# the conversion via local time might be wrong, +# since the computers might be in different time zones, +# but is hard to avoid +# + +getdate(data: array of byte): int +{ + yr := (int data[0] << 8) | int data[1]; + if(yr == 0) + return 0; # unspecified + t := ref Tm; + t.sec = int data[6]; + t.min = int data[5]; + t.hour = int data[4]; + t.mday = int data[3]; + t.mon = int data[2] - 1; + t.year = yr - 1900; + t.wday = 0; + t.yday = 0; + return daytime->tm2epoch(t); +} + +putdate(data: array of byte, time: int): array of byte +{ + t := daytime->local(time); + y := t.year + 1900; + if(time == 0) + y = 0; # `unchanged' + data[7] = byte 0; # pad + data[6] = byte t.sec; + data[5] = byte t.min; + data[4] = byte t.hour; + data[3] = byte t.mday; + data[2] = byte (t.mon + 1); + data[0] = byte ((y >> 8) & 16rff); + data[1] = byte (y & 16rff); + return data; +} diff --git a/appl/cmd/palm/desklink.m b/appl/cmd/palm/desklink.m new file mode 100644 index 00000000..cc8d69c4 --- /dev/null +++ b/appl/cmd/palm/desklink.m @@ -0,0 +1,90 @@ + +# +# desktop/Pilot link protocol +# + +Desklink: module { + + PATH1: con "/dis/palm/desklink.dis"; + + User: adt { + userid: int; + viewerid: int; + lastsyncpc: int; + succsynctime: int; + lastsynctime: int; + username: string; + password: array of byte; + }; + + SysInfo: adt { + romversion: int; + locale: int; + product: string; + }; + + CardInfo: adt { + cardno: int; + version: int; + creation: int; + romsize: int; + ramsize: int; + ramfree: int; + name: string; + maker: string; + }; + + connect: fn(srvfile: string): (Palmdb, string); + hangup: fn(): int; + + # + # Desk Link Protocol functions (usually with the same names as in PalmOS) + # + + ReadUserInfo: fn(): ref User; + WriteUserInfo: fn(u: ref User, flags: int): int; + + # WriteUserInfo update flags + UserInfoModUserID: con 16r80; + UserInfoModSyncPC: con 16r40; + UserInfoModSyncDate: con 16r20; + UserInfoModName: con 16r10; + UserInfoModViewerID: con 16r08; + + ReadSysInfo: fn(): ref SysInfo; + ReadSysInfoVer: fn(): (int, int, int); # DLP 1.2 + + GetSysDateTime: fn(): int; + SetSysDateTime: fn(nil: int): int; + + ReadStorageInfo: fn(cardno: int): (array of ref CardInfo, int, string); + ReadDBCount: fn(cardno: int): (int, int); + + ReadDBList: fn(cardno: int, flags: int, start: int): (array of ref Palm->DBInfo, int, string); # flags must contain DBListRAM and/or DBListROM + FindDBInfo: fn(cardno: int, start: int, name: string, dtype, creator: string): ref Palm->DBInfo; + + # list location and options + DBListRAM: con 16r80; + DBListROM: con 16r40; + DBListMultiple: con 16r20; # ok to return multiple entries + + # OpenDB, CreateDB, ReadAppBlock, ... ResetSyncFlags, ReadOpenDBInfo, MoveCategory are functions in DB + CloseDB_All: fn(): int; + DeleteDB: fn(name: string): int; + + ResetSystem: fn(): int; + + OpenConduit: fn(): int; + EndOfSync: fn(status: int): int; + + # EndOfSync status parameter + SyncNormal, SyncOutOfMemory, SyncCancelled, SyncError, SyncIncompatible: con iota; + + AddSyncLogEntry: fn(entry: string): int; + + # + # Palmdb implementation + # + + init: fn(m: Palm): string; +}; diff --git a/appl/cmd/palm/mkfile b/appl/cmd/palm/mkfile new file mode 100644 index 00000000..2b0cb209 --- /dev/null +++ b/appl/cmd/palm/mkfile @@ -0,0 +1,16 @@ +<../../../mkconfig + +TARG=\ + palmsrv.dis\ + desklink.dis\ + connex.dis\ + +MODULES=\ + desklink.m\ + +SYSMODULES=\ + palm.m\ + +DISBIN=$ROOT/dis/palm + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/palm/palmsrv.b b/appl/cmd/palm/palmsrv.b new file mode 100644 index 00000000..f878be03 --- /dev/null +++ b/appl/cmd/palm/palmsrv.b @@ -0,0 +1,901 @@ +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; +} |
