이것저것/Windows CE

Windows Embedded CE 6.0 에뮬레이터 startup 코드 분석 #1

우담바라 2007. 4. 24. 20:06

Windows Embedded CE 6.0 에뮬레이터

startup 코드 분석 #1

임베디드 2007/04/10 17:47

출처 :  훌륭한녀석의 끄적거림


Windows Embedded CE 6.0의 BSP중에 SMDK2410 based로 만들어진 Device Emulator가 있다. 이 녀석의 startup 코드를 분석해서 시스템을 어떤 지경으로 만들어 놓는지 한번 봐야 쓰것다. 사실 많이 늦은(S3C2410이 언제적 프로세서냐!) 감이 없지 않아 있지만 한번즈음은 분석 해 보는것도 나쁘지 않을듯 해서 분석 한번 해본다. 이 내용은 나~중에 메모리쪽이나 파워 버튼에 관련된 부분에서도 필요한 부분이므로 모르는 분들이 있다면 한번 즈음은 살펴보는것도 좋겠다.

코드는 trace 형태로 따라가 볼 생각이다.

;-------------------------------------------------------------------------------
;   Function: Startup
;
;   Main entry point for CPU initialization.
;

        STARTUPTEXT
        LEAF_ENTRY      StartUp
   
        ; Jump over power-off code.
        b       ResetHandler

        ; Power-off the CPU.
        str     r1, [r0]                        ; enable SDRAM self-refresh.
        str     r3, [r2]                        ; MISCCR setting.
        str     r5, [r4]                        ; POWER OFF!!!!!
        b       .


eboot의 sources 파일을 보면 StartUp 이라는 심볼을 엔트리 포인트로 만들어 두었고 그 심볼이 여기에 있다. 시작하자마자 바로 ResetHandler로 branch한다.


ResetHandler

        ; Make sure that TLB & cache are consistent
        mov     r0, #0
        mcr     p15, 0, r0, c8, c7, 0           ; flush both TLB
        mcr     p15, 0, r0, c7, c5, 0           ; invalidate instruction cache
        mcr     p15, 0, r0, c7, c6, 0           ; invalidate data cache

첫번째 부터 어렵다면 어려운 MCR 명령이 나타났다. 첫번째는 주석에도 나와있듯이 TLB를 Flush하는 구문이다. 여기에 관련된 부분은 ARM Architecture Reference Manual B3-26(MMU) 부분을 참고하길 바란다. 마찬가지로 Invalidate entire instruction cache, Invalidate entire date cache 부분도 B5 부분을 참고하길 바란다. 무엇보다도 가장 먼저 행해지는것이 TLB flush와 cache invalidate이다. 다음으로 계속 진행
       

        ldr     r0, = GPFCON
        ldr     r1, = 0x55aa     
        str     r1, [r0]

GPFCON은 GPIO의 세팅을 위한 Control Register의 주소로 GPIO의 동작 방식을 정하는 부분이다. 해당 내용은 Reference Manual을 살펴봐야 한다. SMDK2410의 GPIO에는 GPF7~4까지가 LED로 세팅(따라서 Output으로 세팅) GPF0~GPF3까지는 EINT로 세팅되도록 만들어져 있다. 그걸 따라간거다.

        ldr     r0, = WTCON                     ; disable watch dog
        ldr     r1, = 0x0        
        str     r1, [r0]

주석에 나와 있듯이 워치독 타이머 Disable시키고

        ldr     r0, = INTMSK
        ldr     r1, = 0xffffffff                ; disable all interrupts
        str     r1, [r0]

        ldr     r0, = INTSUBMSK
        ldr     r1, = 0x7ff                     ; disable all sub interrupt
        str     r1, [r0]

        ldr     r0, = INTMOD
        mov     r1, #0x0                        ; set all interrupt as IRQ
        str     r1, [r0]

인터럽트를 Disable시킨다.

        ldr     r0, = CLKDIVN
        ldr     r1, = 0x3                       ; 0x0 = 1:1:1,  0x1 = 1:1:2
                                                ; 0x2 = 1:2:2,  0x3 = 1:2:4,
                                                ; 0x8 = 1:4:4
        str     r1, [r0]

        ands    r1, r1, #0x2                    ; set AsyncBusMode
        beq     %F10

        mrc     p15, 0, r0, c1, c0, 0
        orr     r0, r0, #R1_nF:OR:R1_iA
        mcr     p15, 0, r0, c1, c0, 0

