Boot / Init · Linux v6.6 · source + diagram note

setup_arch()

FDT 초기 scan에서 memblock·paging을 거쳐 DT/ACPI·PSCI와 CPU 준비로 이어지는 arm64의 조건부 경계를 분석합니다.

전체 흐름

arm64의 setup_arch()는 이른 FDT scan으로 boot arguments와 memory를 읽고, 그 결과를 바탕으로 memblock 범위를 조정한 뒤 paging을 설정한다. 이후 ACPI table을 파싱하지만 DT를 펼칠지와 PSCI 초기화 함수를 고르는 일은 acpi_disabled 조건에 달려 있다.

함수 범위

arm64_memblock_init()은 setup.c에 선언된 본문이 아니라 arch/arm64/mm/init.c의 함수다. 여기서 메모리 범위와 필수 예약을 조정한다.

조건 분기

FDT의 초기 scan은 항상 호출하지만, unflatten_device_tree() 및 psci_dt_init()는 acpi_disabled일 때만 실행된다.

그림 1. setup_arch의 FDT·memblock·paging·CPU 준비 순서
① init_mm·초기 매핑·FDT·early parameters② memblock → paging → 펌웨어 경로 → CPUsetup_arch초기 init_mmboot_command_lineKASLR초기 매핑fixmap·ioremapFDT 임시 매핑 기반DTBsetup_machine_fdt검증·chosen/memoryearly scanearly paramsparse_early_paramDAIF/idmap 이후EFI 검사mm/init.carm64_memblock_init주소·예약 범위 조정mmu.cpaging_init매핑 구성분기ACPI / DT · PSCIbootmem·KASANresource 등록boot CPUinit_bootcpu_opssmp_init_cpusMPIDR hashsetup_arch()의 호출 순서이며, 각 하위 함수의 작업을 단일 필수 동기 단계로 합친 것은 아니다.

위·아래 두 줄은 setup_arch() 호출 순서를 접어 표현했다.

버전 차이: v6.6은 FDT 검증에 가상 주소를 넘기고, arm64_memblock_init() 말미에서 high_memory도 정한다. setup_arch()는 kaslr_requires_kpti() 결과를 early fixmap보다 앞에 반영하고 CPU 준비 뒤 kasan_init_sw_tags()를 호출한다.

소스 코드 위치

먼저 확인할 Linux v6.6 소스 파일

원본 코드:
Linux v6.6: arch/arm64/kernel/setup.c
Linux v6.6: arch/arm64/mm/init.c
Linux v6.6: arch/arm64/mm/mmu.c
Linux v6.6: drivers/of/fdt.c

설명: setup_arch()와 setup_machine_fdt()의 본문은 setup.c, arm64_memblock_init()의 본문은 mm/init.c에 있다. FDT 초기 scan은 drivers/of/fdt.c, paging_init()은 mm/mmu.c에서 이어진다.

대표 코드

Linux v6.6 태그에서 함수 시작과 끝이 이어지는 발췌다. 호출 지점과 함수 본문의 파일을 구분해 읽는다.

setup_machine_fdt() — setup.c L184–218

원본 코드: Linux v6.6, arch/arm64/kernel/setup.c L184–218
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
	int size;
	void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);
	const char *name;

	if (dt_virt)
		memblock_reserve(dt_phys, size);

	if (!dt_virt || !early_init_dt_scan(dt_virt)) {
		pr_crit("\n"
			"Error: invalid device tree blob at physical address %pa (virtual address 0x%px)\n"
			"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
			"\nPlease check your bootloader.",
			&dt_phys, dt_virt);

		/*
		 * Note that in this _really_ early stage we cannot even BUG()
		 * or oops, so the least terrible thing to do is cpu_relax(),
		 * or else we could end-up printing non-initialized data, etc.
		 */
		while (true)
			cpu_relax();
	}

	/* Early fixups are done, map the FDT as read-only now */
	fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);

	name = of_flat_dt_get_machine_name();
	if (!name)
		return;

	pr_info("Machine model: %s\n", name);
	dump_stack_set_arch_desc("%s (DT)", name);
}

읽는 법: fixmap remap 후 유효성 검사를 거쳐 DTB를 read-only로 다시 매핑한다. early_init_dt_scan(dt_virt)는 drivers/of/fdt.c L1273–1319의 초기 검증·scan으로 이어진다.

setup_arch() — setup.c L293–392

