1.8 PRIME Service Bootloader

The PRIME Service Bootloader application implements a bootloader designed to manage firmware images. These images are received through the PRIME upgrade protocol defined in the PRIME specification, but the Bootloader application is not aware of that.

This project is without operative system (Bare-Metal). The following table shows the available PRIME Service Bootloader projects.
Table 1-5. PRIME Service Bootloader projects
NamePath Boards
Service Bootloader\smartenergy_prime_apps\apps\prime_apps\prime_service_bootloader\bootloader_pic32cxmtg.XPIC32CXMTG-EK
Service Bootloader\smartenergy_prime_apps\apps\prime_apps\prime_service_bootloader\bootloader_pic32cxmtsh.XPIC32CXMTSH-DB

Application Example

The PRIME Service Bootloader application implements a bootloader designed to manage firmware images. These images are received through the PRIME upgrade protocol defined in the PRIME specification, but the Bootloader application is not aware of that.

In the example application, the app_bootloader.c file contains:
  • Non-volatile data storage. The PRIME Service Bootloader needs it to manage the bootloading process and the boot configuration.
    • For PIC32CXMT it is implemented using the User Signature.
      static void lAPP_BOOTLOADER_UpdateUserSignature(uint8_t pagesCnt, BOOT_STATE state) 
      {
          /* Update values */
          bootConfig[16] = pagesCnt;
          bootConfig[17] = (uint8_t) state;
      
          /* Erase the user signature */
          SEFC0_UserSignatureErase(BOOT_USER_SIGNATURE_BLOCK);
      
          /* Update the user signature */
          (void) SEFC0_UserSignatureWrite((void*) userSignBuf,
                  (uint32_t) BOOT_USER_SIGNATURE_SIZE_64,
                  (SEFC_USERSIGNATURE_BLOCK) BOOT_USER_SIGNATURE_BLOCK,
                  (SEFC_USERSIGNATURE_PAGE) BOOT_USER_SIGNATURE_PAGE);
      }
  • Memory handling. The PRIME Service Bootloader needs to delete, copy and verify pages.
    • For PIC32CXMTG it is implemented using the SEFC0 peripheral.
      static uint8_t lAPP_BOOTLOADER_DeletePage(uint32_t addr) 
      {
          SEFC0_RegionUnlock(addr);
      
          while (SEFC0_IsBusy()) {
              ;
          }
      
          SEFC0_PageErase(addr); // Function erases 16 pages
      
          while (SEFC0_IsBusy()) {
              ;
          }
      
          return 1;
      }
      
      static uint8_t lAPP_BOOTLOADER_CopyPage(uint32_t srcAddr, uint32_t dstAddr) 
      {
          uint8_t *page;
          uint8_t i;
      
          (void) memcpy(pageBlock, (uint8_t *) (srcAddr), BOOT_FLASH_16PAGE_SIZE);
          page = &pageBlock[0];
      
          for (i = 0; i < BOOT_FLASH_PAGES_NUMBER; i++) {
              if (SEFC0_PageWrite((uint32_t *) page, dstAddr) == false) {
                  return 0;
              }
      
              while (SEFC0_IsBusy()) {
                  ;
              }
      
              page += BOOT_FLASH_PAGE_SIZE;
              dstAddr += BOOT_FLASH_PAGE_SIZE;
          }
      
          return 1;
      }
      
      static uint8_t lAPP_BOOTLOADER_VerifyPage(uint8_t *ramPage, uint8_t *flashPage, uint16_t pageSize) 
      {
          uint16_t i = 0;
      
          while (i < pageSize) {
              if (ramPage[i] != flashPage[i]) {
                  return 0;
              }
      
              i++;
          }
      
          return 1;
      }

Application Functionality

The PRIME Service Bootloader application functionality is very simple as it just reads and writes memory pages according to the information that has been stored previously in the User Signature.

