-
ARM64 리눅스 부팅 초기 어셈블리 코드 분석(head.S) (2/2)개발/컴퓨터사이언스 2018. 1. 27. 17:18
앞 포스팅에서 다루지 못한 부분들을 마저 분석해보자. 처음에는 ARM 어쎔 코드도 생소했고 Exception Level 개념도 없어 많이 헤맸는데 이젠 어느정도 훈련도 되어 있고 앞에서 했던 것들 보다 내용 도 적을 뿐만 아니라 상대적으로 익숙한 작업들이라 쉽다.
4. setup_boot_mode_flag
set_cpu_boot_mode_flag: adr_l x1, __boot_cpu_mode cmp w0, #BOOT_CPU_MODE_EL2 b.ne 1f add x1, x1, #4 1: str w0, [x1] // This CPU has booted in EL1 dmb sy dc ivac, x1 // Invalidate potentially stale cache line ret
현재 실행 되고 있는 cpu의 Exception Level Mode를 설정한다. VHE기능이 도입되면서 리눅스 커널도 EL2에서 돌아갈 수 있게 됐는데 그 기능을 위한 코드인 것 같다. __boot_cpu_mode 변수에 값을 대입하는것 외에는 별다른 작업이 없다.
5. __create_page_tables
페이지 테이블을 생성하는 작업이다. 그런데 모든 커널 영역에 대해서 매핑하는건 아니고 MMU가 켜지기 전에 필요한 일부 영역들을 1:1로 매핑한다. 위 영역으로는 idmap 과 kernel image 앞 부분의 몇 MB 영역이 있다. kernel 부팅으로 넘어가기 전에 필요한 최소한의 작업인가보다
/* * Create the identity mapping. */ adrp x0, idmap_pg_dir adrp x3, __idmap_text_start // __pa(__idmap_text_start) /* Skip ... */ create_pgd_entry x0, x3, x5, x6 mov x5, x3 // __pa(__idmap_text_start) adr_l x6, __idmap_text_end // __pa(__idmap_text_end) create_block_map x0, x7, x3, x5, x6
/* * Map the kernel image (starting with PHYS_OFFSET). */ adrp x0, swapper_pg_dir mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text) add x5, x5, x23 // add KASLR displacement create_pgd_entry x0, x5, x3, x6 adrp x6, _end // runtime __pa(_end) adrp x3, _text // runtime __pa(_text) sub x6, x6, x3 // _end - _text add x6, x6, x5 // runtime __va(_end) create_block_map x0, x7, x3, x5, x6
idmap 영역은 idmap_text_start ~ idmap_text_end 영역으로 선언 되어있는 반면 kernel image 영역은 swapper_pg_dir로 되어있다. kernel image의 앞부분을 swapper 영역이라고 하는건가보다. 그런데 뭐에 쓰는 녀석인지는 아직 잘.... 뭐지?
6. __cpu_setup
함수 콜은 head.S에 있지만 구현은 mm/proc.S 에 있다. 이곳에선 kernel 시작 하기 전에 CPU의 system register값을 설정해준다.
ENTRY(__cpu_setup) tlbi vmalle1 // Invalidate local TLB dsb nsh mov x0, #3 << 20 msr cpacr_el1, x0 // Enable FP/ASIMD mov x0, #1 << 12 // Reset mdscr_el1 and disable msr mdscr_el1, x0 // access to the DCC from EL0 isb // Unmask debug exceptions now, enable_dbg // since this is per-cpu reset_pmuserenr_el0 x0 // Disable PMU access from EL0 ldr x5, =MAIR(0x00, MT_DEVICE_nGnRnE) | \ MAIR(0x04, MT_DEVICE_nGnRE) | \ MAIR(0x0c, MT_DEVICE_GRE) | \ MAIR(0x44, MT_NORMAL_NC) | \ MAIR(0xff, MT_NORMAL) | \ MAIR(0xbb, MT_NORMAL_WT) msr mair_el1, x5 adr x5, crval ldp w5, w6, [x5] mrs x0, sctlr_el1 bic x0, x0, x5 // clear bits orr x0, x0, x6 // set bits ldr x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \ TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 tcr_set_idmap_t0sz x10, x9 mrs x9, ID_AA64MMFR0_EL1 bfi x10, x9, #32, #3 #ifdef CONFIG_ARM64_HW_AFDBM mrs x9, ID_AA64MMFR1_EL1 and x9, x9, #0xf cbz x9, 2f cmp x9, #2 b.lt 1f orr x10, x10, #TCR_HD // hardware Dirty flag update 1: orr x10, x10, #TCR_HA // hardware Access flag update 2: #endif /* CONFIG_ARM64_HW_AFDBM */ msr tcr_el1, x10 ret // return to head.S ENDPROC(__cpu_setup)
코드가 길어서 주석은 다 뺏다. 중요하다고 여겨지는 부분들만 체크해보자.
cpacr_el1의 값을 써주는 작업은 해당 CPU에 FP(Floating Point), ASIMD(Advanced Single Instruction Multiple Data) 작업을 허용하겠다는 작업이다. 컴퓨터 아키턱처 시간에 배운 용어들이 새록새록 떠오른다. FP, ASIMD에 대한 세부 구현은 CPU 제조사들이 하는 거고 개발자들은 그냥 기능만 켜주면 되니까 편리하다.
sctlr_el1은 System Control Register다. 여기선 값을 다시 써주기 보단 값을 읽고 필요한 정보를 가져오는 작업만 하고 있다.
ID_AA64MMFRx_EL1는 현재 하드웨어의 메모리 모델 및 정보를 확인할 수 있는 플래그다. 값을 새로 입력하는 건 불가능하고 값을 읽어오는 것만 가능하다. 위 정보를 통해 PARange bits(커버 할 수 있는 최대 메모리의 크기)와 Access bit, Dirty bit를 Hardware로 업데이트 할 수 있는지에 대한 플래그 값을 읽어온다.
요약하면 커널 내부에서 동작 할 수 있도록 cpu의 system register 값을 입력하는 작업을 한다.
7. __primary_switched
head.S 의 마지막 작업. kernel 함수로 점프하기 전에 필요한 레지스터 값을 복구하는 일을 한다. 마지막 줄에 b start_kernel 작업으로 kernel 함수를 실행하게 된다.
'개발 > 컴퓨터사이언스' 카테고리의 다른 글
tasklet 사용법 (0) 2018.06.17 tasklet과 workqueue의 차이점 (1) 2018.06.15 ARM64 리눅스 부팅 초기 어셈블리 코드 분석(head.S) (1/2) (0) 2018.01.17 입출력제어(ioctl) (0) 2017.02.11 디바이스트리(Device Tree) (2) 2017.01.04