MCU crashes when "declaring" vector table in SRAM - c

I have a new standard c++ project on an imx rt 1024 (an nxp chip), in which I try to move my vector table to SRAM. It fails, depending on a change I apply in the linker script.
The project is a new project from scratch created by MCUxpresso. I am not looking for answers MCUxpresso related, or c/c++/startup code related. I only want to properly understand the consequences of my changed linker script I show below.
The part that works
My starting point is a small program on my evk board, using a simple FreeRTOS task to blink a led. This works fine, when I put my vector table in flash.
linker script:
/* Not relevant for this question, other than showing there is something
written to flash before my vector table, harmless I think, but didn't want to leave
out of this question
*/
.boot_hdr : ALIGN(4)
{
__boot_hdr_start__ = ABSOLUTE(.) ;
KEEP(*(.boot_hdr.conf))
. = 0x1000 ;
KEEP(*(.boot_hdr.ivt))
. = 0x1020 ;
KEEP(*(.boot_hdr.boot_data))
. = 0x1030 ;
KEEP(*(.boot_hdr.dcd_data))
__boot_hdr_end__ = ABSOLUTE(.) ;
. = 0x2000 ;
} >PROGRAM_FLASH
/*
Here I write my vector table to flash
*/
.vector : ALIGN(4)
{
__vector_table_flash_start__ = ADDR(.vector) ;
__vector_table_itc_start__ = LOADADDR(.vector) ;
KEEP(*(.isr_vector))
__vector_table_flash_end__ = ABSOLUTE(.) ;
. = ALIGN(4) ;
} >PROGRAM_FLASH
Disassembled code for vector table
Disassembled code of reset handler
Note: 0x600022e5 corresponds to 0x600022e4, this has something to do with arm .thumb. I don't exactly know how that works tbh.
When I run this app, it runs fine. If I set a breakpoint in the ResetHandler it breaks and I can step through the startup code and jump to main. When I let the program run, my led will blink every second.
The part which fails
I changed my linker script to put my vector table in SRAM as follows
.vector : ALIGN(4)
{
__vector_table_flash_start__ = ADDR(.vector) ;
__vector_table_itc_start__ = LOADADDR(.vector) ;
KEEP(*(.isr_vector))
__vector_table_flash_end__ = ABSOLUTE(.) ;
. = ALIGN(4) ;
} >SRAM_ITC AT>PROGRAM_FLASH
For reference, the memory section:
MEMORY
{
PROGRAM_FLASH (rx) : ORIGIN = 0x60000000, LENGTH = 0x400000
SRAM_DTC (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000
SRAM_ITC (rwx) : ORIGIN = 0x0, LENGTH = 0x10000
SRAM_OC (rwx) : ORIGIN = 0x20200000, LENGTH = 0x20000
}
ENTRY(ResetISR)
When I upload, my program doesn't even reach the reset vector. It goes straight into the woods, and crashes somewhere outside program code.
The questions
What EXACTLY happens when I adjust my linker script with >SRAM_ITC AT>PROGRAM_FLASH?
I am pretty sure the produced elf file still contains the entire vector table starting from address 0x60002000. The >SRAM_ITC only tells the linker where certain parts of memory will end up AFTER the startup code copied all parts to their final ram location. Right? So how on earth can the initial jump to 0x60002004 (the address which holds the location of the reset handler) fail? The nxp bootloader always expects the reset vector on that location. I didn't change that. I only told the linker that the memory on that location will finally end up in SRAM. What am I misunderstanding here?
Maybe a stupid question: If I am completely wrong with my above assumptions, is there a way to see this from disassembly? I think objdump only shows the final addresses, but my debug probe will only write to flash as far as I know. So after uploading my code to my target, I still assume that stuff got written to flash, and after reset the built in bootloader will jump to 0x60002004 and set the PC to the address located at 0x60002000. Where can I see the actual blob of bytes which is programmed to flash memory?

Copying the vector table to sram from my custom bootloader solved the problem. That way the "on chip bootloader" from nxp can jump to my custom bootloader.
Before I just to my app from my custom bootloader, I copy the vector table to sram and set SCB->VTOR to the start of sram vector table.

Related

setting stack pointer before jumping to app from bootloader

I am coding a bootloader for Nucleo-F429ZI. I have two different STM32 projects, one for the bootloader itself and an application to jump from the bootloader.
Linker script for bootloader
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 32K
}
Linker script for app
_estack = ORIGIN(RAM) + LENGTH(RAM);
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
FLASH (rx) : ORIGIN = 0x8008000, LENGTH = 64K
}
I did not forget to set the flash offset of the app.
system_stm32f4xx.c (in the app project)
#define VECT_TAB_BASE_ADDRESS FLASH_BASE // 0x8000000
#define VECT_TAB_OFFSET 0x00008000U
The tutorial of STMicroelectronics about bootloaders has the following code to jump
main.c (in bootloader project)
#define FLASH_APP_ADDR 0x8008000
typedef void (*pFunction)(void);
uint32_t JumpAddress;
pFunction Jump_To_Application;
void go2APP(void)
{
JumpAddress = *(uint32_t*)(FLASH_APP_ADDR + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(uint32_t*)FLASH_APP_ADDR); // in cmsis_gcc.h
Jump_To_Application();
}
cmsis_gcc.h (in bootloader project)
__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack)
{
__ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );
}
As you can see, __set_MSP function sets the main stack pointer before jumping to FLASH_APP_ADDR + 4.
I found the memory location of the target place by debugging. FLASH_APP_ADDR + 4 caused to run Reset_Handler function of app project. Lets see what will be executed.
startup_stm32f429zitx.c (in the app project)
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
First thing of what Reset_Handler does is setting the stack pointer. _estack was defined in linker script.
If Reset_Handler is setting stack pointer, why did we call the __set_MSP function? I remove the function __set_MSP and bootloding process is still working. However I examined some other bootloader codes and found the exact same logic.
I tried what i have said and could not find an explanation.
Cortex-M core the loads SP register with initial value from address FLASH_BASE+0 during boot sequence. Then jumps to the code entry point (Reset vector) from address FLASH_BASE+4. Any bootloader code mimics core behaviour. Note, that FLASH_BASE here is not necessarily actual flash base, but an abstract value, that depends on the used processor, and it's settings.
Provided Reset_Handler code loads the sp register with __estack (Main stack top) value, but it doesn't have to! Bootloader can not expect the main program to do it, but has perform the same boot sequence as the core after reset. This way the main code doesn't have to rely on knowing, who started it - core, bootloader, jtag, or something else.
I've seen startup code, that doesn't load SP, but disables interrupts with the first instruction. Or startup code, written in C, which could use stack with the first instruction.
The real question here could be: Why this startup code loads SP if it is already loaded? But perhaps it should be forwarded to the original code author.
Let's see what's happening line by line.
JumpAddress = *(uint32_t*)(FLASH_APP_ADDR + 4);
Okay, so we take FLASH_APP_ADDR, add 1 word to it, call it a pointer to a word, dereference it. So it's the content of 0x8008004 (which is the one word after start of the vector table - list of interrupt handler pointers). You can find it in the vector table in reference manual. Here is reference manual for your MCU. Page 375
Next,
Jump_To_Application = (pFunction) JumpAddress;
Okay, so we treat reset handler address as a void function(void).
Eventually, you get to the stack
__set_MSP(*(uint32_t*)FLASH_APP_ADDR);
This function, as we see from its source code, simply sets main stack pointer to its argument. The argument is take vector table address, treat it as a pointer to a word, dereference it. So it's the first word of that vector table. And the first word of the vector table is the main stack pointer auto-loaded after power on. By definition of the vector table. You reset the stack to cold boot value, same value as the first word of your Flash. Your bootloader has used some stack until this point, but it won't be needed anymore, and the bootloader function will never return and free that stack, so you just reset stack to its initial value for your program. It will reuse all stack used by the bootloader.
So right now you've reset the stack pointer and you assigned reset handler to the function you call. And then you, well, call it.
Your vector table and the program that the bootloader starts are two different entities in memory. If you don't need to remap the interrupt handlers at runtime, don't move the vector table. It will stay at the beginning of the flash and will lead to the default interrupt handlers. Just make sure the address you execute from contains executable code and you run it from the start (well, if you don't, you will hardfault).

Is writing to a section not defined in linker file allowed?

In my linker file, I have the following memory sections.
MEMORY
{
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64k
mram (rwx) : ORIGIN = 0xA0000010, LENGTH = 1M
}
The actual address of the mram peripheral starts at 0xA0000000. In a C file I can write to a specific memory address as
(*(uint32_t *)(void *)0xA0000000) = 0xaabbccdd;
Will this cause any problems?
The space specified in the linker script defines where and what the linker can locate in the defined memory regions. It will not locate anything to the "hole" you have left at 0xA0000000 to 0xA000000F because it is not aware of it.
In that sense it is "safe" in that the linker will not attempt to use that space. It is entirely in the control of your code - you have taken responsibility for that region by not giving it to the linker. And indeed the statement:
(*(uint32_t *)(void *)0xA0000000) = 0xaabbccdd;
will write a 32 bit value to that location. The point is neither the compiler nor the linker will prevent you from doing what you will in that region.
What is less plausible is LENGTH = 1M. That would make your mram 0x100010 bytes long (i.e. 1M + 0x10). That is a problem because the linker is free to locate objects in the region 0x100000 to 0x10000F. The consequences of that depend on your hardware, but quite possibly it will wrap into the region 0x100000 to 0x10000F that you have attempted to hide from the linker. I would imagine that you need LENGTH = 1M - 0x10 or LENGTH = 0x0FFFF0.
Now while you can absolve the linker from managing that region in order to manage it in your code, it may not be the best approach. It would be better to create a linker symbol at the required absolute address.
So given:
MEMORY
{
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64k
mram (rwx) : ORIGIN = 0xA0000000, LENGTH = 1M
}
Your would create a linker symbol in mram:
SECTIONS
{
...
.mram :
{
reserved_mram = 0 ; /* address is zero offset from .mram1 */
. += 0x10 ; /* create 16 byte "hole" at address reserved_mram */
... /* other mram linker assignment follows */
} < mram
...
}
Then in your code you should be able to declare:
extern uint32_t reserved_mram[] ; // 4 x 32-bit word array.
And through reserved_mram you can access the memory at 0xA00000000 symbolically and the code is always in sync with the linker script so you can relocate the space easily without introducing a conflict.
Of course there is no bounds checking and no size information - you still need to confine your access to reserved_mram[0] to reserved_mram[3].
You might alternatively create a separate symbol for each location (with meaningful names specific to your application):
.mram :
{
reserved_mram1 = 0 ;
. += 4 ;
reserved_mram2 = . ;
. += 4 ;
reserved_mram3 = . ;
. += 4 ;
reserved_mram4 = . ;
. += 4 ;
... /* other mram linker usage follows */
} < mram
Then in your code:
extern uint32_t reserved_mram1 ;
extern uint32_t reserved_mram2 ;
extern uint32_t reserved_mram3 ;
extern uint32_t reserved_mram4 ;
Another alternative; you might create an independent section for the region, then create variables within it using __attribute__((section(.xxx))) directives in the code. e.g:
MEMORY
{
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64k
mram1 (rwx) : ORIGIN = 0xA0000000, LENGTH = 0x10
mram2 (rwx) : ORIGIN = 0xA0000010, LENGTH = 0xFFFF0
}
SECTIONS
{
...
.mram1 :
{
*(.mram1)
} < mram1
...
}
Then in your code:
uint32_t my_mram_data __attribute__ ((section (".mram1"))) ;
The variable my_mram_data will be created somewhare in .mram1, but the linker decides where. The advantage here is that you can create arbitrary variables in the code without modifying the linker script, and if you attempt to allocate to .mram1 more data than is available you will get a linker error.
Note that linker script syntax is arcane and varies between linkers - I am unassuming this relates to the GNU linker? But my linker foo is strictly on demand (i.e. I figure it out when I need to) and I make no claim that any of the above is complete or correct, or even the only possible solutions - regard it as illustrative, and refer to the linker documentation for accurate information.

What exactly does ". = 0x7c00" in a linker script do?

What exactly does ". = 0x7c00" in a linker script do?
More specifically, when I place . = 0x7c00 at the beginning of a linker script, why doesn't the resulting output file begin with 0x7c00 = 31,744 zeros?
I understand that when a PC boots, the BIOS places the 512 byte MBR at memory address 0x7c00. However, I am confused as to how exactly the linker's location counter affects how the output file is laid out.
(For context, I'm trying to thoroughly understand the sample code from the "x86 bare metal" project. https://github.com/cirosantilli/x86-bare-metal-examples. I've included the entire linker script below for context.)
SECTIONS
{
/*
We could also pass the -Ttext 0x7C00 to as instead of doing this.
If your program does not have any memory accesses, you can omit this.
*/
. = 0x7c00;
.text :
{
__start = .;
/*
We are going to stuff everything
into a text segment for now, including data.
Who cares? Other segments only exist to appease C compilers.
*/
*(.text)
/*
Magic bytes. 0x1FE == 510.
We could add this on each Gas file separately with `.word`,
but this is the perfect place to DRY that out.
*/
. = 0x1FE;
SHORT(0xAA55)
*(.stage2)
__stage2_nsectors = ABSOLUTE((. - __start) / 512);
. = ALIGN(512);
__end = .;
__end_align_4k = ALIGN(4k);
}
}
It looks like the ". = 0x7c00" is not meaning a length but an absolute address. It reads to me as 'set the current value of the special variable "." to be the hex value 0x7c00 and then it plans to use that address as an offset later in the script like with the . = ALIGN(512) it is also why it saves that address off as __start so it can then do math on the resulting image. If you manipulate . during the script so it points to the last chunk of memory added to the image then you can use it to determine the total size:
__stage2_nsectors = ABSOLUTE((. - __start) / 512);
in English would be
The difference between the starting place and wherever I ended divided by sector size.

c compiler, overlap memory error

Below is a portion of the C code I am using:
pushbutton_ISR()
{
int press;
int key_pressed;
press = *(KEYS_ptr + 3); // read the pushbutton Edge Det Register interrupt register
*(KEYS_ptr + 3) = 0; // Clear the Edge Det registers.
if (press & 0x1) { // KEY1
key_pressed = KEY1;
//sum = sum + *NEW_NUMBER;
}
else if (press & 0x2) { // KEY2
key_pressed = KEY2;
*GREEN_LEDS = *NEW_NUMBER;
sum = sum + *NEW_NUMBER;
*RED_LEDS = sum;
}
else // i.e. (press & 0x8), which is KEY3
sum = *(NEW_NUMBER); // Read the SW slider switch values; store in pattern
return;
}
The compiler compiles this fine and the code appears to run (on an Altera board) fine. However, when I change the first if statement to:
if (press & 0x1) { // KEY1
//key_pressed = KEY1;
sum = sum + *NEW_NUMBER;
}
the compiler gives the following error messages:
.../nios2-elf/bin/ld.exe: section .data loaded at [00000a00,00000e0f] overlaps section .text loaded at [00000500,00000a0f]
.../nios2-elf/bin/ld.exe: section .ctors loaded at [00000a10,00000a13] overlaps section .data loaded at [00000a00,00000e0f]
.../nios2-elf/bin/ld.exe: Z:/Projects/Altera/3215_W15_LabB/Part2/from_handout.elf: section .data vma 0xa00 overlaps previous sections
.../nios2-elf/bin/ld.exe: Z:/Projects/Altera/3215_W15_LabB/Part2/from_handout.elf: section .ctors vma 0xa10 overlaps previous sections
.../nios2-elf/bin/ld.exe: Z:/Projects/Altera/3215_W15_LabB/Part2/from_handout.elf: section .rodata vma 0xa14 overlaps previous sections
.../nios2-elf/bin/ld.exe: Z:/Projects/Altera/3215_W15_LabB/Part2/from_handout.elf: section .sdata vma 0xe10 overlaps previous sections
.../nios2-elf/bin/ld.exe: Z:/Projects/Altera/3215_W15_LabB/Part2/from_handout.elf: section .sbss vma 0xe18 overlaps previous sections
Could you please advise me about the reasons for these errors, and how to resolve them.
This has nothing to do with your code being incorrect.
These are linker errors (it even tells you ld.exe is the program complaining) about output sections overlapping. This probably means you just ran out of space, but could also mean the linker directive file your project is using has some problems.
When you add in this line, it causes the size of the compiled code to be too big for the memory area that you are loading the code into.
You can see from the first line of the linker error message that .text (the code) is loaded at 0x500, and .data (the non-zero static variables) is loaded at 0xa00. However, the .text section is so long that it is too big to fit in the space between 0x500 and 0xa00.
To fix this you will either need to:
Make your code smaller
Increase the amount of space available for .text
To do the first one, you could use -Os or similar compiler option to compile for minimum code size ; or manually rewrite your code to be smaller.
For the second one you really need to understand the hardware you are loading the code into. Is it a hardware requirement that code goes at 0x500 and data goes at 0xa00? If not, then you may be able to load the code and/or data into different addresses.
These addresses are configured in your linker script (this may be hardcoded into the makefile or it may be an actual file somewhere). Hopefully the hardware device came with documentation that explains how much memory it has and where you're allowed to load your code to.

How to place variables in the access bank - PIC 18 MPASM linker script

I have a linker script which starts
INCLUDE 18f14K50_g.lkr
I want my interrupt service variables to go into the ACCESS bank. (My program's so small at the moment the whole lot can, but maybe in future...). So
SECTION NAME=VarsModemISR RAM=accessram
which results in:
MPLINK 4.39, Linker
Device Database Version 1.1
Copyright (c) 1998-2011 Microchip Technology Inc.
Error - section 'VarsModemISR' has a memory 'accessram' which is not defined in the linker command file.
Errors : 1
Examining the included file I believe it is. Either that or I'm working in extended mode and "gpre" is. I can use an #IFDEF to check, which I tried. The result, it was trying to use "accessram" not "gpre".
Maybe if I try defining the access bank explicitly by copying the line from the include file:
ACCESSBANK NAME=accessram START=0x0 END=0x5F
SECTION NAME=VarsModemISR RAM=accessram
This results in the error
MPLINK 4.39, Linker
Device Database Version 1.1
Copyright (c) 1998-2011 Microchip Technology Inc.
Error - duplicate definition of memory 'accessram'
Errors : 1
Which has me confused. According to the Assembler/Linker documentation I use SECTION with the RAM option, where RAM has previously been declared using ACCESSBANK, SHAREBANK or DATABANK. It should work.
Thanks
- Richard
There is really no need to change linker script, use default one!
Accessed file registers are available at any moment under PIC18 MCPUs.
Just declare variables in appropriate memory databank named ACCESSBANK which start at 0x00 and end at 0x60 address.
If you are using MPLAB than declare:
_Shared udata_acs 0 ;Shared memory file registers
IntReg1 res 1
IntReg2 res 1
;...
_UpperBank0 udata 060h ;Banked file memory registers
RegA res 1
;...
_Bank1 udata 0100h ;Banked file memory registers
N res 1
;...
Linker should automatically set the 'a' bit in code instruction for file register addresses, which are declared in ACCESSBANK.
I am using UDATA_ACS to declare the variables I want in access, so in modem.asm I have
; Variables for the interrupt handler - Access RAM
VarsModemISR UDATA_ACS
wave_index res 1 ; Index into the wave table for current sample
sample_period res 1 ; Sample period in use, TMR0 ticks
sample_count res 1 ; Amount of samples output since last bit boundary
fsrtmpl res 1 ; Temporary store for FSR
fsrtmph res 1 ; Temporary store for FSR
; Variables for the modem code - GPR0, non-Access
VarsModem UDATA
flag res 1 ; Counter for transmitting AX25 flags
bit res 1 ; Bit counter when transmitting a character
ch res 1 ; Current character being transmitted
...
My current linker script uses the supplied script, but defines my segments. I note that there's only one program page defined in the script, unlike on the PIC16s. No more PAGESEL?
INCLUDE 18f14K50_g.lkr
SECTION NAME=CodeModemISR ROM=page
SECTION NAME=CodeModem ROM=page
SECTION NAME=CodeWaveTable ROM=page
SECTION NAME=CodeEepromUtil ROM=page
SECTION NAME=VarsModem RAM=gpr0
SECTION NAME=VarsGPSState RAM=gpr0
SECTION NAME=CodeConfigEEPROM ROM=eedata
The resulting map contains the mappings I expect:
Hard coded locations as expected:
HighInterruptVector code 0x000008 program 0x000004
LowInterruptVector code 0x000018 program 0x000002
Movable locations packed in:
CodeModemISR code 0x00001a program 0x000028
CodeModem code 0x000042 program 0x0000fe
CodeWaveTable code 0x000140 program 0x000040
CodeMain code 0x000180 program 0x000054
EEPROM in the right place
CodeConfigEEPROM code 0xf00000 program 0x000044
And variables in ACCESSRAM and GP0
VarsModemISR udata 0x000000 data 0x000005
VarsModem udata 0x000060 data 0x000027
VarsGPSState udata 0x000087 data 0x00000e
There are more problems to solve, but they may be in other posts. I note that CodeWaveTable is taking 64 bytes so it's not closely packed. Solution - use CODE_PACK and now it's 32 bytes.

Resources