Disable yellow LED driver for Pro-Micro
[pub/lufa.git] / Bootloaders / DFU / BootloaderDFU.c
index 1f86aee..acd0296 100644 (file)
@@ -1,13 +1,13 @@
 /*
              LUFA Library
 /*
              LUFA Library
-     Copyright (C) Dean Camera, 2013.
+     Copyright (C) Dean Camera, 2021.
 
   dean [at] fourwalledcubicle [dot] com
            www.lufa-lib.org
 */
 
 /*
 
   dean [at] fourwalledcubicle [dot] com
            www.lufa-lib.org
 */
 
 /*
-  Copyright 2013  Dean Camera (dean [at] fourwalledcubicle [dot] com)
+  Copyright 2021  Dean Camera (dean [at] fourwalledcubicle [dot] com)
 
   Permission to use, copy, modify, distribute, and sell this
   software and its documentation for any purpose is hereby granted
 
   Permission to use, copy, modify, distribute, and sell this
   software and its documentation for any purpose is hereby granted
@@ -45,8 +45,9 @@ static bool IsSecure = SECURE_MODE;
 /** Flag to indicate if the bootloader should be running, or should exit and allow the application code to run
  *  via a soft reset. When cleared, the bootloader will abort, the USB interface will shut down and the application
  *  jumped to via an indirect jump to location 0x0000 (or other location specified by the host).
 /** Flag to indicate if the bootloader should be running, or should exit and allow the application code to run
  *  via a soft reset. When cleared, the bootloader will abort, the USB interface will shut down and the application
  *  jumped to via an indirect jump to location 0x0000 (or other location specified by the host).
+ *  Use volatile to prevent compiler to do optimization, because we change this variable from interrupt context.
  */
  */
-static bool RunBootloader = true;
+static volatile bool RunBootloader = true;
 
 /** Flag to indicate if the bootloader is waiting to exit. When the host requests the bootloader to exit and
  *  jump to the application address it specifies, it sends two sequential commands which must be properly
 
 /** Flag to indicate if the bootloader is waiting to exit. When the host requests the bootloader to exit and
  *  jump to the application address it specifies, it sends two sequential commands which must be properly
@@ -55,6 +56,21 @@ static bool RunBootloader = true;
  */
 static bool WaitForExit = false;
 
  */
 static bool WaitForExit = false;
 
+/** Minimum time in seconds stay forced into bootloader mode before application will be started. This especially
+ *  helpful if the application does not support any update or reflashing or jumping back into bootloader. It prevents
+ *  to brick the device if now ISP programmer is available.
+ */
+#ifndef BL_TIME
+#define BL_TIME 5 /* seconds */
+#endif
+
+/** Minimum time ticks stay in bootloader before application is started or negative value to prevent leaving
+ *  bootloader. Timer 1 is a 16 bit timer, which overflows after 65536 cycles if it is load with zero. The
+ *  timer 1 prescaler is programmed to divide by 64 and the prescaler engine uses a clock_div_1 divisor, means
+ *  no divisor of the CPU clock frequency F_CPU.
+ */
+static int8_t ForceBootloaderTime = ((BL_TIME) > 0) ? (F_CPU / 64 * (BL_TIME) / 65536 + 1) : -1;
+
 /** Current DFU state machine state, one of the values in the DFU_State_t enum. */
 static uint8_t DFU_State = dfuIDLE;
 
 /** Current DFU state machine state, one of the values in the DFU_State_t enum. */
 static uint8_t DFU_State = dfuIDLE;
 
