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();  }  /* ------------------------------------------------------------------------ */ | 
