diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | firmware/Makefile | 10 | ||||
-rw-r--r-- | firmware/upgrade.c | 176 |
3 files changed, 186 insertions, 1 deletions
@@ -1,5 +1,6 @@ *.o *.tmp +*.raw firmware/*.bin commandline/littleWire_util.o commandline/micronucleus diff --git a/firmware/Makefile b/firmware/Makefile index 996b08c..2e8eca8 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -190,7 +190,7 @@ read_fuses: $(UISP) --rd_fuses clean: - @rm -f main.hex main.bin main.c.lst main.map *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s main.lss + @rm -f main.hex main.bin main.c.lst main.map main.raw *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s main.lss # file targets: main.bin: $(OBJECTS) @@ -201,6 +201,14 @@ main.hex: main.bin @avr-objcopy -j .text -j .data -O ihex main.bin main.hex @avr-size main.bin +upgrade: main.bin + avr-objcopy -O binary main.bin main.raw + avr-objcopy -I binary -O elf32-avr \ + --rename-section .data=.text \ + --redefine-sym _binary_main_raw_start=loader \ + --redefine-sym _binary_main_raw_end=loader_end \ + main.raw bootloader_linkable.o + disasm: main.bin @avr-objdump -d -S main.bin >main.lss diff --git a/firmware/upgrade.c b/firmware/upgrade.c new file mode 100644 index 0000000..698ed28 --- /dev/null +++ b/firmware/upgrade.c @@ -0,0 +1,176 @@ +// 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 <avr/io.h> +#include <avr/interrupt.h> +#include <avr/pgmspace.h> +#include <avr/wdt.h> +#include <avr/boot.h> +#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 reboot(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) { + pinsOff(0xFF); // pull down all pins + outputs(0xFF); // all to ground - force usb disconnect + delay(250); // milliseconds + inputs(0xFF); // let them float + delay(250); + cli(); + + secure_interrupt_vector_table(); // reset our vector table to it's original state + write_new_bootloader(); + forward_interrupt_vector_table(); + + beep(); + + reboot(); + + 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 < SPM_PAGESIZE / 2) { + 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)); + pinOff(1); + + byte i = 0; + while (i < 250) { + delay(1); + pinOn(pin(0)); + delay(1); + pinOff(pin(0)); + i++; + } +} + +void reboot(void) { + void (*ptrToFunction)(); // pointer to a function + ptrToFunction = 0x0000; + (*ptrToFunction)(); // reset! +} + + +////////////// 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 +volatile void FakeISR (void) __attribute__ ((naked)) __attribute__ ((section (".init0"))); +volatile void FakeISR (void) { + // 16 nops to pad out first section of program + 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"); +} |