How to jump to Application code from Bootloader - K10 - CW10.2

cancel
Showing results for 
Search instead for 
Did you mean: 

How to jump to Application code from Bootloader - K10 - CW10.2

Jump to solution
22,577 Views
ignisuti
Contributor IV

I'm REALLY struggling with this. Can someone please share an example?

 

I'm confused because the examples I've seen appear to use the BX or BLX assembly instructions and according to page 20 of the Cortex™-M4 Devices Generic User Guide, that's a big no-no. The User Guide says that using either of these instructions causes the Thumb State bit to be cleared which causes a Hard Fault Lockup!

 

I'm a noob when it comes to Bootloaders. So, please help me get started.

1 Solution
1,900 Views
ndavies
Contributor V

It doesn't say it will clear the bit, It says it can clear the bit.

 

The trick to the BX command is, It uses the address of the destination to determine what instruction mode it jumps into. An even instruction address changes the instruction mode to ARM 7. An odd instruction address keeps the processor in Thumb mode. The ARM core can only run instructions on even addresses. The core masks off the least significant bit of an address and uses it to detremine the instruction set it is running in. So to jump to a bootloader address and stay in thumb mode, you must add 1 to the destination address.

 

On the larger ARM parts this allows you to jump in and out of thumb mode when doing function calls.

 

From Page 59

Note  Bit[0] of any address you write to the PC with a BX, BLX, LDM, LDR, or POP instruction must be 1 for correct execution, because this bit indicates the required instruction set, and the Cortex-M4 processor only supports Thumb instructions.

 

From Page 166

Rm Is a register that indicates an address to branch to. Bit[0] of the value in Rm must be 1, but the address to branch to is created by changing bit[0] to 0.

 

 The BX and BLX instructions result in a UsageFault exception if bit[0] of Rm is 0.

 

 

View solution in original post

0 Kudos
21 Replies
1,901 Views
ndavies
Contributor V

It doesn't say it will clear the bit, It says it can clear the bit.

 

The trick to the BX command is, It uses the address of the destination to determine what instruction mode it jumps into. An even instruction address changes the instruction mode to ARM 7. An odd instruction address keeps the processor in Thumb mode. The ARM core can only run instructions on even addresses. The core masks off the least significant bit of an address and uses it to detremine the instruction set it is running in. So to jump to a bootloader address and stay in thumb mode, you must add 1 to the destination address.

 

On the larger ARM parts this allows you to jump in and out of thumb mode when doing function calls.

 

From Page 59

Note  Bit[0] of any address you write to the PC with a BX, BLX, LDM, LDR, or POP instruction must be 1 for correct execution, because this bit indicates the required instruction set, and the Cortex-M4 processor only supports Thumb instructions.

 

From Page 166

Rm Is a register that indicates an address to branch to. Bit[0] of the value in Rm must be 1, but the address to branch to is created by changing bit[0] to 0.

 

 The BX and BLX instructions result in a UsageFault exception if bit[0] of Rm is 0.

 

 

View solution in original post

0 Kudos
1,900 Views
albertolubeiro
Contributor III

Hi everybody!!

it's the first time that I try to do a jump to an address. I have read all theese posts but i dónt understand very well. Foe example, I want to jump to the 0x4000.

Reading the "ARMv7-M Architecture Reference Manual" it puts the following example at page 114: B label ; branch unconditionally to label.

First off all i don´t know what a label is, and how should I write the code. I got to jump to a function this way:

#pragma push

#pragma thumb

asm void Branch_Aplication(void)

{   

    bl __init_hardware;

}

#pragma pop

But how to jump to a determinate address?. 0x4000 for example. I am using K60.

Thanks

0 Kudos
1,900 Views
LuisCasado
NXP Employee
NXP Employee

Alberto,

In the AN2295 bootloader you have an example:

               AREA    |.text|, CODE, READONLY

               EXPORT JumpToUserApplication

              

JumpToUserApplication

                              msr msp, r0

                              msr psp, r0       

                              mov pc, r1

               END

extern void JumpToUserApplication(LWord userSP, LWord userStartup);

    // Jump to user application

    JumpToUserApplication(*((unsigned long*)RELOCATED_VECTORS), *((unsigned long*)(RELOCATED_VECTORS+4)));  

Luis

0 Kudos
1,900 Views
albertolubeiro
Contributor III

Hi Luis,

What is the following two lines for?

AREA    |.text|, CODE, READONLY

EXPORT JumpToUserApplication

I have just tested the JumpToUserApplication function shown above. Seems it jumps to the desired address, but the application whose vector table starts at the address to wich it jumps doesn't start.

I have the following .lcf files configuration

BOOT.lcf

m_interrupts  (RX) : ORIGIN = 0x00000000, LENGTH = 0x000001E0

m_text        (RX) : ORIGIN = 0x00000800, LENGTH = 0x00004000-0x00000800