At initialization state, the application prepares the User Signature:
case APP_BOOTLOADER_STATE_INIT:
{
    bool appInitialized = true;

    if (appInitialized) {
        /* Ensure all priority bits are assigned as preemption */
        /* priority bits. */
        NVIC_SetPriorityGrouping(__NVIC_PRIO_BITS);
        __set_BASEPRI(0);

        /* Disable User Signature write protection */
        SEFC0_WriteProtectionSet(0);

        /* Enable write and read User Signature rights */
        /* (block 0 / area 1)  */
        SEFC0_UserSignatureRightsSet(
                SEFC_EEFC_USR_RDENUSB1_Msk | SEFC_EEFC_USR_WRENUSB1_Msk);

        app_bootloaderData.state = APP_BOOTLOADER_STATE_SERVICE_TASKS;
    }

    break;
}
After initialization, the application performs several actions:
  • Extract the boot configuration from the User Signature:
    /* Read the boot configuration from user signature */
    (void) SEFC0_UserSignatureRead((void*) userSignBuf,
            (uint32_t) BOOT_USER_SIGNATURE_SIZE_64,
            (SEFC_USERSIGNATURE_BLOCK) BOOT_USER_SIGNATURE_BLOCK,
            (SEFC_USERSIGNATURE_PAGE) BOOT_USER_SIGNATURE_PAGE);
    
    /* Get boot configuration */
    bootConfig = userSignBuf + BOOT_CONFIG_OFFSET_USER_SIGN;
    cfgKey = ((uint32_t) bootConfig[3]) << 24;
    cfgKey += ((uint32_t) bootConfig[2]) << 16;
    cfgKey += ((uint32_t) bootConfig[1]) << 8;
    cfgKey += (uint32_t) bootConfig[0];
  • Extract the boot information, if the boot configuration is valid:
     /* Get boot information */
    imageSize = (uint32_t) (bootConfig[7]) << 24;
    imageSize += (uint32_t) (bootConfig[6]) << 16;
    imageSize += (uint32_t) (bootConfig[5]) << 8;
    imageSize += (uint32_t) (bootConfig[4]);
    origAddr = (uint32_t) (bootConfig[11]) << 24;
    origAddr += (uint32_t) (bootConfig[10]) << 16;
    origAddr += (uint32_t) (bootConfig[9]) << 8;
    origAddr += (uint32_t) (bootConfig[8]);
    destAddr = (uint32_t) (bootConfig[15]) << 24;
    destAddr += (uint32_t) (bootConfig[14]) << 16;
    destAddr += (uint32_t) (bootConfig[13]) << 8;
    destAddr += (uint32_t) (bootConfig[12]);
    pagesCounter = bootConfig[16];
    bootState = bootConfig[17];
  • Trigger the firmware swap, if needed:
    /* Check if swap fw is needed. If not, load defaults. */
    if (lAPP_BOOTLOADER_IsSwapCmd(imageSize, origAddr,
            destAddr) == true) {
        /* Swap fw */
        (void) lAPP_BOOTLOADER_SwapFwVersion(imageSize,
                origAddr,
                destAddr);
    
        /* Clear boot configuration (leave configuration key) */
        (void) memset(&bootConfig[4], 0, 16);
    
        /* Update user signature */
        lAPP_BOOTLOADER_UpdateUserSignature(0, BOOT_IDLE);
    }
  • Start the user application:
    /* Set the stack pointer to the start of the firmware application */
    __set_MSP(*(int *) (BOOT_FLASH_APP_FIRMWARE_START_ADDRESS));
    
    /* Offset the start of the vector table (first 6 bits must be 
     * zero) */
    /* The register containing the offset, from 0x00000000, is at 
     * 0xE000ED08 */
    SCB->VTOR = ((uint32_t) BOOT_FLASH_APP_FIRMWARE_START_ADDRESS &
            SCB_VTOR_TBLOFF_Msk);
    __DSB();
    __ISB();
    __enable_irq();
    /* * (int *) 0xE000ED08 = FIRMWARE_START_ADDRESS; */
    
    /* Jump to the start of the firmware, casting the address as 
     * function pointer to the start of the firmware. We want to jump 
     * to the address of the reset. */
    
    /* Handler function, that is the value that is being pointed at 
     * position FIRMWARE_RESET_ADDRESS */
    
    void (*runFirmware)(void) = NULL;
    runFirmware = (void (*)(void))(*(uint32_t *)
            BOOT_FLASH_APP_FIRMWARE_RESET_ADDRESS);
    runFirmware();
    
    while (1) {
        ;
    }