LPC2000 startup code - bootloader

Started by Jean-Sebastien Stoezel May 18, 2010
Hello,

I have an LPC2148 board that comes preloaded with a USB bootloader.
The bootloader checks if a binary file is located on a uSD card, and
if the file exists, it burns its content to flash. If no file exists,
the bootloader invokes the user application at address 0x10000 in
flash. For those familiar with it, this is the SFE USB bootloader.
This bootloader works great and I have been able to flash my board and
run programs with it.

I would like to use this bootloader to start a freeRTOS application. I
have asked a similar question on the freeRTOS mailing list yesterday
but I have yet to receive any feedback. I thought I would ask on this
list too, since in my mind the question is mostly related to LPC2000
startup code, and there's quite a few experts on the subject on this
list!

The bootloader is expecting to find the user application at 0x10000,
so I relocated the whole freeRTOS application code at this offset.
This includes the freeRTOS startup code, the one that is supposed to
setup stacks, initialize data sections, initialize the interrupt
vectors, initialize stacks, set the CPU in supervisor mode and invoke
main. With this done, the bootloader successfully invokes the freeRTOS
startup code (it does so from a C environment-the bootloader is a C
application), and I can see main (the one in the freeRTOS application)
being called, and I can run code from there.
However the CPU becomes unresponsive once vTaskStartScheduler is
called (freeRTOS is called).

I have no JTAG capabilities on the board so I can't step through the
code. What I can see though, is that no tick interrupt is generated
(checked that with the tick hook), which made me wonder if the
interrupt vector initialization was clashing with what the bootloader
would have initialized before calling the application.

- I'm a bit confused at this point because the startup code for
freeRTOS is called after the bootloader, which should initialize the
CPU into a state suitable for the RTOS. Or is it? Is there such a
thing as "one time initialization" after reset, meaning some registers
can only be accessed once after reset? That may explain why the
freeRTOS startup code fails to properly initialize the CPU.
- The bootloader calls the freeRTOS startup code (asm) like it would
be a C function. This it initializes a functor with the 0x10000 offset
and invokes it. This seems to work since the main function in the
freeRTOS application is invoked. But could this mess up the CPU
initialization in any way?
- I believe the bootloader is compiled in ARM mode. I have tried
running the freeRTOS project in either ARM or THUMB with no success.
Could the issue I'm seeing be related to the bootloader being compiled
in one mode, and the application being compiled in another mode?
- Any other hints?

Thanks,
Jean

An Engineer's Guide to the LPC2100 Series

I would be looking to see how the interrupt vectors are remapped or if they're remapped.

In theory, the IRQ vector could be getting the interrupt handler address from the VIC and this would be position independent.

I guess I would be trying to write an interrupt driven blinking LED with 50 lines of code before I tried to port thousands of lines of something I didn't write. Proof of concept...

Also, you might want to review what FreeRTOS expects in terms of stack locations and sizes. Ordinarily these are set up in the boot code and the processor is left in supervisor mode with the interrupt system enabled (CPSR settings).

And, yes, if you are in user mode, there are registers you can't reach.

Richard

Hi Jean,

> However the CPU becomes unresponsive once vTaskStartScheduler is
> called (freeRTOS is called).

Make sure the SWI exception is propagated by the bootloader code to your FreeRTOS application. This is required for the scheduler to work.

The address 0x8 in the bootloader should have a jump to address 0x10008. Either this, or move your whole vector table into RAM, and use MEMMAP to activate it. I'm in favor of the first solution...

Regards,
Rolf

Richard:
Thank you for the detailed answer.

I understand your point with writing a simple proof of concept for
validating the interrupt vector initialization. Before going further, I
would like to cover the basics though.
To follow on your remark regarding the mode the CPU is in, when I look at
the bootloader code, here are the last few lines before the higher level
bootloader code (written in C):

Reset_Handler:
/* Setup a stack for each mode - note that this only sets up a usable stack
for User mode. Also each mode is setup with interrupts initially disabled.
*/

