kernel/head.S – stext:

리눅스 커널 설정 시작

kernel/head.S 시작:

#if defined(CONFIG_DEBUG_LL) && !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif

/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
#define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

#ifdef CONFIG_ARM_LPAE
        /* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE     0x5000
#define PMD_ORDER       3
#else
#define PG_DIR_SIZE     0x4000
#define PMD_ORDER       2
#endif

        .globl  swapper_pg_dir
        .equ    swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

        .macro  pgtbl, rd, phys
        add     \rd, \phys, #TEXT_OFFSET
        sub     \rd, \rd, #PG_DIR_SIZE
        .endm
  • KERNEL_RAM_VADDR
    • 커널이 위치할 RAM 가상 주소는 PAGE_OFFSET + TEXT_OFFSET을 더한 값이다.
    • 이 주소 값의 뒤 16비트가 0x8000이 아닌경우 즉, 32K align이 안되어 있는 경우 컴파일 시 경고를 만들어낸다.
    • rpi2: (0x8000_0000 + 0x0000_0800)
  • PG_DIR_SIZE
    • 커널 설정용 1차 페이지 디렉토리의 사이즈
    • rpi2: LPAE 옵션을 사용하지 않았으므로 0x4000 (16KB)
  • PMD_ORDER
    • 페이지 변환 단계로 ARM 32비트 아키텍처는 LPAE를 사용하면 3단계 변환을 사용하고 그렇지 않으면 2단계 변환을 사용한다.
    • rpi2: LPAE 옵션을 사용하지 않았으므로 페이지 변환에 2단계 변환 사용
  • swapper_pg_dir
    • 커널이 시작되는 가상 주소값에서 페이지 디렉토리(pgd) 사이즈를 뺀 값
    • rpi2: 0x8000_4000
  • pgtbl 매크로
    • 물리메모리 주소로 페이지테이블의 위치를 계산.
    • 예) pgtbl r4, r8    (r8=0x0000_0000)
      • add r4, r8, #0x8000
      • sub r4, r4, #0x4000
      • r4에 결과는 0x0000_4000

stext:

/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * This is normally called from the decompressor code.  The requirements
 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags or dtb pointer.
 *
 * This code is mostly position independent, so if you link the kernel at
 * 0xc0008000, you call this at __pa(0xc0008000).
 *
 * See linux/arch/arm/tools/mach-types for the complete list of machine
 * numbers for r1.
 *
 * We're trying to keep crap to a minimum; DO NOT add any machine specific
 * crap here - that's what the boot loader (or in extreme, well justified
 * circumstances, zImage) is for.
 */
        .arm

        __HEAD
ENTRY(stext)
 ARM_BE8(setend be )                    @ ensure we are in BE8 mode

 THUMB( adr     r9, BSYM(1f)    )       @ Kernel is always entered in ARM.
 THUMB( bx      r9              )       @ If this is a Thumb-2 kernel,
 THUMB( .thumb                  )       @ switch to Thumb now.
 THUMB(1:                       )
  • __HEAD
    • 코드가 아래 섹션에 들어가도록 컴파일러에게 지시한다.
    • #define __HEAD          .section        “.head.text”,”ax”
  • ENTRY(stext)는 다음과 같은 코드를 만들어낸다.
    • .globl stext ;
    • .align 0 ;
    • stext:
  • ARM_BE8(setend be)
    • ARM_BE8(): ARM 아키텍처가 빅엔디안이 지원되는 경우  CONFIG_CPU_ENDIAN_BE8 커널 설정 옵션을 사용하여 빌드하는 경우 ARM_BE8() 매크로에 들어가는 명령을 실행 시킬 수 있다.
    • setend be: 빅엔디안으로 CPU가 동작. (cpsr BE 비트를 1로 설정)
    • rpi2: 리틀엔디안(ARM_BE8 매크로 동작하지 않음)으로 동작
#ifdef CONFIG_ARM_VIRT_EXT
        bl      __hyp_stub_install
