diff options
Diffstat (limited to 'firmware/main.c')
-rw-r--r-- | firmware/main.c | 439 |
1 files changed, 244 insertions, 195 deletions
diff --git a/firmware/main.c b/firmware/main.c index 16cca65..fdf22e4 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -1,10 +1,9 @@ /* - * Project: Micronucleus - v1.11 - * - * Original author (c) 2012 Jenna Fox + * Project: Micronucleus - v2.0 * - * Optimizations v1.10/v1.11 (c) 2013 Tim Bo"scke - cpldcpu@gmail.com - * v1.11 (c) 2013 Shay Green + * Micronucleus V2.0 (c) 2014 Tim Bo"scke - cpldcpu@gmail.com + * (c) 2014 Shay Green + * Original Micronucleus (c) 2012 Jenna Fox * * Based on USBaspLoader-tiny85 (c) 2012 Louis Beaudoin * Based on USBaspLoader (c) 2007 by OBJECTIVE DEVELOPMENT Software GmbH @@ -12,13 +11,8 @@ * License: GNU GPL v2 (see License.txt) */ -#define MICRONUCLEUS_VERSION_MAJOR 1 -#define MICRONUCLEUS_VERSION_MINOR 11 -// how many milliseconds should host wait till it sends another erase or write? -// needs to be above 4.5 (and a whole integer) as avr freezes for 4.5ms -#define MICRONUCLEUS_WRITE_SLEEP 8 -// Use the old delay routines without NOP padding. This saves memory. -#define __DELAY_BACKWARD_COMPATIBLE__ +#define MICRONUCLEUS_VERSION_MAJOR 2 +#define MICRONUCLEUS_VERSION_MINOR 0 #include <avr/io.h> #include <avr/pgmspace.h> @@ -27,294 +21,349 @@ #include <util/delay.h> #include "bootloaderconfig.h" - - - #include "usbdrv/usbdrv.c" // verify the bootloader address aligns with page size -#if BOOTLOADER_ADDRESS % SPM_PAGESIZE != 0 - #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize" +#if (defined __AVR_ATtiny841__)||(defined __AVR_ATtiny441__) + #if BOOTLOADER_ADDRESS % ( SPM_PAGESIZE * 4 ) != 0 + #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize" + #endif +#else + #if BOOTLOADER_ADDRESS % SPM_PAGESIZE != 0 + #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize" + #endif #endif #if SPM_PAGESIZE>256 #error "Micronucleus only supports pagesizes up to 256 bytes" #endif -// command system schedules functions to run in the main loop -register uint8_t command asm("r3"); // bind command to r3 -register uint16_union_t currentAddress asm("r4"); // r4/r5 current progmem address, used for erasing and writing -register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter +#if ((AUTO_EXIT_MS>0) && (AUTO_EXIT_MS<1000)) + #error "Do not set AUTO_EXIT_MS to below 1s to allow Micronucleus to function properly" +#endif -#if OSCCAL_RESTORE +// Device configuration reply +// Length: 6 bytes +// Byte 0: User program memory size, high byte +// Byte 1: User program memory size, low byte +// Byte 2: Flash Pagesize in bytes +// Byte 3: Page write timing in ms. +// Bit 7 '0': Page erase time equals page write time +// Bit 7 '1': Page erase time equals page write time divided by 4 +// Byte 4: SIGNATURE_1 +// Byte 5: SIGNATURE_2 + +PROGMEM const uint8_t configurationReply[6] = { + (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, + ((uint16_t)PROGMEM_SIZE) & 0xff, + SPM_PAGESIZE, + MICRONUCLEUS_WRITE_SLEEP, + SIGNATURE_1, + SIGNATURE_2 +}; + + typedef union { + uint16_t w; + uint8_t b[2]; + } uint16_union_t; + +#if OSCCAL_RESTORE_DEFAULT register uint8_t osccal_default asm("r2"); #endif -static uint16_t vectorTemp[2]; // remember data to create tinyVector table before BOOTLOADER_ADDRESS +register uint16_union_t currentAddress asm("r4"); // r4/r5 current progmem address, used for erasing and writing +register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter +// command system schedules functions to run in the main loop enum { - cmd_local_nop=0, // also: get device info + cmd_local_nop=0, cmd_device_info=0, cmd_transfer_page=1, cmd_erase_application=2, + cmd_write_data=3, cmd_exit=4, - cmd_write_page=5, + cmd_write_page=64 // internal commands start at 64 }; +register uint8_t command asm("r3"); // bind command to r3 // Definition of sei and cli without memory barrier keyword to prevent reloading of memory variables #define sei() asm volatile("sei") #define cli() asm volatile("cli") #define nop() asm volatile("nop") +#define wdr() asm volatile("wdr") + +// Use the old delay routines without NOP padding. This saves memory. +#define __DELAY_BACKWARD_COMPATIBLE__ /* ------------------------------------------------------------------------ */ static inline void eraseApplication(void); static void writeFlashPage(void); static void writeWordToPageBuffer(uint16_t data); static uint8_t usbFunctionSetup(uint8_t data[8]); -static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length); static inline void leaveBootloader(void); -// erase any existing application and write in jumps for usb interrupt and reset to bootloader -// - Because flash can be erased once and programmed several times, we can write the bootloader -// - vectors in now, and write in the application stuff around them later. -// - if vectors weren't written back in immediately, usb would fail. +// This function is never called, it is just here to suppress a compiler warning. +USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; } + +// erase all pages until bootloader, in reverse order (so our vectors stay in place for as long as possible) +// to minimise the chance of leaving the device in a state where the bootloader wont run, if there's power failure +// during upload static inline void eraseApplication(void) { - // erase all pages until bootloader, in reverse order (so our vectors stay in place for as long as possible) - // while the vectors don't matter for usb comms as interrupts are disabled during erase, it's important - // to minimise the chance of leaving the device in a state where the bootloader wont run, if there's power failure - // during upload - - uint8_t i; uint16_t ptr = BOOTLOADER_ADDRESS; - cli(); while (ptr) { +#if (defined __AVR_ATtiny841__)||(defined __AVR_ATtiny441__) + ptr -= SPM_PAGESIZE * 4; +#else ptr -= SPM_PAGESIZE; +#endif boot_page_erase(ptr); } - - currentAddress.w = 0; - for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors. - writeFlashPage(); // enables interrupts + + // Reset address to ensure the reset vector is written first. + currentAddress.w = 0; } // simply write currently stored page in to already erased flash memory -static void writeFlashPage(void) { - cli(); - boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required - sei(); +static inline void writeFlashPage(void) { + if (currentAddress.w - 2 <BOOTLOADER_ADDRESS) + boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required } -// clear memory which stores data to be written by next writeFlashPage call -#define __boot_page_fill_clear() \ -(__extension__({ \ - __asm__ __volatile__ \ - ( \ - "sts %0, %1\n\t" \ - "spm\n\t" \ - : \ - : "i" (_SFR_MEM_ADDR(__SPM_REG)), \ - "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \ - ); \ -})) - -// write a word in to the page buffer, doing interrupt table modifications where they're required +// Write a word into the page buffer. +// Will patch the bootloader reset vector into the main vectortable to ensure +// the device can not be bricked. Saving user-reset-vector is done in the host +// tool, starting with firmware V2 static void writeWordToPageBuffer(uint16_t data) { - uint8_t previous_sreg; - - // first two interrupt vectors get replaced with a jump to the bootloader's vector table - // remember vectors or the tinyvector table - if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { - vectorTemp[0] = data; - data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; - } - - if (currentAddress.w == USBPLUS_VECTOR_OFFSET * 2) { - vectorTemp[1] = data; - data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; - } - - // at end of page just before bootloader, write in tinyVector table - // see http://embedded-creations.com/projects/attiny85-usb-bootloader-overview/avr-jtag-programmer/ - // for info on how the tiny vector table works - if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET) { - data = vectorTemp[0] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 2 + RESET_VECTOR_OFFSET; - } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_USBPLUS_OFFSET) { - data = vectorTemp[1] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 1 + USBPLUS_VECTOR_OFFSET; -#if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz - } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { - data = OSCCAL; -#endif + +#ifndef ENABLE_UNSAFE_OPTIMIZATIONS + #if BOOTLOADER_ADDRESS < 8192 + // rjmp + if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { + data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; } + #else + // far jmp + if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { + data = 0x940c; + } else if (currentAddress.w == (RESET_VECTOR_OFFSET +1 ) * 2) { + data = (BOOTLOADER_ADDRESS/2); + } + #endif +#endif - previous_sreg=SREG; - cli(); // ensure interrupts are disabled +#if OSCCAL_SAVE_CALIB + if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { + data = OSCCAL; + } +#endif boot_page_fill(currentAddress.w, data); - - // increment progmem address by one word currentAddress.w += 2; - SREG=previous_sreg; } -// This function is never called, it is just here to suppress a compiler warning. -USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; } - /* ------------------------------------------------------------------------ */ static uint8_t usbFunctionSetup(uint8_t data[8]) { usbRequest_t *rq = (void *)data; - - static uint8_t replyBuffer[4] = { - (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, - ((uint16_t)PROGMEM_SIZE) & 0xff, - SPM_PAGESIZE, - MICRONUCLEUS_WRITE_SLEEP - }; - - idlePolls.b[1]=0; // reset idle polls when we get usb traffic - + if (rq->bRequest == cmd_device_info) { // get device info - usbMsgPtr = replyBuffer; - return 4; - } else if (rq->bRequest == cmd_transfer_page) { // transfer page - // clear page buffer as a precaution before filling the buffer in case - // a previous write operation failed and there is still something in the buffer. - __boot_page_fill_clear(); - currentAddress.w = rq->wIndex.word; - return USB_NO_MSG; // hands off work to usbFunctionWrite + usbMsgPtr = (usbMsgPtr_t)configurationReply; + return sizeof(configurationReply); + } else if (rq->bRequest == cmd_transfer_page) { + // Set page address. Address zero always has to be written first to ensure reset vector patching. + // Mask to page boundary to prevent vulnerability to partial page write "attacks" + if ( currentAddress.w != 0 ) { + currentAddress.b[0]=rq->wIndex.bytes[0] & (~ (SPM_PAGESIZE-1)); + currentAddress.b[1]=rq->wIndex.bytes[1]; + } + } else if (rq->bRequest == cmd_write_data) { // Write data + writeWordToPageBuffer(rq->wValue.word); + writeWordToPageBuffer(rq->wIndex.word); + if ((currentAddress.b[0] % SPM_PAGESIZE) == 0) + command=cmd_write_page; // ask runloop to write our page } else { // Handle cmd_erase_application and cmd_exit - command=rq->bRequest; - return 0; + command=rq->bRequest&0x3f; } -} - -// read in a page over usb, and write it in to the flash write buffer -static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length) { - do { - // make sure we don't write over the bootloader! - if (currentAddress.w >= BOOTLOADER_ADDRESS) break; - - writeWordToPageBuffer(*(uint16_t *) data); - data += 2; // advance data pointer - length -= 2; - } while(length); - - // if we have now reached another page boundary, we're done - uint8_t isLast = ((currentAddress.b[0] % SPM_PAGESIZE) == 0); - if (isLast) command=cmd_write_page; // ask runloop to write our page - - return isLast; // let V-USB know we're done with this request -} - -/* ------------------------------------------------------------------------ */ -void PushMagicWord (void) __attribute__ ((naked)) __attribute__ ((section (".init3"))); - -// put the word "B007" at the bottom of the stack (RAMEND - RAMEND-1) -void PushMagicWord (void) { - asm volatile("ldi r16, 0xB0"::); - asm volatile("push r16"::); - asm volatile("ldi r16, 0x07"::); - asm volatile("push r16"::); + return 0; } static void initHardware (void) { // Disable watchdog and set timeout to maximum in case the WDT is fused on +#ifdef CCP + // New ATtinies841/441 use a different unlock sequence and renamed registers + MCUSR=0; + CCP = 0xD8; + WDTCSR = 1<<WDP2 | 1<<WDP1 | 1<<WDP0; +#else MCUSR=0; WDTCR = 1<<WDCE | 1<<WDE; WDTCR = 1<<WDP2 | 1<<WDP1 | 1<<WDP0; +#endif - /* initialize */ - #if OSCCAL_RESTORE - osccal_default = OSCCAL; - #endif - + usbDeviceDisconnect(); /* do this while interrupts are disabled */ - _delay_ms(500); + _delay_ms(300); usbDeviceConnect(); - usbInit(); // Initialize INT settings after reconnect - sei(); + usbInit(); // Initialize INT settings after reconnect } /* ------------------------------------------------------------------------ */ // reset system to a normal state and launch user program static void leaveBootloader(void) __attribute__((__noreturn__)); static inline void leaveBootloader(void) { - + bootLoaderExit(); - cli(); - usbDeviceDisconnect(); /* Disconnect micronucleus */ - USB_INTR_ENABLE = 0; - USB_INTR_CFG = 0; /* also reset config bits */ - - // clear magic word from bottom of stack before jumping to the app - *(uint8_t*)(RAMEND) = 0x00; // A single write is sufficient to invalidate magic word +#if OSCCAL_RESTORE_DEFAULT + OSCCAL=osccal_default; + nop(); // NOP to avoid CPU hickup during oscillator stabilization +#endif - #if OSCCAL_RESTORE - OSCCAL=osccal_default; - nop(); // NOP to avoid CPU hickup during oscillator stabilization - #elif OSCCAL_16_5MHz - // adjust clock to previous calibration value, so user program always starts with same calibration - // as when it was uploaded originally - unsigned char stored_osc_calibration = pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET); - if (stored_osc_calibration != 0xFF && stored_osc_calibration != 0x00) { - OSCCAL=stored_osc_calibration; - nop(); - } - #endif - - asm volatile ("rjmp __vectors - 4"); // jump to application reset vector at end of flash + asm volatile ("rjmp __vectors - 4"); // jump to application reset vector at end of flash - for (;;); // Make sure function does not return to help compiler optimize + for (;;); // Make sure function does not return to help compiler optimize } +void USB_INTR_VECTOR(void); int main(void) { - + uint8_t osccal_tmp; + bootLoaderInit(); - + + /* save default OSCCAL calibration */ +#if OSCCAL_RESTORE_DEFAULT + osccal_default = OSCCAL; +#endif + +#if OSCCAL_SAVE_CALIB + // adjust clock to previous calibration value, so bootloader starts with proper clock calibration + unsigned char stored_osc_calibration = pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET); + if (stored_osc_calibration != 0xFF) { + OSCCAL=stored_osc_calibration; + nop(); + } +#endif + if (bootLoaderStartCondition()||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)) { initHardware(); LED_INIT(); if (AUTO_EXIT_NO_USB_MS>0) { - idlePolls.b[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS) * 10UL)>>8; + idlePolls.b[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS)/5)>>8; } else { idlePolls.b[1]=0; } + command=cmd_local_nop; + currentAddress.w = 0; + do { - _delay_us(100); - wdt_reset(); // Only necessary if WDT is fused on + // 15 clockcycles per loop. + // adjust fastctr for 5ms timeout - command=cmd_local_nop; - usbPoll(); - - idlePolls.w++; + uint16_t fastctr=(uint16_t)(F_CPU/(1000.0f*15.0f/5.0f)); + uint8_t resetctr=20; + + do { + if ((USBIN & USBMASK) !=0) resetctr=20; + + if (!--resetctr) { // reset encountered + usbNewDeviceAddr = 0; // bits from the reset handling of usbpoll() + usbDeviceAddr = 0; +#if (OSCCAL_HAVE_XTAL == 0) + calibrateOscillatorASM(); +#endif + } + + if (USB_INTR_PENDING & (1<<USB_INTR_PENDING_BIT)) { + USB_INTR_VECTOR(); // clears INT_PENDING (See se0: in asmcommon.inc) + idlePolls.b[1]=0; // reset idle polls when we get usb traffic + break; + } + + } while(--fastctr); - // Try to execute program if bootloader exit condition is met - if (AUTO_EXIT_MS&&(idlePolls.w==AUTO_EXIT_MS*10L)) command=cmd_exit; - - LED_MACRO( idlePolls.b[1] ); - - // Wait for USB traffic to finish before a blocking event is executed - // All events will render the MCU unresponsive to USB traffic for a while. - if (command!=cmd_local_nop) _delay_ms(2); - + wdr(); + + #if OSCCAL_SLOW_PROGRAMMING + osccal_tmp = OSCCAL; + OSCCAL = osccal_default; + #endif + // commands are only evaluated after next USB transmission or after 5 ms passed if (command==cmd_erase_application) eraseApplication(); - else if (command==cmd_write_page) - writeFlashPage(); + if (command==cmd_write_page) + writeFlashPage(); + #if OSCCAL_SLOW_PROGRAMMING + OSCCAL = osccal_tmp; + #endif + + + + if (command==cmd_exit) { + if (!fastctr) break; // Only exit after 5 ms timeout + } else { + command=cmd_local_nop; + } + + { + // This is usbpoll() minus reset logic and double buffering + int8_t len; + len = usbRxLen - 3; + if(len >= 0){ + usbProcessRx(usbRxBuf + 1, len); // only single buffer due to in-order processing + usbRxLen = 0; /* mark rx buffer as available */ + } + if(usbTxLen & 0x10){ /* transmit system idle */ + if(usbMsgLen != USB_NO_MSG){ /* transmit data pending? */ + usbBuildTxBlock(); + } + } + } + + idlePolls.w++; + + // Try to execute program when bootloader times out + if (AUTO_EXIT_MS&&(idlePolls.w==(AUTO_EXIT_MS/5))) { + if (pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)!=0xff) break; + } + + LED_MACRO( idlePolls.b[0] ); + + // Test whether another interrupt occurred during the processing of USBpoll and commands. + // If yes, we missed a data packet on the bus. Wait until the bus was idle for 10µs to + // allow synchronising to the next incoming packet. - /* main event loop runs as long as no problem is uploaded or existing program is not executed */ - } while((command!=cmd_exit)||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)); + if (USB_INTR_PENDING & (1<<USB_INTR_PENDING_BIT)) // Usbpoll() collided with data packet + { + uint8_t ctr; + + // loop takes 5 cycles + asm volatile( + " ldi %0,%1 \n\t" + "loop%=: sbic %2,%3 \n\t" + " ldi %0,%1 \n\t" + " subi %0,1 \n\t" + " brne loop%= \n\t" + : "=&d" (ctr) + : "M" ((uint8_t)(10.0f*(F_CPU/1.0e6f)/5.0f+0.5)), "I" (_SFR_IO_ADDR(USBIN)), "M" (USB_CFG_DPLUS_BIT) + ); + USB_INTR_PENDING = 1<<USB_INTR_PENDING_BIT; + } + } while(1); LED_EXIT(); - } + usbDeviceDisconnect(); /* Disconnect micronucleus */ + USB_INTR_ENABLE = 0; + USB_INTR_CFG = 0; /* also reset config bits */ + + } + leaveBootloader(); } /* ------------------------------------------------------------------------ */ |