compressed/head.S – start:

소스는 아래 기준으로 우선 설명합니다.

  • linux kernel v4.0.5를 기준으로 설명하며 주요한 변경 사항이 있으면 추가합니다.
  • 아키텍처는 ARM 32bit를 가장 우선 시 설명합니다.
  • 가끔 결과를 보여주는데 사용하는 시험 보드는 라즈베리파이2 보드입니다.
  • linux kernel v4.0.5에는 rpi2(라즈베리파이2)가 사용하는 머신 디렉토리가 없어서 rpi2가 별도로 배포하는 코드에서 다음 화일들만을 가져왔습니다.

부트로더가  호출하는 이 루틴은 커널이 지원하는 몇 가지 압축 방법중 하나로 압축된 커널을 풀기위한 루틴으로 진입 전에 아래와 같은 조건을 갖춰야 한다.

  • MMU=off
  • D-cache=off
  • I-cache=상관 없음
  • r0=0
  • r1=머신 번호
  • r2=ATAG 또는 DTB가 놓인 주소

decompress_head.s분석1

 

도입부

 

디버그 매크로

arch/arm/boot/compressed/head.S

.arch   armv7-a

/*
 * Debugging stuff
 *
 * Note that these macros must not contain any code which is not
 * 100% relocatable.  Any attempt to do so will result in a crash.
 * Please select one of the following when turning on debugging.
 */

#ifdef DEBUG
#if defined(CONFIG_DEBUG_ICEDCC)
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
                .macro  loadsp, rb, tmp
                .endm
                .macro  writeb, ch, rb
                mcr     p14, 0, \ch, c0, c5, 0
                .endm
#elif defined(CONFIG_CPU_XSCALE)
                .macro  loadsp, rb, tmp
                .endm
                .macro  writeb, ch, rb
                mcr     p14, 0, \ch, c8, c0, 0
                .endm
#else
                .macro  loadsp, rb, tmp
                .endm
                .macro  writeb, ch, rb
                mcr     p14, 0, \ch, c1, c0, 0
                .endm
#endif
#endif
#endif

  • DEBUG
    • 임베디드 장치가 완전히 테스트되어 양산으로 들어갈 때엔 커널 사이즈와 속도를 위해 DEBUG 옵션을 disable해야 하는 것을 추천한다.
  • CONFIG_DEBUG_ICEDCC
    • ARM 하드웨어 디버거를 사용할 때 설정하여 디버깅을 할 수 있다.
  • CONFIG_CPU_V7
    • rpi2는 ARMv7 아키텍처이다.
  •  CONFIG_DEBUG_LL_INCLUDE
    • Low Level Debugging을 할 수 있는 코드가 담긴 화일을 의미하며, 라즈베리파이의 경우 “debug/pl01x.S” 문자열이 담김.
    • Low Level 디버깅은 대부분 UART 포트를 사용하여 printk등의 본격적인 디버깅 코드를  사용할 수 없는 초기에 사용된다. 물론 커널이 아직 초기화 되지 않아 일반적인 커널 함수를 사용할 수 있는 상태가 되면 printk등의 디버깅 코드를 사용할 수 있다.
    • rpi2에서는 SoC 내부에 uart 인터페이스가 2개가 있고, 하나는 SoC 내부 통신 용도로 사용되어 외부로 사용할 수 없게 하였고, 나머지 하나만 사용자가 사용할 수 있게 하였다.
    • rpi2가 사용하는 addruart, senduart, waituwart, busyuart의 매크로는 다음 화일에 정의되어 있다.
      • arch/arm/include/debug/pl01x.S
  • writeb 매크로
    • uart 포트를 통해 바이트를 출력한다.
  • loadsp 매크로
    • uart 통신을 하기위해 uart 관련 레지스터의 물리 및 가상 BASE 주소를 알아온다.

 

                .macro  kputc,val
                mov     r0, \val
                bl      putc
                .endm

                .macro  kphex,val,len
                mov     r0, \val
                mov     r1, #\len
                bl      phex
                .endm
  • kputc 매크로
    • NULL로 끝나는 문자열을 출력한다.
  • kphex 매크로
    • 16진수 형태로 요구한 길이 만큼 덤프한다
                .macro  debug_reloc_start
