diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca6cb64f..9aa4bcdb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,6 +225,9 @@ add_library(libavrdude updi_readwrite.h updi_state.c updi_state.h + urclock.c + urclock.h + urclock_private.h usbasp.c usbasp.h usbdevs.h diff --git a/src/Makefile.am b/src/Makefile.am index 61b93dc5..50e24d4f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -180,6 +180,9 @@ libavrdude_a_SOURCES = \ updi_readwrite.h \ updi_nvm.c \ updi_nvm.h \ + urclock.c \ + urclock.h \ + urclock_private.h \ usbdevs.h \ usb_hidapi.c \ usb_libusb.c \ diff --git a/src/avr.c b/src/avr.c index 9352a2b6..dee653c0 100644 --- a/src/avr.c +++ b/src/avr.c @@ -434,7 +434,7 @@ int avr_read_mem(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, con } if (!failure) return avr_mem_hiaddr(mem); - /* else: fall back to byte-at-a-time write, for historical reasons */ + /* else: fall back to byte-at-a-time read, for historical reasons */ } if (strcmp(mem->desc, "signature") == 0) { @@ -1088,8 +1088,7 @@ int compare_memory_masked(AVRMEM * m, uint8_t b1, uint8_t b2) { * * Return the number of bytes verified, or -1 if they don't match. */ -int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int size) -{ +int avr_verify(const PROGRAMMER *pgm, const AVRPART *p, const AVRPART *v, const char *memtype, int size) { int i; unsigned char * buf1, * buf2; int vsize; @@ -1118,14 +1117,34 @@ int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int s size = vsize; } + int verror = 0, vroerror = 0, maxerrs = verbose >= MSG_DEBUG? size+1: 10; for (i=0; itags[i] & TAG_ALLOCATED) != 0 && buf1[i] != buf2[i]) { uint8_t bitmask = get_fuse_bitmask(a); - if((buf1[i] & bitmask) != (buf2[i] & bitmask)) { + if(pgm->readonly && pgm->readonly(pgm, p, a, i)) { + if(quell_progress < 2) { + if(vroerror < 10) { + if(!(verror + vroerror)) + pmsg_warning("verification mismatch%s\n", + avr_mem_is_flash_type(a)? " in r/o areas, expected for vectors and/or bootloader": ""); + imsg_warning("device 0x%02x != input 0x%02x at addr 0x%04x (read only location)\n", + buf1[i], buf2[i], i); + } else if(vroerror == 10) + imsg_warning("suppressing further mismatches in read-only areas\n"); + } + vroerror++; + } else if((buf1[i] & bitmask) != (buf2[i] & bitmask)) { // Mismatch is not just in unused bits - pmsg_error("verification mismatch, first encountered at addr 0x%04x\n", i); - imsg_error("device 0x%02x != input 0x%02x\n", buf1[i], buf2[i]); - return -1; + if(verror < maxerrs) { + if(!(verror + vroerror)) + pmsg_warning("verification mismatch\n"); + imsg_error("device 0x%02x != input 0x%02x at addr 0x%04x (error)\n", buf1[i], buf2[i], i); + } else if(verror == maxerrs) { + imsg_warning("suppressing further verification errors\n"); + } + verror++; + if(verbose < 1) + return -1; } else { // Mismatch is only in unused bits if ((buf1[i] | bitmask) != 0xff) { @@ -1143,7 +1162,7 @@ int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int s } } - return size; + return verror? -1: size; } diff --git a/src/avrcache.c b/src/avrcache.c index a0fa9082..cca15483 100644 --- a/src/avrcache.c +++ b/src/avrcache.c @@ -204,23 +204,6 @@ int avr_is_and(const unsigned char *s1, const unsigned char *s2, const unsigned } -static int initCache(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p) { - AVRMEM *basemem = avr_locate_mem(p, cp == pgm->cp_flash? "flash": "eeprom"); - - if(!basemem || !avr_has_paged_access(pgm, basemem)) - return LIBAVRDUDE_GENERAL_FAILURE; - - cp->size = basemem->size; - cp->page_size = basemem->page_size; - cp->offset = basemem->offset; - cp->cont = cfg_malloc("initCache()", cp->size); - cp->copy = cfg_malloc("initCache()", cp->size); - cp->iscached = cfg_malloc("initCache()", cp->size/cp->page_size); - - return LIBAVRDUDE_SUCCESS; -} - - static int cacheAddress(int addr, const AVR_Cache *cp, const AVRMEM *mem) { int cacheaddr = addr + (int) (mem->offset - cp->offset); @@ -261,6 +244,29 @@ static int loadCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p, } +static int initCache(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p) { + AVRMEM *basemem = avr_locate_mem(p, cp == pgm->cp_flash? "flash": "eeprom"); + + if(!basemem || !avr_has_paged_access(pgm, basemem)) + return LIBAVRDUDE_GENERAL_FAILURE; + + cp->size = basemem->size; + cp->page_size = basemem->page_size; + cp->offset = basemem->offset; + cp->cont = cfg_malloc("initCache()", cp->size); + cp->copy = cfg_malloc("initCache()", cp->size); + cp->iscached = cfg_malloc("initCache()", cp->size/cp->page_size); + + if((pgm->prog_modes & PM_SPM) && avr_mem_is_flash_type(basemem)) { // Could be vector bootloader + // Caching the vector page gives control to the progammer that then can patch the reset vector + if(loadCachePage(cp, pgm, p, basemem, 0, 0, 0) < 0) + return LIBAVRDUDE_GENERAL_FAILURE; + } + + return LIBAVRDUDE_SUCCESS; +} + + static int writeCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int base, int nlOnErr) { // Write modified page cont to device; if unsuccessful try bytewise access if(avr_write_page_default(pgm, p, mem, base, cp->cont + base) < 0) { @@ -597,11 +603,15 @@ int avr_read_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM * * - Used if paged routines available and if memory is EEPROM or flash * - Otherwise fall back to pgm->write_byte() * - Out of memory addr: synchronise cache with device and return whether successful + * - If programmer indicates a readonly spot, return LIBAVRDUDE_SOFTFAIL * - Cache is automagically created and initialised if needed */ int avr_write_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned long addr, unsigned char data) { + if(pgm->readonly && pgm->readonly(pgm, p, mem, addr)) + return LIBAVRDUDE_SOFTFAIL; + // Use pgm->write_byte() if not EEPROM/flash or no paged access if(!avr_has_paged_access(pgm, mem)) return fallback_write_byte(pgm, p, mem, addr, data); @@ -636,9 +646,10 @@ int avr_chip_erase_cached(const PROGRAMMER *pgm, const AVRPART *p) { { avr_locate_mem(p, "flash"), pgm->cp_flash, 1 }, { avr_locate_mem(p, "eeprom"), pgm->cp_eeprom, 0 }, }; + int rc; - if(pgm->chip_erase(pgm, p) < 0) - return LIBAVRDUDE_GENERAL_FAILURE; + if((rc = pgm->chip_erase(pgm, p)) < 0) + return rc; for(size_t i = 0; i < sizeof mems/sizeof*mems; i++) { AVRMEM *mem = mems[i].mem; diff --git a/src/avrdude.1 b/src/avrdude.1 index a2225a1a..0e3b8e90 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,106 @@ 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, then exit. The -xshow... options +below can be used to assemble a bespoke response consisting of a subset +(or only one item) of all available relevant information about the +connected part and bootloader. +.It Ar showid +Show a unique Urclock ID stored in either flash or EEPROM of the MCU, then 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 showdate +Show the last-modified date of the input file for the flash application, +then 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, then exit. +.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 showapp +Show the size of the programmed application, then exit. +.It Ar showstore +Show the size of the unused flash between the application and metadata, then exit. +.It Ar showmeta +Show the size of the metadata just below the bootloader, then exit. +.It Ar showboot +Show the size of the bootloader, then exit. +.It Ar showversion +Show bootloader version and capabilities, then exit. +.It Ar showvector +Show the vector number and name of the interrupt table vector used by the +bootloader for starting the application, then 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 showpart +Show the part for which the bootloader was compiled, then 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 restore +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 -xrestore 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 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/avrdude.conf.in b/src/avrdude.conf.in index 9cec45a0..5d277349 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -71,7 +71,7 @@ # prog_modes = PM_ {| PM_} # interfaces, eg, PM_SPM|PM_ISP|PM_HVPP|PM_debugWIRE # mcuid = ; # unique id in 0..2039 for 8-bit AVRs # n_interrupts = ; # number of interrupts, used for vector bootloaders -# n_page_erase = ; # if set, number of pages erased during NVM erase +# n_page_erase = ; # if set, number of pages erased during SPM erase # hvupdi_variant = ; # numeric -1 (n/a) or 0..2 # devicecode = ; # deprecated, use stk500_devcode # stk500_devcode = ; # numeric @@ -756,6 +756,19 @@ programmer connection_type = serial; ; +#------------------------------------------------------------ +# urclock +#------------------------------------------------------------ + +# See https://github.com/stefanrueger/urboot +programmer + id = "urclock"; + desc = "Urclock programmer for urboot bootloaders using urprotocol"; + type = "urclock"; + prog_modes = PM_SPM; + connection_type = serial; +; + #------------------------------------------------------------ # xbee #------------------------------------------------------------ diff --git a/src/avrintel.c b/src/avrintel.c index 293362c4..a74d964e 100644 --- a/src/avrintel.c +++ b/src/avrintel.c @@ -6,10 +6,10 @@ * Atmel AVR8L, AVR8, XMEGA and AVR8X family description of interrupts and more * * published under GNU General Public License, version 3 (GPL-3.0) - * meta-author: Stefan Rueger + * meta-author Stefan Rueger * * v 1.1 - * 30.08.2022 + * 20.11.2022 * */ @@ -256,9 +256,9 @@ const uPcore_t uP_table[] = { // Value of -1 typically means unknown {"ATA8515", 224, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42, vtab_ata8515}, // atdf {"ATA664251", 225, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20, vtab_attiny167}, // atdf, avr-gcc 12.2.0 {"M3000", 226, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x1000, 0x1000, -1, -1, 0, NULL}, // avr-gcc 12.2.0 - {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, -1, -1, 0, 0x0200, 4, -1, -1, -1, -1, 0, NULL}, // avrdude - {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, -1, -1, 0, 0x0200, 4, -1, -1, -1, -1, 0, NULL}, // avrdude - {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, -1, -1, 0, 0x0400, 4, -1, -1, -1, -1, 0, NULL}, // avrdude + {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26, vtab_atmega328p}, // avrdude, from ATmega88 + {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26, vtab_atmega328p}, // avrdude, from ATmega168P + {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26, vtab_atmega328p}, // avrdude, from ATmega328P {"ATxmega8E5", 230, F_XMEGA, {0x1E, 0x93, 0x41}, 0, 0x02800, 0x080, 1, 0x0800, 0, 0x0200, 32, 0x2000, 0x0400, 7, 1, 43, vtab_atxmega32e5}, // atdf, avr-gcc 12.2.0, avrdude {"ATxmega16A4", 231, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 94, vtab_atxmega32a4}, // atdf, avr-gcc 12.2.0, avrdude diff --git a/src/avrintel.h b/src/avrintel.h index 77147aad..0ab72822 100644 --- a/src/avrintel.h +++ b/src/avrintel.h @@ -9,7 +9,7 @@ * meta-author Stefan Rueger * * v 1.1 - * 30.08.2022 + * 20.11.2022 * */ @@ -770,7 +770,7 @@ typedef struct { // Value of -1 typically means unknown #define vts_avr128da64 64 #define vts_avr128db64 65 -// Suggested vector bootloader interrupt number (first unused vector or, failing that, slot just above vector table) +// Suggested vector bootloader interrupt: first unused vector or slot just above vector table #define vbu_attiny4 10 #define vbu_attiny5 11 #define vbu_attiny9 10 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/developer_opts.c b/src/developer_opts.c index 54a4b0ef..b6fd722e 100644 --- a/src/developer_opts.c +++ b/src/developer_opts.c @@ -1152,9 +1152,9 @@ static void dev_pgm_raw(const PROGRAMMER *pgm) { dev_raw_dump(dp.usbproduct, strlen(dp.usbproduct)+1, id, "usbprod", 0); // Zap all bytes beyond terminating nul of desc, type and port array - if((len = strlen(dp.type)+1) < sizeof dp.type) + if((len = (int) strlen(dp.type)+1) < (int) sizeof dp.type) memset(dp.type + len, 0, sizeof dp.type - len); - if((len = strlen(dp.port)+1) < sizeof dp.port) + if((len = (int) strlen(dp.port)+1) < (int) sizeof dp.port) memset(dp.port + len, 0, sizeof dp.port - len); // Zap address values diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index cb8bd103..04183324 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -1860,7 +1860,7 @@ part prog_modes = PM_ @{| PM_@} # interfaces, eg, PM_SPM|PM_ISP|PM_HVPP|PM_debugWIRE mcuid = ; # unique id in 0..2039 for 8-bit AVRs n_interrupts = ; # number of interrupts, used for vector bootloaders - n_page_erase = ; # if set, number of pages erased during NVM erase + n_page_erase = ; # if set, number of pages erased during SPM erase hvupdi_variant = ; # numeric -1 (n/a) or 0..2 devicecode = ; # deprecated, use stk500_devcode stk500_devcode = ; # numeric diff --git a/src/libavrdude.h b/src/libavrdude.h index 38bc1f42..83556af3 100644 --- a/src/libavrdude.h +++ b/src/libavrdude.h @@ -580,6 +580,7 @@ const char * pinmask_to_str(const pinmask_t * const pinmask); The target file will be selected at configure time. */ extern long serial_recv_timeout; /* ms */ +extern long serial_drain_timeout; /* ms */ union filedescriptor { @@ -798,6 +799,7 @@ typedef struct programmer_t { int (*parseextparams) (const struct programmer_t *pgm, const LISTID xparams); void (*setup) (struct programmer_t *pgm); void (*teardown) (struct programmer_t *pgm); + int (*flash_readhook) (const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *flm, const char *fname, int size); // Cached r/w API for terminal reads/writes int (*write_byte_cached)(const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m, unsigned long addr, unsigned char value); @@ -806,6 +808,8 @@ typedef struct programmer_t { int (*chip_erase_cached)(const struct programmer_t *pgm, const AVRPART *p); int (*page_erase_cached)(const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m, unsigned int baseaddr); + int (*readonly) (const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m, + unsigned int addr); int (*flush_cache) (const struct programmer_t *pgm, const AVRPART *p); int (*reset_cache) (const struct programmer_t *pgm, const AVRPART *p); AVR_Cache *cp_flash, *cp_eeprom; @@ -885,7 +889,7 @@ int avr_write(const PROGRAMMER *pgm, const AVRPART *p, const char *memtype, int int avr_signature(const PROGRAMMER *pgm, const AVRPART *p); -int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int size); +int avr_verify(const PROGRAMMER *pgm, const AVRPART *p, const AVRPART *v, const char *m, int size); int avr_get_cycle_count(const PROGRAMMER *pgm, const AVRPART *p, int *cycles); diff --git a/src/main.c b/src/main.c index 69db5c7c..5fd65f6a 100644 --- a/src/main.c +++ b/src/main.c @@ -1393,9 +1393,12 @@ int main(int argc, char * argv []) if (uflags & UF_NOWRITE) { pmsg_warning("conflicting -e and -n options specified, NOT erasing chip\n"); } else { - msg_info("erasing chip\n"); + pmsg_info("erasing chip\n"); exitrc = avr_chip_erase(pgm, p); - if(exitrc) + if(exitrc == LIBAVRDUDE_SOFTFAIL) { + imsg_info("delaying chip erase until first -U upload to flash\n"); + exitrc = 1; + } else if(exitrc) goto main_exit; } } diff --git a/src/pgm.c b/src/pgm.c index ccded0cc..dca0ea87 100644 --- a/src/pgm.c +++ b/src/pgm.c @@ -119,12 +119,12 @@ PROGRAMMER *pgm_new(void) { pgm->err_led = pgm_default_led; pgm->pgm_led = pgm_default_led; pgm->vfy_led = pgm_default_led; - pgm->read_byte_cached = avr_read_byte_cached; + pgm->read_byte_cached = avr_read_byte_cached; pgm->write_byte_cached = avr_write_byte_cached; pgm->chip_erase_cached = avr_chip_erase_cached; pgm->page_erase_cached = avr_page_erase_cached; pgm->flush_cache = avr_flush_cache; - pgm->reset_cache = avr_reset_cache; + pgm->reset_cache = avr_reset_cache; /* * optional functions - these are checked to make sure they are @@ -154,6 +154,8 @@ PROGRAMMER *pgm_new(void) { pgm->parseextparams = NULL; pgm->setup = NULL; pgm->teardown = NULL; + pgm->readonly = NULL; + pgm->flash_readhook = NULL; // For allocating "global" memory by the programmer pgm->cookie = NULL; diff --git a/src/pgm_type.c b/src/pgm_type.c index 45ce8dca..f14b065b 100644 --- a/src/pgm_type.c +++ b/src/pgm_type.c @@ -51,6 +51,7 @@ #include "stk500generic.h" #include "stk500v2.h" #include "teensy.h" +#include "urclock.h" #include "usbasp.h" #include "usbtiny.h" #include "wiring.h" @@ -102,6 +103,7 @@ const PROGRAMMER_TYPE programmers_types[] = { // Name(s) the programmers call th {"stk600hvsp", stk600hvsp_initpgm, stk600hvsp_desc}, // "STK600HVSP" {"stk600pp", stk600pp_initpgm, stk600pp_desc}, // "STK600PP" {"teensy", teensy_initpgm, teensy_desc}, // "teensy" + {"urclock", urclock_initpgm, urclock_desc}, // "Urclock" {"usbasp", usbasp_initpgm, usbasp_desc}, // "usbasp" {"usbtiny", usbtiny_initpgm, usbtiny_desc}, // "USBtiny" or "usbtiny" {"wiring", wiring_initpgm, wiring_desc}, // "Wiring" @@ -171,5 +173,3 @@ void walk_programmer_types(walk_programmer_types_cb cb, void *cookie) cb(p->id, p->desc, cookie); } } - - diff --git a/src/ser_posix.c b/src/ser_posix.c index ecc25baf..5f1a7eae 100644 --- a/src/ser_posix.c +++ b/src/ser_posix.c @@ -52,6 +52,7 @@ #include "libavrdude.h" long serial_recv_timeout = 5000; /* ms */ +long serial_drain_timeout = 250; /* ms */ struct baud_mapping { long baud; @@ -550,7 +551,7 @@ static int ser_drain(const union filedescriptor *fd, int display) { unsigned char buf; timeout.tv_sec = 0; - timeout.tv_usec = 250000; + timeout.tv_usec = serial_drain_timeout*1000L; if (display) { msg_info("drain>"); diff --git a/src/ser_win32.c b/src/ser_win32.c index 6ed44646..16df6fe9 100644 --- a/src/ser_win32.c +++ b/src/ser_win32.c @@ -37,6 +37,7 @@ #include "libavrdude.h" long serial_recv_timeout = 5000; /* ms */ +long serial_drain_timeout = 250; /* ms */ #define W32SERBUFSIZE 1024 @@ -635,7 +636,7 @@ static int net_drain(const union filedescriptor *fd, int display) { } timeout.tv_sec = 0; - timeout.tv_usec = 250000; + timeout.tv_usec = serial_drain_timeout*1000L; while (1) { FD_ZERO(&rfds); @@ -712,7 +713,7 @@ static int ser_drain(const union filedescriptor *fd, int display) { return -1; } - serial_w32SetTimeOut(hComPort,250); + serial_w32SetTimeOut(hComPort, serial_drain_timeout); if (display) { msg_info("drain>"); diff --git a/src/term.c b/src/term.c index 44becd3f..01b669b7 100644 --- a/src/term.c +++ b/src/term.c @@ -187,7 +187,7 @@ static int chardump_line(char *buffer, unsigned char *p, int n, int pad) { unsigned char b[128]; // Sanity check - n = n < 1? 1: n > sizeof b? sizeof b: n; + n = n < 1? 1: n > (int) sizeof b? (int) sizeof b: n; memcpy(b, p, n); for (int i = 0; i < n; i++) @@ -664,19 +664,21 @@ static int cmd_write(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { report_progress(0, 1, avr_has_paged_access(pgm, mem)? "Caching": "Writing"); for (i = 0; i < len + data.bytes_grown; i++) { int rc = pgm->write_byte_cached(pgm, p, mem, addr+i, buf[i]); - if (rc) { + if (rc == LIBAVRDUDE_SOFTFAIL) { + pmsg_warning("(write) programmer write protects %s address 0x%04x\n", mem->desc, addr+i); + } else if(rc) { pmsg_error("(write) error writing 0x%02x at 0x%05lx, rc=%d\n", buf[i], (long) addr+i, (int) rc); if (rc == -1) imsg_error("%*swrite operation not supported on memory type %s\n", 8, "", mem->desc); werror = true; - } - - uint8_t b; - rc = pgm->read_byte_cached(pgm, p, mem, addr+i, &b); - if (b != buf[i]) { - pmsg_error("(write) error writing 0x%02x at 0x%05lx cell=0x%02x\n", buf[i], (long) addr+i, b); - werror = true; - } + } else { + uint8_t b; + rc = pgm->read_byte_cached(pgm, p, mem, addr+i, &b); + if (b != buf[i]) { + pmsg_error("(write) verification error writing 0x%02x at 0x%05lx cell=0x%02x\n", buf[i], (long) addr+i, b); + werror = true; + } + } if (werror) pgm->err_led(pgm, ON); @@ -755,8 +757,47 @@ static int cmd_send(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { static int cmd_erase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { term_out("erasing chip ...\n"); + // Erase chip and clear cache - pgm->chip_erase_cached(pgm, p); + int rc = pgm->chip_erase_cached(pgm, p); + + if(rc == LIBAVRDUDE_SOFTFAIL) { + pmsg_info("(erase) emulating chip erase by writing 0xff to flash "); + AVRMEM *flm = avr_locate_mem(p, "flash"); + if(!flm) { + msg_error("but flash not defined for part %s?\n", p->desc); + return -1; + } + int addr, beg = 0, end = flm->size-1; + if(pgm->readonly) { + for(addr=beg; addr < flm->size; addr++) + if(!pgm->readonly(pgm, p, flm, addr)) { + beg = addr; + break; + } + if(addr >= flm->size) { + msg_info("but all flash is write protected\n"); + return 0; + } + for(addr=end; addr >= 0; addr--) + if(!pgm->readonly(pgm, p, flm, addr)) { + end = addr; + break; + } + } + + msg_info("[0x%04x, 0x%04x]; undo with abort\n", beg, end); + for(int addr=beg; addr <= end; addr++) + if(!pgm->readonly || !pgm->readonly(pgm, p, flm, addr)) + if(pgm->write_byte_cached(pgm, p, flm, addr, 0xff) == -1) + return -1; + return 0; + } + + if(rc) { + pmsg_error("(erase) programmer %s failed erasing the chip\n", (char *) ldata(lfirst(pgm->id))); + return -1; + } return 0; } diff --git a/src/update.c b/src/update.c index 319766ee..0a7166cd 100644 --- a/src/update.c +++ b/src/update.c @@ -431,7 +431,7 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags AVRMEM *mem; int size; int rc; - Filestats fs; + Filestats fs, fs_patched; mem = avr_locate_mem(p, upd->memtype); if (mem == NULL) { @@ -486,11 +486,10 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags return LIBAVRDUDE_GENERAL_FAILURE; } - size = rc; pmsg_info("reading input file %s for %s%s\n", update_inname(upd->filename), mem->desc, alias_mem_desc); - if(memstats(p, upd->memtype, size, &fs) < 0) + if(memstats(p, upd->memtype, rc, &fs) < 0) return LIBAVRDUDE_GENERAL_FAILURE; imsg_info("with %d byte%s in %d section%s within %s\n", @@ -507,6 +506,38 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags msg_info("\n"); } + // Patch flash input, eg, for vector bootloaders + if(pgm->flash_readhook) { + AVRMEM *mem = avr_locate_mem(p, upd->memtype); + if(mem && !strcmp(mem->desc, "flash")) { + rc = pgm->flash_readhook(pgm, p, mem, upd->filename, rc); + if (rc < 0) { + pmsg_notice("readhook for file %s failed\n", update_inname(upd->filename)); + return LIBAVRDUDE_GENERAL_FAILURE; + } + if(memstats(p, upd->memtype, rc, &fs_patched) < 0) + return LIBAVRDUDE_GENERAL_FAILURE; + if(memcmp(&fs_patched, &fs, sizeof fs)) { + pmsg_info("preparing flash input for device%s\n", + pgm->prog_modes & PM_SPM? " bootloader": ""); + imsg_notice2("with %d byte%s in %d section%s within %s\n", + fs_patched.nbytes, update_plural(fs_patched.nbytes), + fs_patched.nsections, update_plural(fs_patched.nsections), + update_interval(fs_patched.firstaddr, fs_patched.lastaddr)); + if(mem->page_size > 1) { + imsg_notice2("using %d page%s and %d pad byte%s", + fs_patched.npages, update_plural(fs_patched.npages), + fs_patched.nfill, update_plural(fs_patched.nfill)); + if(fs_patched.ntrailing) + msg_notice2(", and %d trailing 0xff byte%s", + fs_patched.ntrailing, update_plural(fs_patched.ntrailing)); + msg_notice2("\n"); + } + } + } + } + size = rc; + // Write the buffer contents to the selected memory type pmsg_info("writing %d byte%s %s%s ...\n", fs.nbytes, update_plural(fs.nbytes), mem->desc, alias_mem_desc); @@ -585,7 +616,7 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags if (quell_progress < 2) pmsg_notice2("verifying ...\n"); - rc = avr_verify(p, v, upd->memtype, size); + rc = avr_verify(pgm, p, v, upd->memtype, size); if (rc < 0) { pmsg_error("verification mismatch\n"); pgm->err_led(pgm, ON); diff --git a/src/urclock.c b/src/urclock.c new file mode 100644 index 00000000..a987153d --- /dev/null +++ b/src/urclock.c @@ -0,0 +1,2503 @@ +/* + * AVRDUDE - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2022, Stefan Rueger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* $Id$ */ + +/* + * The Urclock programmer + * + * - Reads/writes flash/EEPROM of boards directly via the MCU bootloader and a serial connection + * - Works best in tandem with the urboot bootloader, but can deal with optiboot and similar + * - Implements urprotocol, a communication protocol designed for small bootloader sizes + * - Supports vector bootloaders by patching relevant interrupt vectors during upload: + * + Vector bootloaders run on all devices, not only those with a dedicated boot section + * + Can be considerably smaller than the smallest dedicated boot sections of a part, eg, + * only 256 bytes for ATmega2560 with an otherwise smallest boot section of 1024 bytes + * - Checks sizes of applications so they don't overwrite the bootloader + * - Provides a 4-byte metadata interface for + * + Allowing applications to utilise unused flash in a similar fashion to EEPROM + * + Storing in top flash the file name and last-modified-date of the uploaded application + * + Displaying file name and date of the application that was last uploaded + * + * As an example, the urboot bootloader including EEPROM r/w for the popular ATmega328p is only 384 + * bytes, which frees up 128 bytes. On an ATmega1284p the urboot bootloader without EEPROM r/w is + * only 256 bytes, freeing 786 bytes on that device. Urboot bootloaders can be configured to + * - Upload and download applications + * - Read and write EEPROM + * - Provide an application function that writes flash memory pages; as this function is located + * at FLASHEND-4+1, no linker information is needed for the application + * - Operate dual boot from external SPI flash memory in addition to EEPROM r/w at a slightly + * increased bootloader of 512 bytes for a range of devices from the small ATtiny167, via the + * popular ATmega328p to the mighty ATmega2560. + * + * + * Urprotocol (the gory details, see also https://github.com/stefanrueger/urboot) + * + * The **explicit communication** between an uploader/downloader program (*"the programmer"*) and + * the bootloader is driven by the programmer, which sends command sequences to the bootloader and + * evaluates their return sequences. A command sequence starts by a command byte, followed by its + * parameters, followed by an end-of-parameter byte UR_EOP. In return the bootloader sends a fixed + * byte UR_INSYNC to acknowledge the command, then executes it, possibly returning data, followed + * by sending a different fixed byte UR_OK. + * + * Although the UR_INSYNC and UR_OK are *fixed constants* for a particular bootloader, they *can + * vary* between bootloaders to indicate + * - Which MCU the bootloader sits on (using one of up to 2040 predefined different MCU IDs) + * - Whether or not the bootloader provides a paged read flash command + * - Whether or not the bootloader has implemented the chip erase command + * - Whether or not writing a memory page memory looks like programming NOR memory + * - Two more whether-or-not bits that are currently reserved + * + * As UR_INSYNC and UR_OK should always differ, there are 256*255 possible combinations, one of + * which is reserved for backward compatibility mode where UR_INSYNC and UR_OK coincide with the + * respective STK500v1 constants. This protocol definition enables the bootloader to pass to the + * programmer log2(256*255-1) bits = 15.994331... bits of configuration information without having + * to spend a single additional byte of bootloader code. Subtracting the 5 bits for the "whether or + * not" info leaves 10.994331... bits which allows 2040 ≈ 2**10.994331... MCU ids. + * + * **Parameters.** Paged EEPROM/flash access commands and page erase are the only commands that + * need parameters. In this case the parameters are the address, followed by the length of the + * block to read or write and, if needed, followed by the bytes to be written. As in STK500v1, + * addresses are given as little endian (low byte first) and length as big endian (high byte + * first). The address always is a byte address (unless in compability mode). It is a 16-bit + * address for MCUs that have 65536 bytes flash or less, and a 24-bit address for MCUs with larger + * flash. Zero-length reads or writes are not supported by the protocol. If the *flash* page size + * is 256 or less, then the length parameter is sent as one byte (where 0 means 256 bytes). + * Otherwise the length parameter is sent as two bytes (where 0 means 65536). Note, however, that + * the only valid length for the write flash page command is the MCU page size; also the *maximum* + * valid length for EEPROM writes is 256 or the *flash* page size, whichever is higher. EEPROM + * write page commands should never exceed the size of half of SRAM though. The other two (read) + * paged-access commands are free to request any length between 1 and 256, and 1 and 65536, + * respectively. However, the programmer must never ask for an address block that would access + * bytes outside the range of EEPROM or flash on the device. Whilst the number of parameter bytes + * differs between bootloaders, for a particular bootloader the address and length is given always + * in the same way. This means that the EEPROM address on an MCU with a large flash will be a + * 24-bit address even though the EEPROM might only have 8192 bytes. Even though the write flash + * page command only allows one length, and page erase does not need a page at all, it must always + * be specified. This is to simplify the bootloader effort to decode the programmer's commands. + * + * + * Urprotocol commands + * + * - **UR_GET_SYNC:** The bootloader does nothing except returning the two protocol bytes. Its + * purpose is to synchronise the programmer with the bootloader and to identify the type of + * bootloader and (some of) its properties. For synchronisation, the programmer should issue a + * number of UR_GET_SYNC commands until it receives consistent UR_INSYNC and UR_OK values. + * At this point the programmer knows whether or not to switch to backward compatibility mode + * using the STK500v1 protocol as in -c arduino, which MCU is to be programmed etc. It is + * advised the programmer sets its read timeout in the synchronisation phase to less than 100 ms + * when reading the bootloader reply to avoid triggering the bootloader's watchdog timer. It is + * also recommended that the input is "drained" after successfully reading two response bytes to + * ensure the response has not been brought about by an application program of the connected + * board before the board was reset into bootloader mode. This command can also be used + * periodically to prevent the bootloader from timing out. + * + * - **UR_PROG_PAGE_FL:** One flash page is written to the device. In the absence of a + * UR_CHIP_ERASE (see below), the bootloader is expected to program the flash page as atomic + * page erase, page load and page write. If the bootloader implements UR_CHIP_ERASE, it has the + * choice of erasing a flash page before programming it or not. In case the bootloader erases + * pages before writing them, the payload of the UR_PROG_PAGE is programmed exactly as is; the + * programmer should implement desired sub-page modifications by first reading the flash + * contents of the not-to-be-modified page parts to correctly pad the page payload. If the + * bootloader does not erase pages before writing them, effectively the payload is *and*ed to + * the existing contents of the page thereby exposing the physical property of the underlying + * NOR flash memory; sub-page modifications can be carried out by padding the page buffer + * payload with 0xff, as programming 0xff is a NOP for AVR NOR flash memories. + * + * - **UR_CHIP_ERASE** (optional): If implemented, the bootloader erases to 0xff all flash + * except itself. After issuing the chip erase request it is advised the programmer set its + * timeout for reading the next character to more time than the bootloader will need to erase + * flash to avoid the programmer resuming communication before the bootloader comes back from + * the chip erase. 20 s should be sufficient. If the bootloader does not implement chip erase + * then the programmer should ensure that flash is erased to 0xff by, eg, repeated + * UR_PROG_PAGE calls with 0xff-only contents or equivalent; this normally takes longer than + * bootloader chip erase but is otherwise functionally equivalent to a UR_CHIP_ERASE + * implementation in the bootloader. The protocol does not expect EEPROM to be erased in either + * case. However, when implementing UR_CHIP_ERASE the bootloader is free to read fuses to + * determine whether or not EEPROM should also be erased and erase EEPROM accordingly. + * + * - **UR_READ_PAGE_FL** (optional) returns n=length bytes of flash from the given address + * + * - **UR_READ_PAGE_EE** (optional) returns n=length bytes of EEPROM from the given address + * + * - **UR_PROG_PAGE_EE** (optional) writes n=length bytes to the EEPROM at the given address + * + * - **UR_PAGE_ERASE** (optional) erases to 0xff a page at the given address (length must be given + * but is ignored) + * + * - **UR_LEAVE_PROGMODE** (optional): some bootloaders reduce the Watchdog timeout so that the + * application is started faster after programming + * + * - **Any other command**, should behave like UR_GET_SYNC, ie, the bootloader returns + * UR_INSYNC and UR_OK. + * + * + * **Error handling.** It is generally considered an error if the programmer asks for not + * implemented functionality, as it knows after synchronisation how the bootloader is configured. + * Hence, the bootloader WDT should reset on request of an optional, not implemented command. + * Typically, the bootloader would need to save the payload of EEPROM/flash writes to SRAM; for + * security reasons the bootloader should trigger a WDT reset if an illegitimate length of a paged + * write could overwrite the stack (eg, a request for writing 256 bytes EEPROM on a part with only + * 256 bytes SRAM). A protocol error detected by the bootloader (failure to match UR_EOP) should + * lead to a WDT reset. Protocol errors detected by the programmer (not matching UR_INSYNC or + * UR_OK) should normally lead to a termination of programming attempts. Frame errors in serial + * communication should also lead to a WDT reset or termination of programming, respectively. The + * bootloader should protect itself from being overwritten through own page writes and page erases. + * + * + * **Implicit communication** of further bootloader properties happens through a small table + * located at the top of flash. Normally, the programmer can read this table after establishing the + * MCU id, and therefore the location of top flash of the part for which the bootloader was + * compiled. The 6-byte table contains (top to bottom): + * - Version number: one byte, minor version 0..7 in three lsb, major version 0..31 in the 5 msb + * - Capabilities byte detailing, eg, whether the bootloader supports EEPROM r/w, dual boot etc + * - Two-byte rjmp to a writepage(ram, flash) function or a ret opcode if not implemented + * - Number 1..127 of pages that the bootloader occupies + * - Vector number 1..127 used for the r/jmp to the application if it is a vector bootloader + * If the bootloader does not have read capabilities the user needs to supply necessary information + * such as the bootloader size to the programmer on the command line via -x extended parameters. + * + * **Backward compatibility mode.** When urprotocol after synchronisation with the bootloader + * settles on UR_INSYNC and UR_OK values that turn out to be the STK500v1 values of 0x14 and 0x10, + * this triggers a backward compatibility mode. In this instance the programmer behaves (almost) + * like the STK500v1 implementation in avrdude's arduino programmer, ie, it handles optiboot and + * legacy bootloaders gracefully: in particular, the programmer can issue STK_READ_SIGN and two + * STK_UNIVERSAL requests (load extended address and chip erase) that the bootloader must implement + * in the backward compatibility mode. All EEPROM/flash addresses are sent as two-byte word + * addresses *little* endian, all length arguments are two-byte *big* endian, etc. Unlike avrdude + * -c arduino the programmer for the urprotocol should not pass on get and set hardware parameter + * requests, enquire software and hardware versions etc, as these requests would be wasteful for + * the bootloader. Under the urprotocol, bootloaders should be assured they do not need to even + * provide code to ignore these requests, even if they operate in the backwards compatibility mode. + * + * + * **Limitations.** Urprotocol has only provisions for reading EEPROM and flash, for writing EEPROM + * and for writing flash other than the bootloader area. In particular, urprotocol has no + * provisions for reading other memories such as the signature (other than in backward + * compatibility mode), calibration bytes, locks or fuses, and neither for writing lock bytes. The + * protocol does not consider sub-page flash writes, which are shifted to the programmer. If the + * bootloader's flash write does *not* look like NOR programming *and* if the bootloader does *not* + * provide flash read, then sub-page modifications simply cannot be done. Installing a bootloader + * has security implications as it provides a means to modify flash thus weakening the Harvard + * architecture of AVR microprocessors. Even bootloader implementations that are hardened against + * prohibited address and length parameters have, out of necessity, somewhere a code sequence that + * manipulates flash memory. A flawed application might still give an attacker a way to call these + * code sequences, so be warned here be dragons. + * + */ + +#include "ac_cfg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "avrdude.h" +#include "libavrdude.h" + +#include "urclock.h" +#include "urclock_private.h" + +#define urmax(a, b) ((a) > (b)? (a): (b)) +#define urmin(a, b) ((a) < (b)? (a): (b)) + +static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p); +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); + +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); + + +// Context of the programmer +typedef struct { + char desc[32]; // Text description of bootloader version and capabilities + + bool urprotocol; // Bootloader uses the urboot modification of the STK500v1 protocol + uint8_t urfeatures; // Bootloader features (chip erase, can read flash, ...) + int STK_INSYNC, STK_OK; // Variable but fixed bootloader responses for urprotocol + + struct { + uint8_t seen, stk_ok, stk_insync; + } gs; // Needed for urclock_getsync() + + + unsigned char ext_addr_byte; // Ext-addr byte for STK500v1 protocol and MCUs with > 128k + + uPcore_t uP; // Info about the connected processor (copied from uP_table) + + bool initialised; // Is this structure initialised? + bool bleepromrw; // Bootloader has EEPROM r/w support + bool emulate_ce; // Emulate chip erase when bootloader cannot and user wants it + bool done_ce; // Set when flash of chip has been erased after first write + + int sync_silence; // Temporarily set during start of synchronisation + + // Info needed about bootloader to patch, if needed, the reset vector and one other vector + int vblvectornum, // Vector bootloader vector number for jump to application op code + vbllevel, // 0=n/a, 1=patch externally, 2=bl patches, 3=bl patches & verifies + blurversion, // Octal byte 076 means v7.6 (minor version number is lowest 3 bit) + // Small numbers < 070 probably are optiboot major version number + bloptiversion, // Optiboot version as (major<<8) + minor + blguessed; // Guessed the bootloader from hash data + + int32_t blstart; // Bootloader start address, eg, for bootloader write protection + + 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 + int32_t storesize; // Store size + + // Metadata for free flash memory to be used for store support + char filename[254]; // Filename of uploaded application, must be max 254 bytes incl nul + int16_t yyyy; // Date stamp of uploaded application file: 4 digit year, + int8_t mm, dd, hr, mn; // Month (1..12), day (1..31), hour (0..23) and minute (0..59) + uint8_t freeflash[3]; // 24-bit little endian number (storesize) + uint8_t mcode; // 255 = no metadata, 0 = only freeflash, 1 = freeflash + date, + // 2-254 = freeflash + date + that many bytes filename incl nul + // Note: + // blstart = application size + freeflash + nmeta(mcode, flashsize) + // FLASHEND+1 = application size + freeflash + nmeta(mcode, flashsize) + bootloader size + + // Extended parameters for Urclock + int showall, // Show all pieces of info for connected part and exit + showid, // ... Urclock ID + showdate, // ... last-modified date of last uploaded application + showfilename, // ... filename of last uploaded application + showapp, // ... application size + showstore, // ... store size + showmeta, // ... metadata size + showboot, // ... bootloader size + showversion, // ... bootloader version and capabilities + showvector, // ... vector bootloader level, vector number and name + showpart, // ... part for which bootloader was compiled + xbootsize, // Manual override for size of bootloader section + xvectornum, // ... for vector number (implies vbllevel = 1) + xeepromrw, // ... for EEPROM r/w capability + xemulate_ce, // ... for making avrdude emulate any chip erase + initstore, // Zap store when writing the application, ie, fill with 0xff +//@@@ copystore, // Copy over store as far as possible when writing the application + restore, // Restore a flash backup exactly as it is trimming the bootloader + nofilename, // Don't store application filename when writing the application + nodate, // Don't store application filename and no date either + nometadata, // Don't store any metadata at all (implies no store support) + delay; // Additional delay [ms] after resetting the board, can be negative + + char title[254]; // Use instead of filename for metadata - same size as filename + char iddesc[64]; // Location of Urclock ID, eg F.12324.6 or E.-4.4 (default E.257.6) +} Urclock_t; + +// Use private programmer data as if it were a global structure ur +#define ur (*(Urclock_t *)(pgm->cookie)) + +#define Return(...) do { pmsg_error(__VA_ARGS__); msg_error("\n"); return -1; } while (0) + + +// Return how many bytes metadata are needed given the mcode byte just below bootloader +static int nmeta(int mcode, int flashsize) { + // The size of the structure that holds info about metadata (sits just below bootloader) + int nheader = 2*(flashsize > (1<<16)? 4: 2) + 1; + + return mcode == 0xff? 0: // No metadata at all + mcode > 1? mcode+6+nheader: // Application filename, app date and structure for pgm store + mcode? 6+nheader: // Application date and structure describing pgm store + nheader; // Structure describing pgm store +} + + +// Given the MCU id return index in uP_table or -1 if not found +static int upidxmcuid(int mcuid) { + for(size_t i=0; i< sizeof uP_table/sizeof *uP_table; i++) { + if(mcuid == uP_table[i].mcuid) + return i; + } + return -1; +} + +// Given three signature bytes return index in uP_table or -1 if not found +static int upidxsig(const uint8_t *sigs) { + for(size_t i=0; i< sizeof uP_table/sizeof *uP_table; i++) { + if(0 == memcmp(sigs, uP_table[i].sigs, sizeof uP_table->sigs)) + return i; + } + return -1; +} + +// Given the long name of a part return index in uP table or -1 if not found +static int upidxname(const char *name) { + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) + if(0 == strcasecmp(name, uP_table[i].name)) + return i; + + return -1; +} + +// Given sig bytes return number of matching indices in uP_table and create a list of names in p +static int upmatchingsig(uint8_t sigs[3], char *p, size_t n) { + int matching = 0; + uPcore_t up = {0, }; + + // Scan table for the given signature + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) { + if(0 == memcmp(sigs, uP_table[i].sigs, sizeof uP_table->sigs)) { + if(matching == 0) { // First match, initialise uP information + matching = 1; + up = uP_table[i]; + if(p) { + size_t len = strlen(uP_table[i].name); + if(n > len) { + strcpy(p, uP_table[i].name); + n -= len; p += len; + } + } + } else { + // Same signature, but are these chips materially different as far as urboot is concerned? + if( up.ninterrupts != uP_table[i].ninterrupts || + up.pagesize != uP_table[i].pagesize || + up.nboots != uP_table[i].nboots || + up.bootsize != uP_table[i].bootsize || + up.flashsize != uP_table[i].flashsize || + up.flashoffset != uP_table[i].flashoffset ) { + matching++; + if(p) { + size_t len = 2 + strlen(uP_table[i].name); + if(n > len) { + strcpy(p, ", "); + strcpy(p+2, uP_table[i].name); + n -= len; p += len; + } + } + + } + } + } + } + + return matching; +} + + +// Need to know a bit about avr opcodes, in particular jmp and rjmp for patching vector table + +#define ret_opcode 0x9508 + + +// Is the opcode an rjmp, ie, a relative jump [-4094, 4096] bytes from opcode address? +static int isRjmp(uint16_t opcode) { + return (opcode & 0xf000) == 0xc000; +} + + +/* + * Map distances to [-flashsize/2, flashsize/2) for smaller devices. As rjmp can go +/- 4 kB, so + * smaller flash than 8k (eg, 4k) benefit from wrap around logic. + */ +static int rjmpdistwrap(int addis, int flashsize) { + int size = flashsize > 8182? 8192: flashsize; + + if((size & (size-1)) == 0) { // Sanity check to assert size is a power of 2; will be true + addis &= size-1; + if(addis >= size/2) + addis -= size; + } + + return addis; +} + + +// Compute from rjmp opcode the relative distance in bytes (rjmp address minus destination address) +static int dist_rjmp(uint16_t rjmp, int flashsize) { + int16_t dist; + + dist = rjmp & 0xfff; // Signed 12-bit word distance + dist = (int16_t)(dist<<4)>>3; // Sign-extend and multiply by 2 + + return rjmpdistwrap(dist+2, flashsize); // Wraps around 0 (eg, in flashes smaller than 8k) +} + + +// rjmp opcode from byte distance; 0xcfff is an endless loop, 0xc000 is a nop +static uint16_t rjmp_opcode(int dist, int flashsize) { + dist = rjmpdistwrap(dist, flashsize); + return 0xc000 | (((dist >> 1) - 1) & 0x0fff); +} + + +// rjmp opcode from reset to bootloader start; same as above if bl start is in top half of flash +static uint16_t rjmp_bwd_blstart(int blstart, int flashsize) { // flashsize must be power of 2 + return 0xc000 | (((uint16_t)((blstart-flashsize-2)/2)) & 0xfff); // Urboot uses this formula +} + + +// jmp opcode from byte address +static uint32_t jmp_opcode(int32_t addr) { + // jmp uses word address; hence, shift by that one extra bit more + return (((addr>>1) & 0xffff)<<16) | 0x940c | (((addr>>18) & 31)<<4) | (((addr>>17) & 1)<<0); +} + + +// Byte address from jmp opcode +static int addr_jmp(uint32_t jmp) { + int addr; + + addr = jmp >> 16; // Low 16 bit of word address are in upper word of op code + addr |= (jmp & 1) << 16; // Add extra address bits from least significant bytes of op code + addr |= (jmp & 0x1f0) << (17-4); + addr <<= 1; // Convert to byte address + + return addr; +} + + +// Is the instruction word the lower 16 bit part of a 32-bit instruction? +static int isop32(uint16_t opcode) { + return + (opcode & 0xfe0f) == 0x9200 || // sts + (opcode & 0xfe0f) == 0x9000 || // lds + (opcode & 0xfe0e) == 0x940c || // jmp + (opcode & 0xfe0e) == 0x940e; // call +} + + +// Is the instruction word the lower 16 bit part of a jmp instruction? +static int isJmp(uint16_t opcode) { + return (opcode & 0xfe0e) == 0x940c; +} + + +// Assemble little endian 32-bit word from buffer +static uint32_t buf2uint32(const unsigned char *buf) { + return buf[0] | buf[1]<<8 | buf[2]<<16 | buf[3]<<24; +} + + +// Assemble little endian 16-bit word from buffer +static uint16_t buf2uint16(const unsigned char *buf) { + return buf[0] | buf[1]<<8; +} + + +// Write little endian 32-bit word into buffer +void uint32tobuf(unsigned char *buf, uint32_t opcode32) { + buf[0] = opcode32; + buf[1] = opcode32>>8; + buf[2] = opcode32>>16; + buf[3] = opcode32>>24; +} + + +// Write little endian 16-bit word into buffer +void uint16tobuf(unsigned char *buf, uint16_t opcode16) { + buf[0] = opcode16; + buf[1] = opcode16>>8; +} + + +// Set filename/title and date for metadata +static void set_date_filename(const PROGRAMMER *pgm, const char *fname) { + const char *base; + struct stat b; + struct tm *t; + time_t when; + + // Last modification date of file or, if unavailable, current time + when = fname && *fname && strcmp(fname, "-") && !stat(fname, &b)? b.st_mtime: time(NULL); + when += 30; // Round to minute + if((t=localtime(& when))) { + ur.yyyy = t->tm_year + 1900; + ur.mm = t->tm_mon+1; + ur.dd = t->tm_mday; + ur.hr = t->tm_hour; + ur.mn = t->tm_min; + } + + // Compute basename of file unless title was set + if(*ur.title) + memcpy(ur.filename, ur.title, sizeof ur.filename); + else { + ur.filename[0] = 0; + if(fname && *fname) { +#if !defined (WIN32) + if((base=strrchr(fname, '/'))) + base++; +#else + if((base=strrchr(fname, '\\'))) + base++; +#endif + else + base = fname; + strncpy(ur.filename, base, sizeof ur.filename-1); + ur.filename[sizeof ur.filename-1] = 0; + } + } +} + + + +// 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; +} + + +// What reset looks like for vector bootloaders +static int set_reset(const PROGRAMMER *pgm, unsigned char *jmptoboot, int vecsz) { + // Small part or larger flash that is power or 2: urboot P reset vector protection uses this + if(vecsz == 2 || (ur.uP.flashsize & (ur.uP.flashsize-1)) == 0) { + uint16tobuf(jmptoboot, rjmp_bwd_blstart(ur.blstart, ur.uP.flashsize)); + return 2; + } + + uint32tobuf(jmptoboot, jmp_opcode(ur.blstart)); + return 4; +} + + +// Called after the input file has been read for writing or verifying flash +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); + + // Record how extensive the metadata should be, given the command line options (default: all) + ur.mcode = ur.nometadata? 0xff: ur.nodate? 0: ur.nofilename? 1: strlen(ur.filename)+1; + nmdata = nmeta(ur.mcode, ur.uP.flashsize); + + maxsize = ur.blstart? ur.blstart: flm->size; + + // Compute begin and length of first contiguous block in input + for(firstbeg=0; firstbeg < size; firstbeg++) + if(flm->tags[firstbeg] & TAG_ALLOCATED) + break; + for(firstlen=0; firstbeg+firstlen < size; firstlen++) + if(!(flm->tags[firstbeg+firstlen] & TAG_ALLOCATED)) + break; + + pmsg_notice2("%s %04d.%02d.%02d %02d.%02d meta %d boot %d\n", ur.filename, + ur.yyyy, ur.mm, ur.dd, ur.hr, ur.mn, nmdata, ur.blstart > 0? flm->size-ur.blstart: 0); + + // Force upload of exactly this file, no patching, no metadata update, just trim if too big + if(ur.restore) { + if(size > maxsize) + size = maxsize; + + goto nopatch_nometa; + } + + // Sanity: no patching and no metadata if bootloader location is unknown + if(!ur.blstart) + goto nopatch_nometa; + + // Sanity check the bootloader start address + if(ur.blstart < 0 || ur.blstart >= flm->size) + Return("bootloader at 0x%04x outside flash [0, 0x%04x]?", ur.blstart, flm->size-1); + + // Check size of uploded application and protect bootloader from being overwritten + if(size > ur.blstart) + Return("input [0x%04x, 0x%04x] overlaps bootloader [0x%04x, 0x%04x]", + firstbeg, size-1, ur.blstart, flm->size-1); + + if(nmdata >= nmeta(0, ur.uP.flashsize) && size > ur.blstart - nmeta(0, ur.uP.flashsize)) + Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnometadata", + firstbeg, size-1, ur.blstart-nmeta(0, ur.uP.flashsize), ur.blstart-1); + + if(nmdata >= nmeta(1, ur.uP.flashsize) && size > ur.blstart - nmeta(1, ur.uP.flashsize)) + Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnodate", + firstbeg, size-1, ur.blstart-nmeta(1, ur.uP.flashsize), ur.blstart-1); + + if(size > ur.blstart - nmdata) + Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnofilename", + firstbeg, size-1, ur.blstart-nmdata, ur.blstart-1); + + 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 && ibuf+i); + if(!isRjmp(op16) && !(vecsz == 4 && isJmp(op16))) + llvectors = 0; + } + + if(llcode && !llvectors && ur.vblvectornum > 0 && ur.vbllevel) + pmsg_warning("not patching jmp to application as input does not start with a vector table\n"); + + // Patch vectors if input looks like code and it's a vector bootloader with known vector number + if(llcode && llvectors && ur.vblvectornum > 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)) { + 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 + reset32 = vecsz == 2? reset16: buf2uint32(flm->buf); + + /* + * Compute where the application starts from the reset vector. The assumptions are that the + * - Vector table, and therefore the reset vector, resides at address zero + * - Compiler puts either a jmp or an rjmp at address zero + * - Compiler does not shorten the vector table if no or few interrupts are used + * - Compiler does not utilise unused interrupt vectors to place code there + * These are not necessarily true, but work for run-of-the-mill setups; the code below makes + * a reasonable effort to detect whether the assumptions are violated, so at least there is + * an error thrown if so. + */ + + 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; + } + + // Only patch if appstart does not already point to the bootloader + if(appstart != ur.blstart) { + int vectorsend = vecsz*ur.vblvectornum; + if(appstart < vectorsend || appstart >= size) { // appstart should be in [vectorsend, size) + if(appstart != ur.blstart) { + pmsg_warning("not patching as reset opcode %0*x jumps to 0x%04x,\n", + vecsz*2, reset32, appstart); + imsg_warning("ie, outside code area [0x%04x, 0x%04x)\n", + vectorsend, size); + } + goto nopatch; + } + + // OK, now have bootloader start and application start: patch + set_reset(pgm, flm->buf+0, vecsz); + if(vecsz == 4) + uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart)); + else + uint16tobuf(flm->buf+appvecloc, rjmp_opcode(appstart - appvecloc, ur.uP.flashsize)); + } + } + } + +nopatch: + + if(nmdata) { + int32_t nfree = ur.blstart - size; + + if(nfree >= nmdata) { + unsigned char *p = flm->buf + ur.blstart - nmdata; + + if(ur.mcode > 1) { // Save filename (ur.mcode cannot be 0xff b/c nmdata is non-zero) + memcpy(p, ur.filename, ur.mcode); + p += ur.mcode; + } + + if(ur.mcode >= 1) { // Save date + *p++ = ur.yyyy; + *p++ = ur.yyyy>>8; + *p++ = ur.mm; + *p++ = ur.dd; + *p++ = ur.hr; + *p++ = ur.mn; + } + + *p++ = size; // Save where the pgm store begins + *p++ = size >> 8; + if(ur.uP.flashsize > (1<<16)) { + *p++ = size >> 16; + *p++ = size >> 24; + } + + nfree -= nmdata; + *p++ = nfree; // Save how much is free + *p++ = nfree >> 8; + if(ur.uP.flashsize > (1<<16)) { + *p++ = nfree >> 16; + *p++ = nfree >> 24; + } + *p++ = ur.mcode; + + // Set tags so metadata get burned onto chip + memset(flm->tags + ur.blstart - nmdata, TAG_ALLOCATED, nmdata); + + if(ur.initstore) // Zap the pgm store + memset(flm->tags + size, TAG_ALLOCATED, nfree); + + size = ur.blstart; + } + } + + // Storing no metadata: put a 0xff byte just below bootloader + if(size < ur.blstart && nmdata == 0) { + flm->buf[ur.blstart-1] = 0xff; + flm->tags[ur.blstart-1] = TAG_ALLOCATED; + size = ur.blstart; + } + +nopatch_nometa: + + // Delete metadata on device (if any) that's between new input and metadata + if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable? + uint8_t devmcode; // Metadata marker on the device + if(ur.blstart && ur_readEF(pgm, p, &devmcode, ur.blstart-1, 1, 'F') == 0) { + int devnmeta=nmeta(devmcode, ur.uP.flashsize); + for(int addr=ur.blstart-devnmeta; addr < ur.blstart; addr++) { + if(!(flm->tags[addr] & TAG_ALLOCATED)) { + flm->tags[addr] |= TAG_ALLOCATED; + flm->buf[addr] = 0xff; + } + } + } + } + + // Emulate chip erase if bootloader unable to: mark all bytes for upload on first -U flash:w:... + if(ur.emulate_ce) { + for(int ai = 0; ai < maxsize; ai++) + flm->tags[ai] = TAG_ALLOCATED; + ur.emulate_ce = 0; + } + + + // Ensure that vector bootloaders have correct r/jmp at address 0 + if(ur.blstart && ur.vbllevel==1) { + int rc, set=0; + for(int i=0; i < vecsz; i++) + if(flm->tags[i] & TAG_ALLOCATED) + set++; + + + // Reset vector not programmed? Or -F? Ensure a jmp to bootloader + if(ovsigck || set != vecsz) { + unsigned char jmptoboot[4]; + int resetsize = set_reset(pgm, jmptoboot, vecsz); + + if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable? + int resetdest; + + if(set != vecsz) { + unsigned char device[4]; + // Read reset vector from device flash + if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) + return rc; + + // Mix with already set bytes + for(int i=0; i < vecsz; i++) + if(!(flm->tags[i] & TAG_ALLOCATED)) + flm->buf[i] = device[i]; + } + + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0 || resetdest != ur.blstart) { + for(int i=0; i < resetsize; i++) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } + } + } else { // Flash not readable: patch reset vector unconditionally + for(int i=0; i < resetsize; i++) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } + } + } else { // Double-check reset vector jumps to bootloader + int resetdest; + + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) + Return("input would overwrite the reset vector bricking the bootloader\n" + "%*susing -F will try to patch the input but this may not be what is needed", + (int) strlen(progname)+1, ""); + + if(resetdest != ur.blstart) + Return("input points reset to 0x%04x, not to bootloader at 0x%04x\n" + "%*susing -F will try to patch the input but this may not be what is needed", + resetdest, ur.blstart, (int) strlen(progname)+1, ""); + } + } + + // Effective page size, can be 4*pagesize for 4-page erase parts + int pgsize = p->n_page_erase > 0? p->n_page_erase*ur.uP.pagesize: ur.uP.pagesize; + if((pgsize & (pgsize-1)) || pgsize < 1 || pgsize > maxsize || maxsize % pgsize) + Return("effective page size %d implausible for size %d below bootloader", pgsize, maxsize); + + if(!ur.done_ce) { // Unless chip erase was just issued (where all mem is 0xff) + if((ur.urprotocol && !(ur.urfeatures & UB_FLASH_LL_NOR)) || !ur.urprotocol) { + // Scan the memory for eff pages with unset bytes and read these bytes from device flash + int ai, npe, addr, nset; + + uint8_t spc[2048]; + + for(addr = 0; addr < maxsize; addr += pgsize) { + // How many bytes are set in this effective page? + for(ai = addr, nset = 0; ai < addr + pgsize; ai++) + if(flm->tags[ai] & TAG_ALLOCATED) + nset++; + + // Holes in this page that needs writing? read them in from the chip + if(nset && nset != pgsize) { + for(npe=0; npe < pgsize/ur.uP.pagesize; npe++) { + // Identify a covering interval for all holes in page + int istart, isize, beg, end; + + beg = addr + npe*ur.uP.pagesize; + end = beg + ur.uP.pagesize; + + // Lowest address with unset byte (there might be none) + for(ai = beg; ai < end; ai++) + if(!(flm->tags[ai] & TAG_ALLOCATED)) + break; + istart = ai; + + if(istart < end) { + // Highest address with unset byte + for(ai = end - 1; ai >= istart; ai--) + if(!(flm->tags[ai] & TAG_ALLOCATED)) + break; + isize = ai - istart + 1; + + 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, p, spc, istart, isize, 'F') == 0) { + pmsg_debug("padding [0x%04x, 0x%04x]\n", istart, istart+isize-1); + + for(ai = istart; ai < istart + isize; ai++) + if(!(flm->tags[ai] & TAG_ALLOCATED)) { + flm->tags[ai] |= TAG_ALLOCATED; + flm->buf[ai] = spc[ai-istart]; + } + } else { + pmsg_notice2("cannot read flash [0x%04x, 0x%04x] to pad page bytes\n", + istart, istart+isize-1); + } + } + } + } + } + } + } + ur.done_ce = 0; // From now on can no longer rely on being deleted + + // Fill remaining holes (chip was erased, could not be read or memory looks like NOR memory) + int ai, addr, nset; + + for(addr = 0; addr < maxsize; addr += pgsize) { + for(ai = addr, nset = 0; ai < addr + pgsize; ai++) + if(flm->tags[ai] & TAG_ALLOCATED) + nset++; + + if(nset && nset != pgsize) { // Page has holes: fill them + pmsg_debug("0xff padding page addr 0x%04d\n", addr); + for(ai = addr, nset = 0; ai < addr + pgsize; ai++) + if(!(flm->tags[ai] & TAG_ALLOCATED)) { + flm->tags[ai] |= TAG_ALLOCATED; + flm->buf[ai] = 0xff; + } + } + } + + + return size; +} + + +// Put version string into a buffer of max 19 characters incl nul (normally 15-16 bytes incl nul) +static void urbootPutVersion(const PROGRAMMER *pgm, char *buf, uint16_t ver, uint16_t rjmpwp) { + uint8_t hi = ver>>8, type = ver & 0xff, flags; + + if(ver == 0xffff) // Unknown provenance + hi = type = 0; + + if(hi >= 072) { // These are urboot versions + sprintf(buf, "u%d.%d ", hi>>3, hi&7); + buf += strlen(buf); + *buf++ = (hi < 077 && (type & UR_PGMWRITEPAGE)) || (hi >= 077 && rjmpwp != ret_opcode)? 'w': '-'; + *buf++ = type & UR_EEPROM? 'e': '-'; + if(hi >= 076) { // From urboot version 7.6 URPROTOCOL has its own bit + *buf++ = type & UR_URPROTOCOL? 'u': 's'; + *buf++ = type & UR_DUAL? 'd': '-'; + } else { + *buf++ = '-'; // Dummy bit + flags = (type/UR_DUAL) & 3; + // D = Dual boot with SE & SPI restoration, d = dual boot with SE, f = dual boot only + *buf++ = flags==3? 'D': flags==2? 'd': flags? 'f': '-'; + } + flags = (type/UR_VBL) & 3; + // V = VBL, patch & verify, v = VBL, patch only, j = VBL, jump only + *buf++ = flags==3? 'V': flags==2? 'v': flags? 'j': 'h'; + *buf++ = hi < 077? (type & UR_PROTECTME? 'p': '-'): (type & UR_PROTECTME? 'P': 'p'); + *buf++ = (hi < 077 && (type & UR_RESETFLAGS)) || hi >= 077? 'r': '-'; + *buf++ = hi >= 077 && (type & UR_AUTOBAUD)? 'a': '-'; // - means no + *buf++ = hi >= 077 && (type & UR_HAS_CE)? 'c': hi >= 077? '-': '.'; // . means don't know + *buf = 0; + } else if(hi) { // Version number in binary from optiboot v4.1 + sprintf(buf, "o%d.%d -%cs-%c-r--", hi, type, + ur.blguessed? (ur.bleepromrw? 'e': '-'): '?', + ur.blguessed? "hjvV"[ur.vbllevel & 3]: '?'); + } else + sprintf(buf, "x0.0 ........."); + + return; +} + + +// Return name of the vector with number num +static const char *vblvecname(const PROGRAMMER *pgm, int num) { + // This should never happen + if(num < -1 || num > ur.uP.ninterrupts || !ur.uP.isrtable) + return("unknown"); + if(num == -1) + return "none"; + if(num == ur.uP.ninterrupts) + return "VBL_ADDITIONAL_VECTOR"; + return ur.uP.isrtable[num]; +} + + +// Check protocol bytes and read result if needed +static int urclock_res_check(const PROGRAMMER *pgm, const char *funcname, int ignore, + unsigned char *res, int expected) { + + unsigned char chr; + + if(urclock_recv(pgm, &chr, 1) < 0) + return -1; + if(chr != ur.STK_INSYNC) { + pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x in %s()\n", + ur.STK_INSYNC, chr, funcname); + return -1; + } + + // Potentially ignore some initial bytes of the reply + while(ignore--) + if(urclock_recv(pgm, &chr, 1) < 0) + return -1; + + // Read the reply from previous command if requested + if(res && expected > 0) + if(urclock_recv(pgm, res, expected) < 0) + return -1; + + if(urclock_recv(pgm, &chr, 1) < 0) + return -1; + if(chr != ur.STK_OK) { + pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x in %s()\n", + ur.STK_OK, chr, funcname); + return -1; + } + + return 0; +} + + +// set ur.uP from mcuid, potentially overwritten by p +static void set_uP(const PROGRAMMER *pgm, const AVRPART *p, int mcuid, int mcuid_wins) { + int idx_m = -1, idx_p = -1; + + if(mcuid < 0 && !p) // This should never happen + pmsg_warning("cannot set ur.uP as neither mcuid nor part given\n"); + + if(mcuid >= 0) + if((idx_m = upidxmcuid(mcuid)) < 0) + pmsg_warning("uP_table does not know mcuid %d\n", mcuid); + + if(p) { + if(p->mcuid >= 0) + idx_p = upidxmcuid(p->mcuid); + if(idx_p < 0 && p->desc && *p->desc) + idx_p = upidxname(p->desc); + if(idx_p < 0) + pmsg_warning("uP_table does not know mcuid %d nor part %s\n", + p->mcuid, p->desc && *p->desc? p->desc: "???"); + } + + ur.uP.name = NULL; + if(idx_m >= 0 && idx_p >= 0) + ur.uP = uP_table[mcuid_wins? idx_m: idx_p]; + else if(idx_m >= 0) + ur.uP = uP_table[idx_m]; + else if(idx_p >= 0) + ur.uP = uP_table[idx_p]; + + if(!ur.uP.name && p) { // Not found in uP_table? Fill in from p; -1 means unknown + AVRMEM *mem; + + ur.uP.name = p->desc; + ur.uP.mcuid = p->mcuid; + ur.uP.avrarch = + p->prog_modes & PM_UPDI? F_AVR8X: + p->prog_modes & PM_PDI? F_XMEGA: + p->prog_modes & PM_TPI? F_AVR8L: + p->prog_modes & (PM_ISP | PM_HVPP | PM_HVSP)? F_AVR8: 0; + memcpy(ur.uP.sigs, p->signature, sizeof ur.uP.sigs); + if((mem = avr_locate_mem(p, "flash"))) { + ur.uP.flashoffset = mem->offset; + ur.uP.flashsize = mem->size; + ur.uP.pagesize = mem->page_size; + } else { + ur.uP.flashoffset = -1; + ur.uP.flashsize = -1; + ur.uP.pagesize = -1; + } + ur.uP.nboots = -1; + ur.uP.bootsize = -1; + if((mem = avr_locate_mem(p, "eeprom"))) { + ur.uP.eepromoffset = mem->offset; + ur.uP.eepromsize = mem->size; + ur.uP.eeprompagesize = mem->page_size; + } else { + ur.uP.eepromoffset = -1; + ur.uP.eepromsize = -1; + ur.uP.eeprompagesize = -1; + } + ur.uP.sramstart = -1; + ur.uP.sramsize = -1; + ur.uP.nfuses = -1; + ur.uP.nlocks = -1; + ur.uP.ninterrupts = p->n_interrupts; + ur.uP.isrtable = NULL; + } +} + + +// https://en.wikipedia.org/wiki/Jenkins_hash_function +static uint32_t jenkins_hash(const uint8_t* key, size_t length) { + size_t i = 0; + uint32_t hash = 0; + + while (i != length) { + hash += key[i++]; + hash += hash << 10; + hash ^= hash >> 6; + } + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + + return hash; +} + +typedef struct { + uint16_t sz, ee; + uint32_t h256, hash; +} Blhash_t; + +static int cmpblhash(const void *va, const void *vb) { + const Blhash_t *a = va, *b = vb; + return a->sz > b->sz? 1: a->sz < b->sz? -1: a->hash > b->hash? 1: a->hash < b->hash? -1: 0; +} + +static void guessblstart(const PROGRAMMER *pgm, const AVRPART *p) { + if(ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) // Cannot read flash + return; + + Blhash_t blist[] = { + { 1024, 0, 0x35445c45, 0x9ef77953 }, // ATmegaBOOT-prod-firmware-2009-11-07.hex + { 1024, 0, 0x32b1376c, 0xceba80bb }, // ATmegaBOOT.hex + { 2048, 0, 0x08426ba2, 0x29e81e21 }, // ATmegaBOOT_168.hex + { 4096, 0, 0x1bf8ed1b, 0x272e49ed }, // ATmegaBOOT_168_atmega1280.hex + { 2048, 0, 0x9774b926, 0x335016ed }, // ATmegaBOOT_168_atmega328.hex + { 4096, 0, 0x3242ddd3, 0x809632a3 }, // ATmegaBOOT_168_atmega328_bt.hex + { 2048, 0, 0xc553f5b4, 0x56be91cb }, // ATmegaBOOT_168_atmega328_pro_8MHz.hex + { 2048, 0, 0x12ab8da0, 0xca46a3ca }, // ATmegaBOOT_168_diecimila.hex + { 2048, 0, 0x3242ddd3, 0xf3e94dba }, // ATmegaBOOT_168_ng.hex + { 2048, 0, 0x2eed30b3, 0x47d14ffa }, // ATmegaBOOT_168_pro_8MHz.hex + { 4096, 0, 0xc52edd05, 0xa3371f94 }, // Caterina-LilyPadUSB.hex + { 4096, 0, 0x663b8f7e, 0x7efdda2b }, // Caterina-Robot-Control.hex + { 4096, 0, 0x3c6387e7, 0x7e96eea2 }, // Caterina-Robot-Motor.hex + { 2048, 0, 0x1cef0d75, 0x6cfbac49 }, // LilyPadBOOT_168.hex + { 1024, 1, 0x6ca0f37b, 0x31bae545 }, // bigboot_328.hex + { 512, 0, 0x035cbc07, 0x24ba435e }, // optiboot_atmega168.hex + { 512, 0, 0x455050db, 0x1d53065f }, // optiboot_atmega328-Mini.hex + { 512, 0, 0xd2001ddb, 0x16c9663b }, // optiboot_atmega328.hex v4.4 + { 512, 0, 0x49c1e9a4, 0xa450759b }, // optiboot_atmega328.hex v8.3 + { 512, 0, 0xc54dcd6c, 0x5bfc5d06 }, // optiboot_atmega8.hex + { 256, 0, 0x5a01c55b, 0x5a01c55b }, // picobootArduino168.hex + { 256, 0, 0x1451061b, 0x1451061b }, // picobootArduino168v3b2.hex + { 512, 0, 0x3242ddd3, 0x53348738 }, // picobootArduino328.hex + { 512, 0, 0x858e12de, 0xc80a44a4 }, // picobootArduino328v3beta.hex + { 256, 0, 0xaa62bafc, 0xaa62bafc }, // picobootArduino8v3rc1.hex + { 256, 0, 0x56263965, 0x56263965 }, // picobootSTK500-168p.hex + { 512, 0, 0x3242ddd3, 0x5ba5f5f6 }, // picobootSTK500-328p.hex + }; + + uint8_t buf[4096], b128[128]; + + qsort(blist, sizeof blist/sizeof*blist, sizeof*blist, cmpblhash); + for(int ii, si = 0, sz = 0, bi = 0; si < (int) sizeof blist/sizeof*blist; si++) { + if(blist[si].sz > sz) { // Read in and compare + sz = blist[si].sz; + if(sz > ur.uP.flashsize/2 || (sz+127)/128*128 > (int) sizeof buf) + return; + while(bi < sz) { + if(ur_readEF(pgm, p, b128, ur.uP.flashsize-bi-128, 128, 'F') < 0) + return; + for(int ti=127; ti >= 0; ti--) // read in backwards + buf[bi++] = b128[ti]; + } + + // Does the hash for the full size match? OK: found a known bootloader + uint32_t hash = jenkins_hash(buf, sz); + for(ii = 0; ii < (int) sizeof blist/sizeof*blist; ii++) + if(blist[ii].hash == hash && sz == blist[ii].sz && !(sz & (ur.uP.pagesize-1))) { + // Page aligned bootloader size matches + ur.blstart = ur.uP.flashsize-sz; + if(blist[ii].ee) + ur.bleepromrw = 1; + ur.blguessed = 1; + return; + } + + // Can we exclude the top 256 byte flash from the botloader list? + if(sz == 256) { + for(ii = 0; ii < (int) sizeof blist/sizeof*blist; ii++) + if(hash == blist[ii].h256) + break; + if(ii >= (int) sizeof blist/sizeof*blist) + return; + } + } + } +} + + +/* + * Read signature bytes - Urclock version + * + * Piggy back reading urboot specific configuration from chip + * - whether it is an urboot bootloader, if so, + * + which version + * + where the bootloader starts + * + whether it is a vector bootloader, if so which vector is used + * - urclock board ID from EEPROM + */ +static int urclock_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *sigmem) { + uint8_t buf[16]; + int conn_idx; + + // Signature byte reads are always 3 bytes + if(sigmem->size < 3) { + pmsg_error("memsize too small for sig byte read\n"); + return -1; + } + + if(ur.urprotocol) { // Urprotocol's STK_INSYNC/STK_OK have already identified the part + // Got the signature already + memcpy(sigmem->buf, ur.uP.sigs, 3); + } else { + // Have to ask the bootloader directly + buf[0] = Cmnd_STK_READ_SIGN; + buf[1] = Sync_CRC_EOP; + if(urclock_send(pgm, buf, 2) < 0) + return -1; + if(urclock_res_check(pgm, __func__, 0, sigmem->buf, 3) < 0) + return -1; + } + + if(ur.initialised) + return 3; + + if(ovsigck || !ur.uP.name) { // Keep -p ... MCU when given -F or when not initialised + set_uP(pgm, p, -1, 0); + if(!ur.uP.name) + Return("cannot identify MCU from part %s", p->desc); + } else { + // If signatures of command line part and connected part differ complain and return + if(memcmp(sigmem->buf, p->signature, 3)) { + char names[1024]; + + if(ur.urprotocol) + Return("connected part %s differs in signature from -p %s (override with -F or use -p %s)", + ur.uP.name, p->desc, ur.uP.name); + if((conn_idx = upidxsig(sigmem->buf)) == -1) + Return("no uP_table entry from signature %02x %02x %02x (override with -F)", + sigmem->buf[0], sigmem->buf[1], sigmem->buf[2]); + if(upmatchingsig(sigmem->buf, names, sizeof names) == 1) + Return("connected part %s signature does not match -p %s's " + "(override with -F or use -p %s)", + uP_table[conn_idx].name, p->desc, uP_table[conn_idx].name); + Return("connected part's signature %02x%02x%02x is one of %s; neither matches -p %s's " + "(override with -F or use -p ...)", + sigmem->buf[0], sigmem->buf[1], sigmem->buf[2], names, p->desc); + } + } + + return ur_initstruct(pgm, p); +} + +static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { + uint8_t spc[2048]; + AVRMEM *flm; + int rc; + + if(!(flm = avr_locate_mem(p, "flash"))) + Return("cannot obtain flash memory for %s", p->desc); + + if(flm->page_size > (int) sizeof spc) + Return("%s's flash page size %d is too large (enlarge spc[] and recompile)", + p->desc, flm->page_size); + + if(flm->page_size <= 0) + Return("cannot deal with %s's flash page size of %d", p->desc, flm->page_size); + + if(flm->page_size & (flm->page_size-1)) + Return("cannot deal with %s's flash page size %d as not a power of 2", + p->desc, flm->page_size); + + // Bail if command line part and connected part differ in important aspects (should not happen) + if(ur.uP.flashsize != flm->size) + Return("connected %s's flash size 0x%04x differs from -p %s's (0x%04x); " + "use correct -p ... or override with -F", + ur.uP.name, ur.uP.flashsize, p->desc, flm->size); + if(ur.uP.pagesize != flm->page_size) + Return("connected %s's flash page size %d differs from -p %s's (%d); " + "use correct -p ... or override with -F", + ur.uP.name, ur.uP.pagesize, p->desc, flm->page_size); + if(ur.uP.ninterrupts != p->n_interrupts) + Return("connected %s's number %d of interrupts differs from -p %s's (%d); " + "use correct -p ... or override with -F", + ur.uP.name, ur.uP.ninterrupts, p->desc, p->n_interrupts); + + // No urboot bootloaders on AVR32 parts, neither on really small devices + if((p->prog_modes & PM_aWire) || flm->size < 512) + goto alldone; + + ur.blstart = 0; + ur.vbllevel = 0; + ur.vblvectornum = -1; + ur.bleepromrw = 0; + + // Manual provision of above bootloader parameters + if(ur.xbootsize) { + if(ur.xbootsize % ur.uP.pagesize) + Return("-xbootsize=%d size not a multiple of flash page size %d", + ur.xbootsize, ur.uP.pagesize); + if(ur.xbootsize < 64 || ur.xbootsize > urmin(8192, ur.uP.flashsize/4)) + Return("implausible -xbootsize=%d, should be in [64, %d]", + ur.xbootsize, urmin(8192, ur.uP.flashsize/4)); + ur.blstart = ur.uP.flashsize - ur.xbootsize; + } + + 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); + if(ur.xvectornum > 0) { + ur.vbllevel = 1; + ur.vblvectornum = ur.xvectornum; + } + + if(ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) // Bootloader that cannot read flash? + if(!ur.blstart) + Return("please specify -xbootsize= and, if needed, -xvectornum= or -xeepromrw"); + + uint16_t v16 = 0xffff, rjmpwp = ret_opcode; + + // 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, p, spc, flm->size-6, 6, 'F'))) + return rc; + + // In a urboot bootloader (v7.2 onwards) these six are as follows + uint8_t numpags = spc[0]; // Actually, these two only exist from v7.5 onwards + uint8_t vectnum = spc[1]; + rjmpwp = buf2uint16(spc+2); // rjmp to bootloader pgm_write_page() or ret opcode + uint8_t cap = spc[4]; // Capability byte + uint8_t urver = spc[5]; // Urboot version (low three bits are minor version: 076 is v7.6) + v16 = buf2uint16(spc+4); // Combo word for neatly printed version line of urboot bootloader + + // Extensively check this is an urboot bootloader v7.2 .. v12.7 == 0147 and extract properties + if(urver >= 072 && urver <= 0147 && (isRjmp(rjmpwp) || rjmpwp == ret_opcode)) { // Prob urboot + ur.blurversion = urver; + ur.bleepromrw = iseeprom_cap(cap); + // Vector bootloader: 0 = none, 1 = external patching, 2 = bl patches, 3 = patches + verifies + if(!ur.vbllevel) // Unless manually overwritten + ur.vbllevel = vectorbl_level_cap(cap); + if(urver >= 075) { // Urboot v7.5+ encodes # of bootloader pages and vbl vector number + int blsize = numpags*flm->page_size; + // Size of urboot bootloader should be in [64, 2048] (in v7.6 these are 224-512 bytes) + if(blsize >= 64 && blsize <= 2048 && vectnum <= ur.uP.ninterrupts) { // Within range + int dfromend = dist_rjmp(rjmpwp, ur.uP.flashsize) - 4; + // Further check whether writepage() rjmp opcode jumps backwards into bootloader + if(rjmpwp == ret_opcode || (dfromend >= -blsize && dfromend < -6)) { // Due diligence + if(ur.xbootsize) { + if(flm->size - blsize != ur.blstart) { + pmsg_warning("urboot bootloader size %d explicitly overwritten by -xbootsize=%d\n", + blsize, ur.xbootsize); + if(!ovsigck && ur.vbllevel) { + imsg_warning("this can lead to bricking the vector bootloader\n"); + return -1; + } + } + } else + ur.blstart = flm->size - blsize; + + if(ur.xvectornum != -1) { + if(ur.vblvectornum != vectnum) { + pmsg_warning("urboot vector number %d overwritten by -xvectornum=%d\n", + vectnum, ur.xvectornum); + imsg_warning("the application might not start\n"); + } + } else + ur.vblvectornum = vectnum; + } + } + } + } else if(urver != 0xff) { // Probably optiboot where the version number is two bytes + ur.bloptiversion = (urver<<8) + cap; + } + + if(!ur.blstart && ur.vbllevel) { // An older version urboot vector bootloader? + 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, p, spc, 0, 4, 'F'))) + return rc; + + uint16_t reset16 = buf2uint16(spc); + + if(isRjmp(reset16)) { // rjmp op code (could be from a large or a small part) + if((flm->size & (flm->size-1)) == 0) { // Flash size a power of 2? True for small parts + int guess = dist_rjmp(reset16, ur.uP.flashsize); // Relative destination to reset vector + while(guess < 0) // Convert to absolute address + guess += flm->size; + if((guess & (flm->page_size-1)) == 0) // Page aligned? Good + if(flm->size - guess <= 2048) // Accept unless size of bootloader exceeds 2048 bytes + ur.blstart = guess; + } + } else if(vecsz == 4 && isJmp(reset16)) { // Jmp op code + int guess = addr_jmp(buf2uint32(spc)); + if(guess < flm->size) + if((guess & (flm->page_size-1)) == 0) // Page aligned? Good + if(flm->size - guess <= 2048) // Accept unless size of bootloader exceeds 2048 bytes + ur.blstart = guess; + } + + if(ur.blstart && ur.vblvectornum > 0) + goto vblvecfound; + + if(ur.blstart) { // Read bootloader to identify jump to vbl vector + int i, npages, j, n, toend, dist, wasop32, wasjmp, op16; + uint8_t *q; + uint16_t opcode; + + op16 = wasjmp = wasop32 = 0; + toend = flm->size-ur.blstart; // Number of bytes to FLASHEND + npages = toend/flm->page_size; + for(i=0; ipage_size, flm->page_size, 'F'))) + return rc; + for(n=flm->page_size/2, q=spc, j=0; j\n", + ur.bloptiversion>>8, ur.bloptiversion & 255); + Return("unknown bootloader ... please specify -xbootsize=\n"); + } + } + +vblvecfound: + urbootPutVersion(pgm, ur.desc, v16, rjmpwp); + + ur.mcode = 0xff; + if(ur.blstart) { + int nm = nmeta(1, ur.uP.flashsize); // 6 for date + size of store struct + 1 for mcode byte + // 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.showvector || + ur.showpart || ur.showdate || ur.showfilename) { + + if((rc = ur_readEF(pgm, p, spc, ur.blstart-nm, nm, 'F'))) + return rc; + + if(spc[nm-1] != 0xff) { + int32_t storesize = ur.uP.flashsize > (1<<16)? buf2uint32(spc+nm-5): buf2uint16(spc+nm-3); + int32_t storestart = ur.uP.flashsize > (1<<16)? buf2uint32(spc+nm-9): buf2uint16(spc+nm-5); + uint8_t mcode = spc[nm-1]; + int nmdata = nmeta(mcode, ur.uP.flashsize); + + // Check plausibility of metadata header just below bootloader + if(storestart > 0 && storestart == ur.blstart-nmdata-storesize) { + ur.storestart = storestart; + ur.storesize = storesize; + ur.mcode = mcode; + if(mcode) { + int16_t yyyy; + int8_t mm, dd, hr, mn; + mn = spc[5]; + hr = spc[4]; + dd = spc[3]; + mm = spc[2]; + yyyy = buf2uint16(spc); + + // Is the date plausible? carry on; note this won't work after the year 2999 CE :-O + if(yyyy > 0 && yyyy < 3000 && mm > 0 && mm < 13 && dd > 0 && dd < 32 && + hr >= 0 && hr < 24 && mn >= 0 && mn < 60) { + + ur.yyyy = yyyy; + ur.mm = mm; + ur.dd = dd; + ur.hr = hr; + ur.mn = mn; + if(mcode > 1) { // Copy application name over + 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.showvector || 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 || ur.showpart) + term_out(&" %s"[first], ur.uP.name); + if(!first) { + term_out("\n"); + exit(0); + } + +alldone: + + ur.initialised = 1; + return 3; +} + + +// STK500 section from stk500.c but modified significantly for use with urboot bootloaders + +// 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) { + + 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; + int effpgsiz = classic? ur.uP.pagesize/2: ur.uP.pagesize; + + // 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 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; + buf[3] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT); + urclock_cmd(pgm, buf, buf); + ur.ext_addr_byte = ext_byte; + } + /* + * Ensure next paged r/w will reload ext addr if page is just below a 64k boundary + * to iron out a bug in some bootloaders + */ + if((addr & 0xffff0000) != ((addr+effpgsiz) & 0xffff0000)) + ur.ext_addr_byte = 0xff; + } + + buf[0] = Cmnd_STK_LOAD_ADDRESS; + buf[1] = addr & 0xff; + buf[2] = (addr >> 8) & 0xff; + buf[3] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 4) < 0) + return -1; + + return urclock_res_check(pgm, __func__, 0, NULL, 0); +} + + +/* + * Send a paged cmd to device + * - rwop is Cmnd_STK_READ/PROG_PAGE + * - badd is the byte address, len the length of data + * - mchr is 'F' (flash) or 'E' (EEPROM) + * - payload for bytes to write or NULL for read + */ +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 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) { + if(len != ur.uP.pagesize) + Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); + + if(badd < 4U && ur.blstart && ur.vbllevel==1) { + int vecsz = ur.uP.flashsize <= 8192? 2: 4; + unsigned char jmptoboot[4]; + int resetsize = set_reset(pgm, jmptoboot, vecsz); + + if(badd < (unsigned int) resetsize) { // Ensure reset vector points to bl + int n = urmin((unsigned int) resetsize - badd, (unsigned int) len); + int resetdest; + + if(badd == 0 && len >= vecsz) { + if(reset2addr((unsigned char *) payload, vecsz, ur.uP.flashsize, &resetdest) < 0 || + resetdest != ur.blstart) { + + memcpy(payload, jmptoboot, resetsize); + pmsg_info("forcing reset vector to point to vector bootloader\n"); + } + } else if(memcmp(payload, jmptoboot+badd, n)) { + memcpy(payload, jmptoboot+badd, n); + pmsg_info("forcing partial reset vector to point to vector bootloader\n"); + } + } + } + } + + if(ur.urprotocol) { + 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: + mchr == 'E' && rwop == Cmnd_STK_READ_PAGE? Cmnd_UR_READ_PAGE_EE: 0xff; + + if(op == 0xff) + Return("command not recognised"); + + *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) + *q++ = (badd >> 16) & 0xff; + + if(ur.uP.pagesize <= 256) { + if(len > 256) + Return("urprotocol paged r/w len %d cannot exceed 256", len); + *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); + *q++ = len>>8; // Big endian length when needed + *q++ = len; + } + i = q-buf; + + } else { + int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; + if(len > max) + Return("stk500 paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name); + + buf[0] = rwop; + buf[1] = len>>8; // Big endian length when needed + buf[2] = len; + buf[3] = mchr; + i = 4; + } + + if(payload) { // Bytes to write + if(len < 0 || len > (int) sizeof buf - 5) + Return("too small buf[] for len %d (enlarge buf[] and recompile)", len); + memcpy(buf+i, payload, len); + i += len; + } + + buf[i] = Sync_CRC_EOP; + + return urclock_send(pgm, buf, i+1); +} + + +/* + * Read len bytes at byte address addr of EEPROM (mchr == 'E') or flash (mchr == 'F') from + * device fd into buffer buf, using extended addressing if needed (extd); returns 0 on success + */ +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", + (char *) 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"); + + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + Return("bootloader does not %shave EEPROM access capability", ur.blurversion? "": "seem to "); + + if(len < 1 || len > urmax(ur.uP.pagesize, 256)) + Return("len %d exceeds range [1, %d]", len, urmax(ur.uP.pagesize, 256)); + + // 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; + if(len > urmax(ur.uP.pagesize, 256)) + Return("len+1 = %d odd address exceeds range [1, %d]", len, urmax(ur.uP.pagesize, 256)); + } + + 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 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 mchr, addr, len, size; + + 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 + + *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'; + } + + const char *memtype = mchr == 'E'? "eeprom": "flash"; + + size = mchr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; + + if(ur.uP.name && size > 0) { + if(addr < 0) // X.-4.4 asks for 4 bytes at top memory + addr += size; + + 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); + 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--) + *urclockIDp <<= 8, *urclockIDp |= spc[i]; + ur.idlen = len; + + return 0; +} + + +static int urclock_send(const PROGRAMMER *pgm, unsigned char *buf, size_t len) { + return serial_send(&pgm->fd, buf, len); +} + + +static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len) { + int rv; + + rv = serial_recv(&pgm->fd, buf, len); + if(rv < 0) { + if(!ur.sync_silence) + pmsg_warning("programmer is not responding%s\n", ur.uP.name? "": "; try, eg, -xdelay=200"); + return -1; + } + + return 0; +} + + +#define MAX_SYNC_ATTEMPTS 23 + +/* + * The modified protocol makes stk_insync and stk_ok responses variable but fixed for a single + * programming session, so bootloader can pass on en passant 16 bit info about which part it was + * compiled for and some of its own properties. Urlcock_getsync() therefore tries a few times to + * sync until the stk_insync/ok responses coincide with the the most recent responses. + */ +static int urclock_getsync(const PROGRAMMER *pgm) { + unsigned char iob[2]; + int attempt; + + // Reduce timeout for establishing comms + serial_recv_timeout = 80; // ms + + ur.sync_silence = 1; + iob[0] = Cmnd_STK_GET_SYNC; // Initial sync for autobaud - drain response + iob[1] = Sync_CRC_EOP; + urclock_send(pgm, iob, 2); + serial_drain(&pgm->fd, 0); + + for(attempt = 0; attempt < MAX_SYNC_ATTEMPTS; attempt++) { + iob[0] = Cmnd_STK_GET_SYNC; + iob[1] = Sync_CRC_EOP; + urclock_send(pgm, iob, 2); + if(urclock_recv(pgm, iob, 2) == 0) { // Expect bootloader to respond with two bytes + if(!ur.gs.seen || iob[0] != ur.gs.stk_insync || iob[1] != ur.gs.stk_ok || iob[0] == iob[1]) { + ur.gs.stk_insync = iob[0]; + ur.gs.stk_ok = iob[1]; + if(ur.gs.seen) + serial_drain(&pgm->fd, 0); + ur.gs.seen = 1; + } else + break; + } + if(attempt > 2) { // Don't report first three attempts + ur.sync_silence = 0; + pmsg_warning("attempt %d of %d: not in sync\n", attempt - 2, MAX_SYNC_ATTEMPTS-3); + } + } + ur.sync_silence = 0; + + serial_recv_timeout = 500; // ms + + if(attempt == MAX_SYNC_ATTEMPTS) + return -1; + + ur.STK_INSYNC = ur.gs.stk_insync; + ur.STK_OK = ur.gs.stk_ok; + memset(&ur.uP, 0, sizeof ur.uP); + + // One of the STK500 protocol bytes different from ordinary? If so, it's the urboot protocol + if(ur.gs.stk_insync != Resp_STK_INSYNC || ur.gs.stk_ok != Resp_STK_OK) { + // Regain urboot info from stk_insync and stk_ok + if(ur.gs.stk_insync == 255 && ur.gs.stk_ok == 254) { + ur.gs.stk_insync = Resp_STK_INSYNC; + ur.gs.stk_ok = Resp_STK_OK; + } else if(ur.gs.stk_ok > ur.gs.stk_insync) + ur.gs.stk_ok--; + + int16_t bootinfo = ur.gs.stk_insync*255 + ur.gs.stk_ok; + int mcuid = UB_MCUID(bootinfo); + ur.urfeatures = UB_FEATURES(bootinfo); + ur.urprotocol = 1; + + set_uP(pgm, partdesc? locate_part(part_list, partdesc): NULL, mcuid, 1); + if(!ur.uP.name) + Return("cannot identify MCU"); + if(!partdesc) // Provide partdesc info, so user does not have to set it + partdesc = cache_string(ur.uP.name); + } else { + ur.urprotocol = 0; + if(partdesc) { // Initialise uP from command line for now + set_uP(pgm, locate_part(part_list, partdesc), -1, 0); + if(!ur.uP.name) + Return("cannot identify MCU from partdesc %s", partdesc); + } + } + + return 0; +} + + +/* + * The urclock bootloader ignores all but two STK_UNIVERSAL commands (load extended address and + * chip erase) , so only sending these through. Transmits the device command and returns the + * results; 'cmd' and 'res' must point to at least a 4 byte data buffer + */ +static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) { + if(cmd[0] == (Subc_STK_UNIVERSAL_LEXT>>24) || + (cmd[0] == (Subc_STK_UNIVERSAL_CE>>24) && cmd[1] == (uint8_t)(Subc_STK_UNIVERSAL_CE>>16))) { + + unsigned char buf[32]; + + buf[0] = Cmnd_STK_UNIVERSAL; + buf[1] = cmd[0]; + buf[2] = cmd[1]; + buf[3] = cmd[2]; + buf[4] = cmd[3]; + buf[5] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 6) < 0) + return -1; + if(urclock_recv(pgm, buf, 1) < 0) + return -1; + if(buf[0] != ur.STK_INSYNC) { + pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x\n", ur.STK_INSYNC, buf[0]); + return -1; + } + + res[0] = cmd[1]; + res[1] = cmd[2]; + res[2] = cmd[3]; + if(urclock_recv(pgm, &res[3], 1) < 0) + return -1; + + if(urclock_recv(pgm, buf, 1) < 0) + return -1; + if(buf[0] != ur.STK_OK) { + pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x\n", ur.STK_OK, buf[0]); + return -1; + } + + } else { + // All other requests: pretend call happened and all is good, returning 0xff each time + memcpy(res, cmd+1, 3); + res[3] = 0xff; + } + + return 0; +} + + +// Either emulate chip erase or send appropriate command to bootloader +static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { + unsigned char buf[16]; + long bak_timeout = serial_recv_timeout; + + // Set timeout to 20 ms per page as chip erase may take a long time + serial_recv_timeout = ur.uP.pagesize > 2? 500 + ur.uP.flashsize/ur.uP.pagesize * 20: 20000; + + int emulated = 0; + + if(ur.xemulate_ce || + (ur.urprotocol && !(ur.urfeatures & UB_CHIP_ERASE)) || + ur.bloptiversion || (ur.blurversion && ur.blurversion < 076)) { + + // Bootloader does not implement chip erase: don't send command to bootloader + ur.emulate_ce = 1; + emulated = 1; + + } else if(ur.urprotocol) { // Urprotocol uses chip erase command directly + pmsg_notice2("chip erase via urprotocol\n"); + + buf[0] = Cmnd_STK_CHIP_ERASE; + buf[1] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 2) < 0) + return -1; + if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0) + return -1; + + } else { // Legacy bootloaders use universal extension + pmsg_notice2("chip erase via universal STK500v1 command\n"); + + if (pgm->cmd == NULL) { // Should not happen + pmsg_error("%s programmer does not provide a cmd() method\n", pgm->type); + return -1; + } + + memset(buf, 0, sizeof(buf)); + + buf[0] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>24); + buf[1] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>16); + buf[2] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>8); + buf[3] = (uint8_t) (Subc_STK_UNIVERSAL_CE); + + if(urclock_cmd(pgm, buf, buf+4) < 0) + return -1; + } + + serial_recv_timeout = bak_timeout; + ur.done_ce = 1; + + if(!emulated) { // Write jump to boot section to reset vector + if(ur.blstart && ur.vbllevel==1) { + AVRMEM *flm = avr_locate_mem(p, "flash"); + int vecsz = ur.uP.flashsize <= 8192? 2: 4; + if(flm && flm->page_size >= vecsz) { + unsigned char *page = cfg_malloc(__func__, flm->page_size); + memset(page, 0xff, flm->page_size); + set_reset(pgm, page, vecsz); + if(avr_write_page_default(pgm, p, flm, 0, page) < 0) { + free(page); + return -1; + } + free(page); + } + } + } + + return emulated? LIBAVRDUDE_SOFTFAIL: 0; +} + + +// Issue the 'program enable' command to the AVR device +static int urclock_program_enable(const PROGRAMMER *pgm, const AVRPART *p_unused) { + unsigned char buf[16]; + + buf[0] = Cmnd_STK_ENTER_PROGMODE; + buf[1] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 2) < 0) + return -1; + + return urclock_res_check(pgm, __func__, 0, NULL, 0); +} + + +static void urclock_enable(PROGRAMMER *pgm_unused, const AVRPART *p_unused) { + return; +} + + +// Initialise the AVR device and prepare it to accept commands +static int urclock_initialize(const PROGRAMMER *pgm, const AVRPART *p) { + return pgm->program_enable(pgm, p); +} + + +static void urclock_disable(const PROGRAMMER *pgm) { + unsigned char buf[16]; + + buf[0] = Cmnd_STK_LEAVE_PROGMODE; + buf[1] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 2) < 0) + return; + if(urclock_recv(pgm, buf, 1) < 0) + return; + if(buf[0] != ur.STK_INSYNC) { + pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x\n", ur.STK_INSYNC, buf[0]); + return; + } + + if(urclock_recv(pgm, buf, 1) < 0) + return; + if(buf[0] == ur.STK_OK) + return; + + pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x\n", ur.STK_OK, buf[0]); + return; +} + + +static int urclock_open(PROGRAMMER *pgm, const char *port) { + union pinfo pinfo; + + strcpy(pgm->port, port); + pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200; + pinfo.serialinfo.cflags = SERIAL_8N1; + if(serial_open(port, pinfo, &pgm->fd) == -1) + return -1; + + // Clear DTR and RTS to unload the RESET capacitor + serial_set_dtr_rts(&pgm->fd, 0); + usleep(20*1000); // 20 ms is ample for dis/charging the cap from reset to DTR/RTS + // Set DTR and RTS back to high + serial_set_dtr_rts(&pgm->fd, 1); + + if((80+ur.delay) > 0) + usleep((80+ur.delay)*1000); // Wait until board comes out of reset + + // Drain any extraneous input + serial_drain_timeout = 80; // ms + serial_drain(&pgm->fd, 0); + + if(urclock_getsync(pgm) < 0) + return -1; + + return 0; +} + + +static void urclock_close(PROGRAMMER *pgm) { + serial_set_dtr_rts(&pgm->fd, 0); + serial_close(&pgm->fd); + pgm->fd.ifd = -1; + if(ur.bloptiversion) // Optiboot needs a pause between two successive avrdude calls + usleep(200*1000); +} + + +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; + unsigned int n; + + if(n_bytes) { + // Paged writes only valid for flash and eeprom + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) + return -2; + + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + Return("bootloader does not %shave paged EEPROM write capability", + ur.blurversion? "": "seem to "); + + n = addr + n_bytes; + + for(; addr < n; addr += chunk) { + chunk = n-addr < page_size? n-addr: page_size; + + 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; + } + } + + return n_bytes; +} + + +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; + unsigned int n; + + if(n_bytes) { + // Paged reads only valid for flash and eeprom + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) + return -2; + + if(mchr == 'F' && ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) + Return("bootloader does not have flash read capability"); + + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + Return("bootloader does not %shave paged EEPROM read capability", + ur.blurversion? "": "seem to "); + + n = addr + n_bytes; + for(; addr < n; addr += chunk) { + chunk = n-addr < page_size? n-addr: page_size; + + 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; + + if(addr == 0 && mchr == 'F') { // Ensure reset vector points to bl + int vecsz = ur.uP.flashsize <= 8192? 2: 4; + if(chunk >= vecsz && ur.blstart && ur.vbllevel == 1) { + unsigned char jmptoboot[4]; + int resetsize = set_reset(pgm, jmptoboot, vecsz); + int resetdest; + + if(reset2addr(m->buf, vecsz, ur.uP.flashsize, &resetdest) < 0 || resetdest != ur.blstart) { + memcpy(m->buf, jmptoboot, resetsize); + pmsg_info("en passant forcing reset vector to point to vector bootloader\n"); + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_PROG_PAGE, 0, chunk, mchr, (char *) m->buf) < 0) + return -5; + if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0) + return -6; + } + } + } + } + } + + return n_bytes; +} + + +int urclock_write_byte(const PROGRAMMER *pgm_uu, const AVRPART *p_uu, const AVRMEM *mem, + unsigned long addr_uu, unsigned char data_uu) { + + pmsg_error("bootloader does not implement byte-wise write to %s \n", mem->desc); + return -1; +} + +int urclock_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, + unsigned long addr, unsigned char *value) { + + // Byte-wise read only valid for flash and eeprom + int mchr = avr_mem_is_flash_type(mem)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(mem)) { + if(!strcmp(mem->desc, "signature") && pgm->read_sig_bytes) { + if((int) addr < 0 || (int) addr >= mem->size) { + return -1; + } + pgm->read_sig_bytes(pgm, p, mem); + *value = mem->buf[(int) addr]; + return 0; + } + pmsg_error("bootloader cannot read from %s \n", mem->desc); + return -1; + } + + return ur_readEF(pgm, p, value, (uint32_t) addr, 1, mchr); +} + +// Periodic call in terminal mode to keep bootloader alive +static int urclock_term_keep_alive(const PROGRAMMER *pgm, const AVRPART *p_unused) { + unsigned char buf[16]; + + buf[0] = Cmnd_STK_GET_SYNC; + buf[1] = Sync_CRC_EOP; + + if(urclock_send(pgm, buf, 2) < 0) + return -1; + + return urclock_res_check(pgm, __func__, 0, NULL, 0); +} + + +// Display what we know so far (too early in the process to say much) +static void urclock_display(const PROGRAMMER *pgm, const char *p_unused) { + if(ur.urprotocol) { + imsg_info("Urboot protocol for %s\n", ur.uP.name); + } else { + imsg_info("Bootloader using STK500v1 communication protocol\n"); + } + + return; +} + +// End of STK500 section + + +// Return whether an address is write protected +static int urclock_readonly(const struct programmer_t *pgm, const AVRPART *p_unused, + const AVRMEM *mem, unsigned int addr) { + + if(avr_mem_is_flash_type(mem)) { + if(ur.blstart) { + if(addr >= (unsigned int) ur.blstart) + return 1; + if(addr < 512 && ur.vbllevel) { + unsigned int vecsz = ur.uP.flashsize <= 8192? 2u: 4u; + if(addr < vecsz) + return 1; + if(ur.vblvectornum > 0) { + unsigned int appvecloc = ur.vblvectornum*vecsz; + if(addr >= appvecloc && addr < appvecloc+vecsz) + return 1; + } + } + } + } else if(!avr_mem_is_eeprom_type(mem)) + return 1; + + return 0; +} + +static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { + int help = 0; + + struct { + const char *name; + int *optionp; + int nstrbuf; + char *strbuf; + bool assign; + const char *help; + } options[] = { +#define ARG 0, NULL, 1 +#define NA 0, NULL, 0 + {"showall", &ur.showall, NA, "Show all info for connected part and exit"}, + {"showid", &ur.showid, NA, "Show Urclock ID and exit"}, + {"showdate", &ur.showdate, NA, "Show last-modified date of flash application and exit"}, + {"showfilename", &ur.showfilename, NA,"Show filename of last uploaded application and exit"}, + {"showapp", &ur.showapp, NA, "Show application size and exit"}, + {"showstore", &ur.showstore, NA, "Show store size and exit"}, + {"showmeta", &ur.showmeta, NA, "Show metadata size and exit"}, + {"showboot", &ur.showboot, NA, "Show bootloader size and exit"}, + {"showversion", &ur.showversion, NA, "Show bootloader version and capabilities and exit"}, + {"showvector", &ur.showvector, NA, "Show vector bootloader vector # and name and exit"}, + {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg, F.12345.6"}, + {"title", NULL, sizeof ur.title, ur.title, 1, "Title stored and shown in lieu of a filename"}, + {"bootsize", &ur.xbootsize, ARG, "Override/set bootloader size"}, + {"vectornum", &ur.xvectornum, ARG, "Treat bootloader as vector b/loader using this vector"}, + {"eepromrw", &ur.xeepromrw, NA, "Assert bootloader EEPROM read/write capability"}, + {"emulate_ce", &ur.xemulate_ce, NA, "Emulate chip erase"}, + {"restore", &ur.restore, NA, "Restore a flash backup as is trimming the bootloader"}, + {"initstore", &ur.initstore, NA, "Fill store with 0xff on writing to flash"}, + //@@@ {"copystore", &ur.copystore, NA, "Copy over store on writing to flash"}, + {"nofilename", &ur.nofilename, NA, "Do not store filename on writing to flash"}, + {"nodate", &ur.nodate, NA, "Do not store application filename and no date either"}, + {"nometadata", &ur.nometadata, NA, "Do not store metadata at all (ie, no store support)"}, + {"delay", &ur.delay, ARG, "Add delay [ms] after reset, can be negative"}, + {"help", &help, NA, "Show this help menu and exit"}, + }; + + int rc = 0; + for(LNODEID ln = lfirst(extparms); ln; ln = lnext(ln)) { + const char *extended_param = ldata(ln); + size_t i, olen, plen = strlen(extended_param); + + for(i=0; i olen && extended_param[olen] == '=' && options[i].assign) { + const char *arg = extended_param+olen+1; + char *end; + long ret = strtol(arg, &end, 0); + if(*end || end == arg) { + pmsg_error("cannot parse -x%s\n", arg); + return -1; + } + if((int) ret != ret) { + pmsg_error("out of integer range -x%s\n", arg); + return -1; + } + *options[i].optionp = ret; + pmsg_notice2("%s=%d set\n", options[i].name, (int) ret); + break; + } + } else if(options[i].nstrbuf > 0) { + if(plen <= olen || extended_param[olen] != '=') { + pmsg_error("missing argument for option %s=...\n", extended_param); + rc = -1; + } else { + if(options[i].strbuf) { + strncpy(options[i].strbuf, extended_param+olen+1, options[i].nstrbuf-1); + pmsg_notice2("%s=%s set\n", options[i].name, options[i].strbuf); + } + break; + } + } + } + } + if(i >= sizeof options/sizeof*options) { + pmsg_error("invalid extended parameter %s\n", extended_param); + rc = -1; + } + } + + if(help || rc < 0) { + msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id))); + for(size_t i=0; i": "", + urmax(0, 16-(int) 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; +} + + +static void urclock_setup(PROGRAMMER *pgm) { + // Allocate ur + pgm->cookie = cfg_malloc(__func__, sizeof(Urclock_t)); + + ur.xvectornum = -1; // Initialise, to ascertain whether user had set to 0 + ur.ext_addr_byte = 0xff; // So first memory address will load extended address + ur.STK_INSYNC = Resp_STK_INSYNC; + ur.STK_OK = Resp_STK_OK; +} + + +static void urclock_teardown(PROGRAMMER *pgm) { + free(pgm->cookie); + pgm->cookie = NULL; +} + + +const char urclock_desc[] = "Urclock programmer for urboot bootloaders (arduino compatible)"; + +void urclock_initpgm(PROGRAMMER *pgm) { + strcpy(pgm->type, "Urclock"); + + pgm->read_sig_bytes = urclock_read_sig_bytes; + + // Mandatory functions + pgm->initialize = urclock_initialize; + pgm->display = urclock_display; + pgm->enable = urclock_enable; + pgm->disable = urclock_disable; + pgm->program_enable = urclock_program_enable; + pgm->chip_erase = urclock_chip_erase; + pgm->cmd = urclock_cmd; + pgm->open = urclock_open; + pgm->close = urclock_close; + pgm->read_byte = urclock_read_byte; + pgm->write_byte = urclock_write_byte; + + // Optional functions + pgm->paged_write = urclock_paged_write; + pgm->paged_load = urclock_paged_load; + pgm->setup = urclock_setup; + pgm->teardown = urclock_teardown; + pgm->parseextparams = urclock_parseextparms; + pgm->term_keep_alive = urclock_term_keep_alive; + pgm->readonly = urclock_readonly; + pgm->flash_readhook = urclock_flash_readhook; + + disable_trailing_ff_removal(); +#if defined(HAVE_LIBREADLINE) + pmsg_notice2("libreadline is used; avrdude -t -c urclock should work"); +#else + pmsg_warning("compiled without readline library, cannot use avrdude -t -c urclock"); +#endif +} diff --git a/src/urclock.h b/src/urclock.h new file mode 100644 index 00000000..9ce538d6 --- /dev/null +++ b/src/urclock.h @@ -0,0 +1,29 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2022, Stefan Rueger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* $Id$ */ + +#ifndef urclock_h__ +#define urclock_h__ + +extern const char urclock_desc[]; +void urclock_initpgm (PROGRAMMER *pgm); + +#endif + + diff --git a/src/urclock_private.h b/src/urclock_private.h new file mode 100644 index 00000000..fb4471d6 --- /dev/null +++ b/src/urclock_private.h @@ -0,0 +1,156 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2022, Stefan Rueger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* $Id$ */ + +#ifndef urclock_private_h__ +#define urclock_private_h__ + +#include "avrintel.h" + +// EEPROM or flash cache for bytewise access +typedef struct { + int base, size; + char *page, *copy; +} Cache; + + +// STK500v1 protocol constants + +#define Resp_STK_OK 0x10 +#define Resp_STK_INSYNC 0x14 + +#define Sync_CRC_EOP 0x20 + +#define Cmnd_STK_GET_SYNC 0x30 +#define Cmnd_STK_ENTER_PROGMODE 0x50 +#define Cmnd_STK_LEAVE_PROGMODE 0x51 +#define Cmnd_STK_CHIP_ERASE 0x52 +#define Cmnd_STK_LOAD_ADDRESS 0x55 +#define Cmnd_STK_UNIVERSAL 0x56 + +#define Cmnd_STK_PROG_PAGE 0x64 +#define Cmnd_STK_READ_PAGE 0x74 +#define Cmnd_STK_READ_SIGN 0x75 + + +// Urprotocol command extensions to STK500v1 + +#define Cmnd_UR_PROG_PAGE_EE 0x00 +#define Cmnd_UR_READ_PAGE_EE 0x01 + +#define Cmnd_UR_PROG_PAGE_FL 0x02 +#define Cmnd_UR_READ_PAGE_FL 0x03 + + +// STK_UNIVERSAL commands for backward compatibility +#define Subc_STK_UNIVERSAL_LEXT 0x4d000000u // Load extended address +#define Subc_STK_UNIVERSAL_CE 0xac800000u // Chip erase + + +// Urboot protocol side channel info about MCU id and 5 binary bootloader features + +// Number of differnt MCU ids +#define UB_N_MCU 2040 // MCU id 0..2039 + +// 5 bootloader features +#define UB_RESERVED_1 1 +#define UB_RESERVED_2 2 +#define UB_READ_FLASH 4 // Bootloader can read flash +#define UB_FLASH_LL_NOR 8 // Bootloader flash programming looks like a NOR memory +#define UB_CHIP_ERASE 16 // Bootloader has a flash-only chip erase that protects itself + +#define UB_INFO(ub_features, ub_mcuid) (ub_features*UB_N_MCU + ub_mcuid) +#define UB_FEATURES(ub_info) ((uint16_t)(ub_info)/UB_N_MCU) +#define UB_MCUID(ub_info) ((uint16_t)(ub_info)%UB_N_MCU) + + +/* + * Urboot layout of top six bytes + * + * FLASHEND-5: numblpags, only from v7.5: 1 byte number 1..127 of bootloader flash pages + * FLASHEND-4: vblvecnum, only from v7.5: 1 byte vector number 1..127 for vector bootloader + * FLASHEND-3: 2 byte rjmp opcode to bootloader pgm_write_page(sram, flash) or ret opcode + * FLASHEND-1: capability byte of bootloader + * FLASHEND-0: version number of bootloader: 5 msb = major version, 3 lsb = minor version + */ + +// Capability byte of bootloader from version 7.2 onwards +#define UR_PGMWRITEPAGE 128 // pgm_write_page() can be called from application at FLASHEND+1-4 +#define UR_AUTOBAUD 128 // Bootloader has autobaud detection (from v7.7) +#define UR_EEPROM 64 // EEPROM read/write support +#define UR_URPROTOCOL 32 // Bootloader uses urprotocol that requires avrdude -c urclock +#define UR_DUAL 16 // Dual boot +#define UR_VBLMASK 12 // Vector bootloader bits +#define UR_VBLPATCHVERIFY 12 // Patch reset/interrupt vectors and show original ones on verify +#define UR_VBLPATCH 8 // Patch reset/interrupt vectors only (expect an error on verify) +#define UR_VBL 4 // Merely start application via interrupt vector instead of reset +#define UR_NO_VBL 0 // Not a vector bootloader, must set fuses to HW bootloader support +#define UR_PROTECTME 2 // Bootloader safeguards against overwriting itself +#define UR_RESETFLAGS 1 // Load reset flags into register R2 before starting application +#define UR_HAS_CE 1 // Bootloader has Chip Erase (from v7.7) + +#define verbyte_cv(capver) ((uint8_t) ((uint16_t) (capver) >> 8)) +#define hascapbyte_cv(capver) ({ uint8_t _vh = verbyte_cv(capver); _vh >= 072 && _vh != 0xff; }) +#define hasextendedv_cv(capver) ({ uint8_t _vh = verbyte_cv(capver); _vh >= 075 && _vh != 0xff; }) +#define capabilities_cv(capver) ({ uint16_t _vc = capver; \ + (uint8_t) (hascapbyte_cv(_vc)? _vc&0xff: 0); }) +#define vblvecnum_cv(capver) ({ uint16_t _vc = capver; \ + (uint8_t) (hasextendedv_cv(_vc)? pgm_read_b1(FLASHEND-4): 0); }) +#define numblpages_cv(capver) ({ uint16_t _vc = capver; \ + (uint8_t) (hasextendedv_cv(_vc)? pgm_read_b1(FLASHEND-5): 0); }) +#define blurversion_cv(capver) ({ uint8_t _vh = verbyte_cv(capver); \ + (uint8_t) (_vh >= 072 && _vh != 0xff? _vh: 0); }) + +#define vercapis(capver, mask) ({ uint16_t _vi = capver; !!(capabilities_cv(_vi) & (mask)); }) +#define ispgmwritepage_cv(capver) vercapis(capver, UR_PGMWRITEPAGE) // up to v7.6 +#define isautobaud_cv(capver) vercapis(capver, UR_AUTOBAUD) // from v7.7 +#define iseeprom_cv(capver) vercapis(capver, UR_EEPROM) +#define isurprotocol_cv(capver) vercapis(capver, UR_URPROTOCOL) +#define isdual_cv(capver) vercapis(capver, UR_DUAL) +#define isvectorbl_cv(capver) vercapis(capver, UR_VBLMASK) +#define isprotectme_cv(capver) vercapis(capver, UR_PROTECTME) +#define isresetflags_cv(capver) vercapis(capver, UR_RESETFLAGS) // up to v7.6 +#define ishas_ce_cv(capver) vercapis(capver, UR_HAS_CE) // from v7.7 + +// Capability bits incl position +#define pgmwritepage_bit_cap(cap) ((cap) & UR_PGMWRITEPAGE) // up to v7.6 +#define autibaud_bit_cap(cap) ((cap) & UR_AUTOBAUD) // from v7.7 +#define eeprom_bit_cap(cap) ((cap) & UR_EEPROM) +#define dual_bit_cap(cap) ((cap) & UR_DUAL) +#define vector_bits_cap(cap) ((cap) & UR_VBLMASK)) +#define protectme_bit_cap(cap) ((cap) & UR_PROTECTME) +#define urprotocol_bit_cap(cap) ((cap) & UR_URPROTOCOL) +#define resetflags_bit_cap(cap) ((cap) & UR_RESETFLAGS) // up to v7.6 +#define has_ce_bit_cap(cap) ((cap) & UR_HAS_CE) // from v7.7 + +// Boolean capabilities +#define ispgmwritepage_cap(cap) (!!((cap) & UR_PGMWRITEPAGE)) // up to v7.6 +#define isautobaud_cap(cap) (!!((cap) & UR_AUTOBAUD)) // from v7.7 +#define iseeprom_cap(cap) (!!((cap) & UR_EEPROM)) +#define isdual_cap(cap) (!!((cap) & UR_DUAL)) +#define isvectorbl_cap(cap) (!!((cap) & UR_VBLMASK))) +#define isprotectme_cap(cap) (!!((cap) & UR_PROTECTME)) +#define isurprotocol_cap(cap) (!!((cap) & UR_URPROTOCOL)) +#define isresetflags_cap(cap) (!!((cap) & UR_RESETFLAGS)) // up to v7.6 +#define ishas_ce_cap(cap) (!!((cap) & UR_HAS_CE)) // from v7.7 + +// Capability levels 0, 1, 2 or 3 +#define vectorbl_level_cap(cap) (((cap) & UR_VBLMASK)/UR_VBL) + +#endif