위의 구문은 먼저 CLKDIVN값을 세팅하고(FCLK:HCLK:PCLK) AsyncBusMode냐 아니냐에 따라서 아래의 Label 10으로 브랜치 할 것인지 말 것인지를 결정한다. 여기서 ands는 Rn과 Shift_Operand와의 and값에 따라서 condition flag중 Z값이 결정되어 진다. Z는 and의 결과값(Rd)이 0이면 1로 세팅되고 나머지의 값을 가지면 0으로 클리어된다. 바로 이어지는 beq %F10은 Z값의 여부에 따라서 브랜치 할 것인지 말 것인지를 결정하는데, Z가 1로 세팅되어 있으면 branch한다. 따라서 위의 결과값은 브랜치 하지 않고 바로 아래의 구문을 실행하는 상태가 된다. AsyncBusMode에 대해서는 본인도 아는바가 없어서 skip -_- (FixMe)

10
        ldr     r0, = LOCKTIME                  ; To reduce PLL lock time, adjust the LOCKTIME register.
        ldr     r1, = 0xffffff
        str     r1, [r0]
   
        ldr     r0, = MPLLCON                   ; Configure MPLL
                                                ; Fin=12MHz, Fout=50MHz
        ldr     r1, = PLLVAL
        str     r1, [r0]

        ldr     r0, = UPLLCON                   ; Fin=12MHz, Fout=48MHz
        ldr     r1, = ((0x48 << 12) + (0x3 << 4) + 0x2) 
        str     r1, [r0]

        mov     r0, #0x2000
20  
        subs    r0, r0, #1
        bne     %B20

locktime값을 세팅하고 pll값들을 세팅한다. 여기서 PLLVAL은 (((0xa1 << 12) + (0x3 << 4) + 0x1))로 세팅되어 있어 결국 FCLK는 202.80MHz, HCLK는 101.40MHz, PCLK는 50.7MHz가 되겠다. 그리고 일정 시간 동안은 쉬는 타이밍으로..

;------------------------------------------------------------------------------
;   Add for Power Management

        ldr     r1, =GSTATUS2                   ; Determine Booting Mode
        ldr     r10, [r1]
        tst     r10, #0x2
        beq     %F30                            ; if not wakeup from PowerOffmode
                                                ;    skip MISCCR setting

        ldr     r1, =MISCCR                     ; MISCCR's Bit 17, 18, 19 -> 0
        ldr     r0, [r1]                        ; I don't know why, Just fallow Sample Code.
        bic     r0, r0, #(7 << 17)              ; SCLK0:0->SCLK, SCLK1:0->SCLK, SCKE:L->H
        str     r0, [r1]
30

다음은 Power Management 부분으로 리셋된 이유가 뭐냐에 따라서 다음의 행동이 결정된다. 먼저 reset시에 GSTATUS2 control register의 1번비트의 세팅 유무를 살펴보는데 1번 비트는 Power_OFF reset일 경우에 세팅이 된다. tst 명령어는 Rn과 Shift_Operand를 AND 연산해서 0이 나오면 Z Flag가 1으로 세팅되고 0이 아닌 값을 가지게 되면 Z flag가 0으로 세팅(클리어)된다. 바로 다음 beq는 Z값이 1일 때 branch 이므로 power off reset이 0일 경우에 branch하게 된다. 여기서 Power Off Reset이라는것이 나오는데, 이 값은 나중에 한번 설명할 기회를 가지도록 하자. 일단은 Power버튼을 눌러서(EINT0) 파워를 OFF하게 되면 시스템이 나중에 다시 뜰 수 있도록(wakeup 할 수 있도록) 세팅을 하게 되고 RTC와 Interrupt controller등 몇몇가지 만이 동작 할 수 있는 상태로 만들고 (EINT0~2)의 인터럽트가 발생하게 되면 시스템은 wakeup된다. 이렇게 wakeup될 때 아래의 구문으로 흘러내려가게 된다. 그 외의 경우는 label 30으로 브랜치 한다. 아래 구문을 살펴보면 MISCCR의 17, 18, 19번 비트를 0으로 클리어하는 부분이 보인다. 이것은 suspend(Power_OFF Mode) 될 때 SDRAM의 SCLK0, SCLK1, SCKE를 disable시켜서 sdram의 내용을 보호하던 상태에서 깨어나게 하는 구문이다. 그리고 난 다음 label 30으로 진입한다.

;------------------------------------------------------------------------------
;   Initialize memory controller


        add     r0, pc, #MEMCTRLTAB - (. + 8)
        ldr     r1, = BWSCON                   ; BWSCON Address
        add     r2, r0, #52                     ; End address of MEMCTRLTAB
