ref: ec35f468e0eba87c9f09cbbe5fa8af2591e6f914
dir: /appl/cmd/avr/burn.b/
implement Burn; include "sys.m"; sys: Sys; include "draw.m"; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "timers.m"; timers: Timers; Timer: import timers; include "string.m"; str: String; include "arg.m"; Burn: module { init: fn(nil: ref Draw->Context, nil: list of string); }; Avr: adt { id: int; rev: int; flashsize: int; eepromsize: int; fusebytes: int; lockbytes: int; serfprog: int; # serial fuse programming support serlprog: int; # serial lockbit programming support serflread: int; # serial fuse/lockbit reading support commonlfr: int; # lockbits and fuses are combined sermemprog: int; # serial memory programming support pagesize: int; eeprompagesize: int; selftimed: int; # all instructions are self-timed fullpar: int; # part has full parallel interface polling: int; # polling can be used during SPI access fpoll: int; # flash poll value epoll1: int; # eeprom poll value 1 epoll2: int; # eeprom poll value 2 name: string; signalpagel: int; # posn of PAGEL signal (16rD7 by default) signalbs2: int; # posn of BS2 signal (16rA0 by default) }; F, T: con iota; ATMEGA128: con 16rB2; # 128k devices avrs: array of Avr = array[] of { (ATMEGA128, 1, 131072, 4096, 3, 1, T, T, T, F, T, 256, 8, T, T, T, 16rFF, 16rFF, 16rFF, "ATmega128", 16rD7, 16rA0), }; sfd: ref Sys->FD; cfd: ref Sys->FD; rd: ref Rd; mib510 := 1; Rd: adt { c: chan of array of byte; pid: int; fd: ref Sys->FD; buf: array of byte; new: fn(fd: ref Sys->FD): ref Rd; read: fn(r: self ref Rd, ms: int): array of byte; readn: fn(r: self ref Rd, n: int, ms: int): array of byte; flush: fn(r: self ref Rd); stop: fn(r: self ref Rd); reader: fn(r: self ref Rd, c: chan of int); }; debug := 0; verify := 0; erase := 1; ignore := 0; init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; bufio = ckl(load Bufio Bufio->PATH, Bufio->PATH); str = ckl(load String String->PATH, String->PATH); timers = ckl(load Timers Timers->PATH, Timers->PATH); serial := "/dev/eia0"; fuseext := -1; fuselow := -1; fusehigh := -1; arg := ckl(load Arg Arg->PATH, Arg->PATH); arg->init(args); arg->setusage("burn [-rD] [-d serialdev] file.out"); while((o := arg->opt()) != 0) case o { 'D' => debug++; 'e' => erase = 0; 'r' => verify = 1; 'd' => serial = arg->earg(); 'i' => ignore = 1; 'E' => fuseext = fuseval(arg->earg()); 'L' => fuselow = fuseval(arg->earg()); 'H' => fusehigh = fuseval(arg->earg()); * => arg->usage(); } args = arg->argv(); if(len args != 1) arg->usage(); arg = nil; sfile := hd args; fd := bufio->open(sfile, Sys->OREAD); if(fd == nil) err(sys->sprint("can't open %s: %r", sfile)); timers->init(2); sfd = sys->open(serial, Sys->ORDWR); if(sfd == nil) err(sys->sprint("can't open %s: %r", "/dev/eia0")); cfd = sys->open(serial+"ctl", Sys->ORDWR); sys->fprint(cfd, "f"); sys->fprint(cfd, "b115200"); sys->fprint(cfd, "i8"); # sys->fprint(cfd, "f\nb115200\ni8"); rd = Rd.new(sfd); initialise(); if(fuseext >= 0 || fuselow >= 0 || fusehigh >= 0){ if(fuselow >= 0 && (fuselow & 16rF) == 0) err("don't program external clock"); if(fuseext >= 0 && (fuseext & (1<<0)) == 0) err("don't program ATmega103 compatibility"); if(fusehigh >= 0 && (fusehigh & (1<<7)) == 0) err("don't program OCDEN=0"); if(fusehigh >= 0 && writefusehigh(fusehigh) >= 0) sys->print("set fuse high=%.2ux\n", fusehigh); if(fuselow >= 0 && writefuselow(fuselow) >= 0) sys->print("set fuse low=%.2ux\n", fuselow); if(fuseext >= 0 && writefuseext(fuseext) >= 0) sys->print("set fuse ext=%.2ux\n", fuseext); shutdown(); exit; } if(!verify && erase){ chiperase(); sys->print("Erased flash\n"); } totbytes := 0; while((l := fd.gets('\n')) != nil){ (c, addr, data) := sdecode(l); if(c >= '1' && c <= '3'){ if(verify){ fdata := readflashdata(addr, len data); if(!eq(fdata, data)) sys->print("mismatch: %d::%d at %4.4ux\n", len data, len fdata, addr); }else if(writeflashdata(addr, data) != len data) err("failed to program device"); totbytes += len data; } else if(c == '0') sys->print("title: %q\n", string data); } if(!verify){ flushpage(); sys->print("Programmed %ud (0x%4.4ux) bytes\n", totbytes, totbytes); } shutdown(); } ckl[T](m: T, s: string): T { if(m == nil) err(sys->sprint("can't load %s: %r", s)); return m; } fuseval(s: string): int { (n, t) := str->toint(s, 16); if(t != nil || n < 0 || n > 255) err("illegal fuse value"); return n; } cache: (int, array of byte); readflashdata(addr: int, nbytes: int): array of byte { data := array[nbytes] of byte; ia := addr; ea := addr+nbytes; while(addr < ea){ (ca, cd) := cache; if(addr >= ca && addr < ca+len cd){ n := nbytes; o := addr-ca; if(o+n > len cd) n = len cd - o; if(addr-ia+n > len data) n = len data - (addr-ia); data[addr-ia:] = cd[o:o+n]; addr += n; }else{ ca = addr & ~16rFF; cd = readflashpage(ca, 16r100); cache = (ca, cd); } } return data; } writeflashdata(addr: int, data: array of byte): int { pagesize := avrs[0].pagesize; ia := addr; ea := addr+len data; while(addr < ea){ (ca, cd) := cache; if(addr >= ca && addr < ca+len cd){ n := len data; o := addr-ca; if(o+n > len cd) n = len cd - o; cd[o:] = data[0:n]; addr += n; data = data[n:]; }else{ if(flushpage() < 0) break; cache = (addr & ~16rFF, array[pagesize] of {* => byte 16rFF}); } } return addr-ia; } flushpage(): int { (ca, cd) := cache; if(len cd == 0) return 0; cache = (0, nil); if(writeflashpage(ca, cd) != len cd) return -1; return len cd; } shutdown() { # setisp(0); if(rd != nil){ rd.stop(); rd = nil; } if(timers != nil) timers->shutdown(); } err(s: string) { sys->fprint(sys->fildes(2), "burn: %s\n", s); shutdown(); raise "fail:error"; } dump(a: array of byte): string { s := sys->sprint("[%d]", len a); for(i := 0; i < len a; i++) s += sys->sprint(" %.2ux", int a[i]); return s; } initialise() { if(mib510){ # MIB510-specific: switch rs232 to STK500 for(i:=0; i<8; i++){ setisp0(1); sys->sleep(10); rd.flush(); if(setisp(1)) break; } if(!setisp(1)) err("no response from programmer"); } resync(); resync(); if(!mib510){ r := rpc(array[] of {Cmd_STK_GET_SIGN_ON}, 7); if(r != nil) sys->print("got: %q\n", string r); } r := readsig(); if(len r > 0 && r[0] != byte 16rFF) sys->print("sig: %s\n", dump(r)); (min, maj) := version(); sys->print("Firmware version: %s.%s\n", min, maj); setdevice(avrs[0]); pgmon(); r = readsig(); sys->print("sig: %s\n", dump(r)); pgmoff(); if(len r < 3 || r[0] != byte 16r1e || r[1] != byte 16r97 || r[2] != byte 16r02) if(!ignore) err("unlikely response: check connections"); # could set voltages here... sys->print("fuses: h=%.2ux l=%.2ux e=%.2ux\n", readfusehigh(), readfuselow(), readfuseext()); } resync() { for(i := 0; i < 8; i++){ rd.flush(); r := rpc(array[] of {Cmd_STK_GET_SYNC}, 0); if(r != nil) return; } err("lost sync with programmer"); } getparam(p: byte): int { r := rpc(array[] of {Cmd_STK_GET_PARAMETER, p}, 1); if(len r > 0) return int r[0]; return -1; } version(): (string, string) { maj := getparam(Parm_STK_SW_MAJOR); min := getparam(Parm_STK_SW_MINOR); if(mib510) return (sys->sprint("%c", maj), sys->sprint("%c", min)); return (sys->sprint("%d", maj), sys->sprint("%d", min)); } eq(a, b: array of byte): int { if(len a != len b) return 0; for(i := 0; i < len a; i++) if(a[i] != b[i]) return 0; return 1; } # # Motorola S records # badsrec(s: string) { err("bad S record: "+s); } hexc(c: int): int { if(c >= '0' && c <= '9') return c-'0'; if(c >= 'a' && c <= 'f') return c-'a'+10; if(c >= 'A' && c <= 'F') return c-'A'+10; return -1; } g8(s: string): int { if(len s >= 2){ c0 := hexc(s[0]); c1 := hexc(s[1]); if(c0 >= 0 && c1 >= 0) return (c0<<4) | c1; } return -1; } # S d len sdecode(s: string): (int, int, array of byte) { while(len s > 0 && (s[len s-1] == '\r' || s[len s-1] == '\n')) s = s[0:len s-1]; if(len s < 4 || s[0] != 'S') badsrec(s); l := g8(s[2:4]); if(l < 0) badsrec("length: "+s); if(2*l != len s - 4) badsrec("length: "+s); csum := l; na := 2; if(s[1] >= '1' && s[1] <= '3') na = s[1]-'1'+2; addr := 0; for(i:=0; i<na; i++){ b := g8(s[4+i*2:]); if(b < 0) badsrec(s); csum += b; addr = (addr << 8) | b; } case s[1] { '0' or # used as segment name (seems to be srec file name with TinyOS) '1' to '3' or # data '5' or # plot so far '7' to '9' => # end/start address ; * => badsrec("type: "+s); } data := array[l-na-1] of byte; for(i = 0; i < len data; i++){ c := g8(s[4+(na+i)*2:]); csum += c; data[i] = byte c; } v := g8(s[4+l*2-2:]); csum += v; if((csum & 16rFF) != 16rFF) badsrec("checksum: "+s); return (s[1], addr, data); } # # serial port # Rd.new(fd: ref Sys->FD): ref Rd { r := ref Rd(chan[4] of array of byte, 0, fd, nil); c := chan of int; spawn r.reader(c); <-c; return r; } Rd.reader(r: self ref Rd, c: chan of int) { r.pid = sys->pctl(0, nil); c <-= 1; for(;;){ buf := array[258] of byte; n := sys->read(r.fd, buf, len buf); if(n <= 0){ r.pid = 0; err(sys->sprint("read error: %r")); } if(debug) sys->print("<- %s\n", dump(buf[0:n])); r.c <-= buf[0:n]; } } Rd.read(r: self ref Rd, ms: int): array of byte { if((a := r.buf) != nil){ r.buf = nil; return a; } t := Timer.start(ms); alt{ a = <-r.c => t.stop(); Acc: for(;;){ sys->sleep(5); alt{ b := <-r.c => if(b == nil) break Acc; a = cat(a, b); * => break Acc; } } return a; <-t.timeout => return nil; } } Rd.readn(r: self ref Rd, n: int, ms: int): array of byte { a: array of byte; while((need := n - len a) > 0){ b := r.read(ms); if(b == nil) break; if(len b > need){ r.buf = b[need:]; b = b[0:need]; } a = cat(a, b); } return a; } Rd.flush(r: self ref Rd) { r.buf = nil; sys->sleep(5); for(;;){ alt{ <-r.c => ; * => return; } } } Rd.stop(r: self ref Rd) { pid := r.pid; if(pid){ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); } } cat(a, b: array of byte): array of byte { if(len b == 0) return a; if(len a == 0) return b; c := array[len a + len b] of byte; c[0:] = a; c[len a:] = b; return c; } # # STK500 communication protocol # STK_SIGN_ON_MESSAGE: con "AVR STK"; # Sign on string for Cmd_STK_GET_SIGN_ON # Responses Resp_STK_OK: con byte 16r10; Resp_STK_FAILED: con byte 16r11; Resp_STK_UNKNOWN: con byte 16r12; Resp_STK_NODEVICE: con byte 16r13; Resp_STK_INSYNC: con byte 16r14; Resp_STK_NOSYNC: con byte 16r15; Resp_ADC_CHANNEL_ERROR: con byte 16r16; Resp_ADC_MEASURE_OK: con byte 16r17; Resp_PWM_CHANNEL_ERROR: con byte 16r18; Resp_PWM_ADJUST_OK: con byte 16r19; # Special constants Sync_CRC_EOP: con byte 16r20; # Commands Cmd_STK_GET_SYNC: con byte 16r30; Cmd_STK_GET_SIGN_ON: con byte 16r31; Cmd_STK_SET_PARAMETER: con byte 16r40; Cmd_STK_GET_PARAMETER: con byte 16r41; Cmd_STK_SET_DEVICE: con byte 16r42; Cmd_STK_SET_DEVICE_EXT: con byte 16r45; Cmd_STK_ENTER_PROGMODE: con byte 16r50; Cmd_STK_LEAVE_PROGMODE: con byte 16r51; Cmd_STK_CHIP_ERASE: con byte 16r52; Cmd_STK_CHECK_AUTOINC: con byte 16r53; Cmd_STK_LOAD_ADDRESS: con byte 16r55; Cmd_STK_UNIVERSAL: con byte 16r56; Cmd_STK_UNIVERSAL_MULTI: con byte 16r57; Cmd_STK_PROG_FLASH: con byte 16r60; Cmd_STK_PROG_DATA: con byte 16r61; Cmd_STK_PROG_FUSE: con byte 16r62; Cmd_STK_PROG_LOCK: con byte 16r63; Cmd_STK_PROG_PAGE: con byte 16r64; Cmd_STK_PROG_FUSE_EXT: con byte 16r65; Cmd_STK_READ_FLASH: con byte 16r70; Cmd_STK_READ_DATA: con byte 16r71; Cmd_STK_READ_FUSE: con byte 16r72; Cmd_STK_READ_LOCK: con byte 16r73; Cmd_STK_READ_PAGE: con byte 16r74; Cmd_STK_READ_SIGN: con byte 16r75; Cmd_STK_READ_OSCCAL: con byte 16r76; Cmd_STK_READ_FUSE_EXT: con byte 16r77; Cmd_STK_READ_OSCCAL_EXT: con byte 16r78; # Parameter constants Parm_STK_HW_VER: con byte 16r80; # ' ' - R Parm_STK_SW_MAJOR: con byte 16r81; # ' ' - R Parm_STK_SW_MINOR: con byte 16r82; # ' ' - R Parm_STK_LEDS: con byte 16r83; # ' ' - R/W Parm_STK_VTARGET: con byte 16r84; # ' ' - R/W Parm_STK_VADJUST: con byte 16r85; # ' ' - R/W Parm_STK_OSC_PSCALE: con byte 16r86; # ' ' - R/W Parm_STK_OSC_CMATCH: con byte 16r87; # ' ' - R/W Parm_STK_RESET_DURATION: con byte 16r88; # ' ' - R/W Parm_STK_SCK_DURATION: con byte 16r89; # ' ' - R/W Parm_STK_BUFSIZEL: con byte 16r90; # ' ' - R/W, Range {0..255} Parm_STK_BUFSIZEH: con byte 16r91; # ' ' - R/W, Range {0..255} Parm_STK_DEVICE: con byte 16r92; # ' ' - R/W, Range {0..255} Parm_STK_PROGMODE: con byte 16r93; # ' ' - 'P' or 'S' Parm_STK_PARAMODE: con byte 16r94; # ' ' - TRUE or FALSE Parm_STK_POLLING: con byte 16r95; # ' ' - TRUE or FALSE Parm_STK_SELFTIMED: con byte 16r96; # ' ' - TRUE or FALSE # status bits Stat_STK_INSYNC: con byte 16r01; # INSYNC status bit, '1' - INSYNC Stat_STK_PROGMODE: con byte 16r02; # Programming mode, '1' - PROGMODE Stat_STK_STANDALONE: con byte 16r04; # Standalone mode, '1' - SM mode Stat_STK_RESET: con byte 16r08; # RESET button, '1' - Pushed Stat_STK_PROGRAM: con byte 16r10; # Program button, ' 1' - Pushed Stat_STK_LEDG: con byte 16r20; # Green LED status, '1' - Lit Stat_STK_LEDR: con byte 16r40; # Red LED status, '1' - Lit Stat_STK_LEDBLINK: con byte 16r80; # LED blink ON/OFF, '1' - Blink ispmode := array[] of {byte 16rAA, byte 16r55, byte 16r55, byte 16rAA, byte 16r17, byte 16r51, byte 16r31, byte 16r13, byte 0}; # last byte is 1 to switch isp on 0 to switch off ck(r: array of byte) { if(r == nil) err("programming failed"); } pgmon() { ck(rpc(array[] of {Cmd_STK_ENTER_PROGMODE}, 0)); } pgmoff() { ck(rpc(array[] of {Cmd_STK_LEAVE_PROGMODE}, 0)); } setisp0(on: int) { rd.flush(); buf := array[len ispmode] of byte; buf[0:] = ispmode; buf[8] = byte on; sys->write(sfd, buf, len buf); } setisp(on: int): int { rd.flush(); buf := array[len ispmode] of byte; buf[0:] = ispmode; buf[8] = byte on; r := send(buf, 2); return len r == 2 && ok(r); } readsig(): array of byte { r := send(array[] of {Cmd_STK_READ_SIGN, Sync_CRC_EOP}, 5); # doesn't behave as documented in AVR061: it repeats the command bytes instead if(len r != 5 || r[0] != Cmd_STK_READ_SIGN || r[4] != Sync_CRC_EOP){ sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); return nil; } return r[1:len r-1]; # trim proto bytes } pgrpc(a: array of byte, repn: int): array of byte { pgmon(); r := rpc(a, repn); pgmoff(); return r; } eop := array[] of {Sync_CRC_EOP}; rpc(a: array of byte, repn: int): array of byte { r := send(cat(a, eop), repn+2); if(!ok(r)){ if(len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r-1] == Resp_STK_NODEVICE) err("internal error: programming parameters not correctly set"); if(len r >= 1 && r[0] == Resp_STK_NOSYNC) err("lost synchronisation"); sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); return nil; } return r[1:len r-1]; # trim sync bytes } send(a: array of byte, repn: int): array of byte { if(debug) sys->print("-> %s\n", dump(a)); if(sys->write(sfd, a, len a) != len a) err(sys->sprint("write error: %r")); return rd.readn(repn, 2000); } ok(r: array of byte): int { return len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r -1] == Resp_STK_OK; } universal(req: array of byte): int { r := pgrpc(cat(array[] of {Cmd_STK_UNIVERSAL}, req), 1); if(r == nil) return -1; return int r[0]; } setdevice(d: Avr) { b := array[] of { Cmd_STK_SET_DEVICE, byte d.id, byte d.rev, byte 0, # prog type (CHECK) byte d.fullpar, byte d.polling, byte d.selftimed, byte d.lockbytes, byte d.fusebytes, byte d.fpoll, byte d.fpoll, byte d.epoll1, byte d.epoll2, byte (d.pagesize >> 8), byte d.pagesize, byte (d.eepromsize>>8), byte d.eepromsize, byte (d.flashsize>>24), byte (d.flashsize>>16), byte (d.flashsize>>8), byte d.flashsize }; ck(rpc(b, 0)); if(mib510) return; b = array[] of { Cmd_STK_SET_DEVICE_EXT, byte 4, byte d.eeprompagesize, byte d.signalpagel, byte d.signalbs2, byte 0 # ResetDisable }; ck(rpc(b, 0)); } chiperase() { ck(pgrpc(array[] of {Cmd_STK_CHIP_ERASE}, 0)); } readfuselow(): int { return universal(array[] of {byte 16r50, byte 0, byte 0, byte 0}); } readfusehigh(): int { return universal(array[] of {byte 16r58, byte 8, byte 0, byte 0}); } readfuseext(): int { return universal(array[] of {byte 16r50, byte 8, byte 0, byte 0}); } readlockfuse(): int { return universal(array[] of {byte 16r58, byte 0, byte 0, byte 0}); } readflashpage(addr: int, nb: int): array of byte { return readmem('F', addr/2, nb); } readeeprompage(addr: int, nb: int): array of byte { return readmem('E', addr, nb); } readmem(memtype: int, addr: int, nb: int): array of byte { if(nb > 256) nb = 256; pgmon(); r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); if(r != nil){ r = send(array[] of {Cmd_STK_READ_PAGE, byte (nb>>8), byte nb, byte memtype, Sync_CRC_EOP}, nb+2); l := len r; # AVR601 says last byte should be Resp_STK_OK but it's not, at least on MIB; check for both if(l >= 2 && r[0] == Resp_STK_INSYNC && (r[l-1] == Resp_STK_INSYNC || r[l-1] == Resp_STK_OK)) r = r[1:l-1]; # trim framing bytes else{ sys->print("bad reply: %s\n", dump(r)); r = nil; } if(len r < nb) sys->print("short [%d@%4.4ux]\n", nb, addr); } pgmoff(); return r; } writeflashpage(addr: int, data: array of byte): int { return writemem('F', addr/2, data); } writeeeprompage(addr: int, data: array of byte): int { return writemem('E', addr, data); } writemem(memtype: int, addr: int, data: array of byte): int { nb := len data; if(nb > 256){ nb = 256; data = data[0:nb]; } pgmon(); r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); if(r != nil){ r = rpc(cat(array[] of {Cmd_STK_PROG_PAGE, byte (nb>>8), byte nb, byte memtype},data), 0); if(r == nil) nb = -1; } pgmoff(); return nb; } writefuseext(v: int): int { return universal(array[] of {byte 16rAC, byte 16rA4, byte 16rFF, byte v}); } writefuselow(v: int): int { return universal(array[] of {byte 16rAC, byte 16rA0, byte 16rFF, byte v}); } writefusehigh(v: int): int { return universal(array[] of {byte 16rAC, byte 16rA8, byte 16rFF, byte v}); }