전체 흐름
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일 때만 실행된다.
위·아래 두 줄은 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
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
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
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 정보를 수집한다. 유효하지 않으면 이른 부팅 단계에서 반복 대기한다.
초기 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 선택 조건은 서로 다른 상태다. 하나의 “초기 메모리 완료” 플래그로 축약하면 어디서 주소 범위가 바뀌는지 놓친다.
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_line | FDT /chosen 초기 scan에서 채워질 수 있는 부팅 인자 버퍼. |
memblock.memory / memblock.reserved | 초기 물리 메모리와 예약 범위. FDT memory scan·후속 제한·reserved scan을 분리한다. |
acpi_disabled | DT unflatten 및 PSCI DT/ACPI 호출을 선택하는 조건이다. |
실행 단계와 상태 변화
FDT 초기 scan은 ACPI 부팅 경로에서도 먼저 시도한다. acpi_boot_table_init() 이후 acpi_disabled인 경우에만 DT를 펼친다. bootmem·KASAN·resource 등록 다음에 같은 조건으로 PSCI DT 또는 ACPI 함수를 선택하고 CPU 초기화로 합류한다.
ACPI table parse는 호출되지만, 뒤의 DT/PSCI 선택은 acpi_disabled 분기다.
| 단계 | 소스상의 조건 | 이 시점의 한계 |
|---|---|---|
| DTB early scan | setup_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 및 CPU | psci_dt_init() 또는 psci_acpi_init() 뒤 CPU ops/topology | 두 PSCI 함수를 동시에 필수 실행하지 않는다. |
불변 조건과 실패 사례
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 -b | DTB 오류, Machine model, memory limit, ACPI·PSCI 메시지 | 로그 설정과 출력 조건에 따라 기록이 없을 수 있다. |
/proc/iomem | 부팅 후 RAM과 일부 예약 자원 범위 | 초기 memblock의 모든 중간 변화를 복원하지 않는다. |
/proc/device-tree/model | DT 경로에서 노출된 machine model | ACPI 또는 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 준비 전에 수행하는 버전별 초기화 호출은 무엇인가?