Merge pull request #2090 from stefanrueger/dryboot

Make `-c dryboot` emulate `-c urclock -x nometadata`
This commit is contained in:
Stefan Rueger
2026-04-05 18:55:51 +01:00
committed by GitHub
10 changed files with 424 additions and 71 deletions

View File

@@ -1600,7 +1600,9 @@ versions of the bootloader.
.Bl -tag -offset indent -width indent
.It Ar dryrun
.It Ar dryboot
These two programmers emulate programming and accept the following parameters:
Dryrun emulates external programming without the need to connect a
programmer or a part while dryboot emulates bootloader programming without
the need to connect the target part. They accept the following parameters:
.Bl -tag -offset indent -width indent
.It Ar init
Initialise memories with human-readable patterns. Flash memory will be

View File

@@ -149,6 +149,8 @@ eeprom, programming fuse/lock bits, etc.
@cindex Programmers supported
@cindex Emulating a HW programmer (dryrun)
@cindex Emulating a bootloader (dryboot)
@cindex dryrun
@cindex dryboot
Programming a microcontroller either requires a physical programmer that
sits between the target chip and the PC running AVRDUDE, or a bootloader
@@ -1339,7 +1341,9 @@ extended parameters to be specified on the command line.
@item dryrun
@itemx dryboot
Both dryrun and dryboot programmers emulate programming and accept the following parameters:
Dryrun emulates external programming without the need to connect a
programmer or a part while dryboot emulates bootloader programming without
the need to connect the target part. They accept the following parameters:
@table @code
@cindex @code{flash}
@@ -4574,6 +4578,92 @@ L7ffe: .byte 0xe6, 0x40 ; _@
@end smallexample
@page
@noindent
It is noteworthy that @code{-c dryboot} allows uploading of one urboot
bootloader and, from that point onwards, emulates @code{-c urclock -x
nometadata}. This means, eg, @code{-c dryboot} will not allow the
bootloader be overwritten:
@cindex Emulating a bootloader (dryboot)
@cindex Emulating a HW programmer (dryrun)
@cindex Vector table
@cindex Patching the vector table
@cindex dryrun
@cindex dryboot
@cindex urclock
@smallexample
@cartouche
$ avrdude -q -c dryboot -p m328p -U urboot:autobaud_ee -U blink.hex \
-T "write flash 0x7f80 0x00"
Processing -U flash:w:urboot:autobaud_ee:i
Reading 376 bytes for flash from input file urboot:autobaud_ee
Writing 376 bytes to flash, 376 bytes written, 376 verified
Setting fuses for bootloader urboot:autobaud_ee
Detected urboot bootloader u8.0 weU-jPra- in [0x7e80, 0x7fff] with vector=25
Processing -U flash:w:blink.hex:i
Reading 354 bytes for flash from input file blink.hex
Writing 354 bytes to flash, 354 bytes written, 354 verified
Processing -T write flash 0x7f80 0x00
Warning: (write) programmer write protects flash address 0x7f80
Avrdude done. Thank you.
@end cartouche
@end smallexample
@noindent
More importantly, @code{-c dryboot} patches the vector table of any
to-be-uploaded sketch if the previously installed bootloader is an urboot
vector bootloader just like @code{avrdude -c urclock -x nometadata} would;
this is needed for the vector bootloader to work (for details, see the
@url{https://github.com/stefanrueger/urboot} project):
@smallexample
@cartouche
$ avrdude -qq -c dryboot -p m328p -U urboot:autobaud_ee -U blink.hex \
-T "disasm flash 0 2" -T "disasm flash 100 4"
L0000: 3f cf rjmp .-386 ; L7e80
L0064: 0c 94 34 00 jmp 0x0068
@end cartouche
@end smallexample
@noindent
In contrast, using @code{-c dryrun} does not patch the vector table of a
sketch as it emulates and behaves like an external programmer:
@smallexample
@cartouche
$ avrdude -qq -c dryrun -p m328p -U urboot:autobaud_ee -U blink.hex \
-T "disasm flash 0 2" -T "disasm flash 100 2"
L0000: 33 c0 rjmp .+102 ; L0068
L0064: 11 c0 rjmp .+34 ; L0088
@end cartouche
@end smallexample
@noindent
The following example prepares a patched sketch for upload through a
self-written uploader or through urboot's dual boot. Note that @code{385}
is the size of the bootloader plus 1 and that the generated file
@code{blink-patched.hex} has the size of flash minus 384. In absence of
the option @code{-A} AVRDUDE drops all trailing 0xff from the patched
sketch.
@cindex Dual boot
@smallexample
@cartouche
$ avrdude -qq -c dryboot -p m328p -U urboot:autobaud_ee -U blink.hex \
-T "save flash 0 -385 blink-patched.hex:I"
@end cartouche
@end smallexample
@page
@c

View File

@@ -45,7 +45,7 @@
// Context of the programmer
typedef enum {
DRY_NOBOOTLOADER, // No bootloader, taking to an ordinary programmer
DRY_NOBOOTLOADER, // No bootloader, talking to an ordinary programmer
DRY_TOP, // Bootloader and it sits at top of flash
DRY_BOTTOM, // Bootloader sits at bottom of flash (UPDI parts)
} Dry_prog;
@@ -61,10 +61,17 @@ typedef struct {
int datastart, datasize; // Start and size of application data section (if any)
int bootstart, bootsize; // Start and size of boot section (if any)
int initialised; // 1 once the part memories are initialised
struct {
int vectornum; // Vector bootloader vector number for jump to application op code
int urversion; // Octal byte 076 means v7.6 (minor version number is lowest 3 bit)
int32_t blstart, blend; // Bootloader address range [blstart, blend] for write protection
int32_t pfstart, pfend; // Programmable flash address range [pfstart, pfend]
} urdesc;
} Dryrun_data;
// Use private programmer data as if they were a global structure dry
#define dry (*(Dryrun_data *)(pgm->cookie))
#define ur (dry.urdesc)
#define Return(...) do { pmsg_error(__VA_ARGS__); msg_error("\n"); return -1; } while(0)
#define Retwarning(...) do { pmsg_warning(__VA_ARGS__); \
@@ -72,6 +79,216 @@ typedef struct {
static int dryrun_readonly(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned int addr);
// Initialise urboot descriptor once an urboot bootloader is detected
static int dryrun_init_ur(const PROGRAMMER *pgm, const AVRPART *p) {
// The first urboot bootloader detection freezes the parameters
if(ur.urversion)
return 0;
uint8_t top[6];
const AVRMEM *flm = avr_locate_flash(p);
if(!flm)
Return("cannot locate flash memory for %s\n", p->desc);
// No urboot bootloaders on AVR32 parts, neither on really small devices
if(is_awire(p) || flm->size < 1024)
return 0;
if(!is_updi(p)) {
// Check top 6 bytes from flash to obtain intell about bootloader and type
for(int i = sizeof top - 1; i >= 0; i--) {
if(pgm->read_byte(pgm, p, flm, flm->size - sizeof top + i, top + i) < 0)
return -1;
// Abort if last byte in flash does not indicate urboot v7.5 ... v12.7 == 0147
if(i == sizeof top - 1 && (top[i] < 075 || top[i] > 0147))
return 0;
}
uint8_t numpags = top[0] & 0x7f; // Number of bootloader pages from v7.5
uint8_t vectnum = top[1] & 0x7f; // Vector number for application start from v7.5
uint16_t rjmpwp = buf2uint16(top+2); // rjmp to bootloader pgm_write_page() or ret
// uint8_t cap = top[4]; // Capability byte not needed
uint8_t urver = top[5]; // Urboot version; low 3 bits = minor version: 076 = v7.6
// Could be urboot bootloader v7.5 .. v12.7: check further properties
if(isop(rjmpwp, rjmp) || isop(rjmpwp, ret)) { // OK, valid rjmpwp opcode
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 <= p->n_interrupts) { // Within range
int dfromend = dist_rjmp(rjmpwp, flm->size) - 4;
// Further check whether writepage() rjmp opcode jumps backwards into bootloader
if(isop(rjmpwp, ret) || (dfromend >= -blsize && dfromend < -6)) { // urboot!
ur.blstart = flm->size - blsize;
ur.blend = flm->size - 1;
ur.pfstart = 0;
ur.pfend = ur.blstart - 1;
ur.vectornum = vectnum;
ur.urversion = urver;
}
}
}
} else { // @@@ Fixme: todo when UPDI urboot bootloaders available
}
if(ur.urversion) {
char buf[20];
urbootPutVersion(buf, (uint16_t *) top);
pmsg_info("detected urboot bootloader %s in [0x%04x, 0x%04x] with vector=%d\n",
buf, ur.blstart, ur.blend, ur.vectornum);
}
return 0;
}
// At the beginning of every terminal command
static int dryrun_cmdhook(const PROGRAMMER *pgm, const AVRPART *p, int argc_uu, const char *argv_uu[]) {
return dryrun_init_ur(pgm, p);
}
// At the beginning of every -U command and during execution of terminal backup, restore and verify
static int dryrun_updatehook(const PROGRAMMER *pgm, const AVRPART *p, const UPDATE *upd_uu, int flags_uu) {
return dryrun_init_ur(pgm, p);
}
// Called after the input file has been read for writing or verifying flash
static int dryrun_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm,
const char *fname_uu, int size) {
int maxsize = ur.pfend+1, firstbeg, firstlen;
const int vecsz = flm->size <= 8192? 2: 4; // Small parts use rjmp, large a 4-byte jmp
// 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;
// Sanity: no patching if bootloader location is unknown
if(ur.blend <= ur.blstart)
goto nopatch;
// Sanity check the bootloader position
if(ur.blstart < 0 || ur.blstart >= flm->size || ur.blend < 0 || ur.blend >= flm->size)
Return("bootloader [0x%04x, 0x%04x] outside flash [0, 0x%04x]",
ur.blstart, ur.blend, flm->size-1);
// Check size of uploded application and protect bootloader from being overwritten
if((!is_updi(p) && size > maxsize) || (is_updi(p) && firstbeg <= ur.blend))
Return("input [0x%04x, 0x%04x] overlaps b/loader [0x%04x, 0x%04x]",
firstbeg, size-1, ur.blstart, ur.blend);
if(size > maxsize)
Return("input [0x%04x, 0x%04x] extends programmable area [0x%04x, 0x%04x]",
firstbeg, size-1, ur.pfstart, ur.pfend);
if(is_updi(p))
goto nopatch;
bool llcode = firstbeg == 0 && firstlen > p->n_interrupts*vecsz; // Looks like code
bool llvectors = firstbeg == 0 && firstlen >= p->n_interrupts*vecsz; // Looks like vector table
for(int i = 0; llvectors && i < p->n_interrupts*vecsz; i += vecsz) {
uint16_t op16 = buf2uint16(flm->buf+i);
if(!isop(op16, rjmp) && !(vecsz == 4 && isop(op16, jmp)))
llvectors = 0;
}
if(llcode && !llvectors && ur.vectornum > 0)
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.vectornum > 0) {
uint16_t reset16;
int reset32, appstart, appvecloc;
appvecloc = ur.vectornum*vecsz; // Location of jump-to-application in vector table
reset16 = buf2uint16(flm->buf); // First reset word of to-be-written 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
*/
if(reset2addr(flm->buf, vecsz, flm->size, &appstart) < 0) {
pmsg_warning("not patching input as opcode word %04x at reset is not r/jmp\n", reset16);
goto nopatch;
}
// Only patch if appstart does not already point to the bootloader
if(appstart != ur.blstart) {
int vectorsend = vecsz*ur.vectornum;
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_resetvector(ur.blstart, flm->size, flm->buf+0, vecsz, 1);
if(vecsz == 4)
uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart));
else
uint16tobuf(flm->buf+appvecloc, rjmp_opcode(appstart - appvecloc, flm->size));
}
}
nopatch:
// Ensure that vector bootloaders have correct r/jmp at address 0
if(!is_updi(p) && ur.blstart && ur.vectornum > 0) {
int resetdest, 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_resetvector(ur.blstart, flm->size, jmptoboot, vecsz, 1);
if(set != vecsz) {
unsigned char device[4];
// Read reset vector from device flash
for(int i = 0; i < vecsz; i++)
if(pgm->read_byte(pgm, p, flm, i, device+i) < 0)
return -1;
// 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 if(firstbeg < vecsz) { // Double-check reset vector jumps to bootloader
if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0)
Return("input would overwrite the reset vector bricking the bootloader\n"
" using -F will try to patch the input but this may not be what is needed");
if(resetdest != ur.blstart)
Return("input points reset to 0x%04x, not to bootloader at 0x%04x\n"
" using -F will try to patch the input but this may not be what is needed",
resetdest, ur.blstart);
}
}
return size;
}
// Read expected signature bytes from part description
static int dryrun_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *sigmem) {
pmsg_debug("%s()", __func__);
@@ -515,6 +732,10 @@ static void dryrun_enable(PROGRAMMER *pgm, const AVRPART *p) {
AVRMEM *m, *fusesm = NULL, *prodsigm = NULL, *calm;
AVRPART *q = dry.dp = avr_dup_part(p); // Allocate dryrun part and abbreviate with q
// Initialise urboot descriptor so that all flash is programmable and there is no bootloader
if((m = avr_locate_flash(p)))
ur.pfend = m->size-1;
memset(inifuses, 0xff, sizeof inifuses);
srandom(dry.seed? dry.seed: time(NULL));
@@ -777,9 +998,26 @@ static int dryrun_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVR
Return("cannot write page [0x%04x, 0x%04x] to %s %s as it is incompatible with memory [0, 0x%04x]",
addr, end - 1, dry.dp->desc, dmem->desc, dmem->size - 1);
// Protect reset vector just as -c urclock would
if(dry.bl == DRY_TOP && ur.vectornum > 0 && (mem_is_application(m) || mem_is_flash(m)) && addr == 0)
for(unsigned vecsz = m->size <= 8192? 2u: 4u, i = 0; i < vecsz && i < n_bytes; i++)
pgm->read_byte(pgm, p, dmem, i, m->buf+i);
for(; addr < end; addr += chunk) {
chunk = end - addr < page_size? end - addr: page_size;
// @@@ Check for bootloader write protection here
// Silently skip writing the chunk if that were to overwrite bootloader
if(dry.bl && mchr == 'F' && !mem_is_apptable(m) && ur.blend > ur.blstart) {
const AVRMEM *am;
int testa = addr;
// Translate XMEGA boot addresses to flash addresses
if(is_pdi(p) && mem_is_boot(m) && (am = avr_locate_application(p)))
testa += am->size;
if(testa >= ur.blstart && testa+chunk-1 <= ur.blend)
continue;
}
// Unless it is a bootloader flash looks like NOR-memory
(mchr == 'F' && !dry.bl? memand: memcpy) (dmem->buf + addr, m->buf + addr, chunk);
@@ -953,7 +1191,6 @@ static void dryrun_display(const PROGRAMMER *pgm, const char *p_unused) {
// Return whether an address is write protected
static int dryrun_readonly(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned int addr) {
if(mem_is_readonly(mem))
return 1;
@@ -963,7 +1200,27 @@ static int dryrun_readonly(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM
return 0;
}
// @@@ check for bootloader write protection
// Bootloader
if(mem_is_in_flash(mem) && !mem_is_apptable(mem)) {
const AVRMEM *m;
// Translate XMEGA boot addresses to flash addresses
if(is_pdi(p) && mem_is_boot(mem) && (m = avr_locate_application(p)))
addr += m->size;
if(addr > (unsigned int) ur.pfend)
return 1;
if(addr < (unsigned int) ur.pfstart)
return 1;
// Protect reset vector once vector bootloader detected
if(addr < 4 && !is_updi(p) && ur.vectornum > 0)
if(addr < ((m = avr_locate_flash(p)) && m->size <= 8192? 2u: 4u))
return 1;
}
/* // Below is too realistic as it precludes -U urboot: fuse settings
* else if(is_classic(p) && !mem_is_eeprom(mem))
* return 1;
*/
if(dry.initialised && (mem_is_in_fuses(mem) || mem_is_lock(mem)))
return 1;
@@ -1074,4 +1331,9 @@ void dryrun_initpgm(PROGRAMMER *pgm) {
pgm->term_keep_alive = dryrun_term_keep_alive;
pgm->readonly = dryrun_readonly;
pgm->parseextparams = dryrun_parseextparams;
if(is_spm(pgm)) {
pgm->flash_readhook = dryrun_flash_readhook;
pgm->updatehook = dryrun_updatehook;
pgm->cmdhook = dryrun_cmdhook;
}
}

View File

@@ -981,6 +981,8 @@ typedef struct {
unsigned long ms[LED_N]; // Time in ms after last physical change
} Leds;
typedef struct update UPDATE;
/*
* Any changes in PROGRAMMER, please also ensure changes are made in
* - lexer.l
@@ -1077,6 +1079,8 @@ typedef struct programmer {
void (*teardown)(PROGRAMMER *pgm);
int (*flash_readhook)(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm,
const char *fname, int size);
int (*updatehook)(const PROGRAMMER *pgm, const AVRPART *p, const UPDATE *upd, int flags);
int (*cmdhook)(const PROGRAMMER *pgm, const AVRPART *p, int argc, const char *argv[]);
// Cached r/w API for terminal reads/writes
int (*write_byte_cached)(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
@@ -1767,6 +1771,9 @@ extern "C" {
char *avr_cc_buffer(size_t n);
// Shortcut for testing whether a 16-bit opcode is a certain mnemonic (rjmp, ret, ...)
#define isop(op16, code) op16_is_mnemo(op16, MNEMO_ ## code)
int op16_is_mnemo(int op16, AVR_mnemo mnemo);
int is_opcode32(int op16);
int op_width(int op16);
@@ -1797,6 +1804,9 @@ extern "C" {
uint16_t buf2uint16(const unsigned char *buf);
void uint32tobuf(unsigned char *buf, uint32_t opcode32);
void uint16tobuf(unsigned char *buf, uint16_t opcode16);
int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int *addrp);
int set_resetvector(int blstart, int flsize, uint8_t *reset, int vecsz, int isur);
void urbootPutVersion(char *buf, uint16_t *top6table);
const Uart_conf *getuartsigs(const Avrintel *up, int uart, int alt);
int urbootfuses(const PROGRAMMER *pgm, const AVRPART *part, const char *filename);

View File

@@ -119,6 +119,8 @@ void pgm_init_functions(PROGRAMMER *pgm) {
pgm->parseextparams = NULL;
pgm->readonly = NULL;
pgm->flash_readhook = NULL;
pgm->updatehook = NULL;
pgm->cmdhook = NULL;
}
PROGRAMMER *pgm_new(void) {

View File

@@ -2762,6 +2762,9 @@ static int do_cmd(const PROGRAMMER *pgm, const AVRPART *p, int argc, const char
int hold, matches;
size_t len;
if(pgm->cmdhook)
pgm->cmdhook(pgm, p, argc, argv);
len = strlen(argv[0]);
matches = 0;
for(int i = 0; i < NCMDS; i++)

View File

@@ -640,6 +640,9 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, const UPDATE *upd, enum updat
Filestats fs;
const char *umstr = upd->memstr;
if(pgm->updatehook)
pgm->updatehook(pgm, p, upd, flags);
if(!(flags & UF_NOHEADING)) {
char *heading = update_str(upd);

View File

@@ -9,8 +9,8 @@
* Published under GNU General Public License, version 3 (GPL-3.0)
* Meta-author Stefan Rueger <stefan.rueger@urclocks.com>
*
* v 1.3
* 09.01.2026
* v 1.31
* 02.04.2026
*
*/
@@ -273220,8 +273220,8 @@ static int urlistsearch(const void *p1, const void *p2) {
return ((Ul_urlist *) p1)->n - ((Ul_urlist *) p2)->n;
}
// Put version string into a buffer of max 16 characters incl nul; must be u8.0
static void urbootPutVersion(char *buf, uint16_t *vertable) {
// Put version string into a buffer of max 16 characters incl nul; must be u8.0+
static void urbootPutVersion8(char *buf, uint16_t *vertable) {
uint16_t ver = vertable[2], rjmpwp = vertable[1];
uint8_t hi = ver>>8, type = ver & 0xff, flags;
@@ -273449,7 +273449,7 @@ Urboot_template **urboottemplate(const Avrintel *up, const char *mcu, const char
features |= URFEATURE_U4;
ret[n]->features = features;
urbootPutVersion(ret[n]->urversion, ret[n]->table);
urbootPutVersion8(ret[n]->urversion, ret[n]->table);
if(features & URFEATURE_HW) {
strcpy(ret[n]->type, "hardware-supported");

View File

@@ -9,8 +9,8 @@
* Published under GNU General Public License, version 3 (GPL-3.0)
* Meta-author Stefan Rueger <stefan.rueger@urclocks.com>
*
* v 1.3
* 09.01.2026
* v 1.31
* 02.04.2026
*
*/

View File

@@ -345,15 +345,6 @@ static int nmeta(int mcode, int flashsize) {
// 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 [.-4096, .+4094]
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.
@@ -415,12 +406,6 @@ int addr_jmp(uint32_t jmp) {
}
// 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
uint32_t buf2uint32(const unsigned char *buf) {
return buf[0] | buf[1]<<8 | buf[2]<<16 | buf[3]<<24;
@@ -488,18 +473,17 @@ static void set_date_filename(const PROGRAMMER *pgm, const char *fname) {
}
// Put destination address of reset vector jmp or rjmp into addr, return -1 if not an r/jmp
static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int *addrp) {
int 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)) {
if(vecsz == 4 && isop(op16, jmp)) {
addr = addr_jmp(op32); // Accept compiler's destination (do not normalise)
} else if(isRjmp(op16)) { // rjmp might be generated for larger parts, too
} else if(isop(op16, rjmp)) { // 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
@@ -514,30 +498,29 @@ static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int
return rc;
}
// Can a rjmp at 0 reach the bootloader in a large part?
static int rjmp_reaches_blstart(const PROGRAMMER *pgm) {
if(ur.uP.flashsize & (ur.uP.flashsize-1)) // Only if flash is a power of 2
// Can a rjmp at reset location 0 reach the bootloader in a large part?
static int rjmp_reaches_blstart(int blstart, int flsize) {
if(flsize & (flsize-1)) // No as flash is not a power of 2
return 0;
return ur.blstart <= 4096 || ur.blstart >= ur.uP.flashsize - 4094;
return blstart <= 4096 || blstart >= flsize - 4094;
}
// What reset looks like for vector bootloaders
static int set_reset(const PROGRAMMER *pgm, unsigned char *jmptoboot, int vecsz) {
int set_resetvector(int blstart, int flsize, uint8_t *reset, int vecsz, int isur) {
// Small part or larger flash that is power or 2: urboot P reset vector protection uses this
if(vecsz == 2 || rjmp_reaches_blstart(pgm)) {
uint16tobuf(jmptoboot, rjmp_bwd_blstart(ur.blstart, ur.uP.flashsize));
if(ur.urprotocol && vecsz == 4) {
uint16tobuf(jmptoboot + 2, 0x7275 /* ur */);
if(vecsz == 2 || rjmp_reaches_blstart(blstart, flsize)) {
uint16tobuf(reset, rjmp_bwd_blstart(blstart, flsize));
if(isur && vecsz == 4) {
uint16tobuf(reset + 2, 0x7275 /* ur */);
return 4;
}
return 2;
}
uint32tobuf(jmptoboot, jmp_opcode(ur.blstart));
uint32tobuf(reset, jmp_opcode(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) { // Size is max memory address + 1
@@ -628,7 +611,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const
bool llvectors = firstbeg == 0 && firstlen >= ur.uP.ninterrupts*vecsz; // Looks like vector table
for(int i=0; llvectors && i<ur.uP.ninterrupts*vecsz; i+=vecsz) {
uint16_t op16 = buf2uint16(flm->buf+i);
if(!isRjmp(op16) && !(vecsz == 4 && isJmp(op16)))
if(!isop(op16, rjmp) && !(vecsz == 4 && isop(op16, jmp)))
llvectors = 0;
}
@@ -677,7 +660,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const
}
// OK, now have bootloader start and application start: patch
set_reset(pgm, flm->buf+0, vecsz);
set_resetvector(ur.blstart, ur.uP.flashsize, flm->buf+0, vecsz, ur.urprotocol);
if(vecsz == 4)
uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart));
else
@@ -777,7 +760,7 @@ nopatch_nometa:
// 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);
int resetsize = set_resetvector(ur.blstart, ur.uP.flashsize, jmptoboot, vecsz, ur.urprotocol);
if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable?
int resetdest;
@@ -827,7 +810,7 @@ nopatch_nometa:
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
// Scan memory for effective pages with unset bytes and read these bytes from device flash
int ai, npe, addr, nset;
uint8_t spc[2048];
@@ -907,7 +890,7 @@ nopatch_nometa:
// 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) {
static void allbootPutVersion(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
@@ -916,7 +899,7 @@ static void urbootPutVersion(const PROGRAMMER *pgm, char *buf, uint16_t ver, uin
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++ = (hi < 077 && (type & UR_PGMWRITEPAGE)) || (hi >= 077 && !isop(rjmpwp, ret))? 'w': '-';
*buf++ = type & UR_EEPROM? 'e': '-';
if(hi >= 076) {
if(hi > 077) // From version 8.0 it's always urprotocol
@@ -940,14 +923,18 @@ static void urbootPutVersion(const PROGRAMMER *pgm, char *buf, uint16_t ver, uin
*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]: '?');
pgm && ur.blguessed? (ur.bleepromrw? 'e': '-'): '?',
pgm && ur.blguessed? "hjvV"[ur.vbllevel & 3]: '?');
} else
sprintf(buf, "x0.0 .........");
return;
}
// Puts urboot version and capabilities into 19+ byte buffer from table with top 6 bytes
void urbootPutVersion(char *buf, uint16_t *top6table) {
allbootPutVersion(NULL, buf, top6table[2], top6table[1]);
}
// Return name of the vector with number num
static const char *vblvecname(const PROGRAMMER *pgm, int num) {
@@ -1340,7 +1327,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
Return("please specify -x bootsize=<num> and, if needed, %s-x eepromrw",
ur.boothigh? "-x vectornum=<num> or ": "");
uint16_t v16 = 0xffff, rjmpwp = ret_opcode;
uint16_t v16 = 0xffff, rjmpwp = 0x9508; // 0x9508 is the ret opcode
// Sporting chance that we can read top flash to get intell about bootloader?
if(ur.boothigh && (!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH))) {
@@ -1357,7 +1344,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
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
if(urver >= 072 && urver <= 0147 && (isop(rjmpwp, rjmp) || isop(rjmpwp, ret))) { // Prob urboot
if(urver < 075) { // Early urboot versions don't offer many sanity checks
ur.blurversion = urver;
ur.bleepromrw = iseeprom_cap(cap);
@@ -1369,7 +1356,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
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(isop(rjmpwp, ret) || (dfromend >= -blsize && dfromend < -6)) { // Due diligence
if(ur.xbootsize) {
if(flm->size - blsize != ur.blstart) {
pmsg_warning("urboot bootloader size %d explicitly overwritten by -x bootsize=%d\n",
@@ -1419,7 +1406,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
uint16_t reset16 = buf2uint16(spc);
if(isRjmp(reset16)) { // rjmp op code (could be from a large or a small part)
if(isop(reset16, rjmp)) { // 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
@@ -1431,7 +1418,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
ur.pfend = guess - 1;
}
}
} else if(vecsz == 4 && isJmp(reset16)) { // Jmp op code
} else if(vecsz == 4 && isop(reset16, jmp)) { // Jmp op code
int guess = addr_jmp(buf2uint32(spc));
if(guess < flm->size)
if((guess & (flm->page_size-1)) == 0) // Page aligned? Good
@@ -1469,14 +1456,14 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
op16 = 0;
} else if(wasop32) { // Skip opcode evaluation
wasop32 = 0;
} else if(isRjmp(opcode) && toend > 4) { // 4 top bytes of bl are data, not rjmp
} else if(isop(opcode, rjmp) && toend > 4) { // 4 top bytes of bl are data, not rjmp
// Does that rjmp end in the vector table?
if((dist = dist_rjmp(opcode, ur.uP.flashsize)) > toend &&
dist <= toend+ur.uP.ninterrupts*vecsz) { // "<=" for extended vector table
ur.vblvectornum = (dist-toend)/vecsz; // Solve for the vbl vector number
goto vblvecfound;
}
} else if(isJmp(opcode) && toend > 6) { // 4 top bytes are data + 2 the jmp addr
} else if(isop(opcode, jmp) && toend > 6) { // 4 top bytes are data + 2 the jmp addr
op16 = opcode;
wasjmp = 1; // Look at destination address in next loop iteration
} else if(is_opcode32(opcode)) { // Skip next opcode, too
@@ -1502,7 +1489,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
}
vblvecfound:
urbootPutVersion(pgm, ur.desc, v16, rjmpwp);
allbootPutVersion(pgm, ur.desc, v16, rjmpwp);
ur.mcode = 0xff;
int havemetadata = !ur.nometadata;
@@ -1696,7 +1683,7 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char r
if(badd < 4U && ur.boothigh && ur.blstart && ur.vbllevel == 1) {
int vecsz = ur.uP.flashsize <= 8192? 2: 4;
unsigned char jmptoboot[4];
int resetsize = set_reset(pgm, jmptoboot, vecsz);
int resetsize = set_resetvector(ur.blstart, ur.uP.flashsize, jmptoboot, vecsz, ur.urprotocol);
if(badd < (unsigned int) resetsize) { // Ensure reset vector points to bl
int n = urmin((unsigned int) resetsize - badd, (unsigned int) len);
@@ -2181,7 +2168,7 @@ static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
if(flm && flm->page_size >= vecsz) {
unsigned char *page = mmt_malloc(flm->page_size);
memset(page, 0xff, flm->page_size);
set_reset(pgm, page, vecsz);
set_resetvector(ur.blstart, ur.uP.flashsize, page, vecsz, ur.urprotocol);
if(avr_write_page_default(pgm, p, flm, 0, page) < 0) {
mmt_free(page);
return -1;
@@ -2367,7 +2354,7 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR
int vecsz = ur.uP.flashsize <= 8192? 2: 4;
if(chunk == ur.uP.pagesize && ur.boothigh && ur.blstart && ur.vbllevel == 1) {
unsigned char jmptoboot[4];
int resetsize = set_reset(pgm, jmptoboot, vecsz);
int resetsize = set_resetvector(ur.blstart, ur.uP.flashsize, jmptoboot, vecsz, ur.urprotocol);
int resetdest;
if(reset2addr(m->buf, vecsz, ur.uP.flashsize, &resetdest) < 0 || resetdest != ur.blstart) {
@@ -2444,16 +2431,10 @@ static int urclock_readonly(const PROGRAMMER *pgm, const AVRPART *p_unused, cons
return 1;
if(addr < (unsigned int) ur.pfstart)
return 1;
if(ur.boothigh && addr < 512 && ur.vbllevel) {
unsigned int vecsz = ur.uP.flashsize <= 8192? 2u: 4u;
if(addr < vecsz)
// Protect reset vector once vector bootloader detected
if(addr < 4 && ur.boothigh && ur.vblvectornum > 0)
if(addr < (ur.uP.flashsize <= 8192? 2u: 4u))
return 1;
if(ur.vblvectornum > 0) {
unsigned int appvecloc = ur.vblvectornum*vecsz;
if(addr >= appvecloc && addr < appvecloc+vecsz)
return 1;
}
}
} else if(!mem_is_eeprom(mem))
return 1;