@@ -108,7 +124,17 @@ void Application_Jump_Check(void)
 {
        bool JumpToApplication = false;
 
 {
        bool JumpToApplication = false;
 
-       #if ((BOARD == BOARD_XPLAIN) || (BOARD == BOARD_XPLAIN_REV1))
+       #if (BOARD == BOARD_LEONARDO)
+               /* Enable pull-up on the IO13 pin so we can use it to select the mode */
+               PORTC |= (1 << 7);
+               Delay_MS(10);
+
+               /* If IO13 is not jumpered to ground, start the user application instead */
+               JumpToApplication = ((PINC & (1 << 7)) != 0);
+
+               /* Disable pull-up after the check has completed */
+               PORTC &= ~(1 << 7);
+       #elif ((BOARD == BOARD_XPLAIN) || (BOARD == BOARD_XPLAIN_REV1))
                /* Disable JTAG debugging */
                JTAG_DISABLE();
 
                /* Disable JTAG debugging */
                JTAG_DISABLE();
 
@@ -117,34 +143,85 @@ void Application_Jump_Check(void)
                Delay_MS(10);
 
                /* If the TCK pin is not jumpered to ground, start the user application instead */
                Delay_MS(10);
 
                /* If the TCK pin is not jumpered to ground, start the user application instead */
-               JumpToApplication |= ((PINF & (1 << 4)) != 0);
+               JumpToApplication = ((PINF & (1 << 4)) != 0);
 
                /* Re-enable JTAG debugging */
                JTAG_ENABLE();
 
                /* Re-enable JTAG debugging */
                JTAG_ENABLE();
+       #elif ((BOARD == BOARD_PROMICRO) || (BOARD == BOARD_MICRO))
+               /* Pro-Micro and Arduino Micro board use power-on reset, but no external reset. Both boards have
+                * the hardware bootloader pin HWBE enabled. Unfortunately only the external reset allows together
+                * with an enabled HWBE that the CPU start at the bootloader address independent of the FUSE_BOOTRST.
+                * That means the power-on reset will start just controlled by the FUSE_BOOTRST the bootloader or
+                * direct in the application and cannot be overridden by HWBE signal. Therfore FUSE_BOOTRST shall
+                * be enabled, otherwise the bootloader will not be reached for these boards.
+                * The bootloader checks FUSE_HWBE as *unprogammed* instead of FUSE_BOOTRST as programmed on other
+                * board variants to decide fast application start, without waiting the dedicted bootloader timeout
+                * in case of a USB, watchdog, brown-out or JTAG reset. If the watchdog reset was initiated from
+                * the bootloader marked with the MAGIC_BOOT_KEY this reset flag is reset. All other reset flags
+                * are left untouched to allow the application code checking the reset signals, especially in case
+                * of application fast start.
+                * The bootloader is entered always for external reset and power-on reset. But the bootloader is
+                * anyway exited after that dedicted timeout, if a reset-vector to the application is programmed.
+                * Once a DFU program interacts this the bootloader during this dedicted timeout, the timer stops
+                * and the application needs to be started by DFU bootloader command manually or using a reset.
+                */
+
+               /* Check if the device's forced Bootloader via Hardware Bootenable is unprogrammed */
+               if (BootloaderAPI_ReadFuse(GET_EXTENDED_FUSE_BITS) & ~FUSE_HWBE)
+               {
+                       /* If the reset source was not an external or power-on reset jump to the application */
+                       if (!(MCUSR & ((1 << EXTRF) || (1 << PORF))))
+                         JumpToApplication = true;
+               }
+               /* If the reset source was the bootloader and the key is correct, clear it and jump to the application;
+                * this can happen in the HWBE fuse is set, and the HBE pin is low during the watchdog reset */
+               if ((MCUSR & (1 << WDRF)) && (MagicBootKey == MAGIC_BOOT_KEY))
+               {
+                       JumpToApplication = true;
+
+                       /* Clear reset source */
+                       MCUSR &= ~(1 << WDRF);
+               }
+       #else
+               /* Check if the device's BOOTRST fuse is set */
+               if (!(BootloaderAPI_ReadFuse(GET_HIGH_FUSE_BITS) & ~FUSE_BOOTRST))
+               {
+                       /* If the reset source was not an external reset or the key is correct, clear it and jump to the application */
+                       if (!(MCUSR & (1 << EXTRF)) || (MagicBootKey == MAGIC_BOOT_KEY))
+                         JumpToApplication = true;
+
+                       /* Clear reset source */
+                       MCUSR &= ~(1 << EXTRF);
+               }
+               else
+               {
+                       /* If the reset source was the bootloader and the key is correct, clear it and jump to the application;
+                        * this can happen in the HWBE fuse is set, and the HBE pin is low during the watchdog reset */
+                       if ((MCUSR & (1 << WDRF)) && (MagicBootKey == MAGIC_BOOT_KEY))
+                               JumpToApplication = true;
+
+                       /* Clear reset source */
+                       MCUSR &= ~(1 << WDRF);
+               }
        #endif
 
        #endif
 
-       /* If the reset source was the bootloader and the key is correct, clear it and jump to the application */
-       if ((MCUSR & (1 << WDRF)) && (MagicBootKey == MAGIC_BOOT_KEY))
-         JumpToApplication |= true;
+       /* Clear the boot key in any case */
+       MagicBootKey = 0;
+
+       /* Don't run the user application if the reset vector is blank (no app loaded) */
+       bool ApplicationValid = (pgm_read_word_near(0) != 0xFFFF);
 
        /* If a request has been made to jump to the user application, honor it */
 
        /* If a request has been made to jump to the user application, honor it */
-       if (JumpToApplication)
+       if (JumpToApplication && ApplicationValid)
        {
                /* Turn off the watchdog */
        {
                /* Turn off the watchdog */
-               MCUSR &= ~(1<<WDRF);
-               wdt_disable(); 
-
-               /* Clear the boot key and jump to the user application */
-               MagicBootKey = 0;
+               wdt_disable();
 
                // cppcheck-suppress constStatement
                ((void (*)(void))0x0000)();
        }
 }
 
 
                // cppcheck-suppress constStatement
                ((void (*)(void))0x0000)();
        }
 }
 
