After implementing naive function and printf, let's add ISR to it.
please checkout the commit: 91abc0d3
$ git checkout 91abc0d3
There are many changes from previous commit:
head.S
it becomes more complicated. As we know, its context starts at 0x8000 address.
start:
ldr pc, reset_target /* 0x00 mode: svc */
ldr pc, undefined_target /* 0x04 mode: ? */
ldr pc, swi_target /* 0x08 mode: svc */
ldr pc, prefetch_target /* 0x0c mode: abort */
ldr pc, abort_target /* 0x10 mode: abort */
ldr pc, unused_target /* 0x14 unused */
ldr pc, irq_target /* 0x18 mode: irq */
ldr pc, fiq_target /* 0x1c mode: fiq */
reset_target: .word reset_entry
undefined_target: .word undefined_entry
swi_target: .word syscall_entry
prefetch_target: .word prefetch_entry
abort_target: .word abort_entry
unused_target: .word unused_entry
irq_target: .word irq_entry
fiq_target: .word fiq_entry
After loading the binary, it will jump to a routine named "reset_entry". Before we trace the reset_entry. The 8 "ldr pc, XXXXXX" instructions are so called Exception Vector Table. (FIQ is a special irq mode, it has a advantage - the implementation can be start at the location, the jump is not necessary. Therefore one jump delay is saved. ) It is used to handle system exceptions. Each has corresponding privileged mode to it. Besides, each mode has dedicated LR and SP registers - this means OS / firmware implementation should take care of stack space arrangement for the mode:
In fact, for reset_entry here, its major work is setting stack for each mode:
reset_entry:
/* set VBAR to 0x8000 */
mov r0, #0x8000
mcr p15, 0, r0, c12, c0, 0
/* (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) */
mov r0,#0xD1
msr cpsr_c,r0
ldr sp, stack_fiq_top
... other 4 modes ...
/* (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) */
mov r0,#0xD3
msr cpsr_c,r0
ldr sp, stack_svc_top
cpsie i
bl bare_metal_start
In addition to stack assignment and jump to bare_metal_start , there are two key points here:
- setup Vector Base Address Register (VBAR) - From ARMv6, the exception vector can be placed other than 0x00000000 and 0xFF000000. This is achieved by setting VBAR, please refer to "3.2.43 c12, Secure or Non-secure Vector Base Address Register" in ARM1176JZF-S TRM.
- enable interrupt - cpsie instruction
And we have to trace isr_entry:
irq_entry:For ISR, it is not surprised to backup and restore all (non-dedicated) registers. The most interesting things are - LR register setting and the instruction to leave IRQ mode. For LR setting it is easy to figure out, the target return address is the 'bl isr_entry' not 'add lr, pc, #4". That's the main reason to save "pc+4" to LR. And for leaving each mode, please refer to "2.12.2 Exception entry and exit summary" of ARM1176JZF-S TRM.
stmfd sp!, {r0-r12, lr}
add lr, pc, #4
bl isr_entry
ldmfd sp!, {r0-r12, lr}
subs pc, lr, #4
isr.c
There 3 functions in the source file: timer_enable, timer_check and isr_enty. Here we use "System Timer" in BCM2835, please refer to Chap 7 and Chap 12 of "BCM2835 Peripheral specification". Besides the IRQ number of System Timer is not listed in the document, please refer to the link of "errata and some additional information" on the page.
The timer_enable will enable System Timer 1 or 3 by index and timer_check is used to clear IRQ state and update next timeout interrupt. Therefore the isr_enty just check status and call timer_check for clear the IRQ.
bare_metal.c
For demonstrate IRQ and main thread's progress, a busy loop with counter is added. The loop will print out a number when specified condition is met. And Timer is enabled before the loop, You can see the timer tick with ISR and the main thread keeps counting.
void bare_metal_start(void)
{
int base = 0;
asm volatile (
"mov %0, sp\n\t" : "=r" (base)
);
printf("\n\n%s:%x: Hello World! %s %s %d\n\n", __func__, base, __DATE__, __TIME__, __LINE__);
printf("enter busy loop\n");
timer_enable(1);
volatile int i = 0;
while(1){
if((i++ & 0x00FFFFFF) == 0)
printf("%d\n", i);
}
}
沒有留言:
張貼留言