#endif
        @ ensure svc mode and all interrupts masked
        safe_svcmode_maskall r9
  • __hyp_stub_install
    • 하이퍼 바이저용 stub 설치
  • safe_svcmode_maskall r9
    • CPU 모드가 svc 모드가 아닌경우 svc 모드로 진입한다.
    • 모든 인터럽트가 작동하지 않도록 마스크를 설정한다.
    • 참고: start: | 문c
        mrc     p15, 0, r9, c0, c0              @ get processor id
        bl      __lookup_processor_type         @ r5=procinfo r9=cpuid
        movs    r10, r5                         @ invalid processor (r5=0)?
 THUMB( it      eq )            @ force fixup-able long branch encoding
        beq     __error_p                       @ yes, error 'p'
  • mrc p15, 0, r9, c0, c0
    • MIDR로 cpu id를 읽어온다.
  • bl __lookup_processor_type
    • r9에 cpu id를 갖고 함수를 다녀오면 r5에 검색하여 찾은 프로세서의 proc_info_list 구조체 주소를 담아온다.
    • rpi2: r9 = __v7_ca7mp_setup: 레이블을 가리키는 주소
  • movs r10, r5
    • r5 구조체 주소가 0이면 에러를 출력하는 루틴으로 이동한다.

 

#ifdef CONFIG_ARM_LPAE
        mrc     p15, 0, r3, c0, c1, 4           @ read ID_MMFR0
        and     r3, r3, #0xf                    @ extract VMSA support
        cmp     r3, #5                          @ long-descriptor translation table format?
 THUMB( it      lo )                            @ force fixup-able long branch encoding
        blo     __error_lpae                    @ only classic page table format
#endif
  • mrc p15, 0, r3, c0, c1, 4
    • ID_MMFR0.vmsa를 읽어와서 그 값이 5보다 작으면 long-descriptor translation table을 지원하지 않으므로 __error_lpae 루틴으로 이동하여 에러를 출력한다.
    • ID_MMFR0.vmsa
      • 0b0000 Not supported.
      • 0b0001 Support for IMPLEMENTATION DEFINED VMSA.
      • 0b0010 Support for VMSAv6, with Cache and TLB Type Registers implemented.
      • 0b0011 Support for VMSAv7, with support for remapping and the Access flag. ARMv7-A
        profile.
      • 0b0100 As for 0b0011, and adds support for the PXN bit in the Short-descriptor translation table
        format descriptors.
      • 0b0101 As for 0b0100, and adds support for the Long-descriptor translation table format.
    • rpi2: 0x5

 

#ifndef CONFIG_XIP_KERNEL
        adr     r3, 2f
        ldmia   r3, {r4, r8}
        sub     r4, r3, r4                      @ (PHYS_OFFSET - PAGE_OFFSET)
        add     r8, r8, r4                      @ PHYS_OFFSET
#else
        ldr     r8, =PLAT_PHYS_OFFSET           @ always constant in this case
#endif
  • XIP 커널이 아닌 경우
    • adr r3, 2f
      • r3 = 레이블 2f가 가리키는 물리 주소
    • ldmia r3, {r4, r8}
      • r4 = 레이블 2f가 가리키는 주소의 값: . (빌드 시 만들어진 2f 레이블의 가상주소 값)
        • rpi2: PAGE_OFFSET=0x8000_xxxx
      • r8 = 레이블 2f+4가 가리키는 주소의 값 = PAGE_OFFSET
        • rpi2: PAGE_OFFSET=0x8000_0000
    • sub r4, r3, r4
      • r4 = offset (실행 시 2f 레이블의 물리 주소 – 컴파일 시 만들어진 2f레이블의  가상 주소)
      • rpi2: r4 = 0x8000_0000 = (0x0000_xxxx – 0x8000_xxxx)
    • add r8, r8, r4
      • r8 = PAGE_OFFSET + offset = 물리 시작 주소
      • rpi2: 0x0000_0000
  • XPI 커널인 경우
    • ldr r9, =PLAT_PHYS_OFFSET
    • XIP 커널에서는 코드가 ROM(or Nor flash)에서 동작하므로 adr 방식으로 물리램 주소를 알아올 수 없어서 직접 PLAT_PHYS_OFFSET에 값을 읽어와서 대입한다.

 

        /*
         * r1 = machine no, r2 = atags or dtb,
         * r8 = phys_offset, r9 = cpuid, r10 = procinfo
         */
        bl      __vet_atags