-
-static volatile bool stayinbootloader;
-
 /** Main program entry point. This routine configures the hardware required by the bootloader, then continuously
  *  runs the bootloader processing routine until instructed to soft-exit, or hard-reset via the watchdog to start
  *  the loaded application code.
 /** Main program entry point. This routine configures the hardware required by the bootloader, then continuously
  *  runs the bootloader processing routine until instructed to soft-exit, or hard-reset via the watchdog to start
  *  the loaded application code.
@@ -155,42 +232,17 @@ int main(void)
        SetupHardware();
 
        /* Turn on first LED on the board to indicate that the bootloader has started */
        SetupHardware();
 
        /* Turn on first LED on the board to indicate that the bootloader has started */
-       //LEDs_SetAllLEDs(LEDS_LED1);
+       LEDs_SetAllLEDs(LEDS_LED1);
 
        /* Enable global interrupts so that the USB stack can function */
        GlobalInterruptEnable();
 
        /* Run the USB management task while the bootloader is supposed to be running */
 
        /* Enable global interrupts so that the USB stack can function */
        GlobalInterruptEnable();
 
        /* Run the USB management task while the bootloader is supposed to be running */
-       /*if bit_is_clear(PINB,PB5)     // PB5 is Digital 9 on Arduino Pro Micro
-       {
-               loop_until_bit_is_set(PINB,PB5);
-
-               while ((RunBootloader || WaitForExit) && bit_is_set(PINB,PB5))
-                       USB_USBTask();
-
-               loop_until_bit_is_clear(PINB,PB5);
-       }*/
-
-       stayinbootloader = false;
-
-       uint16_t i = 0;
        while (RunBootloader || WaitForExit)
        while (RunBootloader || WaitForExit)
-       {
-               USB_USBTask();
+         USB_USBTask();
 
 
-               if (!stayinbootloader)
-               {
-                       _delay_ms(1);
-                       if (i++ > 5000)
-                       {
-                               break;
-                       }
-               }
-               else
-               {
-                       i = 0;
-               }
-       }
+       /* Wait a short time to end all USB transactions and then disconnect */
+       _delay_us(1000);
 
        /* Reset configured hardware back to their original states for the user application */
        ResetHardware();
 
        /* Reset configured hardware back to their original states for the user application */
        ResetHardware();
@@ -215,14 +267,11 @@ static void SetupHardware(void)
 
        /* Initialize the USB and other board hardware drivers */
        USB_Init();
 
        /* Initialize the USB and other board hardware drivers */
        USB_Init();
-       //LEDs_Init();
-       DDRB = 1;
-       PORTB = _BV(PB5);
-       DDRD = 0b00100000;
-       PORTD = 0;
+       LEDs_Init();
 
        /* Bootloader active LED toggle timer initialization */
        TIMSK1 = (1 << TOIE1);
 
        /* Bootloader active LED toggle timer initialization */
        TIMSK1 = (1 << TOIE1);