40      ldr     r3, [r0], #4   
        str     r3, [r1], #4   
        cmp     r2, r0     
        bne     %B40

메모리 컨트롤러를 초기화하는 구문이다. 메모리 컨트롤 테이블(MEMCTRLTAB)이 위치해 있는 곳의 주소를 가져오는 것인데 R0=PC+&(MEMCTRLTAB) - (current_instruction_address + 8) 와 같이 된다. 결국 R0=&(MEMCTRLTAB)이 되는데 이는 PC값은 현재 수행되고 있는 instruction의 +8한 값을 가지고 있는 구조에 따라서 위와 같이 계산이 된다. 뭐 결국 R0에는 MEMCTRLTAB의 주소가 들어가게 되고 그 값들을 하나씩 읽어와서 BWSCON부터 차곡차곡 값을 써넣게 되는 형태다. 어려울건 없다. 그래서 마지막 테이블 엔트리(총 13개*4byte = 52byte)까지 넣었다면 아래로 흘러들어간다.

;------------------------------------------------------------------------------
;   Add for Power Management

        tst     r10, #0x2
        beq     BringUpWinCE                    ; Normal Mode Booting

R10에 있는 내용은 GSTATUS2 control register로 이전에 비교한 적이 있다. 마찬가지의 AND 연산 후에 0이 아닌 결과값일 경우 Z flag는 0으로 세팅되고 beq에서 BringUpWinCE로 브랜치하지 않는다. 아닐 경우 브랜치 한다. 해깔리면 다시 읽어보고.. 결국 wakeup일 경우에는 아래로 진행을 계속하고 초기 powerup이나 watchdog에 의한 재부팅일 경우 BringUpWinCE로 브랜치를 한다. 아래를 계속 살펴보자. wakeup일 경우에 수행되는 부분이다.

;------------------------------------------------------------------------------
;   Recover Process : Starting Point
;
;   1. Checksum Calculation saved Data

        ldr     r5, =SLEEPDATA_BASE_PHYSICAL    ; pointer to physical address of reserved Sleep mode info data structure
        mov     r3, r5                          ; pointer for checksum calculation
        mov     r2, #0
        ldr     r0, =SLEEPDATA_SIZE             ; get size of data structure to do checksum on
50      ldr     r1, [r3], #4                    ; pointer to SLEEPDATA
        and     r1, r1, #0x1
        mov     r1, r1, LSL #31
        orr     r1, r1, r1, LSR #1
        add     r2, r2, r1
        subs    r0, r0, #1                      ; dec the count
        bne     %b50                            ; loop till done   

        ldr     r0,=GSTATUS3
        ldr     r3, [r0]                        ; get the Sleep data checksum from the Power Manager Scratch pad register
        teq     r2, r3                          ; compare to what we saved before going to sleep
        bne     BringUpWinCE                    ; bad news - do a cold boot