ldr r0, =_stack_end
msr CPSR_c, #MODE_UND|I_BIT|F_BIT /* Undefined Instruction Mode */
mov sp, r0
sub r0, r0, #UND_STACK_SIZE
msr CPSR_c, #MODE_ABT|I_BIT|F_BIT /* Abort Mode */
mov sp, r0
sub r0, r0, #ABT_STACK_SIZE
msr CPSR_c, #MODE_FIQ|I_BIT|F_BIT /* FIQ Mode */
mov sp, r0
sub r0, r0, #FIQ_STACK_SIZE
msr CPSR_c, #MODE_IRQ|I_BIT|F_BIT /* IRQ Mode */
mov sp, r0
sub r0, r0, #IRQ_STACK_SIZE
msr CPSR_c, #MODE_SVC|I_BIT|F_BIT /* Supervisor Mode */
mov sp, r0
sub r0, r0, #SVC_STACK_SIZE
/* msr CPSR_c, #MODE_SYS|I_BIT|F_BIT */ /* User Mode */
msr CPSR_c, #MODE_SYS|F_BIT /* User Mode */
mov sp, r0
/* copy .data section (Copy from ROM to RAM) */
ldr R1, =_etext
ldr R2, =_data
ldr R3, =_edata
cmp R2, R3
ldrlo R0, [R1], #4
strlo R0, [R2], #4
blo 1b

/* Clear .bss section (Zero init) */
mov R0, #0
ldr R1, =_bss_start
ldr R2, =_bss_end
cmp R1, R2
strlo R0, [R1], #4
blo 2b

/* Enter the C code */
b main
The first few lines setup the stacks for each mode. By the way, I am not
sure I understand the following lines:
/* msr CPSR_c, #MODE_SYS|I_BIT|F_BIT */ /* User Mode */
msr CPSR_c, #MODE_SYS|F_BIT /* User Mode */
mov sp, r0

Could you correct me if I'm wrong, the comments say this is user mode while
it looks like the system mode is set, with interrupts disabled. Also, no
stack seems to be set for this mode...
So it looks like the bootloader would be running in system mode - I'm
guessing it is still in this mode when it calls the user application. This
is still a privilege mode right, and my understanding is that in this mode
we are still able to access all the registers...

Do you think this initialization would be the cause of failure of the
freeRTOS startup code?

Thanks,
Jean

On Tue, May 18, 2010 at 9:55 AM, rtstofer wrote:

> I would be looking to see how the interrupt vectors are remapped or if
> they're remapped.
>
> In theory, the IRQ vector could be getting the interrupt handler address
> from the VIC and this would be position independent.
>
> I guess I would be trying to write an interrupt driven blinking LED with 50
> lines of code before I tried to port thousands of lines of something I
> didn't write. Proof of concept...
>
> Also, you might want to review what FreeRTOS expects in terms of stack
> locations and sizes. Ordinarily these are set up in the boot code and the
> processor is left in supervisor mode with the interrupt system enabled (CPSR
> settings).
>
> And, yes, if you are in user mode, there are registers you can't reach.
>
> Richard
>
>
>
Rolf:

From your explanation I understand that the interrupt vector for the
application is of no use if it is located in flash, anywhere else than at
offset 0x0000;

Is it possible to modify where the interrupt vectors are located in flash
(programmatically, not using the loader script), so instead of pointing to
0x0000, it points to 0x10000?

I feel it would be constraining to modify the vector table for the
bootloader (in flash) to jump to the application's location. After bootup,
can you modify where the interrupt vector points, and point to the
application block?
Or is the only way to use a vector table located in RAM?
Why would you recommend the non volatile solution, over the RAM-based
solution?

Thanks,
Jean
On Tue, May 18, 2010 at 10:18 AM, rolf_meeser wrote:

>
> Hi Jean,
> > However the CPU becomes unresponsive once vTaskStartScheduler is
> > called (freeRTOS is called).
>
> Make sure the SWI exception is propagated by the bootloader code to your
> FreeRTOS application. This is required for the scheduler to work.
>
> The address 0x8 in the bootloader should have a jump to address 0x10008.
> Either this, or move your whole vector table into RAM, and use MEMMAP to
> activate it. I'm in favor of the first solution...
>
> Regards,
> Rolf
>
>
>
Hi Jean,

