cancel
Showing results for 
Search instead for 
Did you mean: 

How to jump to another execution region defined by a scatter file?

Gordon Madden
Associate III
Posted on July 19, 2017 at 17:58

I have successfully built a firmware upgrade app using C# and the bootloader in the STM32F4 To make it more robust, I am adding a scatter file based on CliveOne's recommendation:

I'd generally recommend having a boot loader at the base of memory (0x08000000, 16KB for the STM32 F2/F4), and then never erase that. Then place the app code above that, and have the boot loader validate the app code before jumping to it. This protects you if the power fails during an update, and makes things far more robust.

The 417 will startup at 0x08000000, calculate a checksum on the installed firmware, and then if it matches will continue on to a normal startup (unless an upgrade has been indicated, and then it will do that).

My question is: How to make the jump from the first executable region (0x08000000-0x08003FFF) where the checksum function is, to the normally-running app starting at 0x08004000?

Here is my code for getting into the bootloader:

; NORMAL UPGRADE STARTUP

LDR R0, =0x2001FFF0 ; Load address of WORD placed in SRAM from MAIN.C

LDR R1, =0xDEADBEEF ; Load same value stored above to a register

LDR R2, [R0, #0] ; Read the current value stored at 0x2001FFF0

STR R0, [R0, #0] ; Erase the value store in SRAM (next restart will

be normal) [stores address as value for R0]

CMP R2, R1 ; If values match (DEADBEEF), jump to bootloader,

otherwise continue startup

BEQ Reboot_Loader ; Jump to Reboot Loader below

; INTERRUPTED UPGRADE

LDR R0, =0x2001FFA0 ; Load address of WORD placed in SRAM from MAIN.C,

; indicates that the bootloader was previously active

; This word will be overwritten after the upgrade is completed.

LDR R1, =0xCAFEBABE ; Load same value stored at above address to a register

LDR R2, [R0, #0] ; Read what is stored at 0x2001FFA0

CMP R2, R1 ; If values match (CAFEBABE), jump to bootloader,

otherwise continue startup

BEQ Reboot_Loader ; Jump to Reboot Loader below

; Normal startup path

LDR R0, =SystemInit

BLX R0

LDR R0, =__main

BX R0

ENDP

;...

; Vector into System Loader

; Sets up board for bootloader mode

Reboot_Loader PROC

EXPORT Reboot_Loader

LDR R0, =0x40023844 ; RCC_APB2ENR

LDR R1, =0x00004000 ; ENABLE SYSCFG CLOCK

STR R1, [R0, #0]

LDR R0, =0x40013800 ; SYSCFG_MEMRMP

LDR R1, =0x00000001 ; MAP ROM AT ZERO

STR R1, [R0, #0]

LDR R0, =0x1FFF0000 ; ROM BASE

LDR SP,[R0, #0] ; SP @ +0

LDR R0,[R0, #4] ; PC @ +4

BX R0

ENDP

SystemReset PROC

EXPORT SystemReset

ldr r1, =0xE000ED0C ; NVIC Application Interrupt and Controller

ldr r0, =0x05FA0004 ; Magic

str r0, [r1, #0] ; Reset

b .

ENDP

Do I have to do another system reset or is there a way to jump into the firmware on completion of the checksum. When doing an upgrade, I use the GO command to leave the bootloader and start executing code.

Thank you!

#armlink #linker #execution-region #jump-memory #robust #firmware-upgrade #scatter-file

Note: this post was migrated and contained many threaded conversations, some content may be missing.
1 ACCEPTED SOLUTION

Accepted Solutions
Posted on July 19, 2017 at 18:54

If you build the app with it's vector table based at 0x08004000, code like this will suffice

JumpApp     PROC

                EXPORT  JumpApp

                LDR     R0, =0x08004000 ; APP BASE

                LDR     SP,[R0, #0]     ; SP @ +0

                LDR     R0,[R0, #4]     ; PC @ +4

                BX      R0

                ENDP

Look at some of the IAP examples. In C you can use function pointers, think qsort()

Make sure code in app's SystemInit() sets the vector table, via SCB->VTOR = 0x08004000

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

View solution in original post

10 REPLIES 10
Posted on July 19, 2017 at 18:54

If you build the app with it's vector table based at 0x08004000, code like this will suffice

JumpApp     PROC

                EXPORT  JumpApp

                LDR     R0, =0x08004000 ; APP BASE

                LDR     SP,[R0, #0]     ; SP @ +0

                LDR     R0,[R0, #4]     ; PC @ +4

                BX      R0

                ENDP

Look at some of the IAP examples. In C you can use function pointers, think qsort()

Make sure code in app's SystemInit() sets the vector table, via SCB->VTOR = 0x08004000

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on July 20, 2017 at 22:09

Thanks, Clive!

Now I essentially have two programs, the first of which I will not be upgrading. If the first program is running a checksum on the second (firmware), how can I get access to the checksum value? During testing, I have been putting the checksum value into the first program, but I will not be able to do that when running upgrades. When the firmware is upgraded, I will be able to include a value for the checksum for the new firmware, but how can I get the first program to have access to that constant?

I tried to extern it, but they are two separate programs, not just separate .c files. I can't extern the checksum and then not define it.

Is there a way to store the value somewhere (register or memory) so that the first program can pick it up to do the comparison?

Thanks!

Posted on July 21, 2017 at 00:08

I'd sign the image. You could use one of the empty vectors to encode the size and then append a checksum or crc on the end. Here I'd take the output from the linker, in either .HEX, .BIN or .ELF, process the image and sign it. The loader pulls the size, and for a crc it should divide through itself resulting in a remainder of zero.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on July 21, 2017 at 01:54

I'm sure this is an excellent answer, but I don't yet have sufficient knowledge to understand it. I have been using .HEX files, but I don't know what an empty vector is, or what signing files entails. Looking stuff up on the Internet has produced confusing results �? I'm not sure which info will put me on the right track. Is signing tied to debugging or initialization files? Is this info something I will find in linker literature or elsewhere? If you have suggestions for further study, I would appreciate a nudge in the right direction.

Thank you!

Posted on July 21, 2017 at 03:12

A .BIN and .HEX describe the same data, the .BIN is a more accurate representation of how that data will look in memory. The data has some inferred length as output by the linker, and includes the code followed by data that is copied into RAM when execution. You should look at the files in a file view or hex editor. You can process hex files with SRECORDS, or look at the data as loaded by the ST-LINK Utilities.

The empty vectors are these .word 0 directives below (or equivalent in Keil of DCD 0), the processor doesn't use these so they represent a location of known offset into which a size parameter can be placed, ie at +0x20

If the binary data is of length 0x1234 the code you have processing the linker output places that value at +0x20, and then proceeds to perform a CRC-32 over the data between 0x0000..0x1233, and then writes that computed value at +0x1234. When you write the firmware to flash you send 0x1238 bytes which includes the CRC-32 word. The boot loader can then pull the length from +0x20, perform the same CRC-32 and check the integrity of the image.

startup_stm32f401xx.lst

...

119 /******************************************************************************

120 *

121 * The minimal vector table for a Cortex M3. Note that the proper constructs

122 * must be placed on this to ensure that it ends up at physical address

123 * 0x0000.0000.

124 *

125 *******************************************************************************/

126 .section .isr_vector,'a',%progbits

127 .type g_pfnVectors, %object

128 .size g_pfnVectors, .-g_pfnVectors

129

130

131 g_pfnVectors:

132 0000 00000000 .word _estack  ;+0 initial stack

133 0004 00000000 .word Reset_Handler ; +4 initial program counter

134 0008 00000000 .word NMI_Handler

135 000c 00000000 .word HardFault_Handler

136 0010 00000000 .word MemManage_Handler

137 0014 00000000 .word BusFault_Handler

138 0018 00000000 .word UsageFault_Handler

139 001c 00000000 .word 0

140 0020 00000000 .word 0 ; <- a post link step could write the size here

141 0024 00000000 .word 0

142 0028 00000000 .word 0

143 002c 00000000 .word SVC_Handler

144 0030 00000000 .word DebugMon_Handler

145 0034 00000000 .word 0

146 0038 00000000 .word PendSV_Handler

147 003c 00000000 .word SysTick_Handler

148

149 /* External Interrupts */

150 0040 00000000 .word WWDG_IRQHandler /* Window WatchDog */

151 0044 00000000 .word PVD_IRQHandler /* PVD through EXTI Line detection */

152 0048 00000000 .word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */

153 004c 00000000 .word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */

154 0050 00000000 .word FLASH_IRQHandler /* FLASH */

155 0054 00000000 .word RCC_IRQHandler /* RCC */

..

In this example  I used a CRC-32, but you could sign it with some other hash like MD5 or SHA

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on July 25, 2017 at 19:46

Thank you for all the information — you are a font of wisdom! You have vastly improved my understanding of ARM and programming in general.

I am now using srec_cat.exe to add the CRC to the end of the data, but I have not been able to write into the 'file hole' at 0x20. The linker message (Keil output window) says that there is something there and though it updates the .HEX file, it does not update the 'checked' .HEX file that adds the CRC in post processing after the build. Everything else is working perfectly.

The only problem is that my 'bootloader' program, the one that checks the CRC of the firmware before jumping to it (0x08000000-0x08003FFF), has to have the CRC address hardcoded into it. So, with firmware of variable length, I guess the best choice would be to put the CRC at the end of flash memory. But that results in a lot more bytes to write when upgrading. Is there a way to avoid that situation?

From 'User' Options for Target for APP (0x08004000 on)

.\bin\srec_cat .\Obj\River.hex -intel -crop 0 0x080085FC -fill 0xFF 0x08004000 0x080085FC -crc32-l-e 0x080085FC -o .\Obj\River_checked.hex -intel

From 'bootloader' (in protected, non-upgraded 0x08000000-0x08003FFF)

// Location of CRC value calculated on .HEX file

uint32_t crc_nominal __attribute__((at(0x080085FC)));

I tried to copy the CRC to 0x20 in the APP, but not only did it not work (see above), would it also not interfere with the calculation of the CRC itself, since is part of the APP. I noticed your usage is also at the end of the file.

Thanks again!

Posted on July 25, 2017 at 22:52

Putting it on the end beyond the linker output saves a lot of headaches, I could work the math with the polynomials to internalize the CRC within the front of the image, and get a zero residual out the back end, but that seems a bit unnecessary*.

I don't put fixed addresses like '

uint32_t crc_nominal __attribute__((at(0x080085FC)));

' inside the loader or app, again it just causes unnecessary work and complication where it isn't needed. If the image describes its own length, either by parsing the HEX/ELF file or linker symbol tricks, it is far less of a circus.

Done this with ARM9 images, not tried with 2017 Keil (needs leading spaces, but formated code puts me in moderation jail)

IMPORT ||Image$$ER_IROM1$$RO$$Length||

IMPORT ||Image$$RW_IRAM1$$RW$$Length||

DCD ||Image$$ER_IROM1$$RO$$Length||+\

||Image$$RW_IRAM1$$RW$$Length||

*I've posted code to roll back the STM32 HW CRC

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on July 29, 2017 at 01:13

I don't follow you completely, but it sounds like the direction I want to go.

My current solution defines a block of flash starting at 0x08004000, writes in the firmware and then fills the leftover space with 0xFF until the last 4 bytes, where it writes the checksum for the firmware. The 'bootloader' at 0x08000000-0x08003FFF runs the CRC on the hex file and compares it to the checksum.

This is working, but depending on how much 'extra' room is left at the end of the flash block (for the firmware to grow), there is a lot more hex file for the real bootloader to copy during an upgrade. I would like to keep the firmware image as short as possible and have the CRC at the end.

Without specifying a location to find the CRC, how would the 'bootloader' know where to stop calculating? You said not to specify a fixed address, but what is the alternative? Is finding the length of the file just a matter of counting every byte in the file?

At one time I had a function that searched through the firmware until it found the next four bytes to be 0xFFFFFFFF. Since this doesn't happen in code, it would signify the end of the code. Would something like that work?

Posted on August 03, 2017 at 02:26

A similar conversation on the Keil forum

http://www.onarm.com/forum/62818/#/msg206260

startup.s

...
; Reset handler routine
Reset_Handler PROC
 EXPORT Reset_Handler [WEAK]
 IMPORT __main
 IMPORT SystemInit
 LDR R0, =SystemInit
 BLX R0
 LDR R0, =__main
 BX R0
 ENDP ; sourcer32@gmail.com
 IMPORT ||Load$$LR$$LR_IROM1$$Length||
 EXPORT romlen
romlen DCD ||Load$$LR$$LR_IROM1$$Length||
...�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

main.c

extern uint32_t romlen;
...
printf('%08
X', romlen);�?�?�?

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..