2017-07-19 08:58 AM
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 startupBEQ 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 startupBEQ 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.Solved! Go to Solution.
2017-07-19 09:54 AM
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
2017-07-19 09:54 AM
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
2017-07-20 03:09 PM
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!
2017-07-20 05:08 PM
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.
2017-07-20 06:54 PM
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!
2017-07-20 08:12 PM
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
2017-07-25 12:46 PM
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!
2017-07-25 03:52 PM
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
2017-07-28 06:13 PM
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?
2017-08-02 07:26 PM
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);�?�?�?