원본 코드: Linux v6.6, arch/arm64/kernel/setup.c L293–392
void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
	setup_initial_init_mm(_stext, _etext, _edata, _end);

	*cmdline_p = boot_command_line;

	kaslr_init();

	/*
	 * If know now we are going to need KPTI then use non-global
	 * mappings from the start, avoiding the cost of rewriting
	 * everything later.
	 */
	arm64_use_ng_mappings = kaslr_requires_kpti();

	early_fixmap_init();
	early_ioremap_init();

	setup_machine_fdt(__fdt_pointer);

	/*
	 * Initialise the static keys early as they may be enabled by the
	 * cpufeature code and early parameters.
	 */
	jump_label_init();
	parse_early_param();

	dynamic_scs_init();

	/*
	 * Unmask asynchronous aborts and fiq after bringing up possible
	 * earlycon. (Report possible System Errors once we can report this
	 * occurred).
	 */
	local_daif_restore(DAIF_PROCCTX_NOIRQ);

	/*
	 * TTBR0 is only used for the identity mapping at this stage. Make it
	 * point to zero page to avoid speculatively fetching new entries.
	 */
	cpu_uninstall_idmap();

	xen_early_init();
	efi_init();

	if (!efi_enabled(EFI_BOOT)) {
		if ((u64)_text % MIN_KIMG_ALIGN)
			pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");
		WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND,
			   FW_BUG "Booted with MMU enabled!");
	}

	arm64_memblock_init();

	paging_init();

	acpi_table_upgrade();

	/* Parse the ACPI tables for possible boot-time configuration */
	acpi_boot_table_init();

	if (acpi_disabled)
		unflatten_device_tree();

	bootmem_init();

	kasan_init();

	request_standard_resources();

	early_ioremap_reset();

	if (acpi_disabled)
		psci_dt_init();
	else
		psci_acpi_init();

	init_bootcpu_ops();
	smp_init_cpus();
	smp_build_mpidr_hash();

	/* Init percpu seeds for random tags after cpus are set up. */
	kasan_init_sw_tags();

#ifdef CONFIG_ARM64_SW_TTBR0_PAN
	/*
	 * Make sure init_thread_info.ttbr0 always generates translation
	 * faults in case uaccess_enable() is inadvertently called by the init
	 * thread.
	 */
	init_task.thread_info.ttbr0 = phys_to_ttbr(__pa_symbol(reserved_pg_dir));
#endif

	if (boot_args[1] || boot_args[2] || boot_args[3]) {
		pr_err("WARNING: x1-x3 nonzero in violation of boot protocol:\n"
			"\tx1: %016llx\n\tx2: %016llx\n\tx3: %016llx\n"
			"This indicates a broken bootloader or old kernel\n",
			boot_args[1], boot_args[2], boot_args[3]);
	}
}

읽는 법: memblock 호출은 paging보다 먼저다. ACPI table parse 뒤 acpi_disabled에 따라 DT 펼침과 PSCI 함수를 나눈다.

arm64_memblock_init() — mm/init.c L312–439

