작성자: 문영일
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
- r4 = 레이블 2f가 가리키는 주소의 값: . (빌드 시 만들어진 2f 레이블의 가상주소 값)
- 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
- adr r3, 2f
- 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
- 동일한 C 구조체로 표현하면
- 위와 같이 알 수 없는 주소(변동되는 함수 포인터) 또는 멀리 있는 곳의 주소로 이동을 하려할 때에는 ARM 명령의 제약으로 인해 b 또는 bl 명령을 사용할 수 없다. 이러한 경우 직접 pc 레지스터를 조작하는 방법으로 이동(b)할 수 있고 서브루틴 콜 형식(bl)으로 사용할 때에는 미리 lr 레지스터에 복귀를 원하는 주소를 넣어줘야 한다.
- __v7_ca7mp_setup
- ARMv7의 TLB, 캐시를 초기화하고 MMU를 on 한다.
- b __enable_mmu
- MMU를 가동하기전에 임시로 1차 페이지 테이블을 만들어 사용한다.
- MMU가 켜진 후부터 페이지 변환이 이루어져 커널을 빌드 시 사용했었던 가상 주소를 사용하는데 이 루틴이 종료될 때 __mmap_switched 루틴으로 복귀한다.