#ifdef DEBUG
                kputc   #'\n'
                kphex   r6, 8           /* processor id */
                kputc   #':'
                kphex   r7, 8           /* architecture id */
#ifdef CONFIG_CPU_CP15
                kputc   #':'
                mrc     p15, 0, r0, c1, c0
                kphex   r0, 8           /* control reg */
#endif
                kputc   #'\n'
                kphex   r5, 8           /* decompressed kernel start */
                kputc   #'-'
                kphex   r9, 8           /* decompressed kernel end  */
                kputc   #'>'
                kphex   r4, 8           /* kernel execution address */
                kputc   #'\n'
#endif
                .endm

                .macro  debug_reloc_end
#ifdef DEBUG
                kphex   r5, 8           /* end of kernel */
                kputc   #'\n'
                mov     r0, r4
                bl      memdump         /* dump 256 bytes at start of kernel */
#endif
                .endm
  • debug_reloc_start 매크로
    • 각종 정보를 출력한다.
  • debug_reloc_end 매크로
    • 커널의 끝 주소를 출력하고 커널 시작 부분 256 바이트의 메모리를 덤프한다.

start:

               .section ".start", #alloc, #execinstr
/*
 * sort out different calling conventions
 */

                .align
                .arm                            @ Always enter in ARM state

start:
                .type   start,#function
                .rept   7
                mov     r0, r0
                .endr

   ARM(         mov     r0, r0          )
   ARM(         b       1f              )
 THUMB(         adr     r12, BSYM(1f)   )
 THUMB(         bx      r12             )

                .word   _magic_sig      @ Magic numbers to help the loader
                .word   _magic_start    @ absolute load/run zImage address
                .word   _magic_end      @ zImage end address
                .word   0x04030201      @ endianness flag

 THUMB(         .thumb                  )
1:

 ARM_BE8(       setend  be )                    @ go BE8 if compiled for BE8
                mrs     r9, cpsr
  • .section
    • .start 섹션을 선언하고 이 안에 메모리를 할당할 수 있고 실행가능코드라 지정
  • .align
    • 디폴트로 해당 아키텍처에 최적화된 바이트 단위로 코드와 데이터를 정렬하게 한다.
    • ARM 32bit CPU들은 디폴트로 모두 4바이트이다.
    • 코드와 데이터를 4바이트 단위로 정렬하여야 메모리로 부터 데이터를 읽을 때 별도의 추가 코드 없이 또는 instruction clock cycle이 가장 빠른 코드를 사용하게 할 수 있다
  • .arm
    • ARM 모드로 해석하라고 지시한다.
  • start:
    • 여기서부터 시작 코드가 담긴다.
  • .type
  • .rept 7
    • ARM 아키텍처 메인 테이너인 Russell King이 어느 오래된 부트로더 하나가 0x20번째 주소로 점프를 하는 관계로 커널이 호환성을 갖기위해 아무일도 하지않는 코드를 사용할 수 밖에 없었다고 한다.
    • 참고: http://comments.gmane.org/gmane.linux.ports.arm.kernel/26690
  • ARM() 매크로와 THUMB() 매크로
    • CONFIG_THUMB2_KERNEL 옵션에 따라 둘 중 하나로만 사용될 수 있다.
    • ARM코드로 만들어진 부트로더가 동작중인 경우 커널도 ARM 코드로 시작하는 것이 올바르기 때문에 커널 빌드를 ARM 코드로 빌드해야 함. 만일 특수하게 만든 부트로더가 THUMB2코드로 동작하고 커널로 넘어오게 된 경우를 고려하여 이 때는 커널 빌드 시 THUMB2 모드를 사용할 수 있게 해야 한다.
    • THUMB 코드는 2바이트이지만 THUMB2 코드는 4바이트이다.
  • b 1f
    • branch 1 forward라는 의미로 전진방향으로 1이라는 레이블을 찾아 이동하라는 의미이다.
  • BSYM 매크로
    • THUMB2 코드로 진행되는 경우 레이블 1: + 1을 가리킨다.
    • bx 명령어는 레이블 주소의 하위 비트(LSB)가 1이면 THUMB 모드로 전환하고 0이면 ARM 모드로 전환한다.
  • _magic_sig
    • zImage의 매직 넘버를 가지고 있다. (0x016f_2818)
  • _magic_start
    • zImge가 로드되어 동작하는 시작 주소가 담긴다.
    • rpi2: 0x0000_0000
  • _magic_end
    • zImage의 마지막 주소
    • rpi2: 0x003c_ca28 – 당연히 이미지의 크기는 커널 설정 조건에 따라 다르다.
  • 0x04030201
    • 커널의 만들어졌을 때의 엔디안 값으로 _magic_start+0x30 값부터 시작하는 값에 따라 구분된다.
      • 0x04 03 02 01: 빅엔디안 커널
      • 0x01 02 03 04: 리틀엔디안 커널
    • rp2: 리틀엔디안 커널로 만들어졌다.
  • ARM_BE8() 매크로
    • ARMv6 및 ARMv7 아키텍처는 BIG ENDIAN도 지원한다.
    • 커널 설정 중 CONFIG_CPU_ENDIAN_BE8=y일 ARM_BE8() 매크로가 동작한다.
    • rpi2: CONFIG_CPU_ENDIAN_BE8가 설정되지 않아 매크로가 동작하지 않는다.
  • setend be
    • CPU를 빅엔디안으로 설정
    • 때  설정이 ARM_BE8은 빅엔디안을 지원하는 경우 BIG ENDIAN 모드로 동작하게 함
    • rpi2: 리틀엔디안으로 동작한다.
  • mrs r9, cpsr
    • cpsr 상태 레지스터 값을 r9에 저장한다.

 

