summaryrefslogtreecommitdiff
path: root/appl/cmd/auxi/mangaload.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/auxi/mangaload.b')
-rw-r--r--appl/cmd/auxi/mangaload.b362
1 files changed, 362 insertions, 0 deletions
diff --git a/appl/cmd/auxi/mangaload.b b/appl/cmd/auxi/mangaload.b
new file mode 100644
index 00000000..380dd22e
--- /dev/null
+++ b/appl/cmd/auxi/mangaload.b
@@ -0,0 +1,362 @@
+implement Mangaload;
+
+# to do:
+# - set arp entry based on /lib/ndb if necessary
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "arg.m";
+
+Mangaload: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# manga parameters
+FlashBlocksize: con 16r10000;
+FlashSize: con 16r400000; # 4meg for now
+FlashUserArea: con 16r3C0000;
+
+# magic values
+FooterOffset: con 16rFFEC;
+FooterSig: con 16rA0FFFF9F; # ARM flash library
+FileInfosize: con 64;
+FileNamesize: con FileInfosize - 3*4; # x, y, z
+Packetdatasize: con 1500-28; # ether data less IP + ICMP header
+RequestTimeout: con 500;
+Probecount: con 10; # query unit every so many packets
+
+# manga uses extended TFTP ops in ICMP InfoRequest packets
+Tftp_Req: con 0;
+Tftp_Read: con 1;
+Tftp_Write: con 2;
+Tftp_Data: con 3;
+Tftp_Ack: con 4;
+Tftp_Error: con 5;
+Tftp_Last: con 6;
+
+Icmp: adt
+{
+ ttl: int; # time to live
+ src: IPaddr;
+ dst: IPaddr;
+ ptype: int;
+ code: int;
+ id: int;
+ seq: int;
+ data: array of byte;
+ munged: int; # packet received but corrupt
+
+ unpack: fn(b: array of byte): ref Icmp;
+};
+
+# ICMP packet types
+EchoReply: con 0;
+Unreachable: con 3;
+SrcQuench: con 4;
+EchoRequest: con 8;
+TimeExceed: con 11;
+Timestamp: con 13;
+TimestampReply: con 14;
+InfoRequest: con 15;
+InfoReply: con 16;
+
+Nmsg: con 32;
+Interval: con 1000; # ms
+
+debug := 0;
+flashblock := 1; # never 0, that's the boot firmware
+maxfilesize := 8*FlashBlocksize;
+flashlim := FlashSize/FlashBlocksize;
+loadinitrd := 0;
+maxlen := 512*1024;
+mypid := 0;
+Datablocksize: con 4096;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ timers = load Timers Timers->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("mangaload [-48dr] destination file");
+ while((o := arg->opt()) != 0)
+ case o {
+ '4' =>
+ flashlim = 4*1024*1024/FlashBlocksize;
+ '8' =>
+ flashlim = 8*1024*1024/FlashBlocksize;
+ 'r' =>
+ loadinitrd = 1;
+ flashblock = 9;
+ if(flashlim > 4*1024*1024/FlashBlocksize)
+ maxfilesize = 113*FlashBlocksize;
+ else
+ maxfilesize = 50*FlashBlocksize;
+ 'd' =>
+ debug++;
+ }
+ args = arg->argv();
+ if(len args != 2)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+
+ filename := hd tl args;
+ fd := sys->open(filename, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename);
+ raise "fail:open";
+ }
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename);
+ raise "fail:stat";
+ }
+ if(d.length > big maxfilesize){
+ sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n",
+ filename, maxfilesize);
+ raise "fail:size";
+ }
+ filesize := int d.length;
+
+ port := sys->sprint("%d", 16r8695);
+ addr := netmkaddr(hd args, "icmp", port);
+ (rok, c) := sys->dial(addr, port);
+ if(rok < 0){
+ sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr);
+ raise "fail:dial";
+ }
+
+ tpid := timers->init(20);
+
+ pids := chan of int;
+ replies := chan [2] of ref Icmp;
+ spawn reader(c.dfd, replies, pids);
+ rpid := <-pids;
+
+ flashoffset := flashblock * FlashBlocksize;
+
+ # file name first
+ bname := array of byte filename;
+ l := len bname;
+ buf := array[Packetdatasize] of byte;
+ ip->put4(buf, 0, filesize);
+ ip->put4(buf, 4, l);
+ buf[8:] = bname;
+ l += 2*4;
+ buf[l++] = byte 0;
+ ip->put4(buf, l, flashoffset);
+ l += 4;
+ {
+ if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0)
+ senderr();
+ (op, iseq, data) := recv(replies, 400);
+ sys->print("initial reply: %d %d\n", op, iseq);
+ if(op != Tftp_Ack){
+ why := "no response";
+ if(op == Tftp_Error)
+ why = "manga cannot receive file";
+ sys->fprint(sys->fildes(2), "mangaload: %s\n", why);
+ raise "fail:error";
+ }
+ sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset);
+ seq := 1;
+ nsent := 0;
+ last := 0;
+ while((n := sys->read(fd, buf, len buf)) >= 0 && !last){
+ last = n != len buf;
+ nretry := 0;
+ Retry:
+ for(;;){
+ if(++nsent%10 == 0){ # probe
+ o = Tftp_Req;
+ send(c.dfd, array[0] of byte, Tftp_Req, seq);
+ (op, iseq, data) = recv(replies, 500);
+ if(debug || op != Tftp_Ack)
+ sys->print("ack reply: %d %d\n", op, iseq);
+ if(op == Tftp_Last || op == Tftp_Error){
+ if(op == Tftp_Last)
+ sys->print("timed out\n");
+ else
+ sys->print("error reply\n");
+ raise "disaster";
+ }
+ if(debug)
+ sys->print("ok\n");
+ continue Retry;
+ }
+ send(c.dfd, buf[0:n], Tftp_Data, seq);
+ (op, iseq, data) = recv(replies, 40);
+ case op {
+ Tftp_Error =>
+ sys->fprint(sys->fildes(2), "mangaload: manga refused data\n");
+ raise "disaster";
+ Tftp_Ack =>
+ if(seq == iseq){
+ seq++;
+ break Retry;
+ }
+ sys->print("sequence error: rcvd %d expected %d\n", iseq, seq);
+ if(iseq > seq){
+ sys->print("unrecoverable sequence error\n");
+ send(c.dfd, array[0] of byte, Tftp_Data, ++seq); # stop manga
+ raise "disaster";
+ }
+ # resend
+ sys->seek(fd, -big ((seq-iseq)*len buf), 1);
+ seq = iseq;
+ Tftp_Last =>
+ seq++;
+ break Retry; # timeout ok: manga doesn't usually reply unless packet lost
+ }
+ }
+ }
+ }exception{
+ * =>
+ ;
+ }
+ kill(rpid);
+ kill(tpid);
+ sys->print("ok?\n");
+}
+
+kill(pid: int)
+{
+ if(pid)
+ sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
+
+senderr()
+{
+ sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n");
+ raise "disaster";
+}
+
+send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int
+{
+ buf := array[64*1024+512] of {* => byte 0};
+ buf[Odata:] = data;
+ ip->put2(buf, Oseq, seq);
+ buf[Otype] = byte InfoRequest;
+ buf[Ocode] = byte op;
+ if(sys->write(fd, buf, Odata+len data) < Odata+len data)
+ return -1;
+ if(debug)
+ sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data);
+ return 0;
+}
+
+flush(input: chan of ref Icmp)
+{
+ for(;;)alt{
+ <-input =>
+ ;
+ * =>
+ return;
+ }
+}
+
+recv(input: chan of ref Icmp, msec: int): (int, int, array of byte)
+{
+ t := Timer.start(msec);
+ alt{
+ <-t.timeout =>
+ return (Tftp_Last, 0, nil);
+ ic := <-input =>
+ t.stop();
+ if(ic.ptype == InfoReply)
+ return (ic.code, ic.seq, ic.data);
+ return (Tftp_Last, 0, nil);
+ }
+}
+
+reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int)
+{
+ pid <-= sys->pctl(0, nil);
+ for(;;){
+ buf := array[64*1024+512] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected eof");
+ break;
+ }
+ ic := Icmp.unpack(buf[0:n]);
+ if(ic != nil){
+ if(debug)
+ sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id);
+ out <-= ic;
+ }else
+ sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n");
+ }
+ sys->print("read: %r\n");
+ out <-= nil;
+}
+
+# IP and ICMP packet header
+Ovihl: con 0;
+Otos: con 1;
+Olength: con 2;
+Oid: con Olength+2;
+Ofrag: con Oid+2;
+Ottl: con Ofrag+2;
+Oproto: con Ottl+1;
+Oipcksum: con Oproto+1;
+Osrc: con Oipcksum+2;
+Odst: con Osrc+4;
+Otype: con Odst+4;
+Ocode: con Otype+1;
+Ocksum: con Ocode+1;
+Oicmpid: con Ocksum+2;
+Oseq: con Oicmpid+2;
+Odata: con Oseq+2;
+
+Icmp.unpack(b: array of byte): ref Icmp
+{
+ if(len b < Odata)
+ return nil;
+ ic := ref Icmp;
+ ic.ttl = int b[Ottl];
+ ic.src = IPaddr.newv4(b[Osrc:]);
+ ic.dst = IPaddr.newv4(b[Odst:]);
+ ic.ptype = int b[Otype];
+ ic.code = int b[Ocode];
+ ic.seq = ip->get2(b, Oseq);
+ ic.id = ip->get2(b, Oicmpid);
+ ic.munged = 0;
+ if(len b > Odata)
+ ic.data = b[Odata:];
+ return ic;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}