OP-TEE OS · source analysis

init_primary와 secure runtime 구성

OP-TEE 4.10.0의 init_primary, late, runtime, final 함수가 준비하는 상태와 설정별 경로를 읽습니다.

OP-TEE OS 4.10.0은 코드 분석 기준입니다. 제품 적용 전 공식 OP-TEE 변경 기록과 플랫폼 보안 공지를 확인하세요.

Series
03 / 12
Baseline
4.10.0
Commit
753afbbee168
Source
core/arch/arm/kernel/boot.c:859-947 (발췌 범위)

init_primary()가 실제로 초기화하는 범위는?

OP-TEE OS 4.10.0의 init_primary()는 primary 부트 C 경로의 한 구간이다. 이 함수는 예외 mask, heap pool, 저장된 memory map, 선택적 pager, stack canary와 CPU별 vector를 준비한다. thread pool과 서비스 initcall 전체를 혼자 실행하지 않는다.

boot_init_primary_late(), boot_init_primary_runtime(), boot_init_primary_final()의 설정별 후속 동작을 분리해 읽는다.

초기 C 문맥과 runtime 객체의 경계

그림 1. 부트 단계가 준비하는 객체
BOOTearly C임시 stack · boot mappageable_part (조건부)boot.c L883init_primaryheap · saved mappager · canary · vectorboot.c L988primary_lateDT · NS memorythread pool (비가상화)boot.c L1043runtime / finalinitcalls (조건부)Normal World 전환pager 구성의 curr_thread=0은 init_pager_runtime을 위한 임시 값이다.thread_init_primary()는 user kcode 준비이고 pool 생성은 primary_late의 별도 분기다.

pager의 임시 curr_thread=0과 뒤의 일반 thread pool은 다른 사건이다.

pager와 NS 가상화의 조건부 흐름

그림 2. 설정에 따라 실행·생략되는 초기화
COMMONinit_primaryheap / map / canaryCFG_WITH_PAGER=ypager runtimecurr_thread=0 임시 지정CFG_WITH_PAGER=npager 호출 생략boot_cached_mem_end 갱신CFG_NS_VIRTUALIZATION=nthreads + initcallslate: pool · boot threadruntime: TEE, final: driverCFG_NS_VIRTUALIZATION=yguest별로 미룸late: curr_thread=-1TEE/driver init 생략COMMON FINALfinalcallsNormal World 전환initcall.c: callback 오류 반환은 로그로 남기고 다음 callback으로 진행한다. 일반 rollback 경로는 없다.후속 late/runtime/final: NS virtualization 설정의 별도 분기

위쪽은 init_primary 내부의 pager 분기이고 아래쪽은 이후 단계의 NS 가상화 분기다. 두 설정은 별도로 읽는다. init_tee_runtime()은 비가상화 runtime 분기에서 실행한다. call_finalcalls()는 공통 final 단계다.

함수별로 남는 상태

함수·범위생성·변경조건과 다음 소비자
init_primary L894–955예외 mask, heap pool, saved core map, phys memory, boot allocator 정리pager·가상화·동적 설정에 따라 pool과 allocator가 다름
init_primary L957–972선택적 pager, stack canary, per-CPU vectorCFG_WITH_PAGER이면 curr_thread=0 지정 후 pager
primary_late L1017–1041DT·NS memory, core-local; 비가상화면 pool과 boot thread가상화면 curr_thread=-1로 비움
primary_runtime L1043–1090user kcode, intc, native IRQ 정책, TEE initcallsinit_tee_runtime()과 IRQ unmask는 비가상화만
primary_final L1092–1105비가상화 driver initcall, 공통 finalcallsNormal World 전환 전 native IRQ mask

initcall.c L18–25는 실패를 로그에 남기고 다음 callback으로 진행한다. 성공이나 역순 rollback을 일괄 가정하지 않는다.

부트 단계별 역할

부트 흐름과 각 단계에서 준비하는 상태를 함수별로 구분한다.

그림 3. primary 초기화의 실행 단계

heap/pager는 init_primary, DT/thread array는 후속 late 단계다.

01early C
02init_primary
03primary_late
04primary_runtime
05primary_final
그림 4. secure subsystem 의존성

앞쪽 상태를 뒤쪽에서 처음 사용하는 관계다.

01boot mapcore_mmu_save_mem_mapruntime map
02heappager (선택)pageable execution
03primary_latethread pool (비가상화)SMC slot
04DT / NS memoryTEE initcallsservice path

원본 코드