+       /* config timer 1 prescaler to F_CPU / clock_div_1 / 64 */
        TCCR1B = ((1 << CS11) | (1 << CS10));
 }
 
        TCCR1B = ((1 << CS11) | (1 << CS10));
 }
 
@@ -231,12 +280,8 @@ static void ResetHardware(void)
 {
        /* Shut down the USB and other board hardware drivers */
        USB_Disable();
 {
        /* Shut down the USB and other board hardware drivers */
        USB_Disable();
-       //LEDs_Disable();
-       DDRB = 0;
-       PORTB = 0;
-       DDRD = 0;
-       PORTD = 0;
-       
+       LEDs_Disable();
+
        /* Disable Bootloader active LED toggle timer */
        TIMSK1 = 0;
        TCCR1B = 0;
        /* Disable Bootloader active LED toggle timer */
        TIMSK1 = 0;
        TCCR1B = 0;
@@ -249,10 +294,13 @@ static void ResetHardware(void)
 /** ISR to periodically toggle the LEDs on the board to indicate that the bootloader is active. */
 ISR(TIMER1_OVF_vect, ISR_BLOCK)
 {
 /** ISR to periodically toggle the LEDs on the board to indicate that the bootloader is active. */
 ISR(TIMER1_OVF_vect, ISR_BLOCK)
 {
-       //LEDs_ToggleLEDs(LEDS_LED1 | LEDS_LED2);
-       PORTB &= ~_BV(PB0);
-       _delay_ms(5);
-       PORTB |= _BV(PB0);
+       LEDs_ToggleLEDs(LEDS_LED1 | LEDS_LED2);
+       /* Count number for forced ticks not below zero */
+       if (ForceBootloaderTime > 0)
+         ForceBootloaderTime--;
+       /* check if it is time to leave the bootloader and a valid application exists */
+       if ((ForceBootloaderTime == 0) && (pgm_read_word_near(0) != 0xFFFF))
+         RunBootloader = false;
 }
 
 /** Event handler for the USB_ControlRequest event. This is used to catch and process control requests sent to
 }
 
 /** Event handler for the USB_ControlRequest event. This is used to catch and process control requests sent to
@@ -268,13 +316,11 @@ void EVENT_USB_Device_ControlRequest(void)
                return;
        }
 
                return;
        }
 
-stayinbootloader = true;
+       /* prevent counter to reach zero */
+       ForceBootloaderTime = -1;
 
        /* Activity - toggle indicator LEDs */
 
        /* Activity - toggle indicator LEDs */
-       //LEDs_ToggleLEDs(LEDS_LED1 | LEDS_LED2);
-       PORTB &= ~_BV(PB0);
-       _delay_ms(5);
-       PORTB |= _BV(PB0);
+       LEDs_ToggleLEDs(LEDS_LED1 | LEDS_LED2);
 
        /* Get the size of the command and data from the wLength value */
        SentCommand.DataSize = USB_ControlRequest.wLength;
 
        /* Get the size of the command and data from the wLength value */
        SentCommand.DataSize = USB_ControlRequest.wLength;
@@ -368,7 +414,7 @@ stayinbootloader = true;
                                                        }
 
                                                        /* Write the next word into the current flash page */
                                                        }
 
                                                        /* Write the next word into the current flash page */
-                                                       boot_page_fill(CurrFlashAddress.Long, Endpoint_Read_16_LE());
+                                                       BootloaderAPI_FillWord(CurrFlashAddress.Long, Endpoint_Read_16_LE());
 
                                                        /* Adjust counters */
                                                        WordsInFlashPage      += 1;
 
                                                        /* Adjust counters */
                                                        WordsInFlashPage      += 1;
@@ -378,8 +424,7 @@ stayinbootloader = true;
                                                        if ((WordsInFlashPage == (SPM_PAGESIZE >> 1)) || !(WordsRemaining))
                                                        {
                                                                /* Commit the flash page to memory */
                                                        if ((WordsInFlashPage == (SPM_PAGESIZE >> 1)) || !(WordsRemaining))
                                                        {
                                                                /* Commit the flash page to memory */
-                                                               boot_page_write(CurrFlashPageStartAddress);
-                                                               boot_spm_busy_wait();
+                                                               BootloaderAPI_WritePage(CurrFlashPageStartAddress);
 
                                                                /* Check if programming incomplete */
                                                                if (WordsRemaining)
 
                                                                /* Check if programming incomplete */
                                                                if (WordsRemaining)
@@ -388,17 +433,13 @@ stayinbootloader = true;
                                                                        WordsInFlashPage          = 0;
 
                                                                        /* Erase next page's temp buffer */
                                                                        WordsInFlashPage          = 0;
 
                                                                        /* Erase next page's temp buffer */
-                                                                       boot_page_erase(CurrFlashAddress.Long);
-                                                                       boot_spm_busy_wait();
+                                                                       BootloaderAPI_ErasePage(CurrFlashAddress.Long);
                                                                }
                                                        }
                                                }
 
                                                /* Once programming complete, start address equals the end address */
                                                StartAddr = EndAddr;
                                                                }
                                                        }
                                                }
 
                                                /* Once programming complete, start address equals the end address */
                                                StartAddr = EndAddr;