> From your explanation I understand that the interrupt vector for the
> application is of no use if it is located in flash, anywhere else than at
> offset 0x0000;
>
> Is it possible to modify where the interrupt vectors are located in flash
> (programmatically, not using the loader script), so instead of pointing to
> 0x0000, it points to 0x10000?
>

The CPU uses only the table located at address 0. Anything but the IRQ and the reset vector should jump from there to the known location of the vector table in your application, here: 0x10000+.

It's not possible to have the CPU use your application vector table at 0x10000 directly.
> I feel it would be constraining to modify the vector table for the
> bootloader (in flash) to jump to the application's location. After bootup,
> can you modify where the interrupt vector points, and point to the
> application block?

That's not a real constraint, except that you have to accept a longer exception latency since you add one more jump instruction. IRQ's use the instruction in the bootloader, and do not take more time to execute.
> Or is the only way to use a vector table located in RAM?
> Why would you recommend the non volatile solution, over the RAM-based
> solution?

Having the vector table in RAM has the advantage of faster execution time, since you do not need wait states (the flash does need them here). A little bit dangerous is the fact that the vector table in RAM is mapped to the logical address 0 of the CPU. Null-pointers for data can have a nasty effect on your vector table...
This is a limitation mainly for development (where it can be a p.i.t.a), and it's generally safe to use the SRAM vector table in production code.

Regards,
Rolf

--- In l..., Jean-Sebastien Stoezel

> The first few lines setup the stacks for each mode. By the way, I am not
> sure I understand the following lines:
> /* msr CPSR_c, #MODE_SYS|I_BIT|F_BIT */ /* User Mode */
> msr CPSR_c, #MODE_SYS|F_BIT /* User Mode */
> mov sp, r0
>

OK, this leaves the chip in supervisor mode with the I-Bit clear which enables IRQ interrupts. Note that the F-Bit is set which disables FIQ interrupts.

The problems with comments is that they are almost always wrong. Code gets fixed, remarks never do.

In the end, you don't care much what the existing boot loader does as long as you are in supervison mode when the loader branches to your entry point.

You will have another boot.s file that will set up the stacks, enable interrupts (if you want FIQ) and, most importantly, build the interrupt vector table and copy it to RAM. Then you use MEMMAP to remap the vectors such that the CPU uses your new table.

I don't know anything about MEMMAP, I have never used it. But the gist of it is that the interrupt vectors can reside in one of two places: the start of flash or the start of RAM.

Make sure your linker file has a dedicated block of ram for the vectors such that it gets initialized but not reallocated for .data or .bss. There must be manye examples of this around but I don't have any.

Richard

Rolf:

Thank for the reply.

Here's the vector table in the bootloader code:

_vectors: ldr PC, Reset_Addr
ldr PC, Undef_Addr
ldr PC, SWI_Addr
ldr PC, PAbt_Addr
ldr PC, DAbt_Addr
nop /* Reserved Vector (holds Philips ISP checksum) */
//ldr PC, [PC,#-0xFF0] /* see page 71 of "Insiders Guide
to the Philips ARM7-Based Microcontrollers" by Trevor Martin */
ldr PC, IRQ_Addr //Original line from DCarne implementation
ldr PC, FIQ_Addr

Reset_Addr: .word Reset_Handler /* defined in this module below */
Undef_Addr: .word UNDEF_Routine /* defined in main.c */
SWI_Addr: .word SWI_Routine /* defined in main.c */
PAbt_Addr: .word UNDEF_Routine /* defined in main.c */
DAbt_Addr: .word UNDEF_Routine /* defined in main.c */
IRQ_Addr: .word IRQ_Handler /* defined in main.c */
//IRQ_Addr: .word IRQ_Routine /* defined in main.c */
FIQ_Addr: .word FIQ_Routine /* defined in main.c */
.word 0 /* rounds the vectors and ISR addresses to 64
bytes total */

The SWI vector points to the following routine:

void SWI_Routine (void) __attribute__ ((interrupt("SWI")));
void SWI_Routine (void)
{
}

