From 81ff282291527178abab4ab63b22065d6eb3cb2f Mon Sep 17 00:00:00 2001 From: Bluebie Date: Thu, 4 Oct 2012 11:26:39 +1000 Subject: Added viral in place upgrader tool, to upgrade bootloaders via the bootloader itself (should work with any attiny85 bootloader I think, in either direction) --- upgrade/upgrade.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 upgrade/upgrade.c (limited to 'upgrade/upgrade.c') diff --git a/upgrade/upgrade.c b/upgrade/upgrade.c new file mode 100644 index 0000000..3d54acf --- /dev/null +++ b/upgrade/upgrade.c @@ -0,0 +1,165 @@ +// Upgrade is an in-place firmware upgrader for tiny85 chips - just fill in the +// 'bootloaderAddress' variable in bootloader_data.h, and the bootloaderData +// progmem array with the bootloader data, and you're ready to go. +// +// Upgrade will firstly rewrite the interrupt vector table to disable the bootloader, +// rewriting it to just run the upgrade app. Next it erases and writes each page of the +// bootloader in sequence, erasing over any remaining pages leaving them set to 0xFFFF +// Finally upgrader erases it's interrupt table again and fills it with RJMPs to +// bootloaderAddress, effectively bridging the interrupts in to the new bootloader's +// interrupts table. +// +// While upgrade has been written with attiny85 and micronucleus in mind, it should +// work with other bootloaders and other chips with flash self program but no hardware +// bootloader protection, where the bootloader exists at the end of flash +// +// Be very careful to not power down the AVR while upgrader is running. +// If you connect a piezo between pb0 and pb1 you'll hear a bleep when the update +// is complete. You can also connect an LED with pb0 positive and pb1 or gnd negative and +// it will blink + +#include "./utils.h" +#include +#include +#include +#include +#include +#include "./bootloader_data.c" + +void secure_interrupt_vector_table(void); +void write_new_bootloader(void); +void forward_interrupt_vector_table(void); +void beep(void); + +void load_table(uint16_t address, uint16_t words[SPM_PAGESIZE / 2]); +void erase_page(uint16_t address); +void write_page(uint16_t address, uint16_t words[SPM_PAGESIZE / 2]); + + +int main(void) { + delay(100); // milliseconds + cli(); + + secure_interrupt_vector_table(); // reset our vector table to it's original state + write_new_bootloader(); + forward_interrupt_vector_table(); + + beep(); + + return 0; +} + +// erase first page, removing any interrupt table hooks the bootloader added when +// upgrade was uploaded +void secure_interrupt_vector_table(void) { + uint16_t table[SPM_PAGESIZE / 2]; + + load_table(0, table); + + // wipe out any interrupt hooks the bootloader rewrote + int i = 0; + while (i < 15) { + table[0] = 0xFFFF; + i++; + } + + erase_page(0); + write_page(0, table); +} + +// erase bootloader's section and write over it with new bootloader code +void write_new_bootloader(void) { + uint16_t outgoing_page[SPM_PAGESIZE / 2]; + int iter = 0; + while (iter < sizeof(bootloader_data)) { + + // read in one page's worth of data from progmem + int word_addr = 0; + while (word_addr < SPM_PAGESIZE) { + int subaddress = ((int) bootloader_data) + iter + word_addr; + if (subaddress >= ((int) bootloader_data) + sizeof(bootloader_data)) { + outgoing_page[word_addr / 2] = 0xFFFF; + } else { + outgoing_page[word_addr / 2] = pgm_read_word(subaddress); + } + + word_addr += 2; + } + + // erase page in destination + erase_page(bootloader_address + iter); + // write updated page + write_page(bootloader_address + iter, outgoing_page); + + iter += 64; + } +} + +// write in forwarding interrupt vector table +void forward_interrupt_vector_table(void) { + uint16_t vector_table[SPM_PAGESIZE / 2]; + + int iter = 0; + while (iter < SPM_PAGESIZE / 2) { + // rjmp to bootloader_address's interrupt table + vector_table[iter] = 0xC000 + (bootloader_address / 2) - 1; + iter++; + } + + erase_page(0); + write_page(0, vector_table); +} + +void load_table(uint16_t address, uint16_t words[SPM_PAGESIZE / 2]) { + uint16_t subaddress = 0; + address -= address % SPM_PAGESIZE; // round down to nearest page start + + while (subaddress < SPM_PAGESIZE) { + words[subaddress / 2] = pgm_read_word(address + subaddress); + subaddress += 2; + } +} + +void erase_page(uint16_t address) { + boot_page_erase(address - (address % SPM_PAGESIZE)); + boot_spm_busy_wait(); +} + +void write_page(uint16_t address, uint16_t words[SPM_PAGESIZE / 2]) { + // fill buffer + uint16_t iter = 0; + while (iter < SPM_PAGESIZE / 2) { + boot_page_fill(address + (iter * 2), words[iter]); + iter++; + } + + boot_page_write(address); + boot_spm_busy_wait(); // Wait until the memory is written. +} + +// beep for a quarter of a second +void beep(void) { + outputs(pin(0) | pin(1)); + + byte i = 0; + while (i < 250) { + delay(1); + pinOn(pin(0)); + delay(1); + pinOff(pin(0)); + i++; + } +} + +////////////// Add padding to start of program so no program code could reasonably be erased while program is running +// this never needs to be called - avr-gcc stuff happening: http://www.nongnu.org/avr-libc/user-manual/mem_sections.html +void PadStartOfProgram (void) __attribute__ ((naked)) __attribute__ ((section (".init0"))); +void PushMagicWord (void) { + // 32 words of nop - a whole page's worth + asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); + asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); + asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); + asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); +} + + -- cgit v1.2.3