-
-                                               /* Re-enable the RWW section of flash */
-                                               boot_rww_enable();
                                        }
                                        else                                                   // Write EEPROM
                                        {
                                        }
                                        else                                                   // Write EEPROM
                                        {
@@ -417,7 +458,7 @@ stayinbootloader = true;
                                                        }
 
                                                        /* Read the byte from the USB interface and write to to the EEPROM */
                                                        }
 
                                                        /* Read the byte from the USB interface and write to to the EEPROM */
-                                                       eeprom_write_byte((uint8_t*)StartAddr, Endpoint_Read_8());
+                                                       eeprom_update_byte((uint8_t*)StartAddr, Endpoint_Read_8());
 
                                                        /* Adjust counters */
                                                        StartAddr++;
 
                                                        /* Adjust counters */
                                                        StartAddr++;
@@ -536,6 +577,12 @@ stayinbootloader = true;
                case DFU_REQ_GETSTATUS:
                        Endpoint_ClearSETUP();
 
                case DFU_REQ_GETSTATUS:
                        Endpoint_ClearSETUP();
 
+                       while (!(Endpoint_IsINReady()))
+                       {
+                               if (USB_DeviceState == DEVICE_STATE_Unattached)
+                                 return;
+                       }
+
                        /* Write 8-bit status value */
                        Endpoint_Write_8(DFU_Status);
 
                        /* Write 8-bit status value */
                        Endpoint_Write_8(DFU_Status);
 
@@ -564,6 +611,12 @@ stayinbootloader = true;
                case DFU_REQ_GETSTATE:
                        Endpoint_ClearSETUP();
 
                case DFU_REQ_GETSTATE:
                        Endpoint_ClearSETUP();
 
+                       while (!(Endpoint_IsINReady()))
+                       {
+                               if (USB_DeviceState == DEVICE_STATE_Unattached)
+                                 return;
+                       }
+
                        /* Write the current device state to the endpoint */
                        Endpoint_Write_8(DFU_State);
 
                        /* Write the current device state to the endpoint */
                        Endpoint_Write_8(DFU_State);
 
@@ -696,8 +749,7 @@ static void ProcessMemProgCommand(void)
                        } CurrFlashAddress = {.Words = {StartAddr, Flash64KBPage}};
 
                        /* Erase the current page's temp buffer */
                        } CurrFlashAddress = {.Words = {StartAddr, Flash64KBPage}};
 
                        /* Erase the current page's temp buffer */
-                       boot_page_erase(CurrFlashAddress.Long);
-                       boot_spm_busy_wait();
+                       BootloaderAPI_ErasePage(CurrFlashAddress.Long);
                }
 
                /* Set the state so that the next DNLOAD requests reads in the firmware */
                }
 
                /* Set the state so that the next DNLOAD requests reads in the firmware */
