VPOS 가 처음 부팅하면서 부터 C 언어의 시작이라고 할 수 있는 VPOS_kernel_main() 함수 전까지의 설명을 담고 있다.
VPOS 는 부트로더와 커널이 합친 구조로 되어있다. 각각 컴파일하여 만들어진 ELF 포맷의 부트로더와 커널을 dd 명령어를 이용해서 binary 파일로 만든다.
부트로더의 역할은 최소한의 하드웨어 초기화와 flash 에 저장되어 있는 VPOS 커널을 메모리 상으로 복사하는 데 있다. 그리고 마지막으로 pc 를 커널이 시작되는 주소로 넘겨준다. 이 후, 메모리에 올라간 커널은 실행이 되면서 부팅을 시작한다.
이 문서에서 주의깊게 봐야할 것은 각각의 메모리 주소와 영역이다.
여기서는 실행되는 순서에 따라서 설명하도록 하겠다.
아래는 부트로더가 실행했을 때의 상황을 그림으로 나타낸 것이다.
hal/bootloader 아래의 vpos_bootloader.S 파일이 부트로더가 되는 파일이다. 확장자에서 알수 있듯이 어셈블리어로 만들어져 있다.
#include "../include/vh_cpu_hal.h" #include "../include/vh_io_hal.h" #include "../include/vh_variant_hal.h" .global vh_VPOS_Bootloader_Startup .text vh_VPOS_Bootloader_Startup: b vh_reset --- 1 b vh_undefined_instruction --- 2 b vh_software_interrupt --- 3 b vh_prefetch_abort --- 4 b vh_data_abort --- 5 b vh_not_used --- 6 b vh_irq --- 7 b vh_fiq --- 8 vh_reset: @ disable watch dog timer mov r1, #vh_WTCON mov r2, #vh_WT_Disable str r2, [r1] @disable_interrupt mrs r0, cpsr orr r0, r0, #vh_Disable_Interrupt msr cpsr, r0 mov r1, #vh_INT_CTL_BASE mov r2, #vh_Interrupt_All_Disable str r2, [r1, #vh_oINTMSK] ldr r2, =0x7ff str r2, [r1, #vh_oINTSUBMSK] @ 1:2:4 mov r1, #vh_CLK_CTL_BASE mov r2, #vh_vCLKDIVN str r2, [r1, #vh_oCLKDIVN] mrc p15, 0, r1, c1, c0, 0 @ read ctrl register orr r1, r1, #0xc0000000 @ Asynchronous mcr p15, 0, r1, c1, c0, 0 @ write ctrl register @ now, CPU clock is 200 Mhz mov r1, #vh_CLK_CTL_BASE ldr r2, vh_mpll_200mhz str r2, [r1, #vh_oMPLLCON] mov r1, #vh_MEM_CTL_BASE adrl r2, vh_mem_cfg_val add r3, r1, #52 1: ldr r4, [r2], #4 str r4, [r1], #4 cmp r1, r3 bne 1b ldr r0,=vh_SDRAM_BASE // 0x30000000 ldr r1,=vh_VPOS_BIN_SIZE // 0x30800000 mov r2,#0x00000000 vh_MemClear: // 커널을 로딩할 메모리 영역(0x30000000 ~ 0x30800000) 에 0x00000000 값을 넣어 Clear 시킴 str r2,[r0],#4 cmp r0,r1 bne vh_MemClear ldr r0,=vh_SDRAM_BASE // 0x30000000 ldr r2,=vh_VPOS_BASE // 0x400 vh_LoaderRamCopy: // 커널을 메모리 0x30000000 번지부터 0x400 만큼 복사 ldr r3,[r2],#4 // 0x400 번지의 내용을 읽어서 r3 에 저장 str r3,[r0],#4 // r3 의 내용을 r0 가 가리키는 0x30000000 에 저장 cmp r0,r1 bne vh_LoaderRamCopy ldr pc, vh_remapped_reset // pc 를 0x30000000 으로 변경 vh_undefined_instruction: // 인터럽트 벡터 테이블 ldr pc, vh_remapped_undefined_instruction vh_software_interrupt: ldr pc, vh_remapped_software_interrupt vh_prefetch_abort: ldr pc, vh_remapped_prefetch_abort vh_data_abort: ldr pc, vh_remapped_data_abort vh_not_used: ldr pc, vh_remapped_not_used vh_irq: ldr pc, vh_remapped_irq vh_fiq: ldr pc, vh_remapped_fiq
가장 먼저 호출되는 1 에서는 하드웨어를 초기화하고, VPOS 커널을 메모리상으로 복사하고 PC 를 메모리의 처음 주소로 설정하는 일을 한다.
나머지 2 ~ 8 은 모두 인터럽트 벡터 테이블을 정의한 것으로, 순차적으로 수행되는 것이 아니고 프로그램을 수행중에 각각의 인터럽트가 발생하면 수행된다.
다음은 부트로더의 링크 스크립트 파일이다.
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") // 출력 파일 포맷 OUTPUT_ARCH(arm) ENTRY(vh_VPOS_Bootloader_Startup) // 처음 호출 하는 함수 SECTIONS { . = 0x00000000; // 가장 먼저 수행되는 번지, 여기서 0x00000000 는 flash 의 가장 처음을 나타냄 . = ALIGN(4); // 4 바이트로 정렬, 처리의 효율성을 위함 .text : { *(.text) } // text 섹션 . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); .got : { *(.got) } . = ALIGN(4); .bss : { *(.bss) } }
위의 주석에서도 보듯이 부트로더에서 가장 마지막으로 수행되는 것이 pc 를 0x30000000 으로 옮기는 것이다. 바로 0x30000000 에는 커널이 있다.
소스파일을 보기전에 링크 스크립트 파일을 먼저 살펴보기로 하자!
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(vh_VPOS_STARTUP) // 가장 먼저 호출되는 함수 SECTIONS { . = 0x30000000; // 처음 실행되는 번지, 부트로더에 의해서 메모리의 가장 처음에 커널이 적재되어 있음 . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); .got : { *(.got) } . = ALIGN(4); .bss : { *(.bss) } }
hal/cpu 아래의 HAL_arch_startup.S 파일이 커널의 가장 먼저 시작되는 파일이다. 물론 여기서 hal_swi_handler.c 파일에서 정의한 함수를 호출한다.
#include "../include/vh_cpu_hal.h" .extern VPOS_kernel_main .extern vk_undef_handler .extern vh_hwi_classifier .extern vk_swi_classifier .extern vk_pabort_handler .extern vk_dabort_handler .extern vk_fiq_handler .extern vk_not_used_handler .global vh_VPOS_STARTUP .global vh_entering_swi .global vh_leaving_swi .global vk_save_swi_mode_stack_ptr .global vk_save_swi_current_tcb_bottom .global vh_entering_interrupt .global vh_leaving_interrupt .global vk_save_irq_mode_stack_ptr .global vk_save_irq_current_tcb_bottom .global vh_restore_thread_ctx .global vh_save_thread_ctx .equ vh_USERMODE, 0x10 .equ vh_FIQMODE, 0x11 .equ vh_IRQMODE, 0x12 .equ vh_SVCMODE, 0x13 .equ vh_ABORTMODE, 0x17 .equ vh_UNDEFMODE, 0x1b .equ vh_MODEMASK, 0x1f .equ vh_NOINT, 0xc0 // 여기서 부터 지정하는 stack 주소는 각 모드별로 기본적으로 지정되는 영역이다. 예를 들면, user 모드이면 기본적으로 sp(r13) 레지스터의 값은 0x30600000 로 지정된다. // 그 이후에, 함수 호출등으로 인한 스택의 사용은 0x305FFFEC 이런식으로 사용된다. .equ vh_userstack, 0x30600000 .equ vh_svcstack, 0x30800000 .equ vh_undefstack, 0x31000000 .equ vh_abortstack, 0x31000000 .equ vh_irqstack, 0x30900000 .equ vh_fiqstack, 0x31100000 .text vh_VPOS_STARTUP: // 첫번째로 수행 b vh_VPOS_reset b vk_undef_handler // kernel/exception_handler.c b vh_software_interrupt // 인터럽트 핸들러 루틴 정의 b vk_pabort_handler // pabort 핸들러 루틴 정의 b vk_dabort_handler // dabort 핸들러 루틴 정의 b vk_not_used_handler // not_used 핸들러 루틴 정의 b vh_irq // irq 핸들러 루틴 정의 b vk_fiq_handler // fiq 핸들러 루틴 정의 vh_VPOS_reset: mrs r0, cpsr orr r0, r0, #0xc0 msr cpsr, r0 mov r0, #0 mcr p15, 0, r0, c7, c7, 0 mcr p15, 0, r0, c8, c7, 0 mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 bic r0, r0, #0x00000087 orr r0, r0, #0x00000002 orr r0, r0, #0x00001000 mcr p15, 0, r0, c1, c0, 0 mov r0,#0x0 mrc p15,0,r0,c1,c0,0 orr r0,r0,#(1<<12) mcr p15,0,r0,c1,c0,0 NOP NOP NOP mrs r0,cpsr bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_UNDEFMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_undefstack bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_ABORTMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_abortstack bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_IRQMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_irqstack bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_FIQMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_fiqstack bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_SVCMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_svcstack bic r0,r0,#vh_MODEMASK|vh_NOINT orr r1,r0,#vh_USERMODE|vh_NOINT msr cpsr_cxsf,r1 ldr sp,=vh_userstack b VPOS_kernel_main // C 함수 호출 (kernel/kernel_start.c) vh_software_interrupt: vh_entering_swi: str sp, =vk_save_swi_mode_stack_ptr stmfd sp!, {r0-r14}^ mrs r0, spsr_all stmfd sp!, {r0, lr} mrs r0, cpsr_all orr r0, r0, #0x80 msr cpsr_all, r0 str sp, =vk_save_swi_current_tcb_bottom ldr r0, [sp, #8] bl vk_swi_classifier vh_leaving_swi: ldmfd sp!, {r0, lr} // msr spsr_all, r0 ldmfd sp!, {r0-r14}^ movs pc, lr vh_irq: vh_entering_interrupt: sub lr, lr, #4 str sp, =vk_save_irq_mode_stack_ptr stmfd sp!, {r0-r14}^ mrs r0, spsr_all stmfd sp!, {r0, lr} str sp, =vk_save_irq_current_tcb_bottom bl vh_hwi_classifier vh_leaving_interrupt: ldmfd sp!, {r0, lr} msr spsr_all, r0 ldmfd sp!, {r0-r14}^ movs pc, lr vh_restore_thread_ctx: mrs r1, cpsr_all bic r1, r1, #0xfffffff0 mov r2, #0x2 cmps r1, r2 mov sp, r0 ldmfd sp!, {r0, lr} msr spsr_all, r0 ldmfd sp!, {r0-r14}^ ldreq sp, =vk_save_irq_mode_stack_ptr ldrne sp, =vk_save_swi_mode_stack_ptr movs pc, lr vh_save_thread_ctx: mrs r1, cpsr_all bic r1, r1, #0xfffffff0 mov r2, #0x2 cmps r1, r2 ldreq r2, =vk_save_irq_current_tcb_bottom ldrne r2, =vk_save_swi_current_tcb_bottom .rept 17 ldr r1, [r2], #4 str r1, [r0], #4 .endr mov pc, lr vk_save_swi_mode_stack_ptr: .long 0 vk_save_swi_current_tcb_bottom: .long 0 vk_save_irq_mode_stack_ptr: .long 0 vk_save_irq_current_tcb_bottom: .long 0
다음은 내가 소스 분석한 것을 스캔한 것들이다. 파일이 너무 커서 한 화면에 출력되지 않는 관계로 작게 사이즈를 조정했다.
원본 파일은 vpos_scan.zip 에서 받을 수 있다.