;   2. MMU Enable

        ldr     r10, [r5, #SleepState_MMUDOMAIN] ; load the MMU domain access info
        ldr     r9,  [r5, #SleepState_MMUTTB]    ; load the MMU TTB info
        ldr     r8,  [r5, #SleepState_MMUCTL]    ; load the MMU control info
        ldr     r7,  [r5, #SleepState_WakeAddr ] ; load the LR address
        nop        
        nop
        nop
        nop
        nop

; if software reset

        mov     r1, #0
        teq     r1, r7
        bne     %f60
        bl      BringUpWinCE

; wakeup routine
60      mcr     p15, 0, r10, c3, c0, 0          ; setup access to domain 0
        mcr     p15, 0, r9,  c2, c0, 0          ; PT address
        mcr     p15, 0, r0,  c8, c7, 0          ; flush I+D TLBs
        mcr     p15, 0, r8,  c1, c0, 0          ; restore MMU control

;   3. Jump to Kernel Image's fw.s (Awake_address)

        mov     pc, r7                          ;  jump to new VA (back up Power management stack)
        nop

분석 하는 코드중 가장 긴 코드다. 처음부터 차근차근 살펴보면 가장 먼저 나오는 코드가 checksum을 계산하는 코드로 SLEEPDATA_BASE_PHYSICAL이라는 주소(0x30020800)에서 저장된 데이터를 가져와서 책섬을 계산하고 그 결과값이랑 scratch register(inform register라고 되어있다)인 GSTATUS3과 비교를 해서 올바른 책섬이면 계속 진행 아닐 경우에는 cold 부트의 단계를 거치게 된다. 먼저 책섬을 계산하는 구문을 C-like 코드로 바꾸어 보면 다음과 같이 변형이 된다.

R5 = SLEEPDATA_BASE_PHYSICAL = 0x30020800;
R3 = R5;
R2 = 0;

R0 = SLEEPDATA_SIZE = 26; //(세어봤다;;;)
do {
    R1 = *(R3++);
    R1 = R1 & 0x1;
    R1 = R1 << 31;
    R1 = R1 | R1 >> 1;
    R2 = R2 + R1;
    R0 = R0 - 1;
} while(R0);

R0 = GSTATUS3;
R3 = *(R0);
if (R3 != R2) goto BringUpWinCE;

이해가 쏙쏙 되지 않는가? 다음으로 MMU 세팅하는 부분이다.

R5에는 이미 SLEEPDATA_BASE_PHYSICAL(0x30020800) 주소가 들어 있고 이 주소를 베이스로 SleepState_MMUDOMAIN, SleepState_MMUTTB, SleepState_MMUCTL, SleepState_WakeAddr 등의 offset에서 값을 가져온다. 각각은 R10, R9, R8, R7에 저장해 둔다. 이들은 모두 CPUPowerOff(asm으로되어 있음 : cpupoweroff.s) 에서 저장되는 내용이다.

그 다음은 가져온 SleepState_WakeAddr이 0인지를 확인하고 아니라면 wakeup routine으로 브랜치 0일 경우 cold 부트의 프로세스로 진입한다. wakeup routine에서는 가져온 SleepState_MMUDOMAIN, SleepState_MMUTTB, SleepState_MMUCTL 값을 CP15에 넣고 SleepState_WakeAddr로 점프 하면서 이 스타트업 코드는 마무리가 되게 된다. SleepState_WakeAddr은 위에 잠깐 설명되었듯이 cpupoweroff.s 파일에 들어 있으며 Awake_address 라는 레이블로 점프하게 된다. 추후에 설명이 되겠지만 이 레이블에서는 프로세서의 레지스터들을 모두 복구하고 이전에 호출되었던 곳(lr)으로 점프하여 wake 과정을 거치게 된다.




이젠 GSTATUS2의 Power_OFF reset이 세팅되어 있지 않을 경우를 살펴보자. Power_OFF reset이 세팅되어 있지 않은 경우는 cold reset으로 시스템이 완전한 poweroff에서 시작되는것을 의미한다. BringUpWinCE 레이블로 브랜치 해서 들어가는 구문부터 살펴보자.

BringUpWinCE
   
        ldr     r0, = GPFDAT
        mov     r1, #0x60
        str     r1, [r0]

이 구문은 LED를 세팅하는 부분. 별 내용 없다.

;------------------------------------------------------------------------------
;   Copy boot loader to memory

        ands    r9, pc, #0xFF000000     ; see if we are in flash or in ram
        bne     %f20                    ; go ahead if we are already in ram

        ; This is the loop that perform copying.
        ldr     r0, = 0x21000           ; offset into the RAM
        add     r0, r0, #PHYBASE        ; add physical base
        mov     r1, r0                  ; (r1) copy destination
        ldr     r2, =0x0                ; (r2) flash started at physical address 0
        ldr     r3, =0x10000            ; counter (0x40000/4)
10      ldr     r4, [r2], #4
        str     r4, [r1], #4
        subs    r3, r3, #1
        bne     %b10

        ; Restart from the RAM position after copying.
        mov pc, r0
        nop
        nop
        nop

이 부분은 PC값을 살펴보고 현재 instruction이 Flash 메모리에서 가져온 것인지 RAM에서 가져온 것인지를 판단한다. RAM에서 수행이 된다면 Z flag는 0이 되고 bne에서 20 레이블로 점프를 하고, Flash에서 수행중이라면 Z flag는 1이 되고 bne에서 그대로 내려온다.
내려오면 바로 코드를 복사하는 구문이 나오는데, 플래쉬 메모리에서 특정 바이트 만큼 RAM으로 복사를 하고 RAM 영역의 현재 위치로 PC값을 옮기는 구문이다. 이것도 마찬가지로 C-like 코드로 바꾸어보면 아래와 같다.

R0 = 0x21000;
R0 = R0 + (PHYBASE = 0x30000000);
R1 = R0; // 0x30021000
R2 = 0x0;
R3 = 0x10000;
do {
    R4 = *(R2++);
    *(R1++) = R4;
} while(R3--);
PC = R0; //0x30021000



따라서 이 구문으로 진입을 하게 되면 다시 startup 코드를 RAM에서 동작하는 형식으로 진행되게 된다. 램에서 진행되는 코드라면 bne %f20에 의해서 레이블 20으로 점프한다.

다음에 계속...