Reset vector 는 메모리 0에 위치하고 있다.
Reset Exception handler 에서 가장 먼저 해야할 일은 메모리 맵을 설정해주는 작업이다.
동작 모드 별 할당된 스택 주소를 각 동작 모드 SP 에 설정해야한다.
그 후, C 언어 main() 함수로 진입할 것 이다.
USR,SYS: 0x0010000 ~ 0x002FFFFF (2MB)
SVC: 0x00300000 ~ 0x003FFFFF (1MB)
IRQ: 0x00400000 ~ 0x004FFFFF (1MB)
FIQ: 0x00500000 ~ 0x005FFFFF (1MB)
ABT: 0x00600000 ~ 0x006FFFFF (1MB)
UND: 0x00700000 ~ 0x007FFFFF (1MB)
나머지 태스트 스택은 64MB, 전역 변수 영역은 1MB, 동적할당 영역은 55MB 을 할당할 것 이다.
이러한 메모리 주소를 MemoryMap.h 에 정의한다.
include/MemoryMap.h
1 #define INST_ADDR_START 0
2 #define USRSYS_STACK_START 0x00100000 //User & Sys mode
3 #define SVC_STACK_START 0x00300000 //Supervisor mode
4 #define IRQ_STACK_START 0x00400000 //Interrupt Request mode
5 #define FIQ_STACK_START 0x00500000 //Fast Interrupt Request mode
6 #define ABT_STACK_START 0x00600000 //Abort mode
7 #define UND_STACK_START 0x00700000 //Undefined mode
8 #define TASK_STACK_START 0x00800000 //task stack
9 #define GLOBAL_STACK_START 0x04800000 //glboal stack
10 #define DALLOC_STACK_START 0x04900000 //Dynamically allocated stack
11
12 #define INST_MEM_SIZE (USRSYS_STACK_START - INST_ADDR_START)
13 #define USRSYS_STACK_SIZE (SVC_STACK_START - USRSYS_STACK_START)
14 #define SVC_STACK_SIZE (IRQ_STACK_START - SVC_STACK_START)
15 #define IRQ_STACK_SIZE (FIQ_STACK_START - IRQ_STACK_START)
16 #define FIQ_STACK_SIZE (ABT_STACK_START - FIQ_STACK_START)
17 #define ABT_STACK_SIZE (UND_STACK_START - ABT_STACK_START)
18 #define UND_STACK_SIZE (TASK_STACK_START - UND_STACK_START)
19 #define TASK_STACK_SIZE (GLOBAL_STACK_START - TASK_STACK_START)
20 #define GLOBAL_STACK_SIZE (DALLOC_STACK_START - GLOBAL_STACK_START)
21 #define DALLOC_STACK_SIZE (55 * 1024 * 1024) //55 MB
22
23 #define USRSYS_STACK_TOP (USRSYS_STACK_START + USRSYS_STACK_SIZE - 4)
24 #define SVC_STACK_TOP (SVC_STACK_START + SVC_STACK_SIZE - 4)
25 #define IRQ_STACK_TOP (IRQ_STACK_START + IRQ_STACK_SIZE - 4)
26 #define FIQ_STACK_TOP (FIQ_STACK_START + FIQ_STACK_SIZE - 4)
27 #define ABT_STACK_TOP (ABT_STACK_START + ABT_STACK_SIZE - 4)
28 #define UND_STACK_TOP (UND_STACK_START + UND_STACK_SIZE - 4
또한 cpsr 을 수정하여 동작모드를 변경하기 위한 동작 모드 값을 정의하자.
include/ARMv7AR.h
1 //CPSR; Current Program Status Register "M" bit value
2 #define ARM_MODE_BIT_USR 0x10
3 #define ARM_MODE_BIT_FIQ 0x11
4 #define ARM_MODE_BIT_IRQ 0x12
5 #define ARM_MODE_BIT_SVC 0x13
6 #define ARM_MODE_BIT_ABT 0x17
7 #define ARM_MODE_BIT_UND 0x1B
8 #define ARM_MODE_BIT_USRSYS 0x1F
9 #define ARM_MODE_BIT_MON 0x16
그다음 벡터 테이블을 작성한다.
vector_start 은 각 핸들러의 주소를 Program Counter 에 Write 함.
리셋 핸들러는 cpsr 의 m 비트값을 동작 모드 값으로 변경 후, SP 레지스터에 Top of the Stack 을 Write한다. 이 과정을 모든 동작 모드에 반복시킨다.
BL instruction 은 Branch operation 수행하기전에 R14(LR) 을 BL 바로 다음으로 설정하여 Return addr 을 저장한다. BL 을 통해 C 의 main 함수로 branch 함.
컴파일러는 C 언어 함수 이름을 링커가 자동으로 접근할 수 있는 전역 심벌로 만들기 때문에 이 어셈블리 코드에서 점프 가능한 것이다.
1 #include "MemoryMap.h"
2 #include "ARMv7AR.h"
3
4 .text
5 .code 32
6
7 .global vector_start
8 .global vector_end
9
10 vector_start:
11 LDR PC, reset_handler_addr
12 LDR PC, undef_handler_addr
13 LDR PC, svc_handler_addr
14 LDR PC, pftch_abt_handler_addr
15 LDR PC, data_abt_handler_addr
16 B . @infinite loop
17 LDR PC, irq_handler_addr
18 LDR PC, fiq_handler_addr
19
20 reset_handler_addr: .word reset_handler
21 undef_handler_addr: .word dummy_handler
22 svc_handler_addr: .word dummy_handler
23 pftch_abt_handler_addr: .word dummy_handler
24 data_abt_handler_addr: .word dummy_handler
25 irq_handler_addr: .word dummy_handler
26 fiq_handler_addr: .word dummy_handler
27
28 vector_end:
29
30 reset_handler:
31 @SVC mode
32 MRS r0, cpsr
33 BIC r1, r0, #0x1f
34 ORR r1, r1, #ARM_MODE_BIT_SVC
35 MSR cpsr, r1
36 LDR sp, =SVC_STACK_TOP
.....
73 BL main
74
75 dummy_handler:
76 B .
77
78 .end
79
boot/Main.c
현재는 아무 의미없는 더미 코드이다.
즉, 정상적으로 main 함수로 점프했다면 100MB 메모리 주소에 "4" 라는 것이 작성되어 있어야한다.
1 #include "stdint.h"
2
3 void main(void) {
4 uint32_t *dummyAddr = (uint32_t*)(1024*1024*100);
5 *dummyAddr = sizeof(long);
6 }
다음은 링커 스크립트를 작성해야함.
펌웨어가 동작하는 하드웨어에 맞춰서 펌웨어의 섹션 배치를 세세하게 조정하는 일이 잦다. 그래서 링커 스크립터로 링커의 동작을 제어하여 원하는 형태의 ELF 파일을 생성하도록 함.
navilos.ld
1 ENTRY(vector_start) //First executable instruction in the output
2 SECTIONS //input - output 섹션 매핑 설정
3 {
4 . = 0x0; //first section will be placed in 0x0
5
6 .text : //text 섹션 output 매핑 설정
7 {
8 *(vector_start) //0x0에 리셋 벡터가 위치해야 하므로 vector_start 심벌을 먼저 놔둠.
9 *(.text .rodata)
10 }
11
12 .data :
13 {
14 *(.data)
15 }
16
17 .bss :
18 {
19 *(.bss)
20 }
21 }
빌드 자동화를 위해서 Makefile 을 작성하였음.
1 ARCH = armv7-a
2 MCPU = cortex-a8
3
4 CC = arm-none-eabi-gcc
5 AS = arm-none-eabi-as
6 LD = arm-none-eabi-ld
7 OC = arm-none-eabi-objcopy
8
9 LINKER_SCRIPT = ./navilos.ld
10 MAP_FILE = build/navilos.map
11
12 ASM_SRCS = $(wildcard boot/*.S)
13 ASM_OBJS = $(patsubst boot/%.S, build/%.o, $(ASM_SRCS))
14
15 C_SRCS = $(wildcard boot/*.c)
16 C_OBJS = $(patsubst boot/%.c, build/%.o, $(C_SRCS))
17
18 INC_DIRS = include
19
20 navilos = build/navilos.axf
21 navilos_bin = build/navilos.bin
22
23 .PHONY: all clean run debug gdb
24
25 all: $(navilos)
26
27 clean:
28 @rm -rf build
29
30 run: $(navilos)
31 qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -nographic
32
33 debug: $(navilos)
34 qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4 -nographic
35
36 gdb:
37 arm-none-eabi-gdb
38
39 $(navilos): $(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
40 $(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Map=$(MAP_FILE)
41 $(OC) -O binary $(navilos) $(navilos_bin)
42
43 build/%.o: boot/%.S
44 mkdir -p $(shell dirname $@)
45 $(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
46
47 build/%.o: boot/%.c
48 mkdir -p $(shell dirname $@)
49 $(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
여기까지 Reset vector 을 읽어서 각 동작 별로 Stack 을 할당해주고, C 코드의 main 함수로 넘어오는 과정이다.