From 21d93ec8cb3adefcc5704a3632b8eed906cc25f7 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Mon, 7 Nov 2022 01:26:47 +0000 Subject: [PATCH] Update urclock programmer --- src/avrdude.1 | 115 ++++++++++++++- src/config.c | 8 +- src/urclock.c | 381 +++++++++++++++++++++++++++++--------------------- 3 files changed, 334 insertions(+), 170 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index a2225a1a..e6f356b0 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -147,9 +147,17 @@ programming mode. The programmer type is ``wiring''. Note that the -D option will likely be required in this case, because the bootloader will rewrite the program memory, but no true chip erase can be performed. .Pp -The Arduino (which is very similar to the STK500 1.x) is supported via -its own programmer type specification ``arduino''. This programmer works for -the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader. +Serial bootloaders that run a skeleton of the STK500 1.x protocol are +supported via their own programmer type ``arduino''. This programmer works +for the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader. +.Pp +Urprotocol is a leaner version of the STK500 1.x protocol that is designed +to be backwards compatible with STK500 v1.x, and allows bootloaders to be +much smaller, eg, as implemented in the urboot project +https://github.com/stefanrueger/urboot. The programmer type ``urclock'' +caters for these urboot programmers. Owing to its backward compatibility, +any bootloader that can be served by the arduino programmer can normally +also be served by the urclock programmer. .Pp The BusPirate is a versatile tool that can also be used as an AVR programmer. A single BusPirate can be connected to up to 3 independent AVRs. See @@ -397,8 +405,8 @@ programming requires the memory be erased to 0xFF beforehand. .Fl A should be used when the programmer hardware, or bootloader software for that matter, does not carry out chip erase and -instead handles the memory erase on a page level. The popular -Arduino bootloader exhibits this behaviour; for this reason +instead handles the memory erase on a page level. Popular +Arduino bootloaders exhibit this behaviour; for this reason .Fl A is engaged by default when specifying . Fl c @@ -432,7 +440,7 @@ contents would exclusively cause bits to be programmed from the value .Ql 1 to .Ql 0 . -Note that in order to reprogram EERPOM cells, no explicit prior chip +Note that in order to reprogram EEPROM cells, no explicit prior chip erase is required since the MCU provides an auto-erase cycle in that case before programming the cell. .It Xo Fl E Ar exitspec Ns @@ -1141,6 +1149,101 @@ programmer creates errors during initial sequence. Specify how many connection retry attemps to perform before exiting. Defaults to 10 if not specified. .El +.It Ar Urclock +.Bl -tag -offset indent -width indent +.It Ar showall +Show all info for the connected part and exit. +.It Ar showid +Show a unique Urclock ID stored in either flash or EEPROM of the MCU and exit. +.It Ar id=.. +Historically, the Urclock ID was a six-byte unique little-endian number +stored in Urclock boards at EEPROM address 257. The location of this +number can be set by the -xid=.. extended parameter. E +stands for EEPROM and F stands for flash. A negative address addr counts +from the end of EEPROM and flash, respectively. The length len of the +Urclock ID can be between 1 and 8 bytes. +.It Ar showapp +Show the size of the programmed application and exit. +.It Ar showstore +Show the size of the unused flash between the application and metadata and exit. +.It Ar showmeta +Show the size of the metadata just below the bootloader and exit. +.It Ar showboot +Show the size of the bootloader and exit. +.It Ar showversion +Show bootloader version and capabilities, and exit. +.It Ar showvbl +Show the vector number and name of the interrupt table vector used by the +bootloader for starting the application, and exit. For hardware-supported +bootloaders this will be vector 0 (Reset), and for vector bootloaders this +will be any other vector number of the interrupt vector table or the slot +just behind the vector table with the name VBL_ADDITIONAL_VECTOR. +.It Ar showdate +Show the last-modified date of the input file for the flash application +and exit. If the input file was stdin, the date will be that of the +programming. +.It Ar showfilename +Show the input filename (or title) of the last flash writing session, and exit. +.It Ar bootsize= +Manual override for bootloader size. Urboot bootloaders put the number of used +bootloader pages into a table at the top of flash, so the urclock programmer can +look up the bootloader size itself. In backward-compatibility mode, when programming +via other bootloaders, this option can be used to tell the programmer the +size, and therefore the location, of the bootloader. +.It Ar vectornum= +Manual override for vector number. Urboot bootloaders put the vector +number used by a vector bootloader into a table at the top of flash, so +this option is normally not needed for urboot bootloaders. However, it is +useful in backward-compatibility mode (or when the urboot bootloader does +not offer flash read). Specifying a vector number in these circumstances +implies a vector bootloader whilst the default assumption would be a +hardware-supported bootloader. +.It Ar eepromrw +Manual override for asserting EEPROM read/write capability. Not normally +needed for urboot bootloaders, but useful for in backward-compatibility +mode if the bootloader offers EEPROM read/write. +.It Ar emulate_ce +If an urboot bootloader does not offer a chip erase command it will tell +the urclock programmer so during handshake. In this case the urclock +programmer emulates a chip erase, if warranted by user command line +options, by filling the remainder of unused flash below the bootloader +with 0xff. If this option is specified, the urclock programmer will assume +that the bootloader cannot erase the chip itself. The option is useful +for backwards-compatible bootloaders that do not implement chip erase. +.It Ar forcetrim +Upload unchanged flash input files and trim below the bootloader if +needed. This is most useful when one has a backup of the full flash and +wants to play that back onto the device. No metadata are written in this +case and no vector patching happens either if it is a vector bootloader. +However, for vector bootloaders, even under the option -xforcetrim an +input file will not be uploaded for which the reset vector does not point +to the vector bootloader. This is to avoid writing an input file to the +device that would render the vector bootloader not functional as it would +not be reached after reset. +.It Ar initstore +On writing to flash fill the store space between the flash application and +the metadata section with 0xff. +.It Ar title= +When set, will be used in lieu of the input filename. The maximum +string length for the title/filename field is 254 bytes including +terminating nul. +.It Ar nofilename +On writing to flash do not store the application input filename (nor a title). +.It Ar nodate +On writing to flash do not store the application input filename (nor a +title) and no date either. +.It Ar nometadata +On writing to flash do not store any metadata. The full flash below the +bootloader is available for the application. In particular, no data store +frame is programmed. +.It Ar delay= +Add a ms delay after reset. This can be useful if a board takes a +particularly long time to exit from external reset. can be negative, +in which case the default 80 ms delay after issuing reset will be +shortened accordingly. +.It Ar help +Show this help menu and exit +.El .It Ar buspirate .Bl -tag -offset indent -width indent .It Ar reset={cs,aux,aux2} diff --git a/src/config.c b/src/config.c index efb33a5a..a0cf35cf 100644 --- a/src/config.c +++ b/src/config.c @@ -704,7 +704,7 @@ char *cfg_escape(const char *s) { char buf[50*1024], *d = buf; *d++ = '"'; - for(; *s && d-buf < sizeof buf-7; s++) { + for(; *s && d-buf < (long) sizeof buf-7; s++) { switch(*s) { case '\n': *d++ = '\\'; *d++ = 'n'; @@ -855,7 +855,7 @@ void cfg_update_mcuid(AVRPART *part) { return; // Find an entry that shares the same name, overwrite mcuid with known, existing mcuid - for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++) { + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) { if(strcasecmp(part->desc, uP_table[i].name) == 0) { if(part->mcuid != (int) uP_table[i].mcuid) { if(part->mcuid >= 0 && verbose >= MSG_DEBUG) @@ -867,7 +867,7 @@ void cfg_update_mcuid(AVRPART *part) { } // None have the same name: an entry with part->mcuid might be an error - for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++) + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) if(part->mcuid == (int) uP_table[i].mcuid) { // Complain unless it can be considered a variant, eg, ATmega32L and ATmega32 AVRMEM *flash = avr_locate_mem(part, "flash"); @@ -876,7 +876,7 @@ void cfg_update_mcuid(AVRPART *part) { if(strncasecmp(part->desc, uP_table[i].name, l1 < l2? l1: l2) || flash->size != uP_table[i].flashsize || flash->page_size != uP_table[i].pagesize || - part->n_interrupts != uP_table[i].ninterrupts) + part->n_interrupts != (int8_t) uP_table[i].ninterrupts) yywarning("mcuid %d is reserved for %s, use a free number >= %d", part->mcuid, uP_table[i].name, sizeof uP_table/sizeof *uP_table); } diff --git a/src/urclock.c b/src/urclock.c index 6b8cf2bb..9eafd5d6 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -223,8 +223,9 @@ #define min(a, b) ((a) < (b)? (a): (b)) static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p); -static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t addr, int len, char memtype); -static int readUrclockID(const PROGRAMMER *pgm); +static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t addr, int len, + char memchr); +static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *idp); static int urclock_send(const PROGRAMMER *pgm, unsigned char *buf, size_t len); static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len); static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res); @@ -260,7 +261,8 @@ typedef struct { bloptiversion; // Optiboot version as (major<<8) + minor int32_t blstart; // Bootloader start address, eg, for bootloader write protection - uint64_t urclockID; // Urclock ID read from flash or EEPROM as little endian + int idmchr; // Either 'E' or 'F' for the memory where the Urclock ID is located + int idaddr; // The address of the Urclock ID int idlen; // Number 1..8 of Urclock ID bytes (location, see iddesc below) int32_t storestart; // Store (ie, unused flash) start address, same as application size @@ -280,9 +282,9 @@ typedef struct { // Extended parameters for Urclock int showall, // Show all pieces of info for connected part and exit showid, // ... Urclock ID - showsketch, // ... application size + showapp, // ... application size showstore, // ... store size - showmetadata, // ... metadata size + showmeta, // ... metadata size showboot, // ... bootloader size showversion, // ... bootloader version and capabilities showvbl, // ... vector bootloader level, vector number and name @@ -539,11 +541,39 @@ static void set_date_filename(const PROGRAMMER *pgm, const char *fname) { } + +// Put destination address of reset vector jmp or rjmp into addr, return -1 if not an r/jmp +static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int *addrp) { + int op32, addr, rc = 0; + uint16_t op16; + + op16 = buf2uint16(opcode); // First word of the jmp or the full rjmp + op32 = vecsz == 2? op16: buf2uint32(opcode); + + if(vecsz == 4 && isJmp(op16)) { + addr = addr_jmp(op32); // Accept compiler's destination (do not normalise) + } else if(isRjmp(op16)) { // rjmp might be generated for larger parts, too + addr = dist_rjmp(op16, flashsize); + while(addr < 0) // If rjmp was backwards + addr += flashsize; // OK for small parts, likely(!) OK if flashsize is a power of 2 + while(addr > flashsize) // Sanity (should not happen): rjmp jumps over FLASHEND + addr -= flashsize; + } else + rc = -1; + + if(addrp && rc == 0) + *addrp = addr; + + return rc; +} + + // Called after the input file has been read for writing or verifying flash -static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused, - const AVRMEM *flm, const char *fname, int size) { +static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm, + const char *fname, int size) { int nmdata, maxsize, firstbeg, firstlen; + int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp set_date_filename(pgm, fname); @@ -597,7 +627,6 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnofilename", firstbeg, size-1, ur.blstart-nmdata, ur.blstart-1); - int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp bool llcode = firstbeg == 0 && firstlen > ur.uP.ninterrupts*vecsz; // Looks like code bool llvectors = firstbeg == 0 && firstlen >= ur.uP.ninterrupts*vecsz; // Looks like vector table for(int i=0; llvectors && i 0 && ur.vbllevel) { // From v7.5 patch all levels but for earlier and unknown versions only patch level 1 if(ur.blurversion >= 075 || ((ur.blurversion==0 || ur.blurversion >= 072) && ur.vbllevel==1)) { - int appvecloc, reset32; uint16_t reset16; + int reset32, appstart, appvecloc; - appvecloc = ur.vblvectornum*vecsz; // Location of jump-to-application in vector table - reset16 = buf2uint16(flm->buf); // First reset word of to-be-uploaded application + appvecloc = ur.vblvectornum*vecsz; // Location of jump-to-application in vector table + reset16 = buf2uint16(flm->buf); // First reset word of to-be-uploaded application reset32 = vecsz == 2? reset16: buf2uint32(flm->buf); /* @@ -631,17 +660,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused * an error thrown if so. */ - int appstart; - - if(vecsz == 4 && isJmp(reset16)) { - appstart = addr_jmp(reset32); // Accept compiler's destination for now (do not normalise) - } else if(isRjmp(reset16)) { // rjmp might be generated for larger parts, too - appstart = dist_rjmp(reset16, ur.uP.flashsize); - while(appstart < 0) // If rjmp was backwards - appstart += flm->size; // OK for small parts, likely OK if size is a power of 2 - while(appstart > flm->size) // Sanity (should not happen): rjmp jumps over FLASHEND - appstart -= flm->size; - } else { + if(reset2addr(flm->buf, vecsz, flm->size, &appstart) < 0) { pmsg_warning("not patching input as opcode word %04x at reset is not a%sjmp\n", reset16, vecsz==2? "n r": " "); goto nopatch; @@ -771,7 +790,7 @@ nopatch_nometa: if(isize < 1 || isize > (int) sizeof spc) // Should not happen Return("isize=%d out of range (enlarge spc[] and recompile)", isize); - if(ur_readEF(pgm, spc, istart, isize, 'F') == 0) { // @@@ + if(ur_readEF(pgm, p, spc, istart, isize, 'F') == 0) { for(ai = istart; ai < istart + isize; ai++) if(!(flm->tags[ai] & TAG_ALLOCATED)) flm->buf[ai] = spc[ai-istart]; @@ -785,6 +804,24 @@ nopatch_nometa: } ur.done_ce = 0; // From now on can no longer rely on being deleted + // Last, but not least: ensure that vector bootloaders have correct r/jmp at address 0 + if(ur.blstart && ur.vbllevel==1) { + int set=0; + for(int i=0; i < vecsz; i++) + if(flm->tags[i] & TAG_ALLOCATED) + set++; + + if(set && set != vecsz) + Return("input overwrites the reset vector partially rendering vector bootloader moot, exiting"); + + if(set) { + int resetdest; + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) + Return("input does not hold an r/jmp at reset vector rendering vector bootloader moot, exiting"); + if(resetdest != ur.blstart) + Return("input file points reset to 0x%04x instead of vector bootloader at 0x%04x, exiting", resetdest, ur.blstart); + } + } return size; } @@ -1061,7 +1098,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { ur.blstart = ur.uP.flashsize - ur.xbootsize; } - if(ur.uP.ninterrupts >= 0) + if((int8_t) ur.uP.ninterrupts >= 0) // valid range is 0..127 if(ur.xvectornum < -1 || ur.xvectornum > ur.uP.ninterrupts) Return("unknown interrupt vector #%d for vector bootloader -- should be in [-1, %d]", ur.xvectornum, ur.uP.ninterrupts); @@ -1079,7 +1116,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { // Sporting chance that we can read top flash to get intell about bootloader if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Read top 6 bytes from flash memory to obtain extended information about bootloader and type - if((rc = ur_readEF(pgm, spc, flm->size-6, 6, 'F'))) + if((rc = ur_readEF(pgm, p, spc, flm->size-6, 6, 'F'))) return rc; // In a urboot bootloader (v7.2 onwards) these six are as follows @@ -1134,7 +1171,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Reset vector points to the bootloader and the bootloader has r/jmp to application? - if((rc = ur_readEF(pgm, spc, 0, 4, 'F'))) + if((rc = ur_readEF(pgm, p, spc, 0, 4, 'F'))) return rc; uint16_t reset16 = buf2uint16(spc); @@ -1161,7 +1198,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { if(ur.blstart) { // Read bootloader to identify jump to vbl vector int i, npages, j, n, toend, dist, wasop32, wasjmp, op16; - uint8_t *p; + uint8_t *q; uint16_t opcode; op16 = wasjmp = wasop32 = 0; @@ -1169,10 +1206,10 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { npages = toend/flm->page_size; for(i=0; ipage_size, flm->page_size, 'F'))) + if((rc = ur_readEF(pgm, p, spc, ur.blstart+i*flm->page_size, flm->page_size, 'F'))) return rc; - for(n=flm->page_size/2, p=spc, j=0; jpage_size/2, q=spc, j=0; jsize-ur.blstart? 'o': ur.uP.bootsize > flm->size-ur.blstart? 'O': '-'); - if((rc = readUrclockID(pgm)) == -1) - return rc; - ur.mcode = 0xff; if(ur.blstart) { int nm = nmeta(1, ur.uP.flashsize); // 6 for date + size of store struct + 1 for mcode byte - // If want to show properties, examine the bytes below bootloader for metadata - if(ur.showall || ur.showid || ur.showsketch || ur.showstore || ur.showmetadata || - ur.showboot || ur.showversion || ur.showvbl || ur.showdate || ur.showfilename) { + // Showing properties mostly requires examining the bytes below bootloader for metadata + if(ur.showall || (ur.showid && *ur.iddesc && *ur.iddesc != 'E') || ur.showapp || + ur.showstore || ur.showmeta || ur.showboot || ur.showversion || ur.showvbl || + ur.showdate || ur.showfilename) { - if((rc = ur_readEF(pgm, spc, ur.blstart-nm, nm, 'F'))) + if((rc = ur_readEF(pgm, p, spc, ur.blstart-nm, nm, 'F'))) return rc; if(spc[nm-1] != 0xff) { @@ -1260,7 +1295,7 @@ vblvecfound: ur.hr = hr; ur.mn = mn; if(mcode > 1) { // Copy application name over - rc = ur_readEF(pgm, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F'); + rc = ur_readEF(pgm, p, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F'); if(rc < 0) return rc; int len = mcodesize-ur.blstart: 0), first=0; if(ur.showversion || ur.showall) term_out(" %s"+first, ur.desc+(*ur.desc==' ')), first=0; - if(ur.showvbl || (ur.showall && ur.vbllevel)) - term_out(" vector %d (%s)", ur.vblvectornum, vblvecname(pgm, ur.vblvectornum)), first=0; + if(ur.showvbl || ur.showall) { + int vnum = ur.vbllevel? ur.vblvectornum & 0x7f: 0; + term_out(" vector %d (%s)"+first, vnum, vblvecname(pgm, vnum)), first=0; + } if(ur.showall) term_out(" %s"+first, ur.uP.name); if(!first) { @@ -1314,16 +1355,21 @@ alldone: // STK500 section from stk500.c but modified significantly for use with urboot bootloaders -// STK500v1 load *word* address for flash/eeprom, memtype is 'E'/'F' -static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int waddr) { - unsigned char buf[16]; - unsigned char ext_byte; +// STK500v1 load correct address for flash/eeprom, memchr is 'E'/'F' +static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memchr, + unsigned int baddr) { - // STK500 protocol: support flash > 64K words with the correct extended-address byte - if(memtype == 'F' && ur.uP.flashsize > 128*1024) { - ext_byte = (waddr >> 16) & 0xff; + unsigned char buf[16], ext_byte; + + // For classic parts (think optiboot, avrisp) use word addr, otherwise byte addr (optiboot_x etc) + int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire)); + unsigned int addr = classic? baddr/2: baddr; + + // STK500 protocol: support flash > 64k words/bytes with the correct extended-address byte + if(memchr == 'F' && ur.uP.flashsize > (classic? 128*1024: 64*1024)) { + ext_byte = (addr >> 16) & 0xff; if(ext_byte != ur.ext_addr_byte) { - // Either this is the first addr load, or a 64K word boundary is crossed + // Either this is the first addr load, or a 64k boundary is crossed buf[0] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>24); buf[1] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>16); buf[2] = ext_byte; @@ -1334,8 +1380,8 @@ static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int } buf[0] = Cmnd_STK_LOAD_ADDRESS; - buf[1] = waddr & 0xff; - buf[2] = (waddr >> 8) & 0xff; + buf[1] = addr & 0xff; + buf[2] = (addr >> 8) & 0xff; buf[3] = Sync_CRC_EOP; if(urclock_send(pgm, buf, 4) < 0) @@ -1352,21 +1398,21 @@ static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int * - mchr is 'F' (flash) or 'E' (EEPROM) * - payload for bytes to write or NULL for read */ -static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int badd, - int len, char mchr, char *payload) { +static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char rwop, + unsigned int badd, int len, char mchr, char *payload) { int i; uint8_t buf[1024 + 5]; - // STK500v1 only: tell the bootloader which word address should be used by next paged command - if(!ur.urprotocol && urclock_load_waddr(pgm, mchr, badd/2) < 0) + // STK500v1 only: tell the bootloader which address should be used by next paged command + if(!ur.urprotocol && urclock_load_baddr(pgm, part, mchr, badd) < 0) return -1; if(mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE && len != ur.uP.pagesize) Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); if(ur.urprotocol) { - uint8_t *p = buf, op = + uint8_t *q = buf, op = mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_FL: mchr == 'E' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_EE: mchr == 'F' && rwop == Cmnd_STK_READ_PAGE? Cmnd_UR_READ_PAGE_FL: @@ -1375,25 +1421,25 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int bad if(op == 0xff) Return("command not recognised"); - *p++ = op; - *p++ = badd & 0xff; - *p++ = (badd >> 8) & 0xff; + *q++ = op; + *q++ = badd & 0xff; + *q++ = (badd >> 8) & 0xff; // Flash is larger than 64 kBytes, extend address (even for EEPROM) if(ur.uP.flashsize > 0x10000) - *p++ = (badd >> 16) & 0xff; + *q++ = (badd >> 16) & 0xff; if(ur.uP.pagesize <= 256) { if(len > 256) Return("urprotocol paged r/w len %d cannot exceed 256", len); - *p++ = len; // len==256 is sent as 0 + *q++ = len; // len==256 is sent as 0 } else { int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; if(len > max) Return("urprotocol paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name); - *p++ = len>>8; // Big endian length when needed - *p++ = len; + *q++ = len>>8; // Big endian length when needed + *q++ = len; } - i = p-buf; + i = q-buf; } else { int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; @@ -1424,9 +1470,13 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int bad * Read len bytes at byte address addr of EEPROM (mchr == 'E') or flash (mchr == 'F') from * device fd into buffer buf+1, using extended addressing if needed (extd); returns 0 on success */ -static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len, char mchr) { - pmsg_debug("ur_readEF(%s, %s, %p, 0x%06x, %d, %c)\n", - pgm? pgm->desc: "?", mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); +static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t badd, int len, + char mchr) { + + int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire)); + + pmsg_debug("ur_readEF(%s, %s, %s, %p, 0x%06x, %d, %c)\n", + pgm? ldata(lfirst(pgm->id)): "?", p->desc, mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); if(mchr == 'F' && ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) Return("bootloader does not have flash read capability"); @@ -1437,8 +1487,8 @@ static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len if(len < 1 || len > max(ur.uP.pagesize, 256)) Return("len %d exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); - // Odd byte address under STK500v1 word-address protocol - int odd = !ur.urprotocol && (badd&1); + // Odd byte address under word-address protocol for "classic" parts (optiboot, avrisp etc) + int odd = !ur.urprotocol && classic && (badd&1); if(odd) { // Need to read one extra byte len++; badd &= ~1; @@ -1446,91 +1496,111 @@ static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len Return("len+1 = %d odd address exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); } - if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0) return -1; return urclock_res_check(pgm, __func__, odd, buf, len-odd); } -static int readUrclockID(const PROGRAMMER *pgm) { +static int parseUrclockID(const PROGRAMMER *pgm) { + if(*ur.iddesc) { // User override of ID, eg, -xid=F.-4.2 for penultimate flash word + char *idstr = cfg_strdup(__func__, ur.iddesc), *idlenp, *end; + unsigned long ad, lg; + + if(!(strchr("EF", *idstr) && idstr[1] == '.')) { + pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc); + free(idstr); + return -1; + } + + if(!(idlenp = strchr(idstr+2, '.'))) { + pmsg_warning("-xid=%s string must look like [E|F]..\n", ur.iddesc); + free(idstr); + return -1; + } + *idlenp++ = 0; + ad = strtoul(idstr+2, &end, 0); + if(*end || end == idstr+2) { + pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc); + free(idstr); + return -1; + } + long sad = *(long *) &ad; + if(sad < INT_MIN || sad > INT_MAX) { + pmsg_warning("address %s of -xid=%s has implausible size\n", idstr+2, ur.iddesc); + free(idstr); + return -1; + } + + lg = strtoul(idlenp, &end, 0); + if(*end || end == idlenp) { + pmsg_warning("cannot parse length %s of -xid=%s string\n", idlenp, ur.iddesc); + free(idstr); + return -1; + } + if(!lg || lg > 8) { + pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlenp, ur.iddesc); + free(idstr); + return -1; + } + + ur.idmchr = *idstr; + ur.idaddr = sad; + ur.idlen = lg; + + free(idstr); + } + + return 0; +} + + +static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *urclockIDp) { uint8_t spc[16]; - int addr = 256+1; // Location of DS18B20 ID of Urclock boards - int len = 6; - char mchr = 'E'; + int mchr, addr, len, size; - ur.urclockID = 0; + if(ur.idlen) + mchr = ur.idmchr, addr = ur.idaddr, len = ur.idlen; + else + mchr = 'E', addr = 256+1, len = 6; // Default location for unique id on urclock boards - // Sanity for small boards - if(ur.uP.name && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) { + *urclockIDp = 0; + + // Sanity for small boards in absence of user -xid=... option + if(!ur.idlen && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) { addr = 0; if(ur.uP.eepromsize < 8) mchr = 'F'; } - if(*ur.iddesc) { // User override of ID, eg, -xid=F.-4.2 for penultimate flash word - char *idstr = cfg_strdup(__func__, ur.iddesc), *idlen, *end, *memtype; - unsigned long ad, lg; - int size; + const char *memtype = mchr == 'E'? "eeprom": "flash"; - if(!(strchr("EF", *idstr) && idstr[1] == '.')) { - pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc); - free(idstr); - return -2; - } - memtype = *idstr == 'E'? "EEPROM": "flash"; - size = *idstr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; + size = mchr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; - if(!(idlen = strchr(idstr+2, '.'))) { - pmsg_warning("-xid=%s string must look like [E|F]..\n", ur.iddesc); - free(idstr); - return -2; - } - *idlen++ = 0; - ad = strtoul(idstr+2, &end, 0); - if(*end || end == idstr+2) { - pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc); - free(idstr); - return -2; - } - if(size > 0 && (long) ad < 0) - ad += size; - if(ur.uP.name && size > 0 && ad >= (unsigned long) size) { - pmsg_warning("address %s of -xid=%s string out of %s range [0, 0x%04x]\n", - idstr+2, ur.iddesc, memtype, size-1); - free(idstr); - return -2; - } - lg = strtoul(idlen, &end, 0); - if(*end || end == idlen) { - pmsg_warning("cannot parse length %s of -xid=%s string\n", idlen, ur.iddesc); - free(idstr); - return -2; - } - if(!lg || lg > 8) { - pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlen, ur.iddesc); - free(idstr); - return -2; - } - if(ur.uP.name && size > 0 && ad+lg > (unsigned long) size) { - pmsg_warning("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n", - (int) ad, (int) (ad+lg-1), ur.iddesc, memtype, size-1); - free(idstr); - return -2; - } + if(ur.uP.name && size > 0) { + if(addr < 0) // X.-4.4 asks for 4 bytes at top memory + addr += size; - addr = ad; - len = lg; - mchr = *idstr; - free(idstr); + if(addr < 0 || addr >= size) + Return("effective address %d of -xids=%s string out of %s range [0, 0x%04x]\n", + addr, ur.iddesc, memtype, size-1); + + if(addr+len > size) + Return("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n", + addr, addr+len-1, ur.iddesc, memtype, size-1); } memset(spc, 0, sizeof spc); - (void) ur_readEF(pgm, spc, addr, len, mchr); + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + return -2; + + if(ur_readEF(pgm, p, spc, addr, len, mchr) < 0) + return -1; // Urclock ID for(int i = len-1; i >= 0; i--) - ur.urclockID <<= 8, ur.urclockID |= spc[i]; + *urclockIDp <<= 8, *urclockIDp |= spc[i]; ur.idlen = len; return 0; @@ -1681,7 +1751,7 @@ static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned // Either emulate chip erase or send appropriate command to bootloader -static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { +static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p_unused) { unsigned char buf[16]; long bak_timeout = serial_recv_timeout; @@ -1784,8 +1854,6 @@ static void urclock_disable(const PROGRAMMER *pgm) { static int urclock_open(PROGRAMMER *pgm, const char *port) { union pinfo pinfo; - int showother = ur.showall || ur.showsketch || ur.showstore || ur.showmetadata || ur.showboot || - ur.showversion || ur.showvbl || ur.showdate || ur.showfilename; strcpy(pgm->port, port); pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200; @@ -1809,16 +1877,6 @@ static int urclock_open(PROGRAMMER *pgm, const char *port) { if(urclock_getsync(pgm) < 0) return -1; - // Only asking for the urclock ID: find out and exit fast! - if(ur.showid && !showother && strchr("EF", *ur.iddesc)) { // Also matches *ur.iddesc == 0 - ur.xeepromrw = 1; // Pretend can read EEPROM - if(readUrclockID(pgm) == -1) - return -1; - - term_out("%0*lx\n", ur.idlen, ur.urclockID); - exit(0); - } - return 0; } @@ -1832,7 +1890,7 @@ static void urclock_close(PROGRAMMER *pgm) { } -static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, const AVRMEM *m, +static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { int mchr, chunk; @@ -1840,8 +1898,8 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c if(n_bytes) { // Paged writes only valid for flash and eeprom - mchr = strcmp(m->desc, "flash") == 0? 'F': 'E'; - if(mchr == 'E' && strcmp(m->desc, "eeprom")) + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) return -2; n = addr + n_bytes; @@ -1849,7 +1907,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c for(; addr < n; addr += chunk) { chunk = n-addr < page_size? n-addr: page_size; - if(urclock_paged_rdwr(pgm, Cmnd_STK_PROG_PAGE, addr, chunk, mchr, (char *)&m->buf[addr]) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_PROG_PAGE, addr, chunk, mchr, (char *) m->buf+addr) < 0) return -3; if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0) return -4; @@ -1860,7 +1918,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c } -static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p_unused, const AVRMEM *m, +static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { int mchr, chunk; @@ -1868,15 +1926,15 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p_unused, co if(n_bytes) { // Paged reads only valid for flash and eeprom - mchr = strcmp(m->desc, "flash") == 0? 'F': 'E'; - if(mchr == 'E' && strcmp(m->desc, "eeprom")) + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) return -2; n = addr + n_bytes; for(; addr < n; addr += chunk) { chunk = n-addr < page_size? n-addr: page_size; - if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0) return -3; if(urclock_res_check(pgm, __func__, 0, &m->buf[addr], chunk) < 0) return -4; @@ -1928,9 +1986,9 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { } options[] = { {"showall", &ur.showall, 0, NULL, 0, "Show all info for connected part and exit"}, {"showid", &ur.showid, 0, NULL, 0, " ... unique Urclock ID"}, - {"showsketch", &ur.showsketch, 0, NULL, 0, " ... application size"}, + {"showapp", &ur.showapp, 0, NULL, 0, " ... application size"}, {"showstore", &ur.showstore, 0, NULL, 0, " ... store size"}, - {"showmetadata", &ur.showmetadata, 0, NULL, 0, " ... metadata size"}, + {"showmeta", &ur.showmeta, 0, NULL, 0, " ... metadata size"}, {"showboot", &ur.showboot, 0, NULL, 0, " ... bootloader size"}, {"showversion", &ur.showversion, 0, NULL, 0, " ... bootloader version and capabilities"}, {"showvbl", &ur.showvbl, 0, NULL, 0, " ... vector bootloader level, vec # and name"}, @@ -1949,7 +2007,7 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { {"delay", &ur.delay, 0, NULL, 1, "Add delay [ms] after reset, can be negative"}, {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg F.12324.6"}, {"title", NULL, sizeof ur.title, ur.title, 1, "Title used in lieu of a filename when set"}, - {"?", &help, 0, NULL, 0, "Show this help menu and exit"}, + {"help", &help, 0, NULL, 0, "Show this help menu and exit"}, }; int rc = 0; @@ -2009,12 +2067,15 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id))); for(size_t i=0; i": "", - max(0, 16-strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); + max(0, 16-(long) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); } if(rc == 0) exit(0); } + if(parseUrclockID(pgm) < 0) + return -1; + return rc; }