앞 포스팅에서 다루지 못한 부분들을 마저 분석해보자. 처음에는 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 함수를 실행하게 된다.