#ifdef CONFIG_ARM_VIRT_EXT
                bl      __hyp_stub_install      @ get into SVC mode, reversibly
#endif
                mov     r7, r1                  @ save architecture ID
                mov     r8, r2                  @ save atags pointer

                /*
                 * Booting from Angel - need to enter SVC mode and disable
                 * FIQs/IRQs (numeric definitions from angel arm.h source).
                 * We only do this if we were in user mode on entry.
                 */

                mrs     r2, cpsr                @ get current mode 
                tst     r2, #3                  @ not user?
                bne     not_angel
                mov     r0, #0x17               @ angel_SWIreason_EnterSVC
 ARM(           swi     0x123456        )       @ angel_SWI_ARM
 THUMB(         svc     0xab            )       @ angel_SWI_THUMB

not_angel:
                safe_svcmode_maskall r0
                msr     spsr_cxsf, r9           @ Save the CPU boot mode in
                                                @ SPSR      
                /* 
                 * Note that some cache flushing and other stuff may
                 * be needed here - is there an Angel SWI call for this?
                 */

                /*
                 * some architecture specific code can be inserted
                 * by the linker here, but it should preserve r7, r8, and r9.
                 */
  • CONFIG_ARM_VIRT_EXT
    • 가상화 기술로 ARMv7 아키텍처가 지원함.
  • cpsr 상태값 비교
    • cpsr 레지스터를 읽어와서 bit0과 bit1이 모두 0인 경우는 usr 모드이고 tst 명령으로 플래그를 판단하여 usr 모드가 아닌경우 not_angel 레이블로 이동
    • 디버거 용도로 사용하는 엔젤 부트로더는 svc 모드로 동작한다.
  • 엔젤디버거 인경우
    • r0에 0x17을 갖고 소프트웨어 인터럽트 0x123456를 호출하면 엔젤 부트로더에 있는 루틴에서 Svc모드로 바꿀거라 예상됨. THUMB2 코드가 지원되는 경우 swi 대신 THUMB2용 소프트웨어 인터럽트를 호출한다.
  • safe_svcmode_maskall 매크로
    • CPU 모드가 svc 모드가 아닌경우 svc 모드로 진입한다.
    • 모든 인터럽트가 작동하지 않도록 마스크를 설정한다.
  • mrs r9, cpsr
    • r9 레지스터에 백업해두었던 cpsr 상태 값을 복원한다.

safe_svc_maskall 매크로