My understanding is that you recommend that I replace a jump to this routine
by a jump to application_offset + 0x08. Is this correct?
It doesn't look like it is used by the bootloader anyway...

Jean
On Tue, May 18, 2010 at 11:04 AM, rolf_meeser wrote:

>
> Hi Jean,
> > From your explanation I understand that the interrupt vector for the
> > application is of no use if it is located in flash, anywhere else than at
> > offset 0x0000;
> >
> > Is it possible to modify where the interrupt vectors are located in flash
> > (programmatically, not using the loader script), so instead of pointing
> to
> > 0x0000, it points to 0x10000?
> > The CPU uses only the table located at address 0. Anything but the IRQ and
> the reset vector should jump from there to the known location of the vector
> table in your application, here: 0x10000+.
>
> It's not possible to have the CPU use your application vector table at
> 0x10000 directly.
> > I feel it would be constraining to modify the vector table for the
> > bootloader (in flash) to jump to the application's location. After
> bootup,
> > can you modify where the interrupt vector points, and point to the
> > application block?
>
> That's not a real constraint, except that you have to accept a longer
> exception latency since you add one more jump instruction. IRQ's use the
> instruction in the bootloader, and do not take more time to execute.
> > Or is the only way to use a vector table located in RAM?
> > Why would you recommend the non volatile solution, over the RAM-based
> > solution?
>
> Having the vector table in RAM has the advantage of faster execution time,
> since you do not need wait states (the flash does need them here). A little
> bit dangerous is the fact that the vector table in RAM is mapped to the
> logical address 0 of the CPU. Null-pointers for data can have a nasty effect
> on your vector table...
> This is a limitation mainly for development (where it can be a p.i.t.a),
> and it's generally safe to use the SRAM vector table in production code.
>
> Regards,
> Rolf
>
>
>
Alright so to relocate the vectors to RAM, it looks like I would have to
modify the user application as follow:

- set MEMMAP to 0x02 (User RAM Mode. Interrupt vectors are re-mapped to
Static RAM.)
- change the loader script so the vector section is located at the beginning
of the RAM block.
- I shouldn't have to modify the mode of the CPU

I will try this right away.
Thanks for your help,
Jean
On Tue, May 18, 2010 at 11:28 AM, rtstofer wrote:

> --- In l... , Jean-Sebastien
> Stoezel
>
> > The first few lines setup the stacks for each mode. By the way, I am not
> > sure I understand the following lines:
> > /* msr CPSR_c, #MODE_SYS|I_BIT|F_BIT */ /* User Mode */
> > msr CPSR_c, #MODE_SYS|F_BIT /* User Mode */
> > mov sp, r0
> > OK, this leaves the chip in supervisor mode with the I-Bit clear which
> enables IRQ interrupts. Note that the F-Bit is set which disables FIQ
> interrupts.
>
> The problems with comments is that they are almost always wrong. Code gets
> fixed, remarks never do.
>
> In the end, you don't care much what the existing boot loader does as long
> as you are in supervison mode when the loader branches to your entry point.
>
> You will have another boot.s file that will set up the stacks, enable
> interrupts (if you want FIQ) and, most importantly, build the interrupt
> vector table and copy it to RAM. Then you use MEMMAP to remap the vectors
> such that the CPU uses your new table.
>
> I don't know anything about MEMMAP, I have never used it. But the gist of
> it is that the interrupt vectors can reside in one of two places: the start
> of flash or the start of RAM.
>
> Make sure your linker file has a dedicated block of ram for the vectors
> such that it gets initialized but not reallocated for .data or .bss. There
> must be manye examples of this around but I don't have any.
>
> Richard
>
>
>
Hi Jean,
--- In l..., Jean-Sebastien Stoezel wrote:
> The SWI vector points to the following routine:
>
> void SWI_Routine (void) __attribute__ ((interrupt("SWI")));
> void SWI_Routine (void)
> {
> }
>
> My understanding is that you recommend that I replace a jump to this routine
> by a jump to application_offset + 0x08. Is this correct?

Yes!

Rolf