I'm using Keil µVision for an embedded project that jumps back to the bootloader for updating. Keil previously used ARMCC as a compiler and the following code worked fine.
void run_bootloader(void)
{
uint32_t runBootloaderAddress;
// Read the entry point from bootloader's vector table
runBootloaderAddress = *(uint32_t*)(0x00000004);
void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
runBootloader();
}
Keil is switching to Clang in their newer versions and I'm trying ot port the code over. That code now causes a reset when runBootloader()
is called.
The generated assembly is, of course, different. So I pulled the ARMCC generated assembly from the listing and wrote it as inline assembler.
void run_bootloader(void)
{
__asm("PUSH {r4-r6,lr}");
__asm("MOVS r0,#0");
__asm("LDR r4,[r0,#4]");
__asm("MOV r5,r4");
__asm("BLX r5");
__asm("POP {r4-r6,pc}");
}
Stepping through, the values in the registers seem to change the same as before. But the ARMCC version jumps to the bootloader on the BLX
while the Clang version resets. The address pulled from the vector table is the same in both.
I've seen mentions that Clang does not allow this sort of thing like the comment to this answer. But there has to be a way to jump back to the bootloader. What am I missing? Is there some setting in the linker I need to enable to allow for this behavior?
Update
Some comments lead me to search out a better way to reset which lead me to this article which discusses resetting the processor by writing to the SYSTEMRESETREQ
bit of the Application Interrupt and Reset Control Register (AIRCR
). Which lead me to find this function in a CMSIS header file. I inherited the code, I don't know why this wasn't being used.
/**
\brief System Reset
\details Initiates a system reset request to reset the MCU.
*/
__STATIC_INLINE void __NVIC_SystemReset(void)
{
__DSB(); /* Ensure all outstanding memory accesses included
buffered write are completed before reset */
SCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk ); /* Keep priority group unchanged */
__DSB(); /* Ensure completion of memory access */
for(;;) /* wait until reset */
{
__NOP();
}
}
However, the application still reboots without running the bootloader.
More information
The bootloader and the application are separate projects. The application is running Keil RTX OS with a CMSIS layer. My thought is the application is running in its own address space and does not know about the bootloader. So when it resets, it only resets within its own address space and the bootloader is never run.
I attempted to inform the application about the bootloader by adding this to my scatter file.
LR_IROM0 0x00 0x00012000 {
ER_IROM0 0x0 0x00012000 {
.ANY (+RO)
}
}
But the results are the same.
To clarify, I'm really trying to reset the processor. I'm trying to jump to the bootloader while communicating to it that it is not a normal boot but rather an update.
Toolchain Versions
PS C:\Keil_v5\ARM\ARMCC\bin> .\armcc.exe --version_number
5060750
PS C:\Keil_v5\ARM\ARMCLANG\bin> .\armclang.exe --version
Product: MDK Plus 5.25 (Flex)
Component: ARM Compiler 6.9
Tool: armclang [5ced1d00]
Target: unspecified-arm-none-unspecified
Generated Assembly
C Code:
void run_bootloader(void)
{
volatile uint32_t runBootloaderAddress;
// Read the entry point from bootloader's vector table
runBootloaderAddress = *(uint32_t*)(0x00000004);
void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
runBootloader();
}
ARMCC:
AREA ||i.run_bootloader||, CODE, READONLY, ALIGN=1
run_bootloader PROC
;;;1335 }
;;;1336 void run_bootloader(void)
000000 b570 PUSH {r4-r6,lr}
;;;1337 {
;;;1338 uint32_t runBootloaderAddress;
;;;1339
;;;1340 // Read the entry point from bootloader's vector table
;;;1341 runBootloaderAddress = *(uint32_t*)(0x00000004);
000002 2000 MOVS r0,#0
000004 6844 LDR r4,[r0,#4]
;;;1342 void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
000006 4625 MOV r5,r4
;;;1343 runBootloader();
000008 47a8 BLX r5
;;;1344 }
00000a bd70 POP {r4-r6,pc}
;;;1345
ENDP
Clang:
.section .text.run_bootloader,"ax",%progbits
.hidden run_bootloader @ -- Begin function run_bootloader
.globl run_bootloader
.p2align 2
.type run_bootloader,%function
.code 16 @ @run_bootloader
.thumb_func
run_bootloader:
.Lfunc_begin2:
.loc 2 1338 0 @ ../_Primary/source/can_tools.c:1338:0
.fnstart
.cfi_startproc
@ BB#0:
.save {r7, lr}
push {r7, lr}
.Lcfi8:
.cfi_def_cfa_offset 8
.Lcfi9:
.cfi_offset lr, -4
.Lcfi10:
.cfi_offset r7, -8
.pad #8
sub sp, #8
.Lcfi11:
.cfi_def_cfa_offset 16
.Ltmp8:
.loc 2 1343 28 prologue_end @ ../_Primary/source/can_tools.c:1343:28
movs r0, #4
ldr r0, [r0]
.loc 2 1343 26 is_stmt 0 @ ../_Primary/source/can_tools.c:1343:26
str r0, [sp, #4]
.loc 2 1344 52 is_stmt 1 @ ../_Primary/source/can_tools.c:1344:52
ldr r0, [sp, #4]
.loc 2 1344 12 is_stmt 0 @ ../_Primary/source/can_tools.c:1344:12
str r0, [sp]
.loc 2 1345 5 is_stmt 1 @ ../_Primary/source/can_tools.c:1345:5
ldr r0, [sp]
blx r0
.loc 2 1346 1 @ ../_Primary/source/can_tools.c:1346:1
add sp, #8
pop {r7, pc}
.Ltmp9:
.Lfunc_end2:
.size run_bootloader, .Lfunc_end2-run_bootloader
.cfi_endproc
.cantunwind
.fnend
@ -- End function