Table of Contents

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 에서 받을 수 있다.

Bootloader

Startup