/*
 * Helper macro to enter SVC mode cleanly and mask interrupts. reg is
 * a scratch register for the macro to overwrite.
 *
 * This macro is intended for forcing the CPU into SVC mode at boot time.
 * you cannot return to the original mode.
 */
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
        mrs     \reg , cpsr
        eor     \reg, \reg, #HYP_MODE
        tst     \reg, #MODE_MASK
        bic     \reg , \reg , #MODE_MASK
        orr     \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB(  orr     \reg , \reg , #PSR_T_BIT        )
        bne     1f

        orr     \reg, \reg, #PSR_A_BIT
        adr     lr, BSYM(2f)
        msr     spsr_cxsf, \reg
        __MSR_ELR_HYP(14)
        __ERET

1:      msr     cpsr_c, \reg
2:
#else
/*
 * workaround for possibly broken pre-v6 hardware
 * (akita, Sharp Zaurus C-1000, PXA270-based)
 */
        setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
#endif
.endm

CPU 모드가 하이퍼 모드인 상태로 커널에 진입하게된 경우 Svc 모드로 바꾼다. 모드 전환이 약간 복잡하여 매크로로 만들어졌다.

  • tst \reg, \reg, #HYP_MODE
    • cpsr 값을 읽어 모드 값을 확인하는데 하이퍼 모드인지 확인한다.
    • HYP_MODE: 0x1a
    • MODE_MASK: 0x1f
    • cpsr 레지스터 값
      • cpsr_f: bit31~24 (flag fields)
      • cpsr_s: bit23~16 (status fields)
      • cpsr_x: bit15~8 (extension fields)
      •  …….       bit8:Abort bit(1)
      • cpsr_c: bit7~0 (control fields)
        • bit7=IRQ(1), bit6=FIQ(1), bit5=T(1), bit4~0=MODE(5)
  • 서비스 모드로 진입하기 전에 IRQ, FIQ를 마스크한다.
  • bne 1f
    • 하이퍼 모드가 아니므로 레이블 1:로 진행한다.
  • 현재 HYP_MODE로 진입이된 경우 SVC모드로 진입을 하기 위해 Abort bit를 set 하여 Abort 기능을 disable 하고 lr 레지스터에 레이블 2위치를 기억하여 서브루틴으로 부터 돌아올 장소를 기억한 후 spsr_cxsf 레지스터를 갱신한다.
  • __MSR_ELR_HYP(14)
    • 하이퍼모드-모니터모드에서 사용하는 ELR 레지스터에 lr(14)값을 옮김,
  • __ERET
    • 하이퍼모드에서 리턴 –> 모드를 바꾸면서 점프함.
    • spsr에 저장된 Svc 모드로 진입하게 됨.
  • setmode
    • 하이퍼 모드를 지원하지 않는 ARM 아키텍처에서는 단순히 cpsr만 변경하여 Svc 모드로 들어가게 하였다.
    • setmode 매크로는 ARMv7이 ARM 모드에서 동작하는 경우 아래의 코드와 같다.
      • msr cpsr_c, #\mode
    • PSR_F_BIT: 0x0000_0040
    • PSR_I_BIT: 0x0000_0080
    • SVC_MODE: 0x0000_0000
#define __MSR_ELR_HYP(regnum)   __inst_arm_thumb32(                     \
        0xE12EF300 | regnum,                                            \
        0xF3808E30 | (regnum << 16)                                     \
)
#define __ERET  __inst_arm_thumb32(                                     \
        0xE160006E,                                                     \
        0xF3DE8F00                                                      \
)
#if defined(CONFIG_CPU_V7M)
        /*
         * setmode is used to assert to be in svc mode during boot. For v7-M
         * this is done in __v7m_setup, so setmode can be empty here.
         */
        .macro  setmode, mode, reg
        .endm
#elif defined(CONFIG_THUMB2_KERNEL)
        .macro  setmode, mode, reg
        mov     \reg, #\mode
        msr     cpsr_c, \reg
        .endm
#else
        .macro  setmode, mode, reg
        msr     cpsr_c, #\mode
        .endm
#endif

 

참고

댓글 남기기