4
\$\begingroup\$

I have written a PIC32MX bootloader application. I would like to tell the linker to put it completely in the boot memory, so that all program space is kept for the final application.

Currently, the what I think are the relevant parts of my .ld file look like this:

_RESET_ADDR              = 0xBFC00000;  
_BEV_EXCPT_ADDR          = (0xBFC00000 + 0x380);
_DBG_EXCPT_ADDR          = (0xBFC00000 + 0x480);
_DBG_CODE_ADDR           = 0xBFC02000;
_DBG_CODE_SIZE           = 0xFF0     ;
_GEN_EXCPT_ADDR          = _ebase_address + 0x180;

MEMORY
{
  kseg0_program_mem    (rx)  : ORIGIN = 0x9D000000, LENGTH = 0x10000 /* All C Files will be located here */ 
  kseg0_boot_mem             : ORIGIN = 0x9FC00000, LENGTH = 0x1000 /* This memory region is dummy */ 
  exception_mem              : ORIGIN = 0x9FC01000, LENGTH = 0x200 /* Interrupt vector table */
  config3                    : ORIGIN = 0xBFC02FF0, LENGTH = 0x4
  config2                    : ORIGIN = 0xBFC02FF4, LENGTH = 0x4
  config1                    : ORIGIN = 0xBFC02FF8, LENGTH = 0x4
  config0                    : ORIGIN = 0xBFC02FFC, LENGTH = 0x4
  kseg1_boot_mem             : ORIGIN = 0xBFC00000, LENGTH = 0x2FF0 /* C Startup code */
  kseg1_data_mem       (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x8000
  sfrs                       : ORIGIN = 0xBF800000, LENGTH = 0x100000
  debug_exec_mem             : ORIGIN = 0xBFC02000, LENGTH = 0xFF0
  configsfrs                 : ORIGIN = 0xBFC02FF0, LENGTH = 0x10
}

This is the default generated linker script, for a PIC32MX695F512H, although the length of some sections may have changed - the memory layout of the chip is on page 61 of the datasheet.

Should I simply change the ORIGIN and LENGTH of kseg0_program_mem to the values of kseg0_boot_mem and tell the linker to allow for overlapping sections? That doesn't feel that clean. Is there a way to tell the linker to put the application code not in kseg0_program_mem but in kseg0_boot_mem?

\$\endgroup\$
  • \$\begingroup\$ I've just been reading about this in this ebook - I can't answer myself but take a look around page 44 and there may be useful info. \$\endgroup\$ – Roger Rowland Jan 11 '16 at 11:10
  • \$\begingroup\$ Just below that you probably have something like ".text ORIGIN(kseg0_program_mem) :" which tells the linker that anything in the .text section should be located in the kseg0_program_mem segment. This isn't something I've tried, but you could probably change that to kseg0_boot_mem instead ... \$\endgroup\$ – brhans Jan 11 '16 at 12:23
  • \$\begingroup\$ I understand asking here, but there are some pretty decent folks in the pic32 forum who understand linkers well. \$\endgroup\$ – Erik Friesen Jan 11 '16 at 12:56
4
\$\begingroup\$

The C compiler will place the C-runtime startup code in the kseg0_boot_mem, which sets up the stack, heap, and general processor initialization. The amount of code placed in kseg0_boot_mem will vary between a debug and release build. If I recall correctly, when I was working on a bootloader for a PIC32MX250F, the debug build took up ~70% of the kseg0_boot_mem, which wasn't enough for my bootloader.

If your bootloader is small enough to fit into the kseg0_boot_mem along with the C startup code, reduce the size of the kseg0_boot_mem, and then relocate kseg0_program_mem to above the new boot_mem space. Note that you cannot move the starting location of the kseg0_boot_mem space. Code excecution starts at 0xBFC00000 on the PIC32, and there is nothing you can do to change that. If you want to move the entire bootloader, you will also have to move the ISR table. For example:

MEMORY
{
  kseg0_program_mem    (rx)  : ORIGIN = 0xBFC01200, LENGTH = 0x1DF0 /* kseg1_boot is 2FF0 long.*/ 
  kseg0_boot_mem             : ORIGIN = 0x9FC00000, LENGTH = 0x1000 /* This memory region is dummy */ 
  exception_mem              : ORIGIN = 0xBFC01000, LENGTH = 0x200 /* Relocated ISR table. */
  config3                    : ORIGIN = 0xBFC02FF0, LENGTH = 0x4
  config2                    : ORIGIN = 0xBFC02FF4, LENGTH = 0x4
  config1                    : ORIGIN = 0xBFC02FF8, LENGTH = 0x4
  config0                    : ORIGIN = 0xBFC02FFC, LENGTH = 0x4
  kseg1_boot_mem             : ORIGIN = 0xBFC00000, LENGTH = 0x1000 /*This has been reduced in size.  Was 0x2FF0*/

  kseg1_data_mem       (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x8000
  sfrs                       : ORIGIN = 0xBF800000, LENGTH = 0x100000
  debug_exec_mem             : ORIGIN = 0xBFC02000, LENGTH = 0xFF0
  configsfrs                 : ORIGIN = 0xBFC02FF0, LENGTH = 0x10
}

NOTE: this is just a sample bootloader script, and should in no way be copy-pasted into production!

With that said, I can speak from experience that trying to cram a non-trivial bootloader into the boot_mem program space does not work out. The space left over from the C startup code usually is not enough, particularly if you want some heavy error-handling and validation (and yes, you DO want this!). Removing the debug builds from the very limited arsenal of embedded debug tools is not good either. You will need to be able to debug on hardware at some point in the future. I learned that lesson the hard way.

My recommendation is to leave the kseg0_boot_mem code section alone, and just work with the kseg0_program_mem space. Grab a slice of the kseg0_program_mem at the top or bottom (doesn't matter) and use that for your bootloader code and ISR table. Leave yourself some room for bootloader code growth as well. Dealing with multiple linker scripts is a bit of pain.

\$\endgroup\$
1
\$\begingroup\$

Rather than do all of it in the linker, you can also do it similar to this

MEMORY
{
  kseg0_program_mem    (rx)  : ORIGIN = 0x9D000000, LENGTH = 0x5000
  bootverify_mem       (rx)  : ORIGIN = 0x9D03FFAC, LENGTH = 0x50
  kseg0_boot_mem             : ORIGIN = 0x9FC00490, LENGTH = 0xB70
  exception_mem              : ORIGIN = 0x9FC01000, LENGTH = 0x300
  kseg2_boot_mem             : ORIGIN = 0x9FC01380, LENGTH = 0xC80
  kseg1_boot_mem             : ORIGIN = 0xBFC00000, LENGTH = 0x490
  debug_exec_mem             : ORIGIN = 0xBFC02000, LENGTH = 0xFF0
  config3                    : ORIGIN = 0xBFC02FF0, LENGTH = 0x4
  config2                    : ORIGIN = 0xBFC02FF4, LENGTH = 0x4
  config1                    : ORIGIN = 0xBFC02FF8, LENGTH = 0x4
  config0                    : ORIGIN = 0xBFC02FFC, LENGTH = 0x4
  kseg1_data_mem       (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x10000
  sfrs                       : ORIGIN = 0xBF800000, LENGTH = 0x100000
  configsfrs                 : ORIGIN = 0xBFC02FF0, LENGTH = 0x10
}



SECTIONS
{
  .config_BFC02FF0 0xBFC02FF0 : {
    KEEP(*(.config_BFC02FF0))
  } > config3
  .config_BFC02FF4 : {
    KEEP(*(.config_BFC02FF4))
  } > config2
  .config_BFC02FF8 : {
    KEEP(*(.config_BFC02FF8))
  } > config1
  .config_BFC02FFC : {
    KEEP(*(.config_BFC02FFC))
  } > config0
}

PROVIDE(_DBG_CODE_ADDR = 0xBFC02000) ;
PROVIDE(_DBG_CODE_SIZE = 0xFF0) ;
SECTIONS
{

  .extra_prgm_mem :
  {
    *(extra_prgm_mem)
  } >kseg0_boot_mem

  .extra_prgm_mem2 :
  {
    KEEP(*(extra_prgm_mem2))
  } >kseg2_boot_mem
/*Linker continued*/

Then, in your code, you can go put each function where you want to.

int  __attribute__ ((section ("extra_prgm_mem2")))GetVersion(char * Info){
    int Result = (Info[4] - '0') *  1000;
    Result += ((Info[5] - '0') *  100);
    Result += ((Info[6] - '0') *  10);
    Result += Info[7] - '0';
}

BOOL __attribute__ ((section ("extra_prgm_mem")))BLMedia_LoadEncryptedFile(char *file_name) {
//Function info
}

Also, note that if you don't use any interrupts, you can gain another 0x1000 in space. BTW, if this isn't a serial bootloader, I don't see it fitting.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy