kernel/head.S – __fixup_smp:

SMP(Multi core) 운용을 위해 빌드한 커널이 실제 사용 시 UP(Uni core)에서 동작을 시키는 경우 SMP 전용 명령들을 모두 UP 명령으로 치환(fixup)해주는 루틴

  • 2010년 9월 kernel v2.6.37-rc1에 처음 추가되었고 2011년 1월 v2.6.38-rc3에서 추가 보강되었다.

 

전체 순서도

kernel_head.s분석5

 

UP 시스템 체크

__fixup_smp:

CPU id를 분석하여 4가지 조건 case에서 매치되는 경우 UP 시스템으로 인식하여 __fixup_smp_on_up: 루틴으로 이동하여 처리하고 SMP인 경우 루틴을 빠져나간다.

1) UP case A: ARMv7이 아니면 UP

MIDR을 분석하여 ARMv7이 아니면 UP로 판단한다.

#ifdef CONFIG_SMP_ON_UP
        __HEAD
__fixup_smp:
        and     r3, r9, #0x000f0000     @ architecture version
        teq     r3, #0x000f0000         @ CPU ID supported?
        bne     __fixup_smp_on_up       @ no, assume UP

MIDR을 읽어서 MIDR.architecture가 f가 아니면(armv7이 아니면) UP에서
* 동작한 것이라고 판단한다.

  • and r3, r9, #0x000f0000
    • r9(MIDR, cpu id)에 비트마스크 0x000f_0000(MIDR.architecture)를 한다.
    • MIDR
      • Implementer[31..24]: 0x41 =ARM
      • Variant[23..20]: 0x0 =Major revision number
      • Architecture[19..16]: 0xf =ARMv7
      • Primary part number[15..4]: 0xc07 =Cortex-A7 MPCore part number
      • Revision[3..0]: 0x3 =Minor revision number
  • teq r3, #0x000f0000
    • MIDR.architecture와 ARMv7(0x000f_0000)을 비교한다.
    • tst는 and 연산 후 상태 반영, teq는 xor 연산 후 상태 반영
  • MIDR.architecture가 ARMv7이 아니면 __fixup_smp_on_up으로 이동

 

2) ARM11MPCore인 경우 SMP

ARMv7인 경우 이 루틴에 도착하는데 cpu id를 분석하여 ARM11MPCore 인지 확인되면 SMP라 판단되어 루틴을 종료한다.

        bic     r3, r9, #0x00ff0000
        bic     r3, r3, #0x0000000f     @ mask 0xff00fff0
        mov     r4, #0x41000000
        orr     r4, r4, #0x0000b000
        orr     r4, r4, #0x00000020     @ val 0x4100b020
        teq     r3, r4                  @ ARM 11MPCore?
        reteq   lr                      @ yes, assume SMP
  • bic r3, r9, #0x00ff0000
    • r3 = MIDR.Variant 및 MIDR.Architecture 비트 clear
    • r3 = MIDR.Revision 비트 clear
  • r4 = #0x4100_b020
  • teq r3, r4
    • MIDR.Implementer가 0x41이고 MIDR.Primary part number가 0xb02이면 ARM11MPCore로 판단한다.
  • reteq lr
    • 이 프로세서는 MP 프로세서 이므로 루틴을 더 이상 실행하지 않고 종료.

 

3) UP case B: Multiprocessing Extension 포함되었지만 UP로 구성된 시스템은 UP

ARM11MPCore가 아닌 경우 이 루틴으로 오게되는데 아마 Cortex 시리즈들일 것이라 판단된다. MPIDR을 읽어 Multiprocess Extensions이 설정되어 있지만 UP로 구성되어 동작하게 되어 있는 시스템은 여기서 UP로 판단한다.

        mrc     p15, 0, r0, c0, c0, 5   @ read MPIDR
        and     r0, r0, #0xc0000000     @ multiprocessing extensions and
        teq     r0, #0x80000000         @ not part of a uniprocessor system?
        bne    __fixup_smp_on_up        @ no, assume UP
  • mrc p15, 0, r0, c0, c0, 5
    • MPIDR(Multi Processor Affinity Register)를 읽는다.
  • and r0, r0, #0xc0000000
    • MPIDR.Bits[31]: Multiprocessing Extensions 구현 포함 여부(0=미포함, 1=포함)
    • MPIDR.Bits[30]: 0=MP, 1=UP
  • teq r0, #0x80000000
    • Multiprocessing Extensions가 구현되었지만 UP인 경우 __fixup_smp_on_up 루틴으로 이동

 

4) Cortex-A9 MPCore가 아니면 SMP

Cortex-A9 아키텍처는 MP용도로 설계되었다. 그러나 실제 SoC 제작 시 1 코어 용으로 설계된 사례가 있다. 그래서 이경우만 아니면 SMP로 판단한다. (Aegis 플랫폼에 사용된 SoC 중 하나가 ARM Cortex-A9 with single core)

  •  여기서 부터 추가된 조건들은 2013년 9월 kernel v.3.12-rc6에 추가된 항목이다.
        @ Core indicates it is SMP. Check for Aegis SOC where a single
        @ Cortex-A9 CPU is present but SMP operations fault.
        mov     r4, #0x41000000
        orr     r4, r4, #0x0000c000
        orr     r4, r4, #0x00000090
        teq     r3, r4                  @ Check for ARM Cortex-A9
        retne   lr                      @ Not ARM Cortex-A9,
  • r4 = 0x4100_c090
  • teq r3, r4
    • ARM Cortex-A9가 아닌 경우 루틴을 빠져나간다.
5) UP case C – Cortex-A9 이면서 IO base 주소가 0이면 UP
        @ If a future SoC *does* use 0x0 as the PERIPH_BASE, then the
        @ below address check will need to be #ifdef'd or equivalent
        @ for the Aegis platform.
        mrc     p15, 4, r0, c15, c0     @ get SCU base address
        teq     r0, #0x0                @ '0' on actual UP A9 hardware
        beq     __fixup_smp_on_up       @ So its an A9 UP
  • mrc p15, 4, r0, c15, c0
    • Cortex A9이 UP로 설계되어 있는지 확인하기 위해 CBAR에서 IO 장치의 base 주소를 가져온다.
    • IO base 주소가 0이면 UP 이므로 __fixup_smp_on_up 루틴으로 이동한다.
