From e6c26d8db4cf6bae63aa530fcb8cfef7986b6dd2 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sun, 6 Nov 2022 01:29:07 +0000 Subject: [PATCH 01/31] Provide urclock programmer --- src/CMakeLists.txt | 3 + src/Makefile.am | 3 + src/avrdude.conf.in | 13 + src/avrintel.c | 4 +- src/avrintel.h | 4 +- src/libavrdude.h | 2 + src/pgm.c | 5 +- src/pgm_type.c | 4 +- src/ser_posix.c | 3 +- src/ser_win32.c | 5 +- src/update.c | 11 + src/urclock.c | 2067 +++++++++++++++++++++++++++++++++++++++++ src/urclock.h | 29 + src/urclock_private.h | 144 +++ 14 files changed, 2286 insertions(+), 11 deletions(-) create mode 100644 src/urclock.c create mode 100644 src/urclock.h create mode 100644 src/urclock_private.h 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/avrdude.conf.in b/src/avrdude.conf.in index 0a6f71d7..2e4d2177 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -756,6 +756,19 @@ programmer connection_type = serial; ; +#------------------------------------------------------------ +# urclock +#------------------------------------------------------------ + +# See https://github.com/stefanrueger/urboot +programmer + id = "urclock"; + desc = "Urclock programmer for urboot bootloaders (arduino compatible)"; + type = "urclock"; + prog_modes = PM_SPM; + connection_type = serial; +; + #------------------------------------------------------------ # xbee #------------------------------------------------------------ diff --git a/src/avrintel.c b/src/avrintel.c index 293362c4..cd4b74a0 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 + * 04.11.2022 * */ diff --git a/src/avrintel.h b/src/avrintel.h index 77147aad..16ce8ed0 100644 --- a/src/avrintel.h +++ b/src/avrintel.h @@ -9,7 +9,7 @@ * meta-author Stefan Rueger * * v 1.1 - * 30.08.2022 + * 04.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/libavrdude.h b/src/libavrdude.h index 38bc1f42..dedf4098 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); diff --git a/src/pgm.c b/src/pgm.c index ccded0cc..2d343e27 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,7 @@ PROGRAMMER *pgm_new(void) { pgm->parseextparams = NULL; pgm->setup = NULL; pgm->teardown = 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 46f3aaa4..20165fdf 100644 --- a/src/ser_posix.c +++ b/src/ser_posix.c @@ -51,6 +51,7 @@ #include "libavrdude.h" long serial_recv_timeout = 5000; /* ms */ +long serial_drain_timeout = 250; /* ms */ struct baud_mapping { long baud; @@ -549,7 +550,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/update.c b/src/update.c index 319766ee..574f9573 100644 --- a/src/update.c +++ b/src/update.c @@ -485,6 +485,17 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags pmsg_error("read from file %s failed\n", update_inname(upd->filename)); return LIBAVRDUDE_GENERAL_FAILURE; } + // Patch input if for flash, 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; + } + } + } size = rc; pmsg_info("reading input file %s for %s%s\n", diff --git a/src/urclock.c b/src/urclock.c new file mode 100644 index 00000000..6b8cf2bb --- /dev/null +++ b/src/urclock.c @@ -0,0 +1,2067 @@ +/* + * 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 max(a, b) ((a) > (b)? (a): (b)) +#define min(a, b) ((a) < (b)? (a): (b)) + +static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p); +static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t addr, int len, char memtype); +static int readUrclockID(const PROGRAMMER *pgm); +static int 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); + + +// 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 + + // 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 + int32_t blstart; // Bootloader start address, eg, for bootloader write protection + + uint64_t urclockID; // Urclock ID read from flash or EEPROM as little endian + int 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 + showsketch, // ... application size + showstore, // ... store size + showmetadata, // ... metadata size + showboot, // ... bootloader size + showversion, // ... bootloader version and capabilities + showvbl, // ... vector bootloader level, vector number and name + showdate, // ... last-modified date of last uploaded application + showfilename, // ... filename of last uploaded application + 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 + forcetrim, // Force uploading of exactly this file, possibly trimming it + 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); +} + + +// 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((base=strrchr(fname, '/'))) + base++; + else + base = fname; + strncpy(ur.filename, base, sizeof ur.filename-1); + ur.filename[sizeof ur.filename-1] = 0; + } + } +} + + +// Called after the input file has been read for writing or verifying flash +static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused, + const AVRMEM *flm, const char *fname, int size) { + + int nmdata, maxsize, firstbeg, firstlen; + + 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.forcetrim) { + 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], consider -xforcetrim", + 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); + + int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp + bool llcode = firstbeg == 0 && firstlen > ur.uP.ninterrupts*vecsz; // Looks like code + bool llvectors = firstbeg == 0 && firstlen >= ur.uP.ninterrupts*vecsz; // Looks like vector table + for(int i=0; llvectors && ibuf+i); + if(!isRjmp(op16) && !(vecsz == 4 && isJmp(op16))) + llvectors = 0; + } + + if(llcode && !llvectors && ur.vblvectornum > 0 && ur.vbllevel) + pmsg_warning("not patching input as it appears to 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)) { + int appvecloc, reset32; + uint16_t reset16; + + 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. + */ + + int appstart; + + if(vecsz == 4 && isJmp(reset16)) { + appstart = addr_jmp(reset32); // Accept compiler's destination for now (do not normalise) + } else if(isRjmp(reset16)) { // rjmp might be generated for larger parts, too + appstart = dist_rjmp(reset16, ur.uP.flashsize); + while(appstart < 0) // If rjmp was backwards + appstart += flm->size; // OK for small parts, likely OK if size is a power of 2 + while(appstart > flm->size) // Sanity (should not happen): rjmp jumps over FLASHEND + appstart -= flm->size; + } else { + 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 + if(vecsz == 4) { // Always use absolute jump for large devices + uint32tobuf(flm->buf+0, jmp_opcode(ur.blstart)); + uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart)); + } else { // Must use relative jump for small devices + uint16tobuf(flm->buf+0, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); + 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 above data 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: + + // 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; + } + + 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.bloptiversion || + (ur.blurversion && ur.blurversion < 076)) { + + int ai, addr, nset; + + // Scan the memory for pages with unset bytes and read these bytes from current chip flash + uint8_t spc[2048]; + + for(addr = 0; addr < maxsize; addr += ur.uP.pagesize) { + // How many bytes are set in this page? + for(ai = addr, nset = 0; ai < addr + ur.uP.pagesize; ai++) + if(flm->tags[ai] & TAG_ALLOCATED) + nset++; + + // Holes in this page that needs writing? read them in from the chip + if(nset && nset != ur.uP.pagesize) { + // Identify a covering interval for all holes in page + int istart, isize; + + // Lowest address with unset byte + for(ai = addr; flm->tags[ai] & TAG_ALLOCATED; ai++) + continue; + istart = ai; + + // Highest address with unset byte + for(ai = addr + ur.uP.pagesize - 1; flm->tags[ai] & TAG_ALLOCATED; ai--) + continue; + 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, spc, istart, isize, 'F') == 0) { // @@@ + for(ai = istart; ai < istart + isize; ai++) + if(!(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 + + return size; +} + + +// Put version string into a buffer of max 16 characters incl nul (normally 13-14 bytes incl nul) +static void urbootPutVersion(char *buf, uint16_t ver, uint8_t piggy) { + 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 %c", hi>>3, hi&7, piggy); + buf += 6; + *buf++ = type & UR_PGMWRITEPAGE? '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++ = type & UR_PROTECTME? 'p': '-'; + *buf++ = type & UR_RESETFLAGS? 'r': '-'; + *buf = 0; + } else if(hi) // Version number in binary from optiboot v4.1 + sprintf(buf, "o%d.%d %c??s-??%c", hi, type, piggy, hi>=4? 'r': '-'); + else + sprintf(buf, "x0.0 %c-------", piggy); + + 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; + } +} + + +/* + * 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 > min(2048, ur.uP.flashsize/4)) + Return("implausible -xbootsize=%d, should be in [64, %d]", + ur.xbootsize, min(2048, ur.uP.flashsize/4)); + ur.blstart = ur.uP.flashsize - ur.xbootsize; + } + + if(ur.uP.ninterrupts >= 0) + 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; + + // Sporting chance that we can read top flash to get intell about bootloader + if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { + // Read top 6 bytes from flash memory to obtain extended information about bootloader and type + if((rc = ur_readEF(pgm, spc, flm->size-6, 6, 'F'))) + 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]; + uint16_t 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, and if OK extract properties + if(urver >= 072 && urver != 0xff && (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 manually overwritten by -xbootsize=%d\n", + blsize, ur.xbootsize); + } else + ur.blstart = flm->size - blsize; + + if(ur.xvectornum != -1) { + if(ur.vblvectornum != vectnum) + pmsg_warning("urboot vector number %d manually overwritten by -xvectornum=%d\n", + vectnum, ur.xvectornum); + } else + ur.vblvectornum = vectnum; + } + } + } + } else if(urver != 0xff) { // Probably optiboot where the version number is two bytes + if(!ur.blstart) { + int guessblsize = ur.uP.bootsize >= 512? ur.uP.bootsize: 512; + pmsg_warning("guessing it is optiboot %d.%d with size %d (better use -xbootsize=)\n", + urver, cap, guessblsize); + ur.blstart = flm->size - guessblsize; + } + 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, 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 *p; + 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, p=spc, j=0; j)\n", + guessblsize); + ur.blstart = flm->size - guessblsize; + } + } + +vblvecfound: + urbootPutVersion(ur.desc, v16, // + !ur.blstart || ur.uP.bootsize <= 0 || ur.uP.bootsize == flm->size-ur.blstart? 'o': + ur.uP.bootsize > flm->size-ur.blstart? 'O': '-'); + + if((rc = readUrclockID(pgm)) == -1) + return rc; + + ur.mcode = 0xff; + if(ur.blstart) { + int nm = nmeta(1, ur.uP.flashsize); // 6 for date + size of store struct + 1 for mcode byte + // If want to show properties, examine the bytes below bootloader for metadata + if(ur.showall || ur.showid || ur.showsketch || ur.showstore || ur.showmetadata || + ur.showboot || ur.showversion || ur.showvbl || ur.showdate || ur.showfilename) { + + if((rc = ur_readEF(pgm, 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, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F'); + if(rc < 0) + return rc; + int len = mcodesize-ur.blstart: 0), first=0; + if(ur.showversion || ur.showall) + term_out(" %s"+first, ur.desc+(*ur.desc==' ')), first=0; + if(ur.showvbl || (ur.showall && ur.vbllevel)) + term_out(" vector %d (%s)", ur.vblvectornum, vblvecname(pgm, ur.vblvectornum)), first=0; + if(ur.showall) + 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 *word* address for flash/eeprom, memtype is 'E'/'F' +static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int waddr) { + unsigned char buf[16]; + unsigned char ext_byte; + + // STK500 protocol: support flash > 64K words with the correct extended-address byte + if(memtype == 'F' && ur.uP.flashsize > 128*1024) { + ext_byte = (waddr >> 16) & 0xff; + if(ext_byte != ur.ext_addr_byte) { + // Either this is the first addr load, or a 64K word 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; + } + } + + buf[0] = Cmnd_STK_LOAD_ADDRESS; + buf[1] = waddr & 0xff; + buf[2] = (waddr >> 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 + * - 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, char rwop, unsigned int badd, + int len, char mchr, char *payload) { + + int i; + uint8_t buf[1024 + 5]; + + // STK500v1 only: tell the bootloader which word address should be used by next paged command + if(!ur.urprotocol && urclock_load_waddr(pgm, mchr, badd/2) < 0) + return -1; + + if(mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE && len != ur.uP.pagesize) + Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); + + if(ur.urprotocol) { + uint8_t *p = buf, op = + 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"); + + *p++ = op; + *p++ = badd & 0xff; + *p++ = (badd >> 8) & 0xff; + // Flash is larger than 64 kBytes, extend address (even for EEPROM) + if(ur.uP.flashsize > 0x10000) + *p++ = (badd >> 16) & 0xff; + + if(ur.uP.pagesize <= 256) { + if(len > 256) + Return("urprotocol paged r/w len %d cannot exceed 256", len); + *p++ = len; // len==256 is sent as 0 + } else { + int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; + if(len > max) + Return("urprotocol paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name); + *p++ = len>>8; // Big endian length when needed + *p++ = len; + } + i = p-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+1, using extended addressing if needed (extd); returns 0 on success + */ +static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len, char mchr) { + pmsg_debug("ur_readEF(%s, %s, %p, 0x%06x, %d, %c)\n", + pgm? pgm->desc: "?", mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); + + 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 r/w capability", ur.blurversion? "": "seem to "); + + if(len < 1 || len > max(ur.uP.pagesize, 256)) + Return("len %d exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); + + // Odd byte address under STK500v1 word-address protocol + int odd = !ur.urprotocol && (badd&1); + if(odd) { // Need to read one extra byte + len++; + badd &= ~1; + if(len > max(ur.uP.pagesize, 256)) + Return("len+1 = %d odd address exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); + } + + if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0) + return -1; + + return urclock_res_check(pgm, __func__, odd, buf, len-odd); +} + + +static int readUrclockID(const PROGRAMMER *pgm) { + uint8_t spc[16]; + int addr = 256+1; // Location of DS18B20 ID of Urclock boards + int len = 6; + char mchr = 'E'; + + ur.urclockID = 0; + + // Sanity for small boards + if(ur.uP.name && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) { + addr = 0; + if(ur.uP.eepromsize < 8) + mchr = 'F'; + } + + if(*ur.iddesc) { // User override of ID, eg, -xid=F.-4.2 for penultimate flash word + char *idstr = cfg_strdup(__func__, ur.iddesc), *idlen, *end, *memtype; + unsigned long ad, lg; + int size; + + if(!(strchr("EF", *idstr) && idstr[1] == '.')) { + pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc); + free(idstr); + return -2; + } + memtype = *idstr == 'E'? "EEPROM": "flash"; + size = *idstr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; + + if(!(idlen = strchr(idstr+2, '.'))) { + pmsg_warning("-xid=%s string must look like [E|F]..\n", ur.iddesc); + free(idstr); + return -2; + } + *idlen++ = 0; + ad = strtoul(idstr+2, &end, 0); + if(*end || end == idstr+2) { + pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc); + free(idstr); + return -2; + } + if(size > 0 && (long) ad < 0) + ad += size; + if(ur.uP.name && size > 0 && ad >= (unsigned long) size) { + pmsg_warning("address %s of -xid=%s string out of %s range [0, 0x%04x]\n", + idstr+2, ur.iddesc, memtype, size-1); + free(idstr); + return -2; + } + lg = strtoul(idlen, &end, 0); + if(*end || end == idlen) { + pmsg_warning("cannot parse length %s of -xid=%s string\n", idlen, ur.iddesc); + free(idstr); + return -2; + } + if(!lg || lg > 8) { + pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlen, ur.iddesc); + free(idstr); + return -2; + } + if(ur.uP.name && size > 0 && ad+lg > (unsigned long) size) { + pmsg_warning("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n", + (int) ad, (int) (ad+lg-1), ur.iddesc, memtype, size-1); + free(idstr); + return -2; + } + + addr = ad; + len = lg; + mchr = *idstr; + free(idstr); + } + + memset(spc, 0, sizeof spc); + (void) ur_readEF(pgm, spc, addr, len, mchr); + + // Urclock ID + for(int i = len-1; i >= 0; i--) + ur.urclockID <<= 8, ur.urclockID |= 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) { + pmsg_error("programmer is not responding\n"); + return -1; + } + + return 0; +} + + + +#define MAX_SYNC_ATTEMPTS 20 + +/* + * 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 = 100; // ms + + 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 > 1) // Don't report first two attempts + pmsg_warning("attempt %d of %d: not in sync\n", attempt + 1, MAX_SYNC_ATTEMPTS); + } + + 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; + + if(ur.xemulate_ce || + (ur.urprotocol && !(ur.urfeatures & UB_CHIP_ERASE)) || + ur.bloptiversion || (ur.blurversion && ur.blurversion < 076)) { + + pmsg_notice2("emulating chip erase\n"); + // Bootloader does not implement chip erase: don't send command to bootloader + ur.emulate_ce = 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; + return 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; + int showother = ur.showall || ur.showsketch || ur.showstore || ur.showmetadata || ur.showboot || + ur.showversion || ur.showvbl || ur.showdate || ur.showfilename; + + strcpy(pgm->port, port); + pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200; + 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; + + // Only asking for the urclock ID: find out and exit fast! + if(ur.showid && !showother && strchr("EF", *ur.iddesc)) { // Also matches *ur.iddesc == 0 + ur.xeepromrw = 1; // Pretend can read EEPROM + if(readUrclockID(pgm) == -1) + return -1; + + term_out("%0*lx\n", ur.idlen, ur.urclockID); + exit(0); + } + + return 0; +} + + +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_unused, 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 = strcmp(m->desc, "flash") == 0? 'F': 'E'; + if(mchr == 'E' && strcmp(m->desc, "eeprom")) + return -2; + + n = addr + n_bytes; + + for(; addr < n; addr += chunk) { + chunk = n-addr < page_size? n-addr: page_size; + + if(urclock_paged_rdwr(pgm, Cmnd_STK_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_unused, 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 = strcmp(m->desc, "flash") == 0? 'F': 'E'; + if(mchr == 'E' && strcmp(m->desc, "eeprom")) + return -2; + + n = addr + n_bytes; + for(; addr < n; addr += chunk) { + chunk = n-addr < page_size? n-addr: page_size; + + if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0) + return -3; + if(urclock_res_check(pgm, __func__, 0, &m->buf[addr], chunk) < 0) + return -4; + } + } + + return n_bytes; +} + + +// 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 + + +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[] = { + {"showall", &ur.showall, 0, NULL, 0, "Show all info for connected part and exit"}, + {"showid", &ur.showid, 0, NULL, 0, " ... unique Urclock ID"}, + {"showsketch", &ur.showsketch, 0, NULL, 0, " ... application size"}, + {"showstore", &ur.showstore, 0, NULL, 0, " ... store size"}, + {"showmetadata", &ur.showmetadata, 0, NULL, 0, " ... metadata size"}, + {"showboot", &ur.showboot, 0, NULL, 0, " ... bootloader size"}, + {"showversion", &ur.showversion, 0, NULL, 0, " ... bootloader version and capabilities"}, + {"showvbl", &ur.showvbl, 0, NULL, 0, " ... vector bootloader level, vec # and name"}, + {"showdate", &ur.showdate, 0, NULL, 0, " ... last-modified date of flash application"}, + {"showfilename", &ur.showfilename, 0, NULL, 0, " ... filename of last uploaded application"}, + {"bootsize", &ur.xbootsize, 0, NULL, 1, "Manual override for bootloader size"}, + {"vectornum", &ur.xvectornum, 0, NULL, 1, " ... for vector number"}, // implies vbllevel=1 + {"eepromrw", &ur.xeepromrw, 0, NULL, 0, " ... for EEPROM read/write capability"}, + {"emulate_ce", &ur.xemulate_ce, 0, NULL, 0, " ... for making avrdude emulate chip erase"}, + {"forcetrim", &ur.forcetrim, 0, NULL, 0, "Upload of unchanged files, trim it if needed"}, + {"initstore", &ur.initstore, 0, NULL, 0, "Fill store with 0xff on writing to flash"}, + // @@@ {"copystore", &ur.copystore, 0, NULL, 0, "Copy over store on writing to flash"}, + {"nofilename", &ur.nofilename, 0, NULL, 0, "Don't store filename on writing to flash"}, + {"nodate", &ur.nodate, 0, NULL, 0, " ... application filename and no date either"}, + {"nometadata", &ur.nometadata, 0, NULL, 0, " ... metadata at all (ie, no store support)"}, + {"delay", &ur.delay, 0, NULL, 1, "Add delay [ms] after reset, can be negative"}, + {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg F.12324.6"}, + {"title", NULL, sizeof ur.title, ur.title, 1, "Title used in lieu of a filename when set"}, + {"?", &help, 0, NULL, 0, "Show this help menu and exit"}, + }; + + 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": "", + max(0, 16-strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); + } + if(rc == 0) + exit(0); + } + + 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 = avr_read_byte_cached; + pgm->write_byte = avr_write_byte_cached; + + // 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->flash_readhook = urclock_flash_readhook; +} 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..2302aa70 --- /dev/null +++ b/src/urclock_private.h @@ -0,0 +1,144 @@ +/* + * 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 + +#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_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 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) +#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) + +// Capability bits incl position +#define pgmwritepage_bit_cap(cap) ((cap) & UR_PGMWRITEPAGE) +#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) + +// Boolean capabilities +#define ispgmwritepage_cap(cap) (!!((cap) & UR_PGMWRITEPAGE)) +#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)) + +// Capability levels 0, 1, 2 or 3 +#define vectorbl_level_cap(cap) (((cap) & UR_VBLMASK)/UR_VBL) + +#endif From 21d93ec8cb3adefcc5704a3632b8eed906cc25f7 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Mon, 7 Nov 2022 01:26:47 +0000 Subject: [PATCH 02/31] Update urclock programmer --- src/avrdude.1 | 115 ++++++++++++++- src/config.c | 8 +- src/urclock.c | 381 +++++++++++++++++++++++++++++--------------------- 3 files changed, 334 insertions(+), 170 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index a2225a1a..e6f356b0 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -147,9 +147,17 @@ programming mode. The programmer type is ``wiring''. Note that the -D option will likely be required in this case, because the bootloader will rewrite the program memory, but no true chip erase can be performed. .Pp -The Arduino (which is very similar to the STK500 1.x) is supported via -its own programmer type specification ``arduino''. This programmer works for -the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader. +Serial bootloaders that run a skeleton of the STK500 1.x protocol are +supported via their own programmer type ``arduino''. This programmer works +for the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader. +.Pp +Urprotocol is a leaner version of the STK500 1.x protocol that is designed +to be backwards compatible with STK500 v1.x, and allows bootloaders to be +much smaller, eg, as implemented in the urboot project +https://github.com/stefanrueger/urboot. The programmer type ``urclock'' +caters for these urboot programmers. Owing to its backward compatibility, +any bootloader that can be served by the arduino programmer can normally +also be served by the urclock programmer. .Pp The BusPirate is a versatile tool that can also be used as an AVR programmer. A single BusPirate can be connected to up to 3 independent AVRs. See @@ -397,8 +405,8 @@ programming requires the memory be erased to 0xFF beforehand. .Fl A should be used when the programmer hardware, or bootloader software for that matter, does not carry out chip erase and -instead handles the memory erase on a page level. The popular -Arduino bootloader exhibits this behaviour; for this reason +instead handles the memory erase on a page level. Popular +Arduino bootloaders exhibit this behaviour; for this reason .Fl A is engaged by default when specifying . Fl c @@ -432,7 +440,7 @@ contents would exclusively cause bits to be programmed from the value .Ql 1 to .Ql 0 . -Note that in order to reprogram EERPOM cells, no explicit prior chip +Note that in order to reprogram EEPROM cells, no explicit prior chip erase is required since the MCU provides an auto-erase cycle in that case before programming the cell. .It Xo Fl E Ar exitspec Ns @@ -1141,6 +1149,101 @@ programmer creates errors during initial sequence. Specify how many connection retry attemps to perform before exiting. Defaults to 10 if not specified. .El +.It Ar Urclock +.Bl -tag -offset indent -width indent +.It Ar showall +Show all info for the connected part and exit. +.It Ar showid +Show a unique Urclock ID stored in either flash or EEPROM of the MCU and exit. +.It Ar id=.. +Historically, the Urclock ID was a six-byte unique little-endian number +stored in Urclock boards at EEPROM address 257. The location of this +number can be set by the -xid=.. extended parameter. E +stands for EEPROM and F stands for flash. A negative address addr counts +from the end of EEPROM and flash, respectively. The length len of the +Urclock ID can be between 1 and 8 bytes. +.It Ar showapp +Show the size of the programmed application and exit. +.It Ar showstore +Show the size of the unused flash between the application and metadata and exit. +.It Ar showmeta +Show the size of the metadata just below the bootloader and exit. +.It Ar showboot +Show the size of the bootloader and exit. +.It Ar showversion +Show bootloader version and capabilities, and exit. +.It Ar showvbl +Show the vector number and name of the interrupt table vector used by the +bootloader for starting the application, and exit. For hardware-supported +bootloaders this will be vector 0 (Reset), and for vector bootloaders this +will be any other vector number of the interrupt vector table or the slot +just behind the vector table with the name VBL_ADDITIONAL_VECTOR. +.It Ar showdate +Show the last-modified date of the input file for the flash application +and exit. If the input file was stdin, the date will be that of the +programming. +.It Ar showfilename +Show the input filename (or title) of the last flash writing session, and exit. +.It Ar bootsize= +Manual override for bootloader size. Urboot bootloaders put the number of used +bootloader pages into a table at the top of flash, so the urclock programmer can +look up the bootloader size itself. In backward-compatibility mode, when programming +via other bootloaders, this option can be used to tell the programmer the +size, and therefore the location, of the bootloader. +.It Ar vectornum= +Manual override for vector number. Urboot bootloaders put the vector +number used by a vector bootloader into a table at the top of flash, so +this option is normally not needed for urboot bootloaders. However, it is +useful in backward-compatibility mode (or when the urboot bootloader does +not offer flash read). Specifying a vector number in these circumstances +implies a vector bootloader whilst the default assumption would be a +hardware-supported bootloader. +.It Ar eepromrw +Manual override for asserting EEPROM read/write capability. Not normally +needed for urboot bootloaders, but useful for in backward-compatibility +mode if the bootloader offers EEPROM read/write. +.It Ar emulate_ce +If an urboot bootloader does not offer a chip erase command it will tell +the urclock programmer so during handshake. In this case the urclock +programmer emulates a chip erase, if warranted by user command line +options, by filling the remainder of unused flash below the bootloader +with 0xff. If this option is specified, the urclock programmer will assume +that the bootloader cannot erase the chip itself. The option is useful +for backwards-compatible bootloaders that do not implement chip erase. +.It Ar forcetrim +Upload unchanged flash input files and trim below the bootloader if +needed. This is most useful when one has a backup of the full flash and +wants to play that back onto the device. No metadata are written in this +case and no vector patching happens either if it is a vector bootloader. +However, for vector bootloaders, even under the option -xforcetrim an +input file will not be uploaded for which the reset vector does not point +to the vector bootloader. This is to avoid writing an input file to the +device that would render the vector bootloader not functional as it would +not be reached after reset. +.It Ar initstore +On writing to flash fill the store space between the flash application and +the metadata section with 0xff. +.It Ar title= +When set, will be used in lieu of the input filename. The maximum +string length for the title/filename field is 254 bytes including +terminating nul. +.It Ar nofilename +On writing to flash do not store the application input filename (nor a title). +.It Ar nodate +On writing to flash do not store the application input filename (nor a +title) and no date either. +.It Ar nometadata +On writing to flash do not store any metadata. The full flash below the +bootloader is available for the application. In particular, no data store +frame is programmed. +.It Ar delay= +Add a ms delay after reset. This can be useful if a board takes a +particularly long time to exit from external reset. can be negative, +in which case the default 80 ms delay after issuing reset will be +shortened accordingly. +.It Ar help +Show this help menu and exit +.El .It Ar buspirate .Bl -tag -offset indent -width indent .It Ar reset={cs,aux,aux2} diff --git a/src/config.c b/src/config.c index efb33a5a..a0cf35cf 100644 --- a/src/config.c +++ b/src/config.c @@ -704,7 +704,7 @@ char *cfg_escape(const char *s) { char buf[50*1024], *d = buf; *d++ = '"'; - for(; *s && d-buf < sizeof buf-7; s++) { + for(; *s && d-buf < (long) sizeof buf-7; s++) { switch(*s) { case '\n': *d++ = '\\'; *d++ = 'n'; @@ -855,7 +855,7 @@ void cfg_update_mcuid(AVRPART *part) { return; // Find an entry that shares the same name, overwrite mcuid with known, existing mcuid - for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++) { + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) { if(strcasecmp(part->desc, uP_table[i].name) == 0) { if(part->mcuid != (int) uP_table[i].mcuid) { if(part->mcuid >= 0 && verbose >= MSG_DEBUG) @@ -867,7 +867,7 @@ void cfg_update_mcuid(AVRPART *part) { } // None have the same name: an entry with part->mcuid might be an error - for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++) + for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) if(part->mcuid == (int) uP_table[i].mcuid) { // Complain unless it can be considered a variant, eg, ATmega32L and ATmega32 AVRMEM *flash = avr_locate_mem(part, "flash"); @@ -876,7 +876,7 @@ void cfg_update_mcuid(AVRPART *part) { if(strncasecmp(part->desc, uP_table[i].name, l1 < l2? l1: l2) || flash->size != uP_table[i].flashsize || flash->page_size != uP_table[i].pagesize || - part->n_interrupts != uP_table[i].ninterrupts) + part->n_interrupts != (int8_t) uP_table[i].ninterrupts) yywarning("mcuid %d is reserved for %s, use a free number >= %d", part->mcuid, uP_table[i].name, sizeof uP_table/sizeof *uP_table); } diff --git a/src/urclock.c b/src/urclock.c index 6b8cf2bb..9eafd5d6 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -223,8 +223,9 @@ #define min(a, b) ((a) < (b)? (a): (b)) static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p); -static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t addr, int len, char memtype); -static int readUrclockID(const PROGRAMMER *pgm); +static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t addr, int len, + char memchr); +static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *idp); static int urclock_send(const PROGRAMMER *pgm, unsigned char *buf, size_t len); static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len); static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res); @@ -260,7 +261,8 @@ typedef struct { bloptiversion; // Optiboot version as (major<<8) + minor int32_t blstart; // Bootloader start address, eg, for bootloader write protection - uint64_t urclockID; // Urclock ID read from flash or EEPROM as little endian + int idmchr; // Either 'E' or 'F' for the memory where the Urclock ID is located + int idaddr; // The address of the Urclock ID int idlen; // Number 1..8 of Urclock ID bytes (location, see iddesc below) int32_t storestart; // Store (ie, unused flash) start address, same as application size @@ -280,9 +282,9 @@ typedef struct { // Extended parameters for Urclock int showall, // Show all pieces of info for connected part and exit showid, // ... Urclock ID - showsketch, // ... application size + showapp, // ... application size showstore, // ... store size - showmetadata, // ... metadata size + showmeta, // ... metadata size showboot, // ... bootloader size showversion, // ... bootloader version and capabilities showvbl, // ... vector bootloader level, vector number and name @@ -539,11 +541,39 @@ static void set_date_filename(const PROGRAMMER *pgm, const char *fname) { } + +// Put destination address of reset vector jmp or rjmp into addr, return -1 if not an r/jmp +static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int *addrp) { + int op32, addr, rc = 0; + uint16_t op16; + + op16 = buf2uint16(opcode); // First word of the jmp or the full rjmp + op32 = vecsz == 2? op16: buf2uint32(opcode); + + if(vecsz == 4 && isJmp(op16)) { + addr = addr_jmp(op32); // Accept compiler's destination (do not normalise) + } else if(isRjmp(op16)) { // rjmp might be generated for larger parts, too + addr = dist_rjmp(op16, flashsize); + while(addr < 0) // If rjmp was backwards + addr += flashsize; // OK for small parts, likely(!) OK if flashsize is a power of 2 + while(addr > flashsize) // Sanity (should not happen): rjmp jumps over FLASHEND + addr -= flashsize; + } else + rc = -1; + + if(addrp && rc == 0) + *addrp = addr; + + return rc; +} + + // Called after the input file has been read for writing or verifying flash -static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused, - const AVRMEM *flm, const char *fname, int size) { +static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm, + const char *fname, int size) { int nmdata, maxsize, firstbeg, firstlen; + int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp set_date_filename(pgm, fname); @@ -597,7 +627,6 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnofilename", firstbeg, size-1, ur.blstart-nmdata, ur.blstart-1); - int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp bool llcode = firstbeg == 0 && firstlen > ur.uP.ninterrupts*vecsz; // Looks like code bool llvectors = firstbeg == 0 && firstlen >= ur.uP.ninterrupts*vecsz; // Looks like vector table for(int i=0; llvectors && i 0 && ur.vbllevel) { // From v7.5 patch all levels but for earlier and unknown versions only patch level 1 if(ur.blurversion >= 075 || ((ur.blurversion==0 || ur.blurversion >= 072) && ur.vbllevel==1)) { - int appvecloc, reset32; uint16_t reset16; + int reset32, appstart, appvecloc; - appvecloc = ur.vblvectornum*vecsz; // Location of jump-to-application in vector table - reset16 = buf2uint16(flm->buf); // First reset word of to-be-uploaded application + appvecloc = ur.vblvectornum*vecsz; // Location of jump-to-application in vector table + reset16 = buf2uint16(flm->buf); // First reset word of to-be-uploaded application reset32 = vecsz == 2? reset16: buf2uint32(flm->buf); /* @@ -631,17 +660,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p_unused * an error thrown if so. */ - int appstart; - - if(vecsz == 4 && isJmp(reset16)) { - appstart = addr_jmp(reset32); // Accept compiler's destination for now (do not normalise) - } else if(isRjmp(reset16)) { // rjmp might be generated for larger parts, too - appstart = dist_rjmp(reset16, ur.uP.flashsize); - while(appstart < 0) // If rjmp was backwards - appstart += flm->size; // OK for small parts, likely OK if size is a power of 2 - while(appstart > flm->size) // Sanity (should not happen): rjmp jumps over FLASHEND - appstart -= flm->size; - } else { + if(reset2addr(flm->buf, vecsz, flm->size, &appstart) < 0) { pmsg_warning("not patching input as opcode word %04x at reset is not a%sjmp\n", reset16, vecsz==2? "n r": " "); goto nopatch; @@ -771,7 +790,7 @@ nopatch_nometa: if(isize < 1 || isize > (int) sizeof spc) // Should not happen Return("isize=%d out of range (enlarge spc[] and recompile)", isize); - if(ur_readEF(pgm, spc, istart, isize, 'F') == 0) { // @@@ + if(ur_readEF(pgm, p, spc, istart, isize, 'F') == 0) { for(ai = istart; ai < istart + isize; ai++) if(!(flm->tags[ai] & TAG_ALLOCATED)) flm->buf[ai] = spc[ai-istart]; @@ -785,6 +804,24 @@ nopatch_nometa: } ur.done_ce = 0; // From now on can no longer rely on being deleted + // Last, but not least: ensure that vector bootloaders have correct r/jmp at address 0 + if(ur.blstart && ur.vbllevel==1) { + int set=0; + for(int i=0; i < vecsz; i++) + if(flm->tags[i] & TAG_ALLOCATED) + set++; + + if(set && set != vecsz) + Return("input overwrites the reset vector partially rendering vector bootloader moot, exiting"); + + if(set) { + int resetdest; + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) + Return("input does not hold an r/jmp at reset vector rendering vector bootloader moot, exiting"); + if(resetdest != ur.blstart) + Return("input file points reset to 0x%04x instead of vector bootloader at 0x%04x, exiting", resetdest, ur.blstart); + } + } return size; } @@ -1061,7 +1098,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { ur.blstart = ur.uP.flashsize - ur.xbootsize; } - if(ur.uP.ninterrupts >= 0) + if((int8_t) ur.uP.ninterrupts >= 0) // valid range is 0..127 if(ur.xvectornum < -1 || ur.xvectornum > ur.uP.ninterrupts) Return("unknown interrupt vector #%d for vector bootloader -- should be in [-1, %d]", ur.xvectornum, ur.uP.ninterrupts); @@ -1079,7 +1116,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { // Sporting chance that we can read top flash to get intell about bootloader if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Read top 6 bytes from flash memory to obtain extended information about bootloader and type - if((rc = ur_readEF(pgm, spc, flm->size-6, 6, 'F'))) + if((rc = ur_readEF(pgm, p, spc, flm->size-6, 6, 'F'))) return rc; // In a urboot bootloader (v7.2 onwards) these six are as follows @@ -1134,7 +1171,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Reset vector points to the bootloader and the bootloader has r/jmp to application? - if((rc = ur_readEF(pgm, spc, 0, 4, 'F'))) + if((rc = ur_readEF(pgm, p, spc, 0, 4, 'F'))) return rc; uint16_t reset16 = buf2uint16(spc); @@ -1161,7 +1198,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { if(ur.blstart) { // Read bootloader to identify jump to vbl vector int i, npages, j, n, toend, dist, wasop32, wasjmp, op16; - uint8_t *p; + uint8_t *q; uint16_t opcode; op16 = wasjmp = wasop32 = 0; @@ -1169,10 +1206,10 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { npages = toend/flm->page_size; for(i=0; ipage_size, flm->page_size, 'F'))) + if((rc = ur_readEF(pgm, p, spc, ur.blstart+i*flm->page_size, flm->page_size, 'F'))) return rc; - for(n=flm->page_size/2, p=spc, j=0; jpage_size/2, q=spc, j=0; jsize-ur.blstart? 'o': ur.uP.bootsize > flm->size-ur.blstart? 'O': '-'); - if((rc = readUrclockID(pgm)) == -1) - return rc; - ur.mcode = 0xff; if(ur.blstart) { int nm = nmeta(1, ur.uP.flashsize); // 6 for date + size of store struct + 1 for mcode byte - // If want to show properties, examine the bytes below bootloader for metadata - if(ur.showall || ur.showid || ur.showsketch || ur.showstore || ur.showmetadata || - ur.showboot || ur.showversion || ur.showvbl || ur.showdate || ur.showfilename) { + // Showing properties mostly requires examining the bytes below bootloader for metadata + if(ur.showall || (ur.showid && *ur.iddesc && *ur.iddesc != 'E') || ur.showapp || + ur.showstore || ur.showmeta || ur.showboot || ur.showversion || ur.showvbl || + ur.showdate || ur.showfilename) { - if((rc = ur_readEF(pgm, spc, ur.blstart-nm, nm, 'F'))) + if((rc = ur_readEF(pgm, p, spc, ur.blstart-nm, nm, 'F'))) return rc; if(spc[nm-1] != 0xff) { @@ -1260,7 +1295,7 @@ vblvecfound: ur.hr = hr; ur.mn = mn; if(mcode > 1) { // Copy application name over - rc = ur_readEF(pgm, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F'); + rc = ur_readEF(pgm, p, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F'); if(rc < 0) return rc; int len = mcodesize-ur.blstart: 0), first=0; if(ur.showversion || ur.showall) term_out(" %s"+first, ur.desc+(*ur.desc==' ')), first=0; - if(ur.showvbl || (ur.showall && ur.vbllevel)) - term_out(" vector %d (%s)", ur.vblvectornum, vblvecname(pgm, ur.vblvectornum)), first=0; + if(ur.showvbl || ur.showall) { + int vnum = ur.vbllevel? ur.vblvectornum & 0x7f: 0; + term_out(" vector %d (%s)"+first, vnum, vblvecname(pgm, vnum)), first=0; + } if(ur.showall) term_out(" %s"+first, ur.uP.name); if(!first) { @@ -1314,16 +1355,21 @@ alldone: // STK500 section from stk500.c but modified significantly for use with urboot bootloaders -// STK500v1 load *word* address for flash/eeprom, memtype is 'E'/'F' -static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int waddr) { - unsigned char buf[16]; - unsigned char ext_byte; +// STK500v1 load correct address for flash/eeprom, memchr is 'E'/'F' +static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memchr, + unsigned int baddr) { - // STK500 protocol: support flash > 64K words with the correct extended-address byte - if(memtype == 'F' && ur.uP.flashsize > 128*1024) { - ext_byte = (waddr >> 16) & 0xff; + unsigned char buf[16], ext_byte; + + // For classic parts (think optiboot, avrisp) use word addr, otherwise byte addr (optiboot_x etc) + int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire)); + unsigned int addr = classic? baddr/2: baddr; + + // STK500 protocol: support flash > 64k words/bytes with the correct extended-address byte + if(memchr == 'F' && ur.uP.flashsize > (classic? 128*1024: 64*1024)) { + ext_byte = (addr >> 16) & 0xff; if(ext_byte != ur.ext_addr_byte) { - // Either this is the first addr load, or a 64K word boundary is crossed + // Either this is the first addr load, or a 64k boundary is crossed buf[0] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>24); buf[1] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>16); buf[2] = ext_byte; @@ -1334,8 +1380,8 @@ static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int } buf[0] = Cmnd_STK_LOAD_ADDRESS; - buf[1] = waddr & 0xff; - buf[2] = (waddr >> 8) & 0xff; + buf[1] = addr & 0xff; + buf[2] = (addr >> 8) & 0xff; buf[3] = Sync_CRC_EOP; if(urclock_send(pgm, buf, 4) < 0) @@ -1352,21 +1398,21 @@ static int urclock_load_waddr(const PROGRAMMER *pgm, char memtype, unsigned int * - mchr is 'F' (flash) or 'E' (EEPROM) * - payload for bytes to write or NULL for read */ -static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int badd, - int len, char mchr, char *payload) { +static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char rwop, + unsigned int badd, int len, char mchr, char *payload) { int i; uint8_t buf[1024 + 5]; - // STK500v1 only: tell the bootloader which word address should be used by next paged command - if(!ur.urprotocol && urclock_load_waddr(pgm, mchr, badd/2) < 0) + // STK500v1 only: tell the bootloader which address should be used by next paged command + if(!ur.urprotocol && urclock_load_baddr(pgm, part, mchr, badd) < 0) return -1; if(mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE && len != ur.uP.pagesize) Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); if(ur.urprotocol) { - uint8_t *p = buf, op = + uint8_t *q = buf, op = mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_FL: mchr == 'E' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_EE: mchr == 'F' && rwop == Cmnd_STK_READ_PAGE? Cmnd_UR_READ_PAGE_FL: @@ -1375,25 +1421,25 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int bad if(op == 0xff) Return("command not recognised"); - *p++ = op; - *p++ = badd & 0xff; - *p++ = (badd >> 8) & 0xff; + *q++ = op; + *q++ = badd & 0xff; + *q++ = (badd >> 8) & 0xff; // Flash is larger than 64 kBytes, extend address (even for EEPROM) if(ur.uP.flashsize > 0x10000) - *p++ = (badd >> 16) & 0xff; + *q++ = (badd >> 16) & 0xff; if(ur.uP.pagesize <= 256) { if(len > 256) Return("urprotocol paged r/w len %d cannot exceed 256", len); - *p++ = len; // len==256 is sent as 0 + *q++ = len; // len==256 is sent as 0 } else { int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; if(len > max) Return("urprotocol paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name); - *p++ = len>>8; // Big endian length when needed - *p++ = len; + *q++ = len>>8; // Big endian length when needed + *q++ = len; } - i = p-buf; + i = q-buf; } else { int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256; @@ -1424,9 +1470,13 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, char rwop, unsigned int bad * Read len bytes at byte address addr of EEPROM (mchr == 'E') or flash (mchr == 'F') from * device fd into buffer buf+1, using extended addressing if needed (extd); returns 0 on success */ -static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len, char mchr) { - pmsg_debug("ur_readEF(%s, %s, %p, 0x%06x, %d, %c)\n", - pgm? pgm->desc: "?", mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); +static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t badd, int len, + char mchr) { + + int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire)); + + pmsg_debug("ur_readEF(%s, %s, %s, %p, 0x%06x, %d, %c)\n", + pgm? ldata(lfirst(pgm->id)): "?", p->desc, mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); if(mchr == 'F' && ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) Return("bootloader does not have flash read capability"); @@ -1437,8 +1487,8 @@ static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len if(len < 1 || len > max(ur.uP.pagesize, 256)) Return("len %d exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); - // Odd byte address under STK500v1 word-address protocol - int odd = !ur.urprotocol && (badd&1); + // Odd byte address under word-address protocol for "classic" parts (optiboot, avrisp etc) + int odd = !ur.urprotocol && classic && (badd&1); if(odd) { // Need to read one extra byte len++; badd &= ~1; @@ -1446,91 +1496,111 @@ static int ur_readEF(const PROGRAMMER *pgm, uint8_t *buf, uint32_t badd, int len Return("len+1 = %d odd address exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); } - if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0) return -1; return urclock_res_check(pgm, __func__, odd, buf, len-odd); } -static int readUrclockID(const PROGRAMMER *pgm) { +static int parseUrclockID(const PROGRAMMER *pgm) { + if(*ur.iddesc) { // User override of ID, eg, -xid=F.-4.2 for penultimate flash word + char *idstr = cfg_strdup(__func__, ur.iddesc), *idlenp, *end; + unsigned long ad, lg; + + if(!(strchr("EF", *idstr) && idstr[1] == '.')) { + pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc); + free(idstr); + return -1; + } + + if(!(idlenp = strchr(idstr+2, '.'))) { + pmsg_warning("-xid=%s string must look like [E|F]..\n", ur.iddesc); + free(idstr); + return -1; + } + *idlenp++ = 0; + ad = strtoul(idstr+2, &end, 0); + if(*end || end == idstr+2) { + pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc); + free(idstr); + return -1; + } + long sad = *(long *) &ad; + if(sad < INT_MIN || sad > INT_MAX) { + pmsg_warning("address %s of -xid=%s has implausible size\n", idstr+2, ur.iddesc); + free(idstr); + return -1; + } + + lg = strtoul(idlenp, &end, 0); + if(*end || end == idlenp) { + pmsg_warning("cannot parse length %s of -xid=%s string\n", idlenp, ur.iddesc); + free(idstr); + return -1; + } + if(!lg || lg > 8) { + pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlenp, ur.iddesc); + free(idstr); + return -1; + } + + ur.idmchr = *idstr; + ur.idaddr = sad; + ur.idlen = lg; + + free(idstr); + } + + return 0; +} + + +static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *urclockIDp) { uint8_t spc[16]; - int addr = 256+1; // Location of DS18B20 ID of Urclock boards - int len = 6; - char mchr = 'E'; + int mchr, addr, len, size; - ur.urclockID = 0; + if(ur.idlen) + mchr = ur.idmchr, addr = ur.idaddr, len = ur.idlen; + else + mchr = 'E', addr = 256+1, len = 6; // Default location for unique id on urclock boards - // Sanity for small boards - if(ur.uP.name && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) { + *urclockIDp = 0; + + // Sanity for small boards in absence of user -xid=... option + if(!ur.idlen && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) { addr = 0; if(ur.uP.eepromsize < 8) mchr = 'F'; } - if(*ur.iddesc) { // User override of ID, eg, -xid=F.-4.2 for penultimate flash word - char *idstr = cfg_strdup(__func__, ur.iddesc), *idlen, *end, *memtype; - unsigned long ad, lg; - int size; + const char *memtype = mchr == 'E'? "eeprom": "flash"; - if(!(strchr("EF", *idstr) && idstr[1] == '.')) { - pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc); - free(idstr); - return -2; - } - memtype = *idstr == 'E'? "EEPROM": "flash"; - size = *idstr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; + size = mchr == 'F'? ur.uP.flashsize: ur.uP.eepromsize; - if(!(idlen = strchr(idstr+2, '.'))) { - pmsg_warning("-xid=%s string must look like [E|F]..\n", ur.iddesc); - free(idstr); - return -2; - } - *idlen++ = 0; - ad = strtoul(idstr+2, &end, 0); - if(*end || end == idstr+2) { - pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc); - free(idstr); - return -2; - } - if(size > 0 && (long) ad < 0) - ad += size; - if(ur.uP.name && size > 0 && ad >= (unsigned long) size) { - pmsg_warning("address %s of -xid=%s string out of %s range [0, 0x%04x]\n", - idstr+2, ur.iddesc, memtype, size-1); - free(idstr); - return -2; - } - lg = strtoul(idlen, &end, 0); - if(*end || end == idlen) { - pmsg_warning("cannot parse length %s of -xid=%s string\n", idlen, ur.iddesc); - free(idstr); - return -2; - } - if(!lg || lg > 8) { - pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlen, ur.iddesc); - free(idstr); - return -2; - } - if(ur.uP.name && size > 0 && ad+lg > (unsigned long) size) { - pmsg_warning("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n", - (int) ad, (int) (ad+lg-1), ur.iddesc, memtype, size-1); - free(idstr); - return -2; - } + if(ur.uP.name && size > 0) { + if(addr < 0) // X.-4.4 asks for 4 bytes at top memory + addr += size; - addr = ad; - len = lg; - mchr = *idstr; - free(idstr); + if(addr < 0 || addr >= size) + Return("effective address %d of -xids=%s string out of %s range [0, 0x%04x]\n", + addr, ur.iddesc, memtype, size-1); + + if(addr+len > size) + Return("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n", + addr, addr+len-1, ur.iddesc, memtype, size-1); } memset(spc, 0, sizeof spc); - (void) ur_readEF(pgm, spc, addr, len, mchr); + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + return -2; + + if(ur_readEF(pgm, p, spc, addr, len, mchr) < 0) + return -1; // Urclock ID for(int i = len-1; i >= 0; i--) - ur.urclockID <<= 8, ur.urclockID |= spc[i]; + *urclockIDp <<= 8, *urclockIDp |= spc[i]; ur.idlen = len; return 0; @@ -1681,7 +1751,7 @@ static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned // Either emulate chip erase or send appropriate command to bootloader -static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { +static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p_unused) { unsigned char buf[16]; long bak_timeout = serial_recv_timeout; @@ -1784,8 +1854,6 @@ static void urclock_disable(const PROGRAMMER *pgm) { static int urclock_open(PROGRAMMER *pgm, const char *port) { union pinfo pinfo; - int showother = ur.showall || ur.showsketch || ur.showstore || ur.showmetadata || ur.showboot || - ur.showversion || ur.showvbl || ur.showdate || ur.showfilename; strcpy(pgm->port, port); pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200; @@ -1809,16 +1877,6 @@ static int urclock_open(PROGRAMMER *pgm, const char *port) { if(urclock_getsync(pgm) < 0) return -1; - // Only asking for the urclock ID: find out and exit fast! - if(ur.showid && !showother && strchr("EF", *ur.iddesc)) { // Also matches *ur.iddesc == 0 - ur.xeepromrw = 1; // Pretend can read EEPROM - if(readUrclockID(pgm) == -1) - return -1; - - term_out("%0*lx\n", ur.idlen, ur.urclockID); - exit(0); - } - return 0; } @@ -1832,7 +1890,7 @@ static void urclock_close(PROGRAMMER *pgm) { } -static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, const AVRMEM *m, +static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { int mchr, chunk; @@ -1840,8 +1898,8 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c if(n_bytes) { // Paged writes only valid for flash and eeprom - mchr = strcmp(m->desc, "flash") == 0? 'F': 'E'; - if(mchr == 'E' && strcmp(m->desc, "eeprom")) + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) return -2; n = addr + n_bytes; @@ -1849,7 +1907,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c for(; addr < n; addr += chunk) { chunk = n-addr < page_size? n-addr: page_size; - if(urclock_paged_rdwr(pgm, Cmnd_STK_PROG_PAGE, addr, chunk, mchr, (char *)&m->buf[addr]) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_PROG_PAGE, addr, chunk, mchr, (char *) m->buf+addr) < 0) return -3; if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0) return -4; @@ -1860,7 +1918,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p_unused, c } -static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p_unused, const AVRMEM *m, +static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { int mchr, chunk; @@ -1868,15 +1926,15 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p_unused, co if(n_bytes) { // Paged reads only valid for flash and eeprom - mchr = strcmp(m->desc, "flash") == 0? 'F': 'E'; - if(mchr == 'E' && strcmp(m->desc, "eeprom")) + mchr = avr_mem_is_flash_type(m)? 'F': 'E'; + if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) return -2; n = addr + n_bytes; for(; addr < n; addr += chunk) { chunk = n-addr < page_size? n-addr: page_size; - if(urclock_paged_rdwr(pgm, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0) + if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0) return -3; if(urclock_res_check(pgm, __func__, 0, &m->buf[addr], chunk) < 0) return -4; @@ -1928,9 +1986,9 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { } options[] = { {"showall", &ur.showall, 0, NULL, 0, "Show all info for connected part and exit"}, {"showid", &ur.showid, 0, NULL, 0, " ... unique Urclock ID"}, - {"showsketch", &ur.showsketch, 0, NULL, 0, " ... application size"}, + {"showapp", &ur.showapp, 0, NULL, 0, " ... application size"}, {"showstore", &ur.showstore, 0, NULL, 0, " ... store size"}, - {"showmetadata", &ur.showmetadata, 0, NULL, 0, " ... metadata size"}, + {"showmeta", &ur.showmeta, 0, NULL, 0, " ... metadata size"}, {"showboot", &ur.showboot, 0, NULL, 0, " ... bootloader size"}, {"showversion", &ur.showversion, 0, NULL, 0, " ... bootloader version and capabilities"}, {"showvbl", &ur.showvbl, 0, NULL, 0, " ... vector bootloader level, vec # and name"}, @@ -1949,7 +2007,7 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { {"delay", &ur.delay, 0, NULL, 1, "Add delay [ms] after reset, can be negative"}, {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg F.12324.6"}, {"title", NULL, sizeof ur.title, ur.title, 1, "Title used in lieu of a filename when set"}, - {"?", &help, 0, NULL, 0, "Show this help menu and exit"}, + {"help", &help, 0, NULL, 0, "Show this help menu and exit"}, }; int rc = 0; @@ -2009,12 +2067,15 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id))); for(size_t i=0; i": "", - max(0, 16-strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); + max(0, 16-(long) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); } if(rc == 0) exit(0); } + if(parseUrclockID(pgm) < 0) + return -1; + return rc; } From 715db4c69087d36c79a044a748ece86e9652244a Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Mon, 7 Nov 2022 02:43:34 +0000 Subject: [PATCH 03/31] Make -A default for urclock programmer --- src/urclock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/urclock.c b/src/urclock.c index 9eafd5d6..49be4945 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -2125,4 +2125,6 @@ void urclock_initpgm(PROGRAMMER *pgm) { pgm->parseextparams = urclock_parseextparms; pgm->term_keep_alive = urclock_term_keep_alive; pgm->flash_readhook = urclock_flash_readhook; + + disable_trailing_ff_removal(); } From cf3c81f7146fafcb3b96d6907270085af5315576 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Mon, 7 Nov 2022 18:01:23 +0000 Subject: [PATCH 04/31] Update urclock's -x parameters --- src/avrdude.1 | 41 +++++++++++++++++++++++------------------ src/urclock.c | 29 ++++++++++++++++------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index e6f356b0..3a2e7b5d 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -1152,9 +1152,12 @@ Defaults to 10 if not specified. .It Ar Urclock .Bl -tag -offset indent -width indent .It Ar showall -Show all info for the connected part and exit. +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 and exit. +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 @@ -1162,28 +1165,34 @@ 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 and exit. +Show the size of the programmed application, then exit. .It Ar showstore -Show the size of the unused flash between the application and metadata and exit. +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 and exit. +Show the size of the metadata just below the bootloader, then exit. .It Ar showboot -Show the size of the bootloader and exit. +Show the size of the bootloader, then exit. .It Ar showversion -Show bootloader version and capabilities, and exit. +Show bootloader version and capabilities, then exit. .It Ar showvbl Show the vector number and name of the interrupt table vector used by the -bootloader for starting the application, and exit. For hardware-supported +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 showdate -Show the last-modified date of the input file for the flash application -and exit. If the input file was stdin, the date will be that of the -programming. -.It Ar showfilename -Show the input filename (or title) of the last flash writing session, and exit. +.It Ar 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 @@ -1223,10 +1232,6 @@ not be reached after reset. .It Ar initstore On writing to flash fill the store space between the flash application and the metadata section with 0xff. -.It Ar title= -When set, will be used in lieu of the input filename. The maximum -string length for the title/filename field is 254 bytes including -terminating nul. .It Ar nofilename On writing to flash do not store the application input filename (nor a title). .It Ar nodate diff --git a/src/urclock.c b/src/urclock.c index 49be4945..6c56579c 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -282,14 +282,15 @@ typedef struct { // 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 showvbl, // ... vector bootloader level, vector number and name - showdate, // ... last-modified date of last uploaded application - showfilename, // ... filename of last uploaded application + 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 @@ -812,14 +813,15 @@ nopatch_nometa: set++; if(set && set != vecsz) - Return("input overwrites the reset vector partially rendering vector bootloader moot, exiting"); + Return("input overwrites reset vector, which would render the vector bootloader inoperable"); if(set) { int resetdest; if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) - Return("input does not hold an r/jmp at reset vector rendering vector bootloader moot, exiting"); + Return("input would overwrite the reset vector making the vector bootloader unreachable"); if(resetdest != ur.blstart) - Return("input file points reset to 0x%04x instead of vector bootloader at 0x%04x, exiting", resetdest, ur.blstart); + Return("input file points reset to 0x%04x instead of vector bootloader at 0x%04x, exiting", + resetdest, ur.blstart); } } return size; @@ -1260,7 +1262,7 @@ vblvecfound: // Showing properties mostly requires examining the bytes below bootloader for metadata if(ur.showall || (ur.showid && *ur.iddesc && *ur.iddesc != 'E') || ur.showapp || ur.showstore || ur.showmeta || ur.showboot || ur.showversion || ur.showvbl || - ur.showdate || ur.showfilename) { + ur.showpart || ur.showdate || ur.showfilename) { if((rc = ur_readEF(pgm, p, spc, ur.blstart-nm, nm, 'F'))) return rc; @@ -1313,7 +1315,8 @@ vblvecfound: // Print and exit when option show... was given int first=1; int single = !ur.showall && (!!ur.showid + !!ur.showapp + !!ur.showstore + !!ur.showmeta + - !!ur.showboot + !!ur.showversion + !!ur.showvbl + !!ur.showdate + !!ur.showfilename) == 1; + !!ur.showboot + !!ur.showversion + !!ur.showvbl + !!ur.showpart + !!ur.showdate + + !!ur.showfilename) == 1; if(ur.showid || ur.showall) { uint64_t urclockID; @@ -1339,7 +1342,7 @@ vblvecfound: int vnum = ur.vbllevel? ur.vblvectornum & 0x7f: 0; term_out(" vector %d (%s)"+first, vnum, vblvecname(pgm, vnum)), first=0; } - if(ur.showall) + if(ur.showall || ur.showpart) term_out(" %s"+first, ur.uP.name); if(!first) { term_out("\n"); @@ -1907,7 +1910,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AV 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) + 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; @@ -1986,14 +1989,16 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { } options[] = { {"showall", &ur.showall, 0, NULL, 0, "Show all info for connected part and exit"}, {"showid", &ur.showid, 0, NULL, 0, " ... unique Urclock ID"}, + {"showdate", &ur.showdate, 0, NULL, 0, " ... last-modified date of flash application"}, + {"showfilename", &ur.showfilename, 0, NULL, 0, " ... filename of last uploaded application"}, {"showapp", &ur.showapp, 0, NULL, 0, " ... application size"}, {"showstore", &ur.showstore, 0, NULL, 0, " ... store size"}, {"showmeta", &ur.showmeta, 0, NULL, 0, " ... metadata size"}, {"showboot", &ur.showboot, 0, NULL, 0, " ... bootloader size"}, {"showversion", &ur.showversion, 0, NULL, 0, " ... bootloader version and capabilities"}, {"showvbl", &ur.showvbl, 0, NULL, 0, " ... vector bootloader level, vec # and name"}, - {"showdate", &ur.showdate, 0, NULL, 0, " ... last-modified date of flash application"}, - {"showfilename", &ur.showfilename, 0, NULL, 0, " ... filename of last uploaded application"}, + {"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, 0, NULL, 1, "Manual override for bootloader size"}, {"vectornum", &ur.xvectornum, 0, NULL, 1, " ... for vector number"}, // implies vbllevel=1 {"eepromrw", &ur.xeepromrw, 0, NULL, 0, " ... for EEPROM read/write capability"}, @@ -2005,8 +2010,6 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { {"nodate", &ur.nodate, 0, NULL, 0, " ... application filename and no date either"}, {"nometadata", &ur.nometadata, 0, NULL, 0, " ... metadata at all (ie, no store support)"}, {"delay", &ur.delay, 0, NULL, 1, "Add delay [ms] after reset, can be negative"}, - {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg F.12324.6"}, - {"title", NULL, sizeof ur.title, ur.title, 1, "Title used in lieu of a filename when set"}, {"help", &help, 0, NULL, 0, "Show this help menu and exit"}, }; From 6a6d333849da6fe035de191c41b55eec6740d9c1 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Tue, 8 Nov 2022 15:18:30 +0000 Subject: [PATCH 05/31] Update urclock documentation --- src/avrdude.1 | 2 +- src/avrdude.conf.in | 2 +- src/urclock.c | 58 +++++++++++++++++++++++---------------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index 3a2e7b5d..dfeb5081 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -1185,7 +1185,7 @@ Show the size of the metadata just below the bootloader, then exit. Show the size of the bootloader, then exit. .It Ar showversion Show bootloader version and capabilities, then exit. -.It Ar showvbl +.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 diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 2e4d2177..6f897a6b 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -763,7 +763,7 @@ programmer # See https://github.com/stefanrueger/urboot programmer id = "urclock"; - desc = "Urclock programmer for urboot bootloaders (arduino compatible)"; + desc = "Urclock programmer for urboot bootloaders using urprotocol"; type = "urclock"; prog_modes = PM_SPM; connection_type = serial; diff --git a/src/urclock.c b/src/urclock.c index 6c56579c..bb7504d4 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -289,7 +289,7 @@ typedef struct { showmeta, // ... metadata size showboot, // ... bootloader size showversion, // ... bootloader version and capabilities - showvbl, // ... vector bootloader level, vector number and name + 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) @@ -1261,7 +1261,7 @@ vblvecfound: 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.showvbl || + 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'))) @@ -1315,7 +1315,7 @@ vblvecfound: // Print and exit when option show... was given int first=1; int single = !ur.showall && (!!ur.showid + !!ur.showapp + !!ur.showstore + !!ur.showmeta + - !!ur.showboot + !!ur.showversion + !!ur.showvbl + !!ur.showpart + !!ur.showdate + + !!ur.showboot + !!ur.showversion + !!ur.showvector + !!ur.showpart + !!ur.showdate + !!ur.showfilename) == 1; if(ur.showid || ur.showall) { @@ -1338,7 +1338,7 @@ vblvecfound: term_out(" %s%d"+first, single? "": "boot ", ur.blstart? flm->size-ur.blstart: 0), first=0; if(ur.showversion || ur.showall) term_out(" %s"+first, ur.desc+(*ur.desc==' ')), first=0; - if(ur.showvbl || ur.showall) { + 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; } @@ -1987,30 +1987,32 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { bool assign; const char *help; } options[] = { - {"showall", &ur.showall, 0, NULL, 0, "Show all info for connected part and exit"}, - {"showid", &ur.showid, 0, NULL, 0, " ... unique Urclock ID"}, - {"showdate", &ur.showdate, 0, NULL, 0, " ... last-modified date of flash application"}, - {"showfilename", &ur.showfilename, 0, NULL, 0, " ... filename of last uploaded application"}, - {"showapp", &ur.showapp, 0, NULL, 0, " ... application size"}, - {"showstore", &ur.showstore, 0, NULL, 0, " ... store size"}, - {"showmeta", &ur.showmeta, 0, NULL, 0, " ... metadata size"}, - {"showboot", &ur.showboot, 0, NULL, 0, " ... bootloader size"}, - {"showversion", &ur.showversion, 0, NULL, 0, " ... bootloader version and capabilities"}, - {"showvbl", &ur.showvbl, 0, NULL, 0, " ... vector bootloader level, vec # and name"}, - {"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, 0, NULL, 1, "Manual override for bootloader size"}, - {"vectornum", &ur.xvectornum, 0, NULL, 1, " ... for vector number"}, // implies vbllevel=1 - {"eepromrw", &ur.xeepromrw, 0, NULL, 0, " ... for EEPROM read/write capability"}, - {"emulate_ce", &ur.xemulate_ce, 0, NULL, 0, " ... for making avrdude emulate chip erase"}, - {"forcetrim", &ur.forcetrim, 0, NULL, 0, "Upload of unchanged files, trim it if needed"}, - {"initstore", &ur.initstore, 0, NULL, 0, "Fill store with 0xff on writing to flash"}, - // @@@ {"copystore", &ur.copystore, 0, NULL, 0, "Copy over store on writing to flash"}, - {"nofilename", &ur.nofilename, 0, NULL, 0, "Don't store filename on writing to flash"}, - {"nodate", &ur.nodate, 0, NULL, 0, " ... application filename and no date either"}, - {"nometadata", &ur.nometadata, 0, NULL, 0, " ... metadata at all (ie, no store support)"}, - {"delay", &ur.delay, 0, NULL, 1, "Add delay [ms] after reset, can be negative"}, - {"help", &help, 0, NULL, 0, "Show this help menu and exit"}, +#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, "Manual override for bootloader size"}, + {"vectornum", &ur.xvectornum, ARG, "Manual override for vector number"}, + {"eepromrw", &ur.xeepromrw, NA, "Asssertion of bootloader EEPROM read/write capability"}, + {"emulate_ce", &ur.xemulate_ce, NA, "Emulate chip erase"}, + {"forcetrim", &ur.forcetrim, NA, "Upload of unchanged files, trim it if needed"}, + {"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; From 20b86fb739ba9527340dcb607784d0c241340001 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Tue, 8 Nov 2022 20:14:59 +0000 Subject: [PATCH 06/31] Warn in uclock when bootloader cannot read/write memories --- src/urclock.c | 43 ++++++++++++++++++++++++++++++++++++++++--- src/urclock_private.h | 4 ++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index bb7504d4..77b7b560 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1471,7 +1471,7 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char r /* * Read len bytes at byte address addr of EEPROM (mchr == 'E') or flash (mchr == 'F') from - * device fd into buffer buf+1, using extended addressing if needed (extd); returns 0 on success + * 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) { @@ -1905,6 +1905,9 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AV if(mchr == 'E' && !avr_mem_is_eeprom_type(m)) return -2; + if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) + Return("bootloader does not %shave EEPROM r/w capability", ur.blurversion? "": "seem to "); + n = addr + n_bytes; for(; addr < n; addr += chunk) { @@ -1933,6 +1936,12 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR 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 EEPROM r/w capability", ur.blurversion? "": "seem to "); + n = addr + n_bytes; for(; addr < n; addr += chunk) { chunk = n-addr < page_size? n-addr: page_size; @@ -1948,6 +1957,34 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR } +int urclock_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, + unsigned long addr, unsigned char data) { + + pmsg_error("bootloader does not implement bytewise 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) { + + // Bytewise 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]; @@ -2119,8 +2156,8 @@ void urclock_initpgm(PROGRAMMER *pgm) { pgm->cmd = urclock_cmd; pgm->open = urclock_open; pgm->close = urclock_close; - pgm->read_byte = avr_read_byte_cached; - pgm->write_byte = avr_write_byte_cached; + pgm->read_byte = urclock_read_byte; + pgm->write_byte = urclock_write_byte; // Optional functions pgm->paged_write = urclock_paged_write; diff --git a/src/urclock_private.h b/src/urclock_private.h index 2302aa70..f97f08b6 100644 --- a/src/urclock_private.h +++ b/src/urclock_private.h @@ -29,6 +29,7 @@ typedef struct { char *page, *copy; } Cache; + // STK500v1 protocol constants #define Resp_STK_OK 0x10 @@ -47,6 +48,9 @@ typedef struct { #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 From ea65918dcad6a14f6663c254fed62caeace9ee12 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 9 Nov 2022 15:43:57 +0000 Subject: [PATCH 07/31] Omit verify after write failure in term.c --- src/term.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/term.c b/src/term.c index bd4e57b1..c3f643f0 100644 --- a/src/term.c +++ b/src/term.c @@ -667,14 +667,14 @@ static int cmd_write(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { 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); From c7ba53bca0eb2a84bd9c8fff24434c2762b77096 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 9 Nov 2022 16:16:59 +0000 Subject: [PATCH 08/31] Harden urclock against terminal time outs and vector overwrites --- src/urclock.c | 87 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 77b7b560..24605a29 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -90,7 +90,7 @@ * 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 * @@ -812,7 +812,41 @@ nopatch_nometa: if(flm->tags[i] & TAG_ALLOCATED) set++; - if(set && set != vecsz) + // Reset vector not programmed and flash readable: check what's on the flash + if(set != vecsz && (!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH))) { + unsigned char device[2048], jmptoboot[4]; + int rc; + + // Read reset vector on device flash + if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) + return rc; + + // What reset *should* look like + if(vecsz == 4) + uint32tobuf(jmptoboot, jmp_opcode(ur.blstart)); + else + uint16tobuf(jmptoboot, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); + + int changed = 0; + for(int i=0; i < vecsz; i++) { // Patch reset vector to protect vector bootloader + if((flm->tags[i] & TAG_ALLOCATED? flm->buf[i]: device[i]) != jmptoboot[i]) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + changed = 1; + } + } + // If reset vector patched, ensure to fill in the holes in rest of page + if(changed && flm->page_size > vecsz && flm->page_size <= sizeof device) { + if((rc = ur_readEF(pgm, p, device+vecsz, vecsz, flm->page_size - vecsz, 'F')) < 0) + return rc; + for(int i=vecsz; i < flm->page_size; i++) { + if(!(flm->tags[i] & TAG_ALLOCATED)) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } + } + } + } else if(set && set != vecsz) Return("input overwrites reset vector, which would render the vector bootloader inoperable"); if(set) { @@ -824,20 +858,21 @@ nopatch_nometa: resetdest, ur.blstart); } } + return size; } -// Put version string into a buffer of max 16 characters incl nul (normally 13-14 bytes incl nul) -static void urbootPutVersion(char *buf, uint16_t ver, uint8_t piggy) { +// Put version string into a buffer of max 17 characters incl nul (normally 13-14 bytes incl nul) +static void urbootPutVersion(char *buf, uint16_t ver) { 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 %c", hi>>3, hi&7, piggy); - buf += 6; + sprintf(buf, "u%d.%d ", hi>>3, hi&7); + buf += strlen(buf); *buf++ = type & UR_PGMWRITEPAGE? 'w': '-'; *buf++ = type & UR_EEPROM? 'e': '-'; if(hi >= 076) { // From urboot version 7.6 URPROTOCOL has its own bit @@ -856,9 +891,9 @@ static void urbootPutVersion(char *buf, uint16_t ver, uint8_t piggy) { *buf++ = type & UR_RESETFLAGS? 'r': '-'; *buf = 0; } else if(hi) // Version number in binary from optiboot v4.1 - sprintf(buf, "o%d.%d %c??s-??%c", hi, type, piggy, hi>=4? 'r': '-'); + sprintf(buf, "o%d.%d ??s-??%c", hi, type, hi>=4? 'r': '-'); else - sprintf(buf, "x0.0 %c-------", piggy); + sprintf(buf, "x0.0 -------"); return; } @@ -1160,13 +1195,9 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { } } } else if(urver != 0xff) { // Probably optiboot where the version number is two bytes - if(!ur.blstart) { - int guessblsize = ur.uP.bootsize >= 512? ur.uP.bootsize: 512; - pmsg_warning("guessing it is optiboot %d.%d with size %d (better use -xbootsize=)\n", - urver, cap, guessblsize); - ur.blstart = flm->size - guessblsize; - } ur.bloptiversion = (urver<<8) + cap; + if(!ur.blstart) + Return("bootloader might be optiboot %d.%d? Please use -xbootsize=\n", urver, cap); } if(!ur.blstart && ur.vbllevel) { // An older version urboot vector bootloader @@ -1240,21 +1271,13 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { } } - // Still no bootloader start address but HW support for bootloaders? Guess it - if(!ur.blstart && ur.uP.bootsize > 0 && ur.uP.nboots > 0) { - // With unknown provenance offer max protection otherwise, try smallest bootloader >= 512 - int guessblsize = urver == 0xff? ur.uP.bootsize << (ur.uP.nboots-1): - ur.uP.bootsize >= 512? ur.uP.bootsize: 512; - pmsg_warning("unknown bootloader, guessing size %d (better use -xbootsize=)\n", - guessblsize); - ur.blstart = flm->size - guessblsize; - } + // Still no bootloader start address? + if(!ur.blstart) + Return("unknown bootloader ... please specify -xbootsize=\n"); } vblvecfound: - urbootPutVersion(ur.desc, v16, // - !ur.blstart || ur.uP.bootsize <= 0 || ur.uP.bootsize == flm->size-ur.blstart? 'o': - ur.uP.bootsize > flm->size-ur.blstart? 'O': '-'); + urbootPutVersion(ur.desc, v16); ur.mcode = 0xff; if(ur.blstart) { @@ -1485,7 +1508,7 @@ static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint Return("bootloader does not have flash read capability"); if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) - Return("bootloader does not %shave EEPROM r/w capability", ur.blurversion? "": "seem to "); + Return("bootloader does not %shave EEPROM access capability", ur.blurversion? "": "seem to "); if(len < 1 || len > max(ur.uP.pagesize, 256)) Return("len %d exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); @@ -1874,7 +1897,9 @@ static int urclock_open(PROGRAMMER *pgm, const char *port) { usleep((80+ur.delay)*1000); // Wait until board comes out of reset // Drain any extraneous input +#ifndef WIN32 serial_drain_timeout = 80; // ms +#endif serial_drain(&pgm->fd, 0); if(urclock_getsync(pgm) < 0) @@ -1906,7 +1931,7 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AV return -2; if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) - Return("bootloader does not %shave EEPROM r/w capability", ur.blurversion? "": "seem to "); + Return("bootloader does not %shave paged EEPROM write capability", ur.blurversion? "": "seem to "); n = addr + n_bytes; @@ -1940,7 +1965,7 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR Return("bootloader does not have flash read capability"); if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) - Return("bootloader does not %shave EEPROM r/w capability", ur.blurversion? "": "seem to "); + Return("bootloader does not %shave paged EEPROM read capability", ur.blurversion? "": "seem to "); n = addr + n_bytes; for(; addr < n; addr += chunk) { @@ -1960,14 +1985,14 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR int urclock_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned long addr, unsigned char data) { - pmsg_error("bootloader does not implement bytewise write to %s \n", mem->desc); + 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) { - // Bytewise read only valid for flash and eeprom + // 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) { From 84a3e2cc2b6937ccc2208013b061ab829675ba48 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 9 Nov 2022 19:27:34 +0000 Subject: [PATCH 09/31] Fix avr.c comment --- src/avr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/avr.c b/src/avr.c index 9352a2b6..333f531a 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) { From d5d0b940cc49ae5f8b4923b34711d7e5cb0fbd86 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 9 Nov 2022 19:28:29 +0000 Subject: [PATCH 10/31] Harden vector bootloaders more against reset overwrites --- src/urclock.c | 73 +++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 24605a29..d172f795 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -637,7 +637,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const } if(llcode && !llvectors && ur.vblvectornum > 0 && ur.vbllevel) - pmsg_warning("not patching input as it appears to not start with a vector table\n"); + 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) { @@ -807,19 +807,14 @@ nopatch_nometa: // Last, but not least: ensure that vector bootloaders have correct r/jmp at address 0 if(ur.blstart && ur.vbllevel==1) { - int set=0; + int rc, set=0; for(int i=0; i < vecsz; i++) if(flm->tags[i] & TAG_ALLOCATED) set++; - // Reset vector not programmed and flash readable: check what's on the flash - if(set != vecsz && (!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH))) { - unsigned char device[2048], jmptoboot[4]; - int rc; - - // Read reset vector on device flash - if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) - return rc; + // Reset vector not programmed? Or -F? Ensure a jmp to bootloader + if(ovsigck || set != vecsz) { + unsigned char jmptoboot[4]; // What reset *should* look like if(vecsz == 4) @@ -827,34 +822,46 @@ nopatch_nometa: else uint16tobuf(jmptoboot, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); - int changed = 0; - for(int i=0; i < vecsz; i++) { // Patch reset vector to protect vector bootloader - if((flm->tags[i] & TAG_ALLOCATED? flm->buf[i]: device[i]) != jmptoboot[i]) { - flm->buf[i] = jmptoboot[i]; - flm->tags[i] |= TAG_ALLOCATED; - changed = 1; - } - } - // If reset vector patched, ensure to fill in the holes in rest of page - if(changed && flm->page_size > vecsz && flm->page_size <= sizeof device) { - if((rc = ur_readEF(pgm, p, device+vecsz, vecsz, flm->page_size - vecsz, 'F')) < 0) + if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable? + unsigned char device[2048]; + + // Read reset vector from device flash + if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) return rc; - for(int i=vecsz; i < flm->page_size; i++) { - if(!(flm->tags[i] & TAG_ALLOCATED)) { + + int changed = 0; + for(int i=0; i < vecsz; i++) { + if((flm->tags[i] & TAG_ALLOCATED? flm->buf[i]: device[i]) != jmptoboot[i]) { flm->buf[i] = jmptoboot[i]; flm->tags[i] |= TAG_ALLOCATED; + changed = 1; } } + // If reset vector patched, ensure to fill in the holes in rest of page + if(changed && flm->page_size > vecsz && flm->page_size <= sizeof device) { + pmsg_warning("patching reset vector to protect vector bootloader\n"); + if((rc = ur_readEF(pgm, p, device+vecsz, vecsz, flm->page_size - vecsz, 'F')) < 0) + return rc; + for(int i=vecsz; i < flm->page_size; i++) { + if(!(flm->tags[i] & TAG_ALLOCATED)) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } + } + } + } else { // Flash not readable: patch reset vector + for(int i=0; i < vecsz; i++) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } } - } else if(set && set != vecsz) - Return("input overwrites reset vector, which would render the vector bootloader inoperable"); - - if(set) { + } 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 making the vector bootloader unreachable"); + Return("input overwrites the reset vector bricking the bootloader; use -F to patch input"); if(resetdest != ur.blstart) - Return("input file points reset to 0x%04x instead of vector bootloader at 0x%04x, exiting", + Return("input points reset to 0x%04x, not to bootloader at 0x%04x; use -F to patch input", resetdest, ur.blstart); } } @@ -891,7 +898,7 @@ static void urbootPutVersion(char *buf, uint16_t ver) { *buf++ = type & UR_RESETFLAGS? 'r': '-'; *buf = 0; } else if(hi) // Version number in binary from optiboot v4.1 - sprintf(buf, "o%d.%d ??s-??%c", hi, type, hi>=4? 'r': '-'); + sprintf(buf, "o%d.%d -?s-?-%c", hi, type, hi>=4? 'r': '-'); else sprintf(buf, "x0.0 -------"); @@ -1931,7 +1938,8 @@ static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AV return -2; if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) - Return("bootloader does not %shave paged EEPROM write capability", ur.blurversion? "": "seem to "); + Return("bootloader does not %shave paged EEPROM write capability", + ur.blurversion? "": "seem to "); n = addr + n_bytes; @@ -1965,7 +1973,8 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR 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 "); + Return("bootloader does not %shave paged EEPROM read capability", + ur.blurversion? "": "seem to "); n = addr + n_bytes; for(; addr < n; addr += chunk) { From afa408e2c6fc6f2588e8cb1035dd051e47a53950 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 9 Nov 2022 21:00:16 +0000 Subject: [PATCH 11/31] Make urclock.c iron out a bug in some bootloaders --- src/urclock.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/urclock.c b/src/urclock.c index d172f795..50b8f4f4 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1397,6 +1397,7 @@ static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memc // 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)) { @@ -1410,6 +1411,12 @@ static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memc 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; From 321bddbf7b945d4141553542c78e0ce48f1705fc Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Thu, 10 Nov 2022 22:35:08 +0000 Subject: [PATCH 12/31] Rename urclock's option forcetrim to restore --- src/avrdude.1 | 4 ++-- src/urclock.c | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index dfeb5081..0e3b8e90 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -1219,12 +1219,12 @@ options, by filling the remainder of unused flash below the bootloader with 0xff. If this option is specified, the urclock programmer will assume that the bootloader cannot erase the chip itself. The option is useful for backwards-compatible bootloaders that do not implement chip erase. -.It Ar forcetrim +.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 -xforcetrim an +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 diff --git a/src/urclock.c b/src/urclock.c index 50b8f4f4..7378a2dd 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -297,7 +297,7 @@ typedef struct { 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 - forcetrim, // Force uploading of exactly this file, possibly trimming it + 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) @@ -596,7 +596,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const 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.forcetrim) { + if(ur.restore) { if(size > maxsize) size = maxsize; @@ -613,7 +613,7 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const // 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], consider -xforcetrim", + 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)) @@ -859,10 +859,14 @@ nopatch_nometa: int resetdest; if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) - Return("input overwrites the reset vector bricking the bootloader; use -F to patch input"); + Return("input would overwrite the reset vector bricking the bootloader\n" + "%*susing -F will 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; use -F to patch input", - resetdest, ur.blstart); + Return("input points reset to 0x%04x, not to bootloader at 0x%04x\n" + "%*susing -F will patch the input but this may not be what is needed", + resetdest, ur.blstart, (int) strlen(progname)+1, ""); } } @@ -2083,7 +2087,7 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { {"vectornum", &ur.xvectornum, ARG, "Manual override for vector number"}, {"eepromrw", &ur.xeepromrw, NA, "Asssertion of bootloader EEPROM read/write capability"}, {"emulate_ce", &ur.xemulate_ce, NA, "Emulate chip erase"}, - {"forcetrim", &ur.forcetrim, NA, "Upload of unchanged files, trim it if needed"}, + {"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"}, From c67bfe39a3d5573304f086039ded8292b97c30cd Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Thu, 10 Nov 2022 23:00:18 +0000 Subject: [PATCH 13/31] Show input file staistics before patching in update.c --- src/update.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/update.c b/src/update.c index 574f9573..c14d6d44 100644 --- a/src/update.c +++ b/src/update.c @@ -485,23 +485,11 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags pmsg_error("read from file %s failed\n", update_inname(upd->filename)); return LIBAVRDUDE_GENERAL_FAILURE; } - // Patch input if for flash, 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; - } - } - } - 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", @@ -518,6 +506,25 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags msg_info("\n"); } + // Patch input if for flash, eg, for vector bootloaders? + if(pgm->flash_readhook) { + AVRMEM *mem = avr_locate_mem(p, upd->memtype); + if(mem && !strcmp(mem->desc, "flash")) { + Filestats fs_patched; + 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)) + imsg_info("and patching flash input for device%s\n", + pgm->prog_modes & PM_SPM? " bootloader": ""); + } + } + 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); From 22bd977365bb6972a89d6999bde857f1bc236002 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Thu, 10 Nov 2022 23:18:43 +0000 Subject: [PATCH 14/31] Indent erasing chip message in main --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 23ef5aef..40f40c36 100644 --- a/src/main.c +++ b/src/main.c @@ -1393,7 +1393,7 @@ 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) goto main_exit; From 6e3a99be874184052f1e5d13b612c31cf68bfea7 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Fri, 11 Nov 2022 01:27:55 +0000 Subject: [PATCH 15/31] Add write statistics for patched flash input files at notice2 level --- src/update.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/update.c b/src/update.c index c14d6d44..48e2b94f 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) { @@ -506,11 +506,10 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags msg_info("\n"); } - // Patch input if for flash, eg, for vector bootloaders? + // 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")) { - Filestats fs_patched; 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)); @@ -518,9 +517,23 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags } if(memstats(p, upd->memtype, rc, &fs_patched) < 0) return LIBAVRDUDE_GENERAL_FAILURE; - if(memcmp(&fs_patched, &fs, sizeof fs)) - imsg_info("and patching flash input for device%s\n", + 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; From b178deef5f4d76567a8b885b6a96e8eadafee0da Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Fri, 11 Nov 2022 01:33:42 +0000 Subject: [PATCH 16/31] Handle n_page_erase in urclock for parts t441, t841 and t1634 --- src/avrdude.conf.in | 2 +- src/doc/avrdude.texi | 2 +- src/urclock.c | 145 +++++++++++++++++++++++++------------------ 3 files changed, 85 insertions(+), 64 deletions(-) diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 6f897a6b..19fbdc07 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 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/urclock.c b/src/urclock.c index 7378a2dd..501e3ccf 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -757,55 +757,8 @@ nopatch_nometa: ur.emulate_ce = 0; } - 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.bloptiversion || - (ur.blurversion && ur.blurversion < 076)) { - int ai, addr, nset; - - // Scan the memory for pages with unset bytes and read these bytes from current chip flash - uint8_t spc[2048]; - - for(addr = 0; addr < maxsize; addr += ur.uP.pagesize) { - // How many bytes are set in this page? - for(ai = addr, nset = 0; ai < addr + ur.uP.pagesize; ai++) - if(flm->tags[ai] & TAG_ALLOCATED) - nset++; - - // Holes in this page that needs writing? read them in from the chip - if(nset && nset != ur.uP.pagesize) { - // Identify a covering interval for all holes in page - int istart, isize; - - // Lowest address with unset byte - for(ai = addr; flm->tags[ai] & TAG_ALLOCATED; ai++) - continue; - istart = ai; - - // Highest address with unset byte - for(ai = addr + ur.uP.pagesize - 1; flm->tags[ai] & TAG_ALLOCATED; ai--) - continue; - 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) { - for(ai = istart; ai < istart + isize; ai++) - if(!(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 - - // Last, but not least: ensure that vector bootloaders have correct r/jmp at address 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++) @@ -829,24 +782,10 @@ nopatch_nometa: if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) return rc; - int changed = 0; for(int i=0; i < vecsz; i++) { if((flm->tags[i] & TAG_ALLOCATED? flm->buf[i]: device[i]) != jmptoboot[i]) { flm->buf[i] = jmptoboot[i]; flm->tags[i] |= TAG_ALLOCATED; - changed = 1; - } - } - // If reset vector patched, ensure to fill in the holes in rest of page - if(changed && flm->page_size > vecsz && flm->page_size <= sizeof device) { - pmsg_warning("patching reset vector to protect vector bootloader\n"); - if((rc = ur_readEF(pgm, p, device+vecsz, vecsz, flm->page_size - vecsz, 'F')) < 0) - return rc; - for(int i=vecsz; i < flm->page_size; i++) { - if(!(flm->tags[i] & TAG_ALLOCATED)) { - flm->buf[i] = jmptoboot[i]; - flm->tags[i] |= TAG_ALLOCATED; - } } } } else { // Flash not readable: patch reset vector @@ -870,6 +809,88 @@ nopatch_nometa: } } + // 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; } From e2b69dec7572b6173f7dd5021bcfb25b0cdbe5e8 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sat, 12 Nov 2022 01:12:31 +0000 Subject: [PATCH 17/31] Remove MacOS compiler warnings for urclock.c --- src/urclock.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 501e3ccf..416cb72c 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1380,25 +1380,25 @@ vblvecfound: term_out("%0*lx", 2*ur.idlen, urclockID), first=0; } if(ur.showdate || ur.showall) - term_out(" %04d-%02d-%02d %02d.%02d"+first, ur.yyyy, ur.mm, ur.dd, ur.hr, ur.mn), first=0; + term_out(&" %04d-%02d-%02d %02d.%02d"[first], ur.yyyy, ur.mm, ur.dd, ur.hr, ur.mn), first=0; if(ur.showfilename || ur.showall) - term_out(" %s"+first, *ur.filename? ur.filename: ""), first=0; + term_out(&" %s"[first], *ur.filename? ur.filename: ""), first=0; if(ur.showapp || ur.showall) - term_out(" %s%d"+first, single || *ur.filename? "": "application ", ur.storestart), first=0; + term_out(&" %s%d"[first], single || *ur.filename? "": "application ", ur.storestart), first=0; if(ur.showstore || ur.showall) - term_out(" %s%d"+first, single? "": "store ", ur.storesize), first=0; + term_out(&" %s%d"[first], single? "": "store ", ur.storesize), first=0; if(ur.showmeta || ur.showall) - term_out(" %s%d"+first, single? "": "meta ", nmeta(ur.mcode, ur.uP.flashsize)), first=0; + term_out(&" %s%d"[first], single? "": "meta ", nmeta(ur.mcode, ur.uP.flashsize)), first=0; if(ur.showboot || ur.showall) - term_out(" %s%d"+first, single? "": "boot ", ur.blstart? flm->size-ur.blstart: 0), first=0; + term_out(&" %s%d"[first], single? "": "boot ", ur.blstart? flm->size-ur.blstart: 0), first=0; if(ur.showversion || ur.showall) - term_out(" %s"+first, ur.desc+(*ur.desc==' ')), first=0; + 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; + term_out(&" vector %d (%s)"[first], vnum, vblvecname(pgm, vnum)), first=0; } if(ur.showall || ur.showpart) - term_out(" %s"+first, ur.uP.name); + term_out(&" %s"[first], ur.uP.name); if(!first) { term_out("\n"); exit(0); From ff9c8bbe46afa2bbc5aa2d69bed36664557e7061 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sat, 12 Nov 2022 13:31:33 +0000 Subject: [PATCH 18/31] Silence min()/max() compiler warnings, fix urclock.c typo etc --- src/urclock.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 416cb72c..bcfafacf 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -219,8 +219,8 @@ #include "urclock.h" #include "urclock_private.h" -#define max(a, b) ((a) > (b)? (a): (b)) -#define min(a, b) ((a) < (b)? (a): (b)) +#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, @@ -1161,9 +1161,9 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { 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 > min(2048, ur.uP.flashsize/4)) + if(ur.xbootsize < 64 || ur.xbootsize > urmin(2048, ur.uP.flashsize/4)) Return("implausible -xbootsize=%d, should be in [64, %d]", - ur.xbootsize, min(2048, ur.uP.flashsize/4)); + ur.xbootsize, urmin(2048, ur.uP.flashsize/4)); ur.blstart = ur.uP.flashsize - ur.xbootsize; } @@ -1549,16 +1549,16 @@ static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw) Return("bootloader does not %shave EEPROM access capability", ur.blurversion? "": "seem to "); - if(len < 1 || len > max(ur.uP.pagesize, 256)) - Return("len %d exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); + 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 > max(ur.uP.pagesize, 256)) - Return("len+1 = %d odd address exceeds range [1, %d]", len, max(ur.uP.pagesize, 256)); + 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) @@ -2104,9 +2104,9 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { {"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, "Manual override for bootloader size"}, - {"vectornum", &ur.xvectornum, ARG, "Manual override for vector number"}, - {"eepromrw", &ur.xeepromrw, NA, "Asssertion of bootloader EEPROM read/write capability"}, + {"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"}, @@ -2175,7 +2175,7 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id))); for(size_t i=0; i": "", - max(0, 16-(long) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); + urmax(0, 16-(long) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); } if(rc == 0) exit(0); @@ -2235,4 +2235,10 @@ void urclock_initpgm(PROGRAMMER *pgm) { 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 + } From afc2f7cf0c535963f69c3b111b7eea6a90ab144e Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sat, 12 Nov 2022 13:47:48 +0000 Subject: [PATCH 19/31] Hint at option -xdelay=... for urclock programmer not responding messages --- src/urclock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urclock.c b/src/urclock.c index bcfafacf..c2f8586a 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1682,7 +1682,7 @@ static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len) { rv = serial_recv(&pgm->fd, buf, len); if(rv < 0) { - pmsg_error("programmer is not responding\n"); + pmsg_warning("programmer is not responding%s\n", ur.uP.name? "": "; try, eg, -xdelay=200"); return -1; } From 82b9491cbdc5e22c2ac56be7590436be791740a0 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 16 Nov 2022 00:08:46 +0000 Subject: [PATCH 20/31] Harden urclock against bootloader bricking - Detect write restrictions with new pgm->readonly(..., addr) - Check in byte-wise cached write whether mem/addr allows write - Emulated chip erase tells user CE is delayed until first -U - After bootloader CE urclock_chip_erase will init reset vector - Low level paged write @ 0 unconditionally protects reset vector - Low level paged read @ 0 checks and repairs reset vector --- src/avrcache.c | 49 +++++++++++++--------- src/libavrdude.h | 2 + src/pgm.c | 1 + src/term.c | 4 +- src/urclock.c | 103 ++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 128 insertions(+), 31 deletions(-) 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/libavrdude.h b/src/libavrdude.h index dedf4098..9683255b 100644 --- a/src/libavrdude.h +++ b/src/libavrdude.h @@ -808,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; diff --git a/src/pgm.c b/src/pgm.c index 2d343e27..dca0ea87 100644 --- a/src/pgm.c +++ b/src/pgm.c @@ -154,6 +154,7 @@ 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 diff --git a/src/term.c b/src/term.c index c3f643f0..640218a7 100644 --- a/src/term.c +++ b/src/term.c @@ -662,7 +662,9 @@ 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); diff --git a/src/urclock.c b/src/urclock.c index c2f8586a..4ff14656 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -230,6 +230,9 @@ 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 { @@ -569,6 +572,15 @@ static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int } +// What reset *should* look like for vector bootloaders +static void set_reset(const PROGRAMMER *pgm, unsigned char *jmptoboot, int vecsz) { + if(vecsz == 4) + uint32tobuf(jmptoboot, jmp_opcode(ur.blstart)); + else + uint16tobuf(jmptoboot, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); +} + + // 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) { @@ -768,12 +780,7 @@ nopatch_nometa: // Reset vector not programmed? Or -F? Ensure a jmp to bootloader if(ovsigck || set != vecsz) { unsigned char jmptoboot[4]; - - // What reset *should* look like - if(vecsz == 4) - uint32tobuf(jmptoboot, jmp_opcode(ur.blstart)); - else - uint16tobuf(jmptoboot, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); + set_reset(pgm, jmptoboot, vecsz); if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable? unsigned char device[2048]; @@ -1457,7 +1464,7 @@ static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memc /* - * Send a paged cmd + * 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) @@ -1473,8 +1480,25 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char r if(!ur.urprotocol && urclock_load_baddr(pgm, part, mchr, badd) < 0) return -1; - if(mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE && len != ur.uP.pagesize) - Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); + if(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); + + int vecsz = ur.uP.flashsize <= 8192? 2: 4; + if(badd < (unsigned int) vecsz) { // Ensure reset vector points to bl + if(ur.blstart && ur.vbllevel==1) { + unsigned char jmptoboot[4]; + int n = urmin((unsigned int) vecsz - badd, (unsigned int) len); + + set_reset(pgm, jmptoboot, vecsz); + + if(memcmp(payload, jmptoboot+badd, n)) { + memcpy(payload, jmptoboot+badd, n); + pmsg_info("forcing reset vector to point to vector bootloader\n"); + } + } + } + } if(ur.urprotocol) { uint8_t *q = buf, op = @@ -1816,20 +1840,23 @@ static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned // Either emulate chip erase or send appropriate command to bootloader -static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p_unused) { +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)) { - pmsg_notice2("emulating chip erase\n"); + pmsg_info("delaying chip erase to first -U upload to flash\n"); // 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"); @@ -1863,6 +1890,24 @@ static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p_unused) { 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 0; } @@ -2016,6 +2061,23 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR 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]; + set_reset(pgm, jmptoboot, vecsz); + + if(memcmp(&m->buf[addr], jmptoboot, vecsz)) { + memcpy(&m->buf[addr], jmptoboot, vecsz); + 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[addr]) < 0) + return -5; + if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0) + return -6; + } + } + } } } @@ -2079,6 +2141,24 @@ static void urclock_display(const PROGRAMMER *pgm, const char *p_unused) { // End of STK500 section +// Return whether an address is write protected +static int urclock_readonly(const struct programmer_t *pgm, const AVRPART *p, + 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(ur.vbllevel) + if(addr < (unsigned int) (ur.uP.flashsize <= 8192? 2: 4)) + 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; @@ -2232,6 +2312,7 @@ void urclock_initpgm(PROGRAMMER *pgm) { 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(); From d901e0a768598f014fbb1aa0b70457eeb9853b4d Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 16 Nov 2022 00:42:36 +0000 Subject: [PATCH 21/31] Update urbootPutVersion() to reflect urboot v7.7 changes --- src/urclock.c | 20 +++++++++++--------- src/urclock_private.h | 20 ++++++++++++++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 4ff14656..1dad925c 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -902,8 +902,8 @@ nopatch_nometa: } -// Put version string into a buffer of max 17 characters incl nul (normally 13-14 bytes incl nul) -static void urbootPutVersion(char *buf, uint16_t ver) { +// Put version string into a buffer of max 19 characters incl nul (normally 15-16 bytes incl nul) +static void urbootPutVersion(char *buf, uint16_t ver, uint16_t rjmpwp) { uint8_t hi = ver>>8, type = ver & 0xff, flags; if(ver == 0xffff) // Unknown provenance @@ -912,7 +912,7 @@ static void urbootPutVersion(char *buf, uint16_t ver) { if(hi >= 072) { // These are urboot versions sprintf(buf, "u%d.%d ", hi>>3, hi&7); buf += strlen(buf); - *buf++ = type & UR_PGMWRITEPAGE? 'w': '-'; + *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'; @@ -927,12 +927,14 @@ static void urbootPutVersion(char *buf, uint16_t ver) { // V = VBL, patch & verify, v = VBL, patch only, j = VBL, jump only *buf++ = flags==3? 'V': flags==2? 'v': flags? 'j': 'h'; *buf++ = type & UR_PROTECTME? 'p': '-'; - *buf++ = type & UR_RESETFLAGS? 'r': '-'; + *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': '.'; // . means don't know *buf = 0; } else if(hi) // Version number in binary from optiboot v4.1 - sprintf(buf, "o%d.%d -?s-?-%c", hi, type, hi>=4? 'r': '-'); + sprintf(buf, "o%d.%d -?s-?-r--", hi, type); else - sprintf(buf, "x0.0 -------"); + sprintf(buf, "x0.0 ........."); return; } @@ -1187,7 +1189,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { if(!ur.blstart) Return("please specify -xbootsize= and, if needed, -xvectornum= or -xeepromrw"); - uint16_t v16 = 0xffff; + 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)) { @@ -1198,7 +1200,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { // 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]; - uint16_t rjmpwp = buf2uint16(spc+2); // rjmp to bootloader pgm_write_page() or ret opcode + 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 @@ -1316,7 +1318,7 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { } vblvecfound: - urbootPutVersion(ur.desc, v16); + urbootPutVersion(ur.desc, v16, rjmpwp); ur.mcode = 0xff; if(ur.blstart) { diff --git a/src/urclock_private.h b/src/urclock_private.h index f97f08b6..fb4471d6 100644 --- a/src/urclock_private.h +++ b/src/urclock_private.h @@ -92,6 +92,7 @@ typedef struct { // 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 @@ -102,6 +103,7 @@ typedef struct { #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; }) @@ -116,31 +118,37 @@ typedef struct { (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) +#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) +#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) +#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) +#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)) +#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)) +#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) From 2abb666bd2a04b8ecbd381366218b9b895611ad7 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 16 Nov 2022 02:08:13 +0000 Subject: [PATCH 22/31] Prepare urclock for autobaud synchronisation --- src/urclock.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 1dad925c..3024cfb3 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -256,6 +256,8 @@ typedef struct { 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 @@ -928,8 +930,8 @@ static void urbootPutVersion(char *buf, uint16_t ver, uint16_t rjmpwp) { *buf++ = flags==3? 'V': flags==2? 'v': flags? 'j': 'h'; *buf++ = type & UR_PROTECTME? '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': '.'; // . means don't know + *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 -?s-?-r--", hi, type); @@ -1708,7 +1710,8 @@ static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len) { rv = serial_recv(&pgm->fd, buf, len); if(rv < 0) { - pmsg_warning("programmer is not responding%s\n", ur.uP.name? "": "; try, eg, -xdelay=200"); + if(!ur.sync_silence) + pmsg_warning("programmer is not responding%s\n", ur.uP.name? "": "; try, eg, -xdelay=200"); return -1; } @@ -1716,8 +1719,7 @@ static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len) { } - -#define MAX_SYNC_ATTEMPTS 20 +#define MAX_SYNC_ATTEMPTS 23 /* * The modified protocol makes stk_insync and stk_ok responses variable but fixed for a single @@ -1730,7 +1732,13 @@ static int urclock_getsync(const PROGRAMMER *pgm) { int attempt; // Reduce timeout for establishing comms - serial_recv_timeout = 100; // ms + 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; @@ -1746,9 +1754,12 @@ static int urclock_getsync(const PROGRAMMER *pgm) { } else break; } - if(attempt > 1) // Don't report first two attempts - pmsg_warning("attempt %d of %d: not in sync\n", attempt + 1, MAX_SYNC_ATTEMPTS); + 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 From a3eeedd176b08d2d0e46ea9f77d918420fa45df7 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Wed, 16 Nov 2022 02:32:32 +0000 Subject: [PATCH 23/31] Silence some compiler warnings --- src/developer_opts.c | 4 ++-- src/term.c | 2 +- src/urclock.c | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) 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/term.c b/src/term.c index 640218a7..edd6c8e3 100644 --- a/src/term.c +++ b/src/term.c @@ -185,7 +185,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++) diff --git a/src/urclock.c b/src/urclock.c index 3024cfb3..3395ef50 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1569,7 +1569,7 @@ static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire)); pmsg_debug("ur_readEF(%s, %s, %s, %p, 0x%06x, %d, %c)\n", - pgm? ldata(lfirst(pgm->id)): "?", p->desc, mchr=='F'? "flash": "eeprom", buf, badd, len, mchr); + (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"); @@ -2098,8 +2098,8 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR } -int urclock_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, - unsigned long addr, unsigned char data) { +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; @@ -2155,7 +2155,7 @@ static void urclock_display(const PROGRAMMER *pgm, const char *p_unused) { // Return whether an address is write protected -static int urclock_readonly(const struct programmer_t *pgm, const AVRPART *p, +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)) { @@ -2268,7 +2268,7 @@ static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) { msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id))); for(size_t i=0; i": "", - urmax(0, 16-(long) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); + urmax(0, 16-(int) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help); } if(rc == 0) exit(0); From d65a9a3ceeeed126edf762a5513d3bc652ab5192 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sat, 19 Nov 2022 19:39:39 +0000 Subject: [PATCH 24/31] Adapt -c urclock to new reset vector protection in urboot v7.7 --- src/urclock.c | 122 +++++++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 3395ef50..c2e6ef70 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -448,6 +448,12 @@ static uint16_t rjmp_opcode(int dist, int flashsize) { } +// 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 @@ -574,12 +580,16 @@ static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int } -// What reset *should* look like for vector bootloaders -static void set_reset(const PROGRAMMER *pgm, unsigned char *jmptoboot, int vecsz) { - if(vecsz == 4) - uint32tobuf(jmptoboot, jmp_opcode(ur.blstart)); - else - uint16tobuf(jmptoboot, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); +// 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; } @@ -695,13 +705,11 @@ static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const } // OK, now have bootloader start and application start: patch - if(vecsz == 4) { // Always use absolute jump for large devices - uint32tobuf(flm->buf+0, jmp_opcode(ur.blstart)); + set_reset(pgm, flm->buf+0, vecsz); + if(vecsz == 4) uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart)); - } else { // Must use relative jump for small devices - uint16tobuf(flm->buf+0, rjmp_opcode(ur.blstart - 0, ur.uP.flashsize)); + else uint16tobuf(flm->buf+appvecloc, rjmp_opcode(appstart - appvecloc, ur.uP.flashsize)); - } } } } @@ -744,7 +752,7 @@ nopatch: } *p++ = ur.mcode; - // Set tags so above data get burned onto chip + // Set tags so metadata get burned onto chip memset(flm->tags + ur.blstart - nmdata, TAG_ALLOCATED, nmdata); if(ur.initstore) // Zap the pgm store @@ -754,7 +762,7 @@ nopatch: } } - //storing no metadata: put a 0xff byte just below bootloader + // 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; @@ -779,26 +787,35 @@ nopatch_nometa: 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]; - set_reset(pgm, jmptoboot, vecsz); + int resetsize = set_reset(pgm, jmptoboot, vecsz); if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable? - unsigned char device[2048]; + int resetdest; - // Read reset vector from device flash - if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0) - return rc; + 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; - for(int i=0; i < vecsz; i++) { - if((flm->tags[i] & TAG_ALLOCATED? flm->buf[i]: device[i]) != jmptoboot[i]) { + // 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 - for(int i=0; i < vecsz; i++) { + } 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; } @@ -808,12 +825,12 @@ nopatch_nometa: if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) Return("input would overwrite the reset vector bricking the bootloader\n" - "%*susing -F will patch the input but this may not be what is needed", + "%*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 patch the input but this may not be what is needed", + "%*susing -F will try to patch the input but this may not be what is needed", resetdest, ur.blstart, (int) strlen(progname)+1, ""); } } @@ -928,7 +945,7 @@ static void urbootPutVersion(char *buf, uint16_t ver, uint16_t rjmpwp) { 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++ = type & UR_PROTECTME? 'p': '-'; + *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 @@ -1172,9 +1189,9 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { 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(2048, ur.uP.flashsize/4)) + if(ur.xbootsize < 64 || ur.xbootsize > urmin(8192, ur.uP.flashsize/4)) Return("implausible -xbootsize=%d, should be in [64, %d]", - ur.xbootsize, urmin(2048, ur.uP.flashsize/4)); + ur.xbootsize, urmin(8192, ur.uP.flashsize/4)); ur.blstart = ur.uP.flashsize - ur.xbootsize; } @@ -1222,16 +1239,23 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { // 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 manually overwritten by -xbootsize=%d\n", + 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 manually overwritten by -xvectornum=%d\n", + 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; } @@ -1488,17 +1512,25 @@ static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char r if(len != ur.uP.pagesize) Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize); - int vecsz = ur.uP.flashsize <= 8192? 2: 4; - if(badd < (unsigned int) vecsz) { // Ensure reset vector points to bl - if(ur.blstart && ur.vbllevel==1) { - unsigned char jmptoboot[4]; - int n = urmin((unsigned int) vecsz - badd, (unsigned int) len); + 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); - 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(memcmp(payload, jmptoboot+badd, n)) { + 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 reset vector to point to vector bootloader\n"); + pmsg_info("forcing partial reset vector to point to vector bootloader\n"); } } } @@ -2077,14 +2109,15 @@ static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVR 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) { + if(chunk >= vecsz && ur.blstart && ur.vbllevel == 1) { unsigned char jmptoboot[4]; - set_reset(pgm, jmptoboot, vecsz); + int resetsize = set_reset(pgm, jmptoboot, vecsz); + int resetdest; - if(memcmp(&m->buf[addr], jmptoboot, vecsz)) { - memcpy(&m->buf[addr], jmptoboot, vecsz); + 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[addr]) < 0) + 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; @@ -2334,5 +2367,4 @@ void urclock_initpgm(PROGRAMMER *pgm) { #else pmsg_warning("compiled without readline library, cannot use avrdude -t -c urclock"); #endif - } From ee25a62df6df4bd268f469acaba2a832c28611dd Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sat, 19 Nov 2022 23:09:18 +0000 Subject: [PATCH 25/31] Emulate chip erase in terminal when pgm->chip_erase() soft fails --- src/main.c | 5 ++++- src/term.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/urclock.c | 3 +-- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 40f40c36..3e87019f 100644 --- a/src/main.c +++ b/src/main.c @@ -1395,7 +1395,10 @@ int main(int argc, char * argv []) } else { 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/term.c b/src/term.c index edd6c8e3..78f37216 100644 --- a/src/term.c +++ b/src/term.c @@ -755,8 +755,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/urclock.c b/src/urclock.c index c2e6ef70..d828c24f 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -1898,7 +1898,6 @@ static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { (ur.urprotocol && !(ur.urfeatures & UB_CHIP_ERASE)) || ur.bloptiversion || (ur.blurversion && ur.blurversion < 076)) { - pmsg_info("delaying chip erase to first -U upload to flash\n"); // Bootloader does not implement chip erase: don't send command to bootloader ur.emulate_ce = 1; emulated = 1; @@ -1953,7 +1952,7 @@ static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { } } - return 0; + return emulated? LIBAVRDUDE_SOFTFAIL: 0; } From f276d325ece31a185f2f5007d5c3dc8824652b9a Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sun, 20 Nov 2022 00:27:49 +0000 Subject: [PATCH 26/31] Handle verification errors in read only memory areas gracefully --- src/avr.c | 33 ++++++++++++++++++++++++++------- src/libavrdude.h | 2 +- src/update.c | 2 +- src/urclock.c | 13 ++++++++++--- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/avr.c b/src/avr.c index 333f531a..dee653c0 100644 --- a/src/avr.c +++ b/src/avr.c @@ -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/libavrdude.h b/src/libavrdude.h index 9683255b..83556af3 100644 --- a/src/libavrdude.h +++ b/src/libavrdude.h @@ -889,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/update.c b/src/update.c index 48e2b94f..0a7166cd 100644 --- a/src/update.c +++ b/src/update.c @@ -616,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 index d828c24f..b51b8ffa 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -2194,9 +2194,16 @@ static int urclock_readonly(const struct programmer_t *pgm, const AVRPART *p_unu if(ur.blstart) { if(addr >= (unsigned int) ur.blstart) return 1; - if(ur.vbllevel) - if(addr < (unsigned int) (ur.uP.flashsize <= 8192? 2: 4)) - 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; From 53de22cb833d3c52dd4111e50e66f72a75badfa4 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sun, 20 Nov 2022 01:12:14 +0000 Subject: [PATCH 27/31] Update avrintel.c for LGT8F(8|16|32)8P parts --- src/avrintel.c | 8 ++++---- src/avrintel.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/avrintel.c b/src/avrintel.c index cd4b74a0..a74d964e 100644 --- a/src/avrintel.c +++ b/src/avrintel.c @@ -9,7 +9,7 @@ * meta-author Stefan Rueger * * v 1.1 - * 04.11.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 16ce8ed0..0ab72822 100644 --- a/src/avrintel.h +++ b/src/avrintel.h @@ -9,7 +9,7 @@ * meta-author Stefan Rueger * * v 1.1 - * 04.11.2022 + * 20.11.2022 * */ From c0e4dd494e39793c9c20dc13036a0aac8ae1aac1 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sun, 20 Nov 2022 01:48:39 +0000 Subject: [PATCH 28/31] Use file basename in -c urclock metadata under WIN32 --- src/urclock.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/urclock.c b/src/urclock.c index b51b8ffa..8dd2d599 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -542,8 +542,13 @@ static void set_date_filename(const PROGRAMMER *pgm, const char *fname) { 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); From 7f4474f0497099f9ad048948a53383b4cd729327 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Sun, 20 Nov 2022 13:02:51 +0000 Subject: [PATCH 29/31] Delete previous metadata when writing new file to flash in urclock --- src/urclock.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/urclock.c b/src/urclock.c index 8dd2d599..c66c616e 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -774,9 +774,22 @@ nopatch: 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++) From 2e398916a72cb54e8577d4daf074a3c70d4f8e76 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Tue, 22 Nov 2022 00:47:26 +0000 Subject: [PATCH 30/31] Hash known bootloaders for urclock; they don't need -xbootsize=.... --- src/urclock.c | 135 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 12 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index c66c616e..2fb31558 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -263,7 +263,9 @@ typedef struct { 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 + 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 @@ -940,7 +942,7 @@ nopatch_nometa: // Put version string into a buffer of max 19 characters incl nul (normally 15-16 bytes incl nul) -static void urbootPutVersion(char *buf, uint16_t ver, uint16_t rjmpwp) { +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 @@ -968,9 +970,11 @@ static void urbootPutVersion(char *buf, uint16_t ver, uint16_t rjmpwp) { *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 -?s-?-r--", hi, type); - else + } 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; @@ -1096,6 +1100,107 @@ static void set_uP(const PROGRAMMER *pgm, const AVRPART *p, int mcuid, int mcuid } +// 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 * @@ -1242,8 +1347,8 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { 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, and if OK extract properties - if(urver >= 072 && urver != 0xff && (isRjmp(rjmpwp) || rjmpwp == ret_opcode)) { // Prob urboot + // 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 @@ -1281,11 +1386,9 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { } } else if(urver != 0xff) { // Probably optiboot where the version number is two bytes ur.bloptiversion = (urver<<8) + cap; - if(!ur.blstart) - Return("bootloader might be optiboot %d.%d? Please use -xbootsize=\n", urver, cap); } - if(!ur.blstart && ur.vbllevel) { // An older version urboot vector bootloader + 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? @@ -1356,13 +1459,21 @@ static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) { } } - // Still no bootloader start address? + // Still no bootloader start address? Read in top flash and guess bootloader start if(!ur.blstart) + guessblstart(pgm, p); + + // Still no bootloader start address? + if(!ur.blstart) { + if(ur. bloptiversion) + Return("bootloader might be optiboot %d.%d? Please use -xbootsize=\n", + ur.bloptiversion>>8, ur.bloptiversion & 255); Return("unknown bootloader ... please specify -xbootsize=\n"); + } } vblvecfound: - urbootPutVersion(ur.desc, v16, rjmpwp); + urbootPutVersion(pgm, ur.desc, v16, rjmpwp); ur.mcode = 0xff; if(ur.blstart) { From f9aea24fba683fc66cfef94e478396cf5e3a2c9e Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Tue, 22 Nov 2022 00:49:34 +0000 Subject: [PATCH 31/31] Reduce drain timeout value for Windows in urclock.c --- src/urclock.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/urclock.c b/src/urclock.c index 2fb31558..a987153d 100644 --- a/src/urclock.c +++ b/src/urclock.c @@ -2154,9 +2154,7 @@ static int urclock_open(PROGRAMMER *pgm, const char *port) { usleep((80+ur.delay)*1000); // Wait until board comes out of reset // Drain any extraneous input -#ifndef WIN32 serial_drain_timeout = 80; // ms -#endif serial_drain(&pgm->fd, 0); if(urclock_getsync(pgm) < 0)