아래 코드는 OP-TEE OS 4.10.0의 core/arch/arm/kernel/boot.c에서 859-947줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commit753afbbee1682f5d16fd30e87b31058a4fd4f4b8anchor · L883static void init_primary(unsigned long pageable_part)
859	thread_init_core_local_pauth_keys();
860	thread_init_thread_pauth_keys();
861
862	/*
863	 * Reinitialize canaries around the stacks with crypto_rng_read().
864	 *
865	 * TODO: Updating canaries when CFG_NS_VIRTUALIZATION is enabled will
866	 * require synchronization between thread_check_canaries() and
867	 * thread_update_canaries().
868	 */
869	if (!IS_ENABLED(CFG_NS_VIRTUALIZATION))
870		thread_update_canaries();
871}
872
873static bool add_padding_to_pool(vaddr_t va, size_t len, void *ptr __unused)
874{
875#ifdef CFG_NS_VIRTUALIZATION
876	nex_malloc_add_pool((void *)va, len);
877#else
878	malloc_add_pool((void *)va, len);
879#endif
880	return true;
881}
882
883static void init_primary(unsigned long pageable_part)
884{
885	vaddr_t va = 0;
886
887	/*
888	 * Mask asynchronous exceptions before switch to the thread vector
889	 * as the thread handler requires those to be masked while
890	 * executing with the temporary stack. The thread subsystem also
891	 * asserts that the foreign interrupts are blocked when using most of
892	 * its functions.
893	 */
894	thread_set_exceptions(THREAD_EXCP_ALL);
895	primary_save_cntfrq();
896	init_vfp_sec();
897
898	if (IS_ENABLED(CFG_CRYPTO_WITH_CE))
899		check_crypto_extensions();
900
901	init_asan();
902
903	/*
904	 * By default whole OP-TEE uses malloc, so we need to initialize
905	 * it early. But, when virtualization is enabled, malloc is used
906	 * only by TEE runtime, so malloc should be initialized later, for
907	 * every virtual partition separately. Core code uses nex_malloc
908	 * instead.
909	 */
910#ifdef CFG_WITH_PAGER
911	/* Add heap2 first as heap1 may be too small as initial bget pool */
912	malloc_add_pool(__heap2_start, __heap2_end - __heap2_start);
913#endif
914#ifdef CFG_NS_VIRTUALIZATION
915	nex_malloc_add_pool(__nex_heap_start, __nex_heap_end -
916					      __nex_heap_start);
917#else
918	malloc_add_pool(__heap1_start, __heap1_end - __heap1_start);
919#endif
920	IMSG_RAW("\n");
921	if (IS_ENABLED(CFG_DYN_CONFIG)) {
922		size_t sz = sizeof(struct thread_core_local) *
923			    CFG_TEE_CORE_NB_CORE;
924		void *p = boot_mem_alloc(sz, alignof(void *) * 2);
925
926#ifdef CFG_NS_VIRTUALIZATION
927		nex_malloc_add_pool(p, sz);
928#else
929		malloc_add_pool(p, sz);
930#endif
931	}
932
933	core_mmu_save_mem_map();
934	core_mmu_init_phys_mem();
935	boot_mem_foreach_padding(add_padding_to_pool, NULL);
936	va = boot_mem_release_unused();
937	if (!IS_ENABLED(CFG_WITH_PAGER)) {
938		/*
939		 * We must update boot_cached_mem_end to reflect the memory
940		 * just unmapped by boot_mem_release_unused().
941		 */
942		assert(va && va <= boot_cached_mem_end);
943		boot_cached_mem_end = va;
944	}
945
946	if (IS_ENABLED(CFG_DYN_CONFIG)) {
947		/*

발췌 범위와 후속 함수

대표 코드 boot.c L859–947의 기준 정의는 L883이다. 발췌는 L947에서 끝나지만 함수는 L972까지 이어진다. 아래 관련 원문과 고정 commit 전체 파일을 함께 본다.

thread_init_primary()는 thread pool 생성 함수가 아니다. 비가상화 pool/boot-thread 설정은 primary_late L1037–1038이다.

함께 읽어야 하는 원본 코드

첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.

01. thread, service와 runtime initcall을 마무리하는 단계

core/arch/arm/kernel/boot.c 976-1080줄이다.

filecore/arch/arm/kernel/boot.canchor · L988void __weak boot_init_primary_late
976#if defined(ARM32)
977	return read_sctlr() & SCTLR_NMFI;
978#else
979	/* Note: ARM64 does not feature non-maskable FIQ support. */
980	return false;
981#endif
982}
983
984/*
985 * Note: this function is weak just to make it possible to exclude it from
986 * the unpaged area.
987 */
988void __weak boot_init_primary_late(unsigned long fdt __unused,
989				   unsigned long manifest __unused)
990{
991	size_t fdt_size = CFG_DTB_MAX_SIZE;
992
993	if (IS_ENABLED(CFG_TRANSFER_LIST) && mapped_tl) {
994		struct transfer_list_entry *tl_e = NULL;
995
996		tl_e = transfer_list_find(mapped_tl, TL_TAG_FDT);
997		if (tl_e) {
998			/*
999			 * Expand the data size of the DTB entry to the maximum
1000			 * allocable mapped memory to reserve sufficient space
1001			 * for inserting new nodes, avoid potentially corrupting
1002			 * next entries.
1003			 */
1004			uint32_t dtb_max_sz = mapped_tl->max_size -
1005					      mapped_tl->size + tl_e->data_size;
1006
1007			if (!transfer_list_set_data_size(mapped_tl, tl_e,
1008							 dtb_max_sz)) {
1009				EMSG("Failed to extend DTB size to %#"PRIx32,
1010				     dtb_max_sz);
1011				panic();
1012			}
1013			fdt_size = tl_e->data_size;
1014		}
1015	}
1016
1017	init_external_dt(boot_arg_fdt, fdt_size);
1018	reinit_manifest_dt();
1019#ifdef CFG_CORE_FFA
1020	tpm_map_log_area(get_manifest_dt());
1021#else
1022	tpm_map_log_area(get_external_dt());
1023#endif
1024	discover_nsec_memory();
1025	update_external_dt();
1026	configure_console_from_dt();
1027
1028	if (IS_ENABLED(CFG_NS_VIRTUALIZATION)) {
1029		/*
1030		 * Virtualization: We can't initialize threads right now because
1031		 * threads belong to "tee" part and will be initialized
1032		 * separately per each new virtual guest. So, we'll clear
1033		 * "curr_thread" and call it done.
1034		 */
1035		thread_get_core_local()->curr_thread = -1;
1036	} else {
1037		thread_init_threads(CFG_NUM_THREADS);
1038		thread_init_boot_thread();
1039	}
1040	thread_init_thread_core_local(CFG_TEE_CORE_NB_CORE);
1041}
1042
1043void __weak boot_init_primary_runtime(void)
1044{
1045	thread_init_primary();
1046	IMSG("OP-TEE version: %s", core_v_str);
1047	if (IS_ENABLED(CFG_INSECURE)) {
1048		IMSG("WARNING: This OP-TEE configuration might be insecure!");
1049		IMSG("WARNING: Please check https://optee.readthedocs.io/en/latest/architecture/porting_guidelines.html");
1050	}
1051	IMSG("Primary CPU initializing");
1052#ifdef CFG_CORE_ASLR
1053	DMSG("Executing at offset %#lx with virtual load address %#"PRIxVA,
1054	     (unsigned long)boot_mmu_config.map_offset, VCORE_START_VA);
1055#endif
1056#ifdef CFG_NS_VIRTUALIZATION
1057	DMSG("NS-virtualization enabled, supporting %u guests",
1058	     CFG_VIRT_GUEST_COUNT);
1059#endif
1060	if (IS_ENABLED(CFG_MEMTAG))
1061		DMSG("Memory tagging %s",
1062		     memtag_is_enabled() ?  "enabled" : "disabled");
1063
1064	/* Check if platform needs NMFI workaround */
1065	if (cpu_nmfi_enabled())	{
1066		if (!IS_ENABLED(CFG_CORE_WORKAROUND_ARM_NMFI))
1067			IMSG("WARNING: This ARM core has NMFI enabled, please apply workaround!");
1068	} else {
1069		if (IS_ENABLED(CFG_CORE_WORKAROUND_ARM_NMFI))
1070			IMSG("WARNING: This ARM core does not have NMFI enabled, no need for workaround");
1071	}
1072
1073	boot_primary_init_intc();
1074	init_vfp_nsec();
1075	if (!IS_ENABLED(CFG_NS_VIRTUALIZATION)) {
1076		/*
1077		 * Unmask native interrupts during driver initcalls.
1078		 *
1079		 * NS-virtualization still uses the temporary stack also
1080		 * used for exception handling so it must still have native

02. linker section의 service initcall을 실행

core/kernel/initcall.c 43-74줄이다.

filecore/kernel/initcall.canchor · L53void __weak call_service_initcalls(void)
43 */
44void __weak call_early_initcalls(void)
45{
46	DO_INIT_CALLS(early_initcall);
47}
48
49/*
50 * Note: this function is weak just to make it possible to exclude it from
51 * the unpaged area.
52 */
53void __weak call_service_initcalls(void)
54{
55	DO_INIT_CALLS(service_initcall);
56}
57
58/*
59 * Note: this function is weak just to make it possible to exclude it from
60 * the unpaged area.
61 */
62void __weak call_driver_initcalls(void)
63{
64	DO_INIT_CALLS(driver_initcall);
65}
66
67/*
68 * Note: this function is weak just to make it possible to exclude it from
69 * the unpaged area.
70 */
71void __weak call_finalcalls(void)
72{
73	DO_INIT_CALLS(finalcall);
74}

초기화 경계의 차이

01

heap과 boot allocator

L910–955의 pool 선택과 boot allocator 정리는 pager·가상화·동적 설정에 따라 달라진다. 동일한 heap 경로를 모든 빌드에 적용할 수 없다.

02

임시 current thread

pager 빌드는 L965에서 curr_thread=0을 지정해 pager runtime을 초기화한다. 일반 thread array와 boot thread는 late 단계에서 별도로 설정한다.

03

initcall 반환

do_init_calls()는 오류를 로그로 남기지만 루프를 중단하거나 이전 callback을 역순 정리하지 않는다.

설정이 바꾸는 경로

01

CFG_WITH_PAGER

pageable_part는 pager 설정에서만 init_pager_runtime()에 전달된다. pager가 없으면 boot memory 해제 뒤 boot_cached_mem_end를 갱신한다.

02

CFG_NS_VIRTUALIZATION

late 단계에서 현재 thread를 비우고, runtime/final에서 일반 TEE·driver initcall을 건너뛴다. guest별 준비를 early boot 완료로 오해하지 않는다.

03

native interrupt mask

초기에는 예외를 모두 막고, 비가상화 runtime에서는 driver initcall 중 native interrupt를 풀었다가 final에서 다시 막는다.

첫 standard SMC 전에 확인할 계약

그림 5. 단계별 소비 관계
01boot map

MMU·allocator 입력

02heap/pager

설정별 pool

03DT/NS memory

late 단계

04thread/initcall

비가상화 경로

05SMC entry

요청별 검증

모든 칸이 모든 빌드에서 실행된다는 뜻은 아니다. message buffer 검사는 실제 SMC 요청 경로를 추적한다.

관찰고정 소스해석
pager boot 중단boot.c L957–967pageable_part와 pool, 임시 current-thread 설정을 비교
DT 빌드에서만 실패L1017–1026DT 초기화와 NS memory 발견을 분리
서비스 첫 요청 실패init_tee_runtime와 SMC pathinitcall 로그와 요청별 validation 결과를 분리
가상화에서 pool 없음L1028–1039early boot의 의도적 지연인지 guest 생성 오류인지 구분

초기 객체의 수명

대상생산·변경확인
boot memory mapcore_mmu_save_mem_map()runtime map에 필요한 항목
malloc / nex_malloc poolinit_primary L910–931pager·가상화 설정별 pool
per-CPU vectorthread_init_per_cpu()CPU별 exception 처리
thread array / boot threadprimary_late 비가상화 분기runtime SMC에서 재사용
initcall descriptorlinker sectiondispatcher에 일반 rollback 없음

실패 지점과 관찰

현상먼저 볼 코드판정
pager 부팅 중단init_pager_runtime()pageable_part·pool·설정 확인
DT/NS memory 오류primary_late L1017–1025해당 함수의 panic/진행 정책 개별 확인
initcall 오류 로그 뒤 진행initcall.c L18–25다음 callback과 첫 소비자도 확인
첫 SMC 실패thread allocation / message parserpool 준비와 요청 검증을 분리

빌드 산출물과 로그

같은 빌드의 tee.elf를 읽는다. nm은 C 선언이 아니라 심볼 이름을 출력한다.

목적읽기 전용 명령·관찰해석
section${CROSS_COMPILE64}readelf -W -lS out/arm/core/tee.elfpager/pageable과 core 영역 배치
심볼${CROSS_COMPILE64}nm -an out/arm/core/tee.elf | grep -E " (init_primary|boot_init_primary_late|boot_init_primary_runtime)$"함수 이름·링크 주소를 확인
secure consoleinitcall 오류와 Normal World 전환 로그오류 로그 뒤에도 다음 callback이 실행될 수 있음
Linux 측dmesg | grep -i optee서비스별 성공과 별개로 해석

최소 계측 지점

  1. 01
    pager 조건

    pageable_part, heap pool, pager 진입을 같은 설정에서 기록한다.

  2. 02
    thread pool

    primary_late의 비가상화 분기와 첫 SMC의 FREE slot 수를 비교한다.

  3. 03
    initcall 오류

    callback 이름·반환값과 첫 소비자의 결과를 기록한다. 자동 rollback을 가정하지 않는다.

원문과 다음 글