원본 코드: Linux v6.6, arch/arm64/mm/init.c L312–439
void __init arm64_memblock_init(void)
{
	s64 linear_region_size = PAGE_END - _PAGE_OFFSET(vabits_actual);

	/*
	 * Corner case: 52-bit VA capable systems running KVM in nVHE mode may
	 * be limited in their ability to support a linear map that exceeds 51
	 * bits of VA space, depending on the placement of the ID map. Given
	 * that the placement of the ID map may be randomized, let's simply
	 * limit the kernel's linear map to 51 bits as well if we detect this
	 * configuration.
	 */
	if (IS_ENABLED(CONFIG_KVM) && vabits_actual == 52 &&
	    is_hyp_mode_available() && !is_kernel_in_hyp_mode()) {
		pr_info("Capping linear region to 51 bits for KVM in nVHE mode on LVA capable hardware.\n");
		linear_region_size = min_t(u64, linear_region_size, BIT(51));
	}

	/* Remove memory above our supported physical address size */
	memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);

	/*
	 * Select a suitable value for the base of physical memory.
	 */
	memstart_addr = round_down(memblock_start_of_DRAM(),
				   ARM64_MEMSTART_ALIGN);

	if ((memblock_end_of_DRAM() - memstart_addr) > linear_region_size)
		pr_warn("Memory doesn't fit in the linear mapping, VA_BITS too small\n");

	/*
	 * Remove the memory that we will not be able to cover with the
	 * linear mapping. Take care not to clip the kernel which may be
	 * high in memory.
	 */
	memblock_remove(max_t(u64, memstart_addr + linear_region_size,
			__pa_symbol(_end)), ULLONG_MAX);
	if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
		/* ensure that memstart_addr remains sufficiently aligned */
		memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
					 ARM64_MEMSTART_ALIGN);
		memblock_remove(0, memstart_addr);
	}

	/*
	 * If we are running with a 52-bit kernel VA config on a system that
	 * does not support it, we have to place the available physical
	 * memory in the 48-bit addressable part of the linear region, i.e.,
	 * we have to move it upward. Since memstart_addr represents the
	 * physical address of PAGE_OFFSET, we have to *subtract* from it.
	 */
	if (IS_ENABLED(CONFIG_ARM64_VA_BITS_52) && (vabits_actual != 52))
		memstart_addr -= _PAGE_OFFSET(48) - _PAGE_OFFSET(52);

	/*
	 * Apply the memory limit if it was set. Since the kernel may be loaded
	 * high up in memory, add back the kernel region that must be accessible
	 * via the linear mapping.
	 */
	if (memory_limit != PHYS_ADDR_MAX) {
		memblock_mem_limit_remove_map(memory_limit);
		memblock_add(__pa_symbol(_text), (u64)(_end - _text));
	}

	if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
		/*
		 * Add back the memory we just removed if it results in the
		 * initrd to become inaccessible via the linear mapping.
		 * Otherwise, this is a no-op
		 */
		u64 base = phys_initrd_start & PAGE_MASK;
		u64 size = PAGE_ALIGN(phys_initrd_start + phys_initrd_size) - base;

		/*
		 * We can only add back the initrd memory if we don't end up
		 * with more memory than we can address via the linear mapping.
		 * It is up to the bootloader to position the kernel and the
		 * initrd reasonably close to each other (i.e., within 32 GB of
		 * each other) so that all granule/#levels combinations can
		 * always access both.
		 */
		if (WARN(base < memblock_start_of_DRAM() ||
			 base + size > memblock_start_of_DRAM() +
				       linear_region_size,
			"initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
			phys_initrd_size = 0;
		} else {
			memblock_add(base, size);
			memblock_clear_nomap(base, size);
			memblock_reserve(base, size);
		}
	}

	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
		extern u16 memstart_offset_seed;
		u64 mmfr0 = read_cpuid(ID_AA64MMFR0_EL1);
		int parange = cpuid_feature_extract_unsigned_field(
					mmfr0, ID_AA64MMFR0_EL1_PARANGE_SHIFT);
		s64 range = linear_region_size -
			    BIT(id_aa64mmfr0_parange_to_phys_shift(parange));

		/*
		 * If the size of the linear region exceeds, by a sufficient
		 * margin, the size of the region that the physical memory can
		 * span, randomize the linear region as well.
		 */
		if (memstart_offset_seed > 0 && range >= (s64)ARM64_MEMSTART_ALIGN) {
			range /= ARM64_MEMSTART_ALIGN;
			memstart_addr -= ARM64_MEMSTART_ALIGN *
					 ((range * memstart_offset_seed) >> 16);
		}
	}

	/*
	 * Register the kernel text, kernel data, initrd, and initial
	 * pagetables with memblock.
	 */
	memblock_reserve(__pa_symbol(_stext), _end - _stext);
	if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
		/* the generic initrd code expects virtual addresses */
		initrd_start = __phys_to_virt(phys_initrd_start);
		initrd_end = initrd_start + phys_initrd_size;
	}

	early_init_fdt_scan_reserved_mem();

	high_memory = __va(memblock_end_of_DRAM() - 1) + 1;
}

읽는 법: FDT scan에서 등록된 memory를 지원 물리 주소·선형 매핑 범위에 맞게 제한하고, 조건부 mem=/initrd 처리와 kernel·reserved-memory 예약을 한다. 이 함수의 본문은 setup.c가 아니다.

함수별 분석

setup_machine_fdt()의 FDT 준비는 초기 scan과 뒤의 unflatten을 하나로 묶을 수 없다. 먼저 fixmap으로 DTB를 읽고 /chosen·memory 정보를 수집한다. 유효하지 않으면 이른 부팅 단계에서 반복 대기한다.

