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?
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.
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.
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
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
what version of clang and how is it being used?
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();
}
gcc 8.2.0
00000000 <run_bootloader>:
0: 2304 movs r3, #4
2: b510 push {r4, lr}
4: 681b ldr r3, [r3, #0]
6: 4798 blx r3
8: bd10 pop {r4, pc}
a: 46c0 nop ; (mov r8, r8)
clang/llvm 3.8.0
00000000 <run_bootloader>:
0: b580 push {r7, lr}
2: af00 add r7, sp, #0
4: 2004 movs r0, #4
6: 6800 ldr r0, [r0, #0]
8: 4780 blx r0
a: bd80 pop {r7, pc}
both should function correctly as they are using blx and not bl.
if you want to simulate a reset though you need to do something like this
.thumb
mov r0,#0
ldr r1,[r0,#0]
ldr r2,[r0,#4]
mov sp,r1
bx r2
00000000 <.text>:
0: 2000 movs r0, #0
2: 6801 ldr r1, [r0, #0]
4: 6842 ldr r2, [r0, #4]
6: 468d mov sp, r1
8: 4710 bx r2
and do it in assembly not compiled C.
User contributions licensed under CC BY-SA 3.0