#ifdef CONFIG_SMP_ON_UP
        bl      __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
        bl      __fixup_pv_table
#endif
        bl      __create_page_tables
  • bl __vet_atags
    • atag또는 dtb가 유효한지 확인한다. 유효하지 않으면 r2=0
  • CONFIG_SMP_ON_UP
    • SMP 커널이 UP(Uni core)에서 동작할 수 있도록 지원하는 설정이다.
  • bl __fixup_smp
    • SMP(Multi core)코드가 UP(Uni core)에서 동작시 해당 코드를 치환(fixup)해주는 루틴
  • CONFIG_ARM_PATCH_PHYS_VIRT
    • 물리주소를 가상 주소로 변환해주는 함수를 런타임 시 패치할 수 있도록 지원하는 설정이다.
  • bl__fixup_pv_table
    • 커널 빌드 시 설정된 물리메모리의 시작위치가 실제 커널 구동 시 물리메모리의 시작위치가 서로 다를 수 있기 때문에 pv_offset를 다시 갱신하고 가상메모리와 물리메모리의 주소 변환 함수를 사용하는 코드를 patch하기 위해 필요한 루틴이다.
    • 디바이스트리 등의 사용으로 빌드된 커널을 재사용하고 메모리 주소 위치만 달라진 시스템에서 구동하기 위해 사용된다.

 

        /*
         * The following calls CPU specific code in a position independent
         * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
         * xxx_proc_info structure selected by __lookup_processor_type
         * above.  On return, the CPU will be ready for the MMU to be
         * turned on, and r0 will hold the CPU control register value.
         */
        ldr     r13, =__mmap_switched           @ address to jump to after
                                                @ mmu has been enabled
        adr     lr, BSYM(1f)                    @ return (PIC) address
        mov     r8, r4                          @ set TTBR1 to swapper_pg_dir
 ARM(   add     pc, r10, #PROCINFO_INITFUNC     )
 THUMB( add     r12, r10, #PROCINFO_INITFUNC    )
 THUMB( ret     r12                             )
1:      b       __enable_mmu
ENDPROC(stext)
  • ldr r13, =__mmap_switched
    • mmu가 켜진 후 실행될 __mmap_switched 레이블의 가상 주소를 미리 r13에 담아둔다.
  • adr     lr, BSYM(1f)
    • 함수 리턴 시 사용되는 lr 레지스터에 1f 레이블의 주소를 미리 담아둔다.
  • mov r8, r4
  • ARM( add pc, r10, #PROCINFO_INITFUNC )
    •  PROCINFO_INITFUNC= 16 (16번째 바이트를 가리킨다)
    • r10+16
      • 동일한 C 구조체로 표현하면
        • (proc_info_list *)__v7_ca7mp_proc_info->__cpu_flush
      • rpi2:
        • r10 = __v7_ca7mp_proc_info
        • r10+16 = __v7_ca7mp_setup
    • 위와 같이 알 수 없는 주소(변동되는 함수 포인터) 또는 멀리 있는 곳의 주소로 이동을 하려할 때에는 ARM 명령의 제약으로 인해 b 또는 bl 명령을 사용할 수 없다. 이러한 경우 직접 pc 레지스터를 조작하는 방법으로 이동(b)할 수 있고 서브루틴 콜 형식(bl)으로 사용할 때에는 미리 lr 레지스터에 복귀를 원하는 주소를 넣어줘야 한다.
    • __v7_ca7mp_setup
      • ARMv7의 TLB, 캐시를 초기화하고 MMU를 on 한다.
  • b __enable_mmu
    • MMU를 가동하기전에 임시로 1차 페이지 테이블을 만들어 사용한다.
    • MMU가 켜진 후부터 페이지 변환이 이루어져 커널을 빌드 시 사용했었던 가상 주소를 사용하는데 이 루틴이 종료될 때 __mmap_switched 루틴으로 복귀한다.

 

댓글 남기기