Jumping to a second firmware on stm32f4 - c

I'm building a bootloader for an application running on a stm32.
The purpose of this is to be able to update the main application.
Since our software is pretty modular, my idea was to just configure a minimal version of it. All the initializations are the same, it jumps to a main function that contains all bootloader functionalities (checking if a new firmware is available on external flash, writing it to internal flash if that's the case) and in the end jumping to the actual application - which does the initialization all over again, but this time with additional peripherals, etc., eventually calling the real main.
The memory layout on the internal flash is like this
|0x08000000 boot loader
|----------------------
|0x08006000 application
bootloader main looks like this
extern void CallApplication(void);
int main(void) {
printf("starting bootloader\n");
printf("will jump to " TOSTRING(APP_START_ADDRESS) "\n");
CallApplication();
return 0;
}
where CallApplication is written in assembler
#define VTABLE_START_ADDRESS APP_START_ADDRESS
#define NVIC_VTABLE 0xE000ED08 // Vector Table Offset
.globl CallApplication
.thumb_func
CallApplication:
// Set the application's vector table start address.
movw r0, #(VTABLE_START_ADDRESS & 0xffff)
movt r0, #(VTABLE_START_ADDRESS >> 16)
movw r1, #(NVIC_VTABLE & 0xffff)
movt r1, #(NVIC_VTABLE >> 16)
str r0, [r1]
// Load the stack pointer from the application's vector table.
ldr sp, [r0]
// Load the initial PC from the application's vector table and branch to
// the application's entry point.
ldr r0, [r0, #4]
bx r0
This almost works - the 'real' application is called, does its initialization but eventually crashes for a yet unknown reason.
What's interesting though is that the fault ISR of the bootloader (0x080022ae) is being called, not that of the real application (> 0x08006000) so something about setting the new vector table obviously failed.
2016-02-11 00:21:16,958 - INFO # init UART
2016-02-11 00:21:16,963 - INFO # Application: boot_loader
2016-02-11 00:21:16,973 - INFO # -- init done, starting main --
2016-02-11 00:21:16,974 - INFO # starting bootloader
2016-02-11 00:21:16,976 - INFO # will jump to 0x8006000
2016-02-11 00:21:16,978 - INFO # init UART
2016-02-11 00:21:16,985 - INFO # Application: hello_world
2016-02-11 00:21:17,797 - INFO # -- init done, starting main --
(hard fault led starts flashing)
What am I missing here?
The linker script for the main application defines
MEMORY
{
FLASH (rx) : ORIGIN = 0x08006000, LENGTH = 488K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
whereas the bootloader does
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 24K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
the rest is shared
SECTIONS
{
.text :
{
_text = .;
/*
* The vector table must be placed to the top of the
* memory map. To achieve this, it was assigned to a
* special section called ".isr_vector"
*/
KEEP(*(.isr_vector))
/* followed by .text and .rodata: */
*(.text*)
*(.rodata*)
_etext = .;
} > FLASH
/* Just to make sure that the contents does not exceed the flash size */
. = ORIGIN(FLASH) + LENGTH(FLASH);
/*
* .data and .bss are placed into SRAM:
*/
.data : AT(ADDR(.text) + SIZEOF(.text))
{
_data = .;
*(.data*)
_edata = .;
} > SRAM
.bss :
{
/* _bss and _ebss will be required during initialization */
_bss = .;
*(.bss*)
_ebss = .;
} > SRAM
.aux : {
. = ALIGN(4);
*(.auxdata) /* .auxdata section */
. = ALIGN(4);
} > SRAM
/* Just to make sure that the contents does not exceed the SRAM size */
. = ORIGIN(SRAM) + LENGTH(SRAM);
}
Edit: I rewrote the section where VTOR is set in C to make it clearer for me what's going on, but I still end up in the bootloader's DefaultISR
printf("starting bootloader\n");
printf("will jump to " TOSTRING(APP_START_ADDRESS) "\n");
printf("before: %x\n", SCB->VTOR);
SCB->VTOR += APP_START_ADDRESS;
printf("after: %x\n", SCB->VTOR);
asm volatile("mov r0, #0x6000");
asm volatile("ldr sp, [r0]");
asm volatile("ldr r0, [r0, #4]");
asm volatile("bx r0");
outputs
2016-02-11 23:49:31,833 - INFO # starting bootloader
2016-02-11 23:49:31,835 - INFO # will jump to 0x6000
2016-02-11 23:49:31,836 - INFO # before: 8000000
2016-02-11 23:49:31,837 - INFO # after: 8006000
2016-02-11 23:49:31,839 - INFO # init UART
2016-02-11 23:49:31,841 - INFO # …

In my case is a STM32L Cortex-M3, but I think it works in the same way.
In the bootloader, after disabling all sources of interrupt (not masking them), I do the following:
#define APP_LOCATION 0x08006000
typedef void (*pFunction)(void);
pFunction jump;
volatile uint32_t jumpAddress;
register uint32_t regMainStackPointer __ASM("msp");
void Jump( void ) {
jumpAddress = *( volatile uint32_t* )( APP_LOCATION + 4 );
jump = ( pFunction )jumpAddress;
mainStackPointer = *( volatile uint32_t* )APP_LOCATION;
jump();
}
And in the application itself, the first thing to do before enabling any interrupt is:
SCB->VTOR = 0x0x08006000;
The linkers here are equal.
I noticed something strange in your code:
SCB->VTOR += APP_START_ADDRESS;
If APP_START_ADDRESS contains the address (0x08006000) instead of the offset (0x6000), the resulting value in VTOR will be 0x08000000 + 0x08006000, perhaps the problem is here?
It might help if you show some code from the application.
Hope it helps.

the CPU init function from the HAL was doing
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
and by that overwriting my setting to SCB->VTOR.
It works when this is removed, no further magic required.

Related

Relocating interrupt vector table using linker script

I'm trying to move interrupt vector to DTCMRAM. The test code is simple blinking LED by timer interrupt.
There I've changed load adress of .isr_vector:
MEMORY
{
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
}
/* Define output sections */
SECTIONS
{
_sivector = LOADADDR(.isr_vector);
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
_svector = .;
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
_evector = .;
} >ITCMRAM AT> FLASH
After that I've added data copyer before main call (generated according to .data copier) in startup:
ldr r0, =_svector
ldr r1, =_evector
ldr r2, =_sivector
movs r3, #0
b LoopCopyVectorInit
CopyVectorInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyVectorInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyVectorInit
Now I want to tell MCU that new vector table is availible using SCR->VTOR according to here.
Then here is main code:
extern uint32_t _sivector;
extern uint32_t _svector;
extern uint32_t _evector;
int main(void)
{
/* USER CODE BEGIN 1 */
__disable_irq();
SCB->VTOR = (uint32_t)*_sivector;
__DSB();
__enable_irq();
But in this way debugger shows _svector and _sivector is equal to 0x24080000 and _evector=0x504f105.
Code line that reinitialize VTOR causes fault. Obviously _svector and _sivector has wrong address. Why? Even by commenting ITCMRAM AT> the _*vector variables carry wrong value.
It will never work this way.
You end in the fault as when the uC boots up, it does not have the interrupt vector table at the standard place (which is required). Later you can copy and set the interrupt vector table in the place you want.
So your startup code will never be executed as the reset vector is not set to anything meaningful.
extern uint32_t _sivector;
extern uint32_t _svector;
extern uint32_t _evector;
void __attribute__((constructor)) copyVect(void)
{
memcpy(&_sivector, &_svector, (&_evector - &_svector) * sizeof(uint32_t));
}
int main(void)
{
/* USER CODE BEGIN 1 */
__disable_irq();
SCB->VTOR = (uint32_t)&_sivector;
__DSB();
__enable_irq();
or
extern uint32_t _sivector[];
extern uint32_t _svector[];
extern uint32_t _evector[];
void __attribute__((constructor)) copyVect(void)
{
memcpy(_sivector, _svector, (_evector - _svector) * sizeof(uint32_t));
}
int main(void)
{
/* USER CODE BEGIN 1 */
__disable_irq();
SCB->VTOR = (uint32_t)_sivector;
__DSB();
__enable_irq();
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
_svector = .;
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
_evector = .;
} >FLASH
/* ... */
.isr_vector_itcm :
{
. = ALIGN(4);
_sivector = .;
. += _evector - _svector;
} > ITCMRAM
BTW the copy in the assembly file is not needed anymore.
So, the obvious question is, why are those values incorrect.
I think you need to take address of those variables. SCB->VTOR = (uint32_t)&_sivector, not with asterisk. You want the address of that variable, not its content (which is undefined btw) dereferenced as a pointer and casted to uint32_t.
It's exactly the same logic with putting vars into data sections. Variables from linker scripts don't have a usable value. They're needed as address pointers, so just like you take &_stackstart or whatever you call it, similar thing should apply here.
EDIT: I've used the word "variable" a little too liberally. Linker script defines a symbol. There is no defined value associated with it.

Stm32 relocating vector table placement in flash

I was trying to achieve this memory structure in my mcu flash
My Linker Script declares in the following order
Bootloader firmware
Main Firmware
Main Firmware Image Info (ie. crc, version number)
Main Firmware vector table
.
.everything else
But after the bootloader jumps to the Main Firmware Reset Handler an exception occours sometime when initalizing the .bss section (it correctly jumps to the reset handler and updates the VTOR)
Everything works if the Main firmware vector table is located before the Main Firmware Image Info, but when I try to swap the twos my firmware crashes during the .bss initialization of the main firmware after bootloader launches it.
Am i missing something? Is there any reason why I cannot seem to interpose a reserved section before the isr vector's?
In the system_stm32wlxx.c in the SystemInit function for the main firmware I have
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
Where
VECT_TAB_OFFSET = Size of Bootloader section, if the vector table is placed before the image infos.
or
VECT_TAB_OFFSET = Size of Bootloadersection +Size of Image info section, if the vector table is placed after the image infos.
To perform the jump in the bootloader I have
main_app_code = (uint32_t*) ((uint32_t)&__program1_start+(uint32_t)&__vect_start_offset); // main application base address
uint32_t main_app_stack_pointer = main_app_code[0]; // first word contains the address of the stack pointer
uint32_t main_app_reset_handler = main_app_code[1]; // second word contains the address of the reset handler
where __program1_start is defined in the linker script the address of base flash+bootloader size
and __vect_start_offset is also defined in the linker script as the size of the image info section ( or 0 if the isr table is placed before the image info section)
The code is then followed by
/** set the main stack pointer and then perform a jump to the main app reset handler*/
__set_MSP(main_app_stack_pointer);
/// Jump to application
((void(*)())main_app_reset_handler)();
Linker script of main firmware memory partitioning
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K
BOOT (rx) : ORIGIN = 0x08000000, LENGTH = __boot_size
FLASH (rx) : ORIGIN = 0x08000000+LENGTH(BOOT), LENGTH = __program_size
FLASH2 (rx) : ORIGIN = ORIGIN(FLASH)+LENGTH(FLASH), LENGTH = __program_size
DATA (rx) : ORIGIN = ORIGIN(FLASH2)+LENGTH(FLASH2), LENGTH = __data_size
}
/* Sections */
SECTIONS
{
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* burn specific firmware data into a section*/
.fw_infos :
{
. = ALIGN(4);
__fw_crc = ABSOLUTE(.); /* memory address*/
KEEP(*(.fw_infos)) /* Startup code */
. = ALIGN(4);
} >FLASH
etc etc...
I found the cause of the problem, when the image info section was placed before the isr vector section the expression:
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
should have been equal to 0x8000000|5820 = 0x8005820
But SCB->VTOR is only writable from bit7 - bit31
(https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-control-block/vector-table-offset-register?lang=en)
So SCB->VTOR was set to 0x8005800 instead of 0x8005820, this caused the main firmware to crash.
When instead isr section was placed first
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET
was equal to 0x8000000|5800 and said value is correctly assigned to the SCB->VTOR address and everything worked fine.
My solution was to change the main firmware linker script, altering the alignment of the Image Info section with the ALIGN(256) command (intead of the previous ALIGN(4)), in this way the following section ( the isr vector section) is placed onto an address whose Bit0-7 are zero and therefore there is no risk of corrupting the SCB->VTOR value.
SECTIONS
{
/* burn specific firmware data into a section*/
.fw_infos :
{
. = ALIGN(256);
__fw_crc = ABSOLUTE(.); /* memory address*/
KEEP(*(.fw_infos)) /* Startup code */
. = ALIGN(256);
} >FLASH
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(256);
__vector_start = ABSOLUTE(.); /* memory address*/
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
In some cases Alig(128) would work, but in my case ALIGN(256) is necessary because I have 62 interrupt sources and according to Armv7 Architecture:
The Vector table must be naturally aligned to a power of two whose alignment value is greater than or equal to (Number of Exceptions supported x 4), with a minimum alignment of 128 bytes. On power-on or reset, the processor uses the entry at offset 0 as the initial value for SP_main, see The SP registers. All other entries must have bit [0] set to 1, because this bit defines the EPSR.T bit on exception entry. See Reset behavior and Exception entry behavior for more information.
from
https://developer.arm.com/documentation/ddi0403/d/System-Level-Architecture/System-Level-Programmers--Model/ARMv7-M-exception-model/The-vector-table
So 62*4 = 148-->256 alignment required

Adding a RAM section in linker file STM32

I am trying very hard to understand how to use a linker file, but my brain is apparently not getting it at all. I am using an STM32L476, which has two RAM regions, RAM and RAM2 (memory definition below). I would like to put a buffer into RAM2, but there is no section for RAM2 in the default linker script that is generated by Cube. Seems like a good exercise for me. I really thought that the following would do the trick, where all I've added is the .sensor_buffer section:
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
}
/* Sections */
SECTIONS
{
.sensor_buffer :
{
KEEP(*(.sensor_buffer))
} >RAM2
/* The startup code into "RAM" Ram type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >RAM
/* The program code and other data into "RAM" Ram type memory */
.text :
{
...
} >RAM
// Other Cube-generated sections here
}
However, this adds a .sensor_buffer section to an address that is not in RAM2. From the .map file:
...
.igot.plt 0x0000000020000190 0x0 load address 0x000000000800f29c
.igot.plt 0x0000000020000190 0x0 c:/st/stm32cubeide_1.6.0/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.9-2020-q2-update.win32_1.5.0.202011040924/tools/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v7e-m+fp/hard/crtbegin.o
.sensor_buffer 0x0000000020000190 0x2000 load address 0x000000000800f29c
.sensor_buffer
0x0000000020000190 0x2000 Core/Src/main.o
0x0000000020002190 . = ALIGN (0x4)
.bss 0x0000000020002190 0x1fb0 load address 0x000000000801129c
0x0000000020002190 _sbss = .
0x0000000020002190 __bss_start__ = _sbss
...
Can someone point out either where I went wrong here, and/or, even better, somewhere that I can work through some "easy" examples to get used to LD? I really want to get this stuff, but the first steps are really brutal for me without any resources except a rather dense set of man pages.
EDIT Adding the code used to declare the buffer. In main.c, global scope:
static uint8_t data[DATA_BUFFERS][DATA_SIZE] __attribute__((section(".sensor_buffer")));
You have an error somewhere else. Maybe you simply do not use this linker script (you forgot to add or change the name in the command line)
I have compiled it and linked it without any problems with CubeIDE (I use 100 and 100 in the buffer declarations as I do not know the values of your macros [100x100 = 0x2710])
.sensor_buffer 0x0000000010000000 0x2710
*(.sensor_buffer)
.sensor_buffer
0x0000000010000000 0x2710 Core/Src/main.o
.isr_vector 0x0000000020000000 0x0
0x0000000020000000 . = ALIGN (0x4)

How can I duplicate the Interrupt Vector Table using the linker script on a Cortex M0+?

First of all, I hope I'm not asking something that has already been asked before. I've searched as much as I can but I haven't found an answer to my specific problem or something useful.
I'm working on a FRDM-KL82Z board which runs a Cortex M0+ core. I'm using MCUXpresso IDE v10.0.2 and a Segger J-Link programmer, although I think this is not relevant for this question.
This project will need a custom bootloader and app coded by different developers, each block with its own flash memory space: 8K for the bootloader and 120K for the app (this may change in the future but it's no big deal at the moment).
Once the bootloader is completed, it will manage the jump to App space and the app will change de Vector Table Offset Register (VTOR) so that the Interrupt Vector Table changes form the Boot IVT to the App IVT. This has already been tested successfully.
My aim is to set up the linker script file so that the app developers can build and debug their project on the board before the bootloader is completed, as they will be developed at the same time. The reason for this is that they can work with the App space as it will be in the final version.
I think the Reset vector and the Config bits must be at their default position because the hardware will go to the same position every time it needs to read them.
My first idea consist in disabling the automatic linker script generation and modifying the MyProject_Debug.ld file.
What the script automatically generates:
INCLUDE "LEDTest_Debug_library.ld"
INCLUDE "LEDTest_Debug_memory.ld"
ENTRY(ResetISR)
SECTIONS
{
/* MAIN TEXT SECTION */
.text : ALIGN(4)
{
FILL(0xff)
__vectors_start__ = ABSOLUTE(.) ;
KEEP(*(.isr_vector))
/* Global Section Table */
. = ALIGN(4) ;
__section_table_start = .;
__data_section_table = .;
LONG(LOADADDR(.data));
LONG( ADDR(.data));
LONG( SIZEOF(.data));
LONG(LOADADDR(.data_RAM2));
LONG( ADDR(.data_RAM2));
LONG( SIZEOF(.data_RAM2));
__data_section_table_end = .;
__bss_section_table = .;
LONG( ADDR(.bss));
LONG( SIZEOF(.bss));
LONG( ADDR(.bss_RAM2));
LONG( SIZEOF(.bss_RAM2));
__bss_section_table_end = .;
__section_table_end = . ;
/* End of Global Section Table */
*(.after_vectors*)
/* Kinetis Flash Configuration data */
. = 0x400 ;
PROVIDE(__FLASH_CONFIG_START__ = .) ;
KEEP(*(.FlashConfig))
PROVIDE(__FLASH_CONFIG_END__ = .) ;
ASSERT(!(__FLASH_CONFIG_START__ == __FLASH_CONFIG_END__), "Linker Flash Config Support Enabled, but no .FlashConfig section provided within application");
/* End of Kinetis Flash Configuration data */
} >PROGRAM_FLASH
.text : ALIGN(4)
{
*(.text*)
*(.rodata .rodata.* .constdata .constdata.*)
. = ALIGN(4);
} > PROGRAM_FLASH
/*
* for exception handling/unwind - some Newlib functions (in common
* with C++ and STDC++) use this.
*/
.ARM.extab : ALIGN(4)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > PROGRAM_FLASH
__exidx_start = .;
.ARM.exidx : ALIGN(4)
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > PROGRAM_FLASH
__exidx_end = .;
_etext = .;
/* USB_RAM */
.m_usb_data (NOLOAD) :
{
*(m_usb_bdt)
*(m_usb_global)
} > USB_RAM
/* possible MTB section for USB_RAM */
.mtb_buffer_RAM2 (NOLOAD) :
{
KEEP(*(.mtb.$RAM2*))
KEEP(*(.mtb.$USB_RAM*))
} > USB_RAM
/* DATA section for USB_RAM */
.data_RAM2 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM2 = .) ;
*(.ramfunc.$RAM2)
*(.ramfunc.$USB_RAM)
*(.data.$RAM2*)
*(.data.$USB_RAM*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM2 = .) ;
} > USB_RAM AT>PROGRAM_FLASH
/* MAIN DATA SECTION */
/* Default MTB section */
.mtb_buffer_default (NOLOAD) :
{
KEEP(*(.mtb*))
} > SRAM
.uninit_RESERVED : ALIGN(4)
{
KEEP(*(.bss.$RESERVED*))
. = ALIGN(4) ;
_end_uninit_RESERVED = .;
} > SRAM
/* Main DATA section (SRAM) */
.data : ALIGN(4)
{
FILL(0xff)
_data = . ;
*(vtable)
*(.ramfunc*)
*(.data*)
. = ALIGN(4) ;
_edata = . ;
} > SRAM AT>PROGRAM_FLASH
/* BSS section for USB_RAM */
.bss_RAM2 : ALIGN(4)
{
PROVIDE(__start_bss_RAM2 = .) ;
*(.bss.$RAM2*)
*(.bss.$USB_RAM*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM2 = .) ;
} > USB_RAM
/* MAIN BSS SECTION */
.bss : ALIGN(4)
{
_bss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4) ;
_ebss = .;
PROVIDE(end = .);
} > SRAM
/* NOINIT section for USB_RAM */
.noinit_RAM2 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM2*)
*(.noinit.$USB_RAM*)
. = ALIGN(4) ;
} > USB_RAM
/* DEFAULT NOINIT SECTION */
.noinit (NOLOAD): ALIGN(4)
{
_noinit = .;
*(.noinit*)
. = ALIGN(4) ;
_end_noinit = .;
} > SRAM
.heap : ALIGN(4)
{
_pvHeapStart = .;
. += 0x1000;
. = ALIGN(4);
_pvHeapLimit = .;
} > SRAM
.heap2stackfill :
{
. += 0x1000;
} > SRAM
.stack ORIGIN(SRAM) + LENGTH(SRAM) - 0x1000 - 0: ALIGN(4)
{
_vStackBase = .;
. = ALIGN(4);
_vStackTop = . + 0x1000;
} > SRAM
}
I've tried to find information in this guide about de GNU linker but my ideas haven't worked so far.
What I've tried:
Setting the location counter to a different value after the Config Words and copying the ISR_vector code snipped before the text section:
...
/* End of Kinetis Flash Configuration data */
} >PROGRAM_FLASH
.text : ALIGN(4)
{
/* MODIFIED CODE */
. = 0x2000; /* First position of App code */
FILL(0xff)
__vectors_start__ = ABSOLUTE(.) ;
KEEP(*(.isr_vector))
/* END OF MODIFIED CODE */
*(.text*)
*(.rodata .rodata.* .constdata .constdata.*)
. = ALIGN(4);
} > PROGRAM_FLASH
...
When I do this and I open the .hex file, the space between the Config Words (0x400) and the start of the App space (0x2000) is effectively empty (full of 0xFF) but the code after 0x2000 is nothing like the IVT table.
If I move location counter to 0x2000 before the IVT code lines it effectively moves the IVT adresses to the 0x2000 position. To do this, I move the Config Words part before the IVT part because de location counter can't move backwards.
I've tried creating a Bootloader section in the memory map, with the correct starting and length positions, and copying every line that by default gets placeD in the PROGRAM_FLASH section into a new one that goes to BOOTLOADER (the same code with ">BOOTLOADER" at the end). In this case de IVT only appears in the Boot space.
Is it possible that the linker script places de IVT only in the first place it is indicated and then ignores every other call? What am I doing wrong? Should I try another way to achieve this?
Thank you very much, I know it's quite a long!
I don't think it's possible to make a copy of the vector table using only linker shenanigans. The linker script will not let you match the same symbol multiple times so that you can output it twice.
From the binutils 2.29 manual:
If a file name matches more than one wildcard pattern, or if a file name appears explicitly and is also matched by a wildcard pattern, the linker will use the first match in the linker script.
I tested it without using any wildcard patterns at all with similar results, so I don't think the linker will ever let you output the same symbol twice.
I also tried using objcopy to create a renamed copy of the vector table that could referenced from the linker script but that table ended up as all zeroes and the whole approach was rather convoluted, so I don't think that's worth pursuing.
If you want to keep the application code as similar as possible between now and when the bootloader is completed, I would suggest a different approach:
Make use of the __vectors_start__ symbol provided by the existing linker script so that your code always knows where the vector table is placed, even if you make changes to the linker script.
void relocate_vector_table(void) {
extern unsigned __vectors_start__;
SCB->VTOR = (unsigned)&__vectors_start__;
}
This will allow the same code to work with your current configuration (no bootloader, ROM starting at 0x0) and your eventual bootloader configuration (ROM starting at 0x2000).
My experience with M4 application and bootloader shows that it is enough to set the Flash start at some offset address, and then in the application to initialize VTOR to this address.
From linker script:
#
/* Specify the memory areas */
MEMORY
{
CLASSBRAM (rw) : ORIGIN = 0x20000000, LENGTH = 0x80
/*RAM length = 192K - CLASSBRAM-length */
RAM (xrw) : ORIGIN = 0x20000080, LENGTH = 0x2FF80
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
/* FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K */
FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 448K /*in case of 64K for Bootloader*/
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
PROVIDE( _Rom_Start = . );
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
.....
#
code:
extern const uint32_t _Rom_Start;
....
#define ROM_START ((uint32_t *)&_Rom_Start)
...
SCB->VTOR = (uint32_t)ROM_START;
My MCU projects typically have Makefile targets that will actually flash the chip (with building as a dependency of course), so what I did for this was make a special target that flashes the main firmware "solo".
My openocd-driven programmers can flash flat binaries and not just hex files, so I was able to do this by using dd to copy just the vector table off the start of the main firmware binary into its own file. I then write this to the start of flash, and the main firmware to its usual location in separate operations. The chip boots, gets the reset and stack addresses out of the copied vector table, starts the main firmware, and that then repoints the vector table address to its own copy at the higher address.
If your programmer doesn't support flat binaries you can use objdump or some other tool to turn a flat binary back into a hex file, or likely to change the base address of a hex file / fragment.

QEMU triple faults when enabling paging

I'm implementing my own kernel and I'm stuck. I'm trying to load my kernel into the high half virtual addresses. I've tackled the identity addresses problem by identity mapping the low 1M of RAM. I've created an init section that is relocated to the kernel's physical address in order to take care of paging initialisation. My kernel's virtual offset is 0xc0000000. This is my linker script:
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
KERNEL_VIRTUAL_OFFSET = 0xC0000000;
SECTIONS
{
. = 1M;
kernel_start = .;
start_init = .;
.init ALIGN(4K) :
{ *(.multiboot);
*(.init);
*(.tables);
}
end_init = .;
. += KERNEL_VIRTUAL_OFFSET;
kernel_high_half_start = .;
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VIRTUAL_OFFSET)
{*(.text) }
.data ALIGN(4K) : AT(ADDR(.data) - KERNEL_VIRTUAL_OFFSET)
{ *(.data) }
.rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_VIRTUAL_OFFSET)
{ *(.rodata) }
.bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_VIRTUAL_OFFSET)
{ *(.bss) }
kernel_high_half_end = .;
kernel_end = . - KERNEL_VIRTUAL_OFFSET;
}
Here's my entry point. I'm using GRUB as my bootloader. It successfully boots and jumps into my entry point because of the init section:
its 32
section .multiboot
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
; Declarations
global start
extern kmain
extern paging_init
extern kernel_page_directory
section .init
enable_paging:
mov eax, kernel_page_directory
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax ; ***** PAGING ENABLED HERE *****
ret
start:
cli ;block interrupts
mov esp, init_stack
call paging_init
call enable_paging
;mov eax, 0xb8000
;mov byte[eax], 'h'
;mov byte[eax+1], 0x7
; Now high half kernel is mapped to the page directory
mov esp, stack_space ;set stack pointer
push ebx ; grub boot info
call kmain
loop:
hlt ;halt the CPU
jmp loop
resb 4096; 4KB small stack for my init section.
init_stack:
section .bss
resb 8192 ;8KB for stack
stack_space:
Here is my code the fills the page table and the page directory of the kernel. As you can see, the whole code is linked into the init section, to avoid relocation problems:
page_table_t kernel_page_directory[PAGE_DIR_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
page_pointer_t kernel_page_tables[PAGE_TABLE_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
page_pointer_t identity_page_table[PAGE_TABLE_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
/* Identity map the low 1M
* In early boot stage.
*/
static void __attribute__((section(".init"))) map_identity()
{
//map bios
unsigned int current_page = 0;
for(int i = 0; i < BIOS_PAGE_TABLE_ENTRIES; i++, current_page += PAGE_SIZE)
{
identity_page_table[i] = (current_page) | 0x3;
}
//map init
current_page = INIT_START;
for(int i = INIT_START >> 12 & 0x3FF;
i < ((INIT_START >> 12 & 0x3FF) + (INIT_SIZE / PAGE_SIZE));
i++, current_page += PAGE_SIZE)
{
identity_page_table[i] = (current_page) | 0x3;
}
kernel_page_directory[0] = ((unsigned long)(identity_page_table)) | 0x3;
}
/* Map the kernel memory to its page directory,
* **in early boot stage.
* We don't need to map the init section, we don't need it anymore.
*/
__attribute__((section(".init"))) static void map_kernel_memory()
{
//Identity map the init section
//Start at 1MB i.e. its page aligned.
unsigned int start_index = 256;
unsigned long current_page = KERNEL_START;
for(int i = start_index;
i < start_index + (KERNEL_SIZE / PAGE_SIZE) + 1;
i++, current_page += PAGE_SIZE)
{
kernel_page_tables[i] = current_page | 0x3;
}
kernel_page_directory[KERNEL_DIRECTORY_ENTRY] = ((unsigned long)kernel_page_tables) | 0x3;
}
__attribute__((section(".init"))) void paging_init()
{
map_identity();
map_kernel_memory();
}
I tried to point to the exact assembly instruction, but that makes my kernel work incorrectly and I think it is because of mov cr0, eax when I enable paging. CR3 does contain the address of kernel_page_directory or with 0x3. As soon as I enable paging, QEMU stops responding and the system reboots constantly. The screen is being flushed and then printed repeatedly. Any ideas why this is happening? How I can fix it?
Is the address of your page directory Page-Aligned? The size of each page (frame) is 4 KB. I suggest to create a struct for page directory like this:
typedef struct page_directory{
page_table_t *tables[1024];
size_t tablesPhysical[1024]; // Physical address of page tables
size_t physicalAddr; // Physical address of `tablesPhysical'
} page_directory_t;
So, your address of the directory must be the multiple of 4 KB (0x1000). James Molloy's Tutorial may help you.

Resources