m_data        (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00010000

m_cfmprotrom  (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010

APLICATION.lcf

m_cfmprotrom  (RX) : ORIGIN = 0x00004400, LENGTH = 0x00000010

m_text        (RX) : ORIGIN = 0x00004800, LENGTH = 0x00040000-0x00000800

m_interrupts  (RX) : ORIGIN = 0x00004000, LENGTH = 0x000001E0

vectorram     (RWX): ORIGIN = 0x1FFF8000, LENGTH = 0x000001E0

m_data        (RW) : ORIGIN = 0x1FFF81E0, LENGTH = 0x00010000-0x000001E0

What i do is; First i program the K60 with the application, then i program the K60 with the boot. When loading the boot, obviously i don't erase the memory in wich the application is.

Any help will be appreciated.

Thanks and best regards.

0 Kudos
1,892 Views
LuisCasado
NXP Employee
NXP Employee

Hi Alberto,

Normally the bootloader is programmed first, bootloader use the flash vectors and the application will use the vectors in RAM. Your application will not start if the boot is not there, managing the hardware reset vectors. The linker file of the application can not start without a bootloader, it doesn't have code (vectors) in 0x00000000.

I am not sure what you mean with "but the application whose vector table starts at the address to wich it jumps doesn't start”

Your ‘boot' should jump to application entry point, and install the new vector table in RAM with the interrupts vectors that will point to the right interrupts routines.

Luis

0 Kudos
1,892 Views
albertolubeiro
Contributor III

Hola Luis,

En castellano me voy a apañar mejor.

Es la primera vez que ando con temas de estos y estoy un poco perdido. No acabo de entender muy bien lo que quieres decir.

Si cargo en la flash sólo la aplicación o sólo el boot siguiendo los ficheros del linker mostrados arriba, ambos corren bien. Cuando cargo los dos en la flash y se ejecuta la función "JumpToUserApplication" desde el boot, es cuando no entra la aplicación a pesar que las direcciones que se le pasan a la función son la 0x4000 y 0x4004 (ver APLICATION.lcf en el post de arriba), que son las direcciones del SP y Startup respectivamente

>>Your application will not start if the boot is not there, managing the hardware reset vectors. The linker file of the application can not start without a bootloader, it doesn't have code (vectors) in 0x00000000.

Cuando cargo sólo la aplicación tampoco tiene vectores en la dirección 0x0000_0000 ya que la tabla de vectores empieza en la dirección 0x000_4000 y funciona correctamente.


>>Your ‘boot' should jump to application entry point, and install the new vector table in RAM with the interrupts vectors that will point to the right interrupts routines.

Actualmente en el Startup de la aplicación se hace un salto a una función llamada "__init_hardware" y ahí ya se hace una copia de la tabla de vectores a RAM posicionando "SCB_VTOR" a esa copia.

Si me lo pudieras explicar un poco más en detalle te lo agradecería.

Gracias Luis.

Saludos

0 Kudos
1,900 Views
ignisuti
Contributor IV

ndavies, you're AWESOME!!!

I added +1 to my function pointer address and it works now!!!

 

This whole business of using multiple instruction sets on the same MCU and needing to understand nuances like this seem to be dirty voodoo to me. Is this a common thing in the embedded world? Or, is this specific to the Kinetis platform with ARM cores?

0 Kudos
1,900 Views
konrada
Contributor IV

There's B, which branches, and BX, which branches and chooses instruction set. You probably don't want to use Branch-and-Link for a one-way jump.

 

Perhaps you can find a copy of the ARM-v7M Architecture Reference Manual somewhere.

 

HTH

KA 

0 Kudos
1,900 Views
mjbcswitzerland
Specialist V

Hi

 

Try the following:

 

// Allow the jump to a foreign application as if it were a reset (load SP and PC)
//
extern void start_application(unsigned long app_link_location){    asm(" ldr sp, [r0,#0]");    asm(" ldr pc, [r0,#4]");}

 

Just link the application to whatever address it needs and use the standard startup code there (starts with stack pointer initial valeu followed by program counter initial value). From the boot loader call

start_application(0x00001000);

with whatever address the application is at .

This works with all compilers apart from Keil since Keil doesn't support inline assembler in Thumb node (no idea why not...). If you happen to use keil the function can be written directly as an assembler function.

 

Regards

 

Mark

 

0 Kudos
1,900 Views
Mike_d
Contributor IV

Hi Mark,

I added your start_application() to my KL26 (M0+) CW 10.6 project and I get:

Error: lo register required -- `ldr sp,[r0,#0]'

Error: lo register required -- `ldr pc,[r0,#4]'

Do you know what do I need to do to fix this?

Thanks,

-Mike

0 Kudos
1,900 Views
mjbcswitzerland
Specialist V

Hi

The code is not valid for the Cortex-m0+ instruction set.

The equivalent for this (as used by the KL26) is

asm(" ldr r1, [r0,#0]"); // get the stack pointer value from the program's reset vector

asm(" mov sp, r1");      // copy the value to the stack pointer

asm(" ldr r0, [r0,#4]"); // get the program counter value from the program's reset vector

asm(" blx r0");          // jump to the start address

Regards

Mark

1,900 Views
deekay
Contributor I

Hi Mark.

Just a note:

I am using the mk60Dn512 and are trying this, however, the function parameter is loaded into r4, not r0 in my case....

BR

0 Kudos
1,900 Views
mjbcswitzerland
Specialist V

Hi Dee

Interesting. I can't imagine that the register passing convention is processor type dependent but maybe CW10.2 setup dependent (somewhere)?

In any case, whenever assember is used it will need to be matched to the C-compiler convention in order to be able to work, since there is no automation involved.

Regards

Mark

0 Kudos
1,900 Views
dcantero
Contributor II

Hi everybody!!

 

I´m trying to jump to application code using the code snippet used in this discussion and it works well if I use as a base application (the application where the jump is done) a project whitout operative system. I use a "hello world" example of the kinetis family example projects downloaded from freescale web and I include the jump code after "hello world" message.

 

Then I tried to do the same thing using a MQX project wich has a shell and a SD driver (the idea was to copy the new firmware from SD and run it). The application (again the hello world project mentioned above) start to run but suddenly appears the message "Default isr entered on vector 15". I thought that maybe MQX initialize some peripherals that must to be stopped before jump and I have disabled all interrupts and stopped all peripherals (using CGM registers) but the problem remains (in fact now program falls in vector 3 isr too).

 

I dont now how to do now. So any idea???

 

Thanks.

 

0 Kudos
1,900 Views
ndavies
Contributor V

ISR 3 is the hard fault interrupt. It cannot be turned off. One of the causes of ISR 3 is trying to access a peripheral register while that peripheral has it's clock turned off. So turning off the CGM for a peripheral without removing the code that accesses the peripherals register is bad.

 

ISR 15 is the systick clock interrupt. It is also permenantly enabled. The systick clock is typically used by the OS task scheduler. It looks like you haven't correctly replaced the default interrupt handler with the systick timers interrupt.

 

You need to review the interrupt vector table initialization. 

0 Kudos
1,900 Views
dcantero
Contributor II

Thanks for the reply ndavies!!

 

Now I have used the "_int_disable()" function to disable all interrupts before jump and the application code runs well until the statup code calls "flash_identify" function wich read data from internal flash. If I comment this funtion application code is executed correctly and shows "Hello world" message. But when I try to jump to a MQX based application the processor resets and bootloader is executed again.

 

Any idea?

0 Kudos
1,900 Views
ndavies
Contributor V

Sorry, I'm not sure I can be of much more help. I'm not using MQX.

 

However if I had a guess. MQX probably requires the interrupt vectors to be correctly set. I'm sure MQX is enabling them shortly after it starts. I can't think of a single OS that operates without the interrupts. Just disabling interrupts is not going to work.

 

The other place to look is at the watchdog. If the watchdog is enabled by MQX it may be timing out and forcing a reset.

0 Kudos
1,900 Views
ignisuti
Contributor IV

Mark, I put your function in my Bootloader SECTION, and called my application code (address 0x0000_3400) like this: 

start_application(0x3400);

However, my code just branched off into some random location (0x2EF8DF26). 

 

Your function takes a parameters called app_link_location, but never uses that parameters by name. Are you assuming the parameter is stored at register r0?

 


0 Kudos
1,900 Views
mjbcswitzerland
Specialist V

Hi

 

It is convention that the parameter is passed in r0.

 

In the case of the code used, the application must have its standard startup code located at that address:

- Stack Pointer init value (this is set to the stack pointer)

- Program counter init value (this is set to the PC so that the next instruction starts executing at the address set)

 

Regards

 

Mark

 

 

0 Kudos
1,900 Views
ignisuti
Contributor IV

Fellas, thanks for the help.

 

I think I discovered another strategy that is a bit more clean in my opinion. Instead of using the Linker symbol and adding 1 in order to jump to a known address. I can just simply call the function by name if I first lock that function down to always be at the same address.

 

I can do that by creating an entry Section for the Application code in my Linker Configuration File, like this:

.GROUP__APPLICATION__ENTRY_POINT:  {           *(.LINKER_SECTION__start__application)        . = ALIGN (0x8);    } > memory_map__application

 

And then, I tell the linker which function to put in this Section, like this:

#pragma define_section LINKER_SECTION__start__application ".LINKER_SECTION__start__application" far_abs RX#pragma section LINKER_SECTION__start__application begin/* =================================================================================================  FUNCTION DESCRIPTION:==      Startup function for Application Code====  OUTPUTS:== N/A== =============================================================================================*/void STARTUP__APPLICATION__entry_point  (  void  )    {    application__code();    } /* STARTUP__APPLICATION__entry_point() */#pragma section LINKER_SECTION__start__application end

 

If my reasoning is correct, the linker will always build STARTUP__APPLICATION__entry_point() at the start of my Application code and it will therefore always have the same address.

 

Please let me know if I've overlooked something.

0 Kudos