그림 2. DTB remap·검증과 early FDT scan
boot protocolDTB 물리 주소__fdt_pointer8-byte alignmentsetup_machine_fdtfixmap_remap_fdtDTB를 임시 VA로성공 시 memblock 예약drivers/of/fdt.cearly_init_dt_scanheader/root 확인chosen·memory scan유효RO remapmachine name있으면 로그무효cpu_relax 반복매핑·검증 실패이른 단계 정지초기 FDT scan은 boot_command_line과 memory 영역을 읽는다.reserved-memory 처리는 뒤 arm64_memblock_init()에서 호출한다.ACPI 부팅에서도 setup_arch()는 먼저 FDT를 scan한다. 뒤의 unflatten_device_tree()만 조건부다.

초기 scan의 memory 정보가 memblock에 들어가며 reserved-memory scan은 뒤의 arm64_memblock_init()에서 요청된다.

호출·위치실제 역할조건·한계
setup_machine_fdt()fixmap remap, DTB 범위 예약, 초기 scan, read-only remap매핑·검증 실패 시 뒤의 paging 단계에 가지 않는다.
early_init_dt_scan()FDT header/root/chosen/memory 검사·초기 등록버전마다 인자와 physical-address 보존 방식이 다르다.
arm64_memblock_init()주소 가능 범위·linear map·예약 조정mem=/initrd와 일부 VA/KVM 경로는 조건부다.
paging_init()memblock 정리 뒤 커널 페이징 초기화FDT 유효성 검사 함수와 같은 본문이 아니다.
acpi_boot_table_init()가능한 ACPI 부팅 정보를 읽는다이 호출 자체가 이후 DT 경로를 금지하진 않는다.

구조체와 필드

DTB의 물리 주소, fixmap 가상 주소, memblock의 memory/reserved 영역과 ACPI 선택 조건은 서로 다른 상태다. 하나의 “초기 메모리 완료” 플래그로 축약하면 어디서 주소 범위가 바뀌는지 놓친다.

그림 3. FDT memory에서 memblock 조정과 paging까지
FDT early scanmemory 등록/memory → memblockchosen의 initrd 정보mm/init.c물리·선형 한계PHYS_MASK 범위linear map 조정조건부 입력mem= / initrd커널 영역 보전initrd 접근 검사고정 예약kernel·reservedFDT reserved memmemblock 예약이후 setup_arch: paging_init() → ACPI table parse → bootmem_init()페이지 테이블 구성과 일반 메모리 초기화는 memblock 범위 선택 이후다.FDT가 기록한 memory를 arm64_memblock_init()가 제한·예약한다. mem=와 initrd 분기는 설정에 따라 달라진다.

memblock_init 함수 본문은 mm/init.c에 있고, 그 결과를 setup_arch()가 paging_init()에 연결한다.

값·대상추적할 의미
__fdt_pointer부트 경로가 넘긴 DTB 물리 주소. fixmap_remap_fdt() 입력이다.
dt_virt초기 fixmap 가상 주소. v6.18.37은 스캔 시 dt_phys도 별도로 전달한다.
boot_command_lineFDT /chosen 초기 scan에서 채워질 수 있는 부팅 인자 버퍼.
memblock.memory / memblock.reserved초기 물리 메모리와 예약 범위. FDT memory scan·후속 제한·reserved scan을 분리한다.
acpi_disabledDT unflatten 및 PSCI DT/ACPI 호출을 선택하는 조건이다.

실행 단계와 상태 변화

FDT 초기 scan은 ACPI 부팅 경로에서도 먼저 시도한다. acpi_boot_table_init() 이후 acpi_disabled인 경우에만 DT를 펼친다. bootmem·KASAN·resource 등록 다음에 같은 조건으로 PSCI DT 또는 ACPI 함수를 선택하고 CPU 초기화로 합류한다.

그림 4. ACPI 여부에 따른 DT 펼침·PSCI 분기
① paging 뒤 ACPI table parse · DT 펼침 여부② bootmem·KASAN·resource 등록 뒤 PSCI 선택항상ACPI table parsetable_upgradeboot_table_init조건acpi_disabled?true / falsetrueunflatten DTflat → device_nodefalseDT 펼침 생략ACPI 구성 사용합류bootmem 등KASAN·resourceearly_ioremap_reset같은 조건acpi_disabled?PSCI 구현 선택truepsci_dt_initDT 경로falsepsci_acpi_initACPI 경로합류CPU 준비init_bootcpu_opssmp_init_cpusFDT 초기 scan은 이 분기보다 앞이다. DT 펼침과 PSCI DT/ACPI 선택은 각각 조건부다.