@@ -786,28 +838,17 @@ static void ProcessWriteCommand(void)
                        }
                        else                                                               // Start via jump
                        {
                        }
                        else                                                               // Start via jump
                        {
-                               /* Set the flag to terminate the bootloader at next opportunity */
-                               RunBootloader = false;
+                               /* Set the flag to terminate the bootloader at next opportunity if a valid application has been loaded */
+                               if (pgm_read_word_near(0) != 0xFFFF)
+                                 RunBootloader = false;
                        }
                }
        }
        else if (IS_TWOBYTE_COMMAND(SentCommand.Data, 0x00, 0xFF))                 // Erase flash
        {
                        }
                }
        }
        else if (IS_TWOBYTE_COMMAND(SentCommand.Data, 0x00, 0xFF))                 // Erase flash
        {
-               uint32_t CurrFlashAddress = 0;
-
                /* Clear the application section of flash */
                /* Clear the application section of flash */
-               while (CurrFlashAddress < (uint32_t)BOOT_START_ADDR)
-               {
-                       boot_page_erase(CurrFlashAddress);
-                       boot_spm_busy_wait();
-                       boot_page_write(CurrFlashAddress);
-                       boot_spm_busy_wait();
-
-                       CurrFlashAddress += SPM_PAGESIZE;
-               }
-
-               /* Re-enable the RWW section of flash as writing to the flash locks it out */
-               boot_rww_enable();
+               for (uint32_t CurrFlashAddress = 0; CurrFlashAddress < (uint32_t)BOOT_START_ADDR; CurrFlashAddress += SPM_PAGESIZE)
+                       BootloaderAPI_ErasePage(CurrFlashAddress);
 
                /* Memory has been erased, reset the security bit so that programming/reading is allowed */
                IsSecure = false;
 
                /* Memory has been erased, reset the security bit so that programming/reading is allowed */
                IsSecure = false;
@@ -820,13 +861,44 @@ static void ProcessWriteCommand(void)
 static void ProcessReadCommand(void)
 {
        const uint8_t BootloaderInfo[3] = {BOOTLOADER_VERSION, BOOTLOADER_ID_BYTE1, BOOTLOADER_ID_BYTE2};
 static void ProcessReadCommand(void)
 {
        const uint8_t BootloaderInfo[3] = {BOOTLOADER_VERSION, BOOTLOADER_ID_BYTE1, BOOTLOADER_ID_BYTE2};
-       const uint8_t SignatureInfo[3]  = {AVR_SIGNATURE_1,    AVR_SIGNATURE_2,     AVR_SIGNATURE_3};
+       const uint8_t SignatureInfo[4]  = {0x58, AVR_SIGNATURE_1, AVR_SIGNATURE_2, AVR_SIGNATURE_3};
 
 
-       uint8_t DataIndexToRead = SentCommand.Data[1];
+       uint8_t DataIndexToRead    = SentCommand.Data[1];
+       bool    ReadAddressInvalid = false;
 
 
-       if (IS_ONEBYTE_COMMAND(SentCommand.Data, 0x00))                         // Read bootloader info
-         ResponseByte = BootloaderInfo[DataIndexToRead];
+       if (IS_ONEBYTE_COMMAND(SentCommand.Data, 0x00))                        // Read bootloader info
+       {
+               if (DataIndexToRead < 3)
+                 ResponseByte = BootloaderInfo[DataIndexToRead];
+               else
+                 ReadAddressInvalid = true;
+       }
        else if (IS_ONEBYTE_COMMAND(SentCommand.Data, 0x01))                    // Read signature byte
        else if (IS_ONEBYTE_COMMAND(SentCommand.Data, 0x01))                    // Read signature byte
-         ResponseByte = SignatureInfo[DataIndexToRead - 0x30];
-}
+       {
+               switch (DataIndexToRead)
+               {
+                       case 0x30:
+                               ResponseByte = SignatureInfo[0];
+                               break;
+                       case 0x31:
+                               ResponseByte = SignatureInfo[1];
+                               break;
+                       case 0x60:
+                               ResponseByte = SignatureInfo[2];
+                               break;
+                       case 0x61:
+                               ResponseByte = SignatureInfo[3];
+                               break;
+                       default:
+                               ReadAddressInvalid = true;
+                               break;
+               }
+       }
 
 
+       if (ReadAddressInvalid)
+       {
+               /* Set the state and status variables to indicate the error */
+               DFU_State  = dfuERROR;
+               DFU_Status = errADDRESS;
+       }
+}