6) UP case D – Cortex-A9 이면서 CPU 수가 1개이면 UP
        ldr     r0, [r0, #4]            @ read SCU Config
ARM_BE8(rev     r0, r0)                 @ byteswap if big endian
        and     r0, r0, #0x3            @ number of CPUs
        teq     r0, #0x0                @ is 1?
        retne   lr
  • ldr r0, [r0, #4]
    • IO base 주소 + 4의 위치에서 SCU 설정값을 읽어온다.
  • and r0, r0, #0x3
    • 이 값의 하위 2비트로 CPU 수를 표현한다.
  • teq r0, #0x0
    • 이 값이 0이 아니면 SMP로 판단하여 루틴을 빠져나간다.
      • 0=CPU 1개, 1=CPU 2개, 2=CPU 3개, 3=CPU 4개
    • 이 값이 0이면 UP로 인식하여 아래 루틴(__fixup_smp_on_up:)으로 계속 진행한다.

 

ALT_SMP() & ALT_UP() 매크로

ALT_SMP() 매크로는 SMP에서만 동작하는 명령이 들어가고 ALT_UP() 매크로는 UP 시스템에서만 동작하는 명령이 들어간다.

  • SMP 시스템에서는 smp_func() 함수를 수행하는데 UP 시스템에서는 아무것도 하지 않을 때의 예)
    • ALT_SMP(bl smp_func);
    • ALT_UP(nop);

kernel_head.s분석4

두 매크로의 규칙은 다음과 같다.

  • ALT_SMP()와 ALT_UP()는 항상 짝을 이루어 붙어 다닌다.
  • ALT_SMP()와 ALT_UP()에 들어가는 명령(instruction)은 항상 4바이트 이다.
  • ALT_SMP()는 실제 코드섹션 영역에 그대로 저장된다.
  • ALT_UP()는 .alt.smp.init 섹션에 4바이트씩 두 번 push되는데 처음 word는 ALT_SMP()의 명령주소를 가리키고 있고(나중에 치환할 주소를 알아내기 위함), 두 번째 word는 동작할 UP 코드(명령)이다.

SMP 코드를 UP 코드로 fixup

__fixup_smp_on_up:

__fixup_smp_on_up:
        adr     r0, 1f
        ldmia   r0, {r3 - r5}
        sub     r3, r0, r3
        add     r4, r4, r3
        add     r5, r5, r3
        b       __do_fixup_smp_on_up
ENDPROC(__fixup_smp)

        .align
1:      .word   .
        .word   __smpalt_begin
        .word   __smpalt_end
  • r3: offset
  • r4: __smpalt_begin
  • r5: __smpalt_end
  • 위의 레지스터를 준비한 후 __do_fixup_smp_on_up: 루틴으로 이동

 

__do_fixup_smp_on_up:

이 루틴에서 루프를 돌며 SMP 코드를 UP 코드로 치환한다.

        .text
__do_fixup_smp_on_up:
        cmp     r4, r5
        reths   lr
        ldmia   r4!, {r0, r6}
 ARM(   str     r6, [r0, r3]    )
 THUMB( add     r0, r0, r3      )
#ifdef __ARMEB__
 THUMB( mov     r6, r6, ror #16 )       @ Convert word order for big-endian.
#endif
 THUMB( strh    r6, [r0], #2    )       @ For Thumb-2, store as two halfwords
 THUMB( mov     r6, r6, lsr #16 )       @ to be robust against misaligned r3.
 THUMB( strh    r6, [r0]        )
        b       __do_fixup_smp_on_up
ENDPROC(__do_fixup_smp_on_up)
  • cmp r4, r5
    • r4(__smpalt_begin으로 시작되는 카운터)와 r5(__smpalt_end)와 비교
  • reths lr
    • 끝까지 간 경우 종료(r4 >= r5)
  • ldmia r4!, {r0, r6}
    • r4 카운터 주소가 가리키는 곳에서 2 워드를 r0와 r6레지스터로 읽어오고 r4 카운터 주소를 1 word 감소시킨다.
    • r0: ALT_SMP에 있는 instr 주소
    • r6: ALT_UP에 있는 instr 값
  • str r6, [r0, r3]
    • r6(ALT_UP에 있는 instr 값)을 r0(ALT_SMP를 가리키는 instr 주소) + r3(offset)  주소에 저장한다.
  • b __do_fixup_smp_on_up
    • 루프를 돌기 위해 처음으로 이동한다.

 

kernel/head.S – __lookup_processor_type:

__lookup_processor_type:

processor id를 사용하여 링커가 만들어내는 별도의 섹션 공간에서 지원되는 프로세서 리스트를 검색하여 찾아낸다. MMU를 사용하지 않으므로 여기서 절대주소로 __proc_info 리스트를 사용할 수 없어서 offset을 계산하여 사용해야 한다.

arch/arm/kernel/head-common.S

        __FINIT
        .text

/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 *      r9 = cpuid
 * Returns:
 *      r3, r4, r6 corrupted
 *      r5 = proc_info pointer in physical address space
 *      r9 = cpuid (preserved)
 */
__lookup_processor_type:
        adr     r3, __lookup_processor_type_data
        ldmia   r3, {r4 - r6} 
        sub     r3, r3, r4                      @ get offset between virt&phys
        add     r5, r5, r3                      @ convert virt addresses to
        add     r6, r6, r3                      @ physical address space
1:      ldmia   r5, {r3, r4}                    @ value, mask
        and     r4, r4, r9                      @ mask wanted bits
        teq     r3, r4
        beq     2f
        add     r5, r5, #PROC_INFO_SZ           @ sizeof(proc_info_list)
        cmp     r5, r6
        blo     1b
        mov     r5, #0                          @ unknown processor
2:      ret     lr
ENDPROC(__lookup_processor_type)
  • __FINIT
    • 해당 매크로엔 .previous 지시자가 담겨있다.
    • 현재 사용하고 있는 섹션 이전에 사용했던 섹션으로 변경된다.
  • r3를 사용하여 offset을 계산한다.
    • rpi2: 0x8000_0000 (물리 주소 – 가상 주소)
  • r5: __proc_info_begin 물리 주소
  • r6: __proc_info_end 물리 주소
  • ldmia r5, {r3, r4}
    • 루프의 시작으로 proc_info_list 구조체의 첫 부분에 있는 cpu value와 cpu mask를 읽어서 r3, r4에 잃어온다.
  • beq     2f
    • cpu를 판별하여 일치하면 2f 레이블로 이동하고 리턴한다.
  • add     r5, r5, #PROC_INFO_SZ
    • r5를 구조체 사이즈만큼 증가시킨다.
    • #PROC_INFO_SZ = 52
    • 구조체 proc_info_list와 동일한 사이즈
  • cmp r5, r6
    • r5가 아직 r6보다 작으면 1f 레이블로 이동하여 루프를 돈다.

 

프로세서 정보 리스트(proc_info_list)가 저장되는 곳

  • 지원되는 각종 프로세서 정보들은 .init.proc.info 섹션에 저장된다.
  • rpi2:
    • 이 위치에 저장된 정보는 ARMv7 관련된 것만 약 520여 바이트로 구성되어 있다.
    • arch/arm/mm/proc-v7.S – __v7_ca7mp_proc_info: 레이블 위치
  • 저장되는 섹션 위치를 기술한 소스

arch/arm/kernel/vmlinux.lds

.init.proc.info : {
        . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
}

 

__lookup_processor_type_data: 객체

arch/arm/kernel/head-common.S

/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
        .align  2
        .type   __lookup_processor_type_data, %object
__lookup_processor_type_data:
        .long   .
        .long   __proc_info_begin
        .long   __proc_info_end
        .size   __lookup_processor_type_data, . - __lookup_processor_type_data

 

__v7_ca7mp_proc_info: 객체

아래는 ARM Cortex-A7 아키텍처가 사용하는 프로세서 정보 리스트로 proc_info_list 구조체와 동일하다.

arch/arm/mm/proc-v7.S

        /*
         * ARM Ltd. Cortex A7 processor.
         */
        .type   __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
        .long   0x410fc070
        .long   0xff0ffff0
        __v7_proc __v7_ca7mp_setup
        .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info
  • __v7_proc __v7_ca7mp_setup
    • __v7_proc 매크로
      • ARMv7 아키텍처는 모두 공통적으로 사용된다.
        • 현재 소스에는 11개 프로세서 정보 리스트가 등록되어 있다.
    • __v7_ca7mp_setup
      • ARM Cortex-A7의 설정 함수가 __v7_proc 매크로의 인수로 사용된다.
      • proc_info_list->__cpu_flush와 동일하다.

 

__v7_proc 매크로

arch/arm/mm/proc-v7.S

        .section ".rodata"

        string  cpu_arch_name, "armv7"
        string  cpu_elf_name, "v7"
        .align

        .section ".proc.info.init", #alloc, #execinstr

        /*
         * Standard v7 proc info content
         */

.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
        ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
        ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
        .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
        W(b)    \initfunc
        .long   cpu_arch_name
        .long   cpu_elf_name
        .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
                HWCAP_EDSP | HWCAP_TLS | \hwcaps
        .long   cpu_v7_name
        .long   \proc_fns
        .long   v7wbi_tlb_fns
        .long   v6_user_fns
        .long   v7_cache_fns
.endm
  • 44 바이트로 구성되어 있고 proc_info_list(56 바이트)에서 cpu value와 mask 정보 이후의 중간을 구성하는 정보이다.
  • 참고: start_kernel() – setup_arch() – setup_processor() | 문c

 

proc_info_list 구조체

arch/arm/include/asm/procinfo.h

/*      
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *      
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
struct proc_info_list {
        unsigned int            cpu_val;
        unsigned int            cpu_mask;
        unsigned long           __cpu_mm_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_io_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_flush;            /* used by head.S */
        const char              *arch_name;
        const char              *elf_name;
        unsigned int            elf_hwcap;
        const char              *cpu_name;
        struct processor        *proc;
        struct cpu_tlb_fns      *tlb;
        struct cpu_user_fns     *user;
        struct cpu_cache_fns    *cache;
};

 

compressed 커널의 내부를 hexdump하여 분석

 

kernel_head.s분석2

 

kernel_head.s분석3

 

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 루틴으로 복귀한다.