ACPI table parse는 호출되지만, 뒤의 DT/PSCI 선택은 acpi_disabled 분기다.

단계소스상의 조건이 시점의 한계
DTB early scansetup_machine_fdt()에서 유효성 검사·chosen/memory 수집전체 DT node가 아직 unflatten된 상태는 아니다.
memblock 조정arm64_memblock_init()가 물리·선형 범위와 예약을 정리paging_init() 이전이다.
paging 구성paging_init()가 memblock 조정 뒤 호출CPU/PSCI 초기화 완료로 읽지 않는다.
firmware 경로ACPI table parse 뒤 acpi_disabled면 DT unflatten초기 FDT scan을 생략한 대체 경로가 아니다.
PSCI 및 CPUpsci_dt_init() 또는 psci_acpi_init() 뒤 CPU ops/topology두 PSCI 함수를 동시에 필수 실행하지 않는다.
초기 FDT scan → memblock 조정 → paging → ACPI/DT 조건 → PSCI 조건 → CPU 준비

불변 조건과 실패 사례

DTB 유효성

fixmap remap과 FDT header 검증이 성공해야 이른 정보를 읽을 수 있다. 잘못된 blob이면 setup_machine_fdt()는 계속 cpu_relax()하며 진행하지 않는다.

물리 주소와 가상 주소

DTB를 fixmap 가상 주소로 읽더라도 예약할 물리 범위에는 dt_phys를 쓴다. v6.18.37은 FDT scan에도 두 주소를 전달한다.

메모리 범위

FDT memory 등록이 곧 모든 RAM의 최종 사용 허가는 아니다. arm64_memblock_init()가 물리 한계와 linear map 범위, 조건부 limit/initrd를 정리한다.

펌웨어 선택

FDT early scan과 ACPI/DT 후반 선택을 혼동하지 않는다. acpi_disabled 조건은 DT 펼침과 PSCI 구현을 가른다.

주의

setup.c의 arm64_memblock_init() 호출을 함수 본문으로 표기하지 않는다.

주의

FDT scan, memblock 예약, paging을 한 번에 끝나는 DTB 검증으로 그리지 않는다.

주의

ACPI 경로에서 초기 FDT scan도 생략된다고 가정하지 않는다.

계측과 검증

이미 남은 부팅 로그와 읽기 전용 인터페이스를 고정 소스의 조건과 맞춘다. Machine model 로그는 FDT machine name이 있을 때 출력된다. 로그가 없다는 이유만으로 FDT scan이 호출되지 않았다고 판정할 수 없다.

읽기 전용 자료볼 수 있는 정보해석의 한계
dmesg / journalctl -k -bDTB 오류, Machine model, memory limit, ACPI·PSCI 메시지로그 설정과 출력 조건에 따라 기록이 없을 수 있다.
/proc/iomem부팅 후 RAM과 일부 예약 자원 범위초기 memblock의 모든 중간 변화를 복원하지 않는다.
/proc/device-tree/modelDT 경로에서 노출된 machine modelACPI 또는 DT 미노출 시스템에는 없을 수 있다.
고정 setup.c / mm/init.c / fdt.c소스의 실제 순서와 조건특정 보드의 경로 선택은 별도 부팅 증거와 대조한다.
읽기 전용 예:
dmesg | grep -Ei 'Machine model|invalid device tree|Memory limited|ACPI|PSCI'
cat /proc/iomem
cat /proc/device-tree/model 2>/dev/null

마지막 파일은 DT node가 노출된 환경에만 있다. 명령은 tracefs나 부팅 상태를 바꾸지 않는다.

추가 확인 사항

  • 부트로더가 넘긴 DTB 주소와 길이는 fixmap_remap_fdt()의 제약을 만족하는가?
  • FDT /chosen과 memory scan 결과가 이후 memblock 제한·예약에서 어떻게 바뀌는가?
  • 현재 부팅에서 acpi_disabled의 값은 무엇이고, 어떤 PSCI 초기화 함수로 들어가는가?
  • 해당 버전의 arm64_memblock_init() 본문과 setup_arch() 호출 위치를 다른 파일로 구분했는가?
  • paging 이후 CPU ops·topology 준비 전에 수행하는 버전별 초기화 호출은 무엇인가?