fdt를 최초 셋업하는 함수입니다. 함수 인자로 phys_addr_t 자료형의 dt_phys를 전달받는다. 전달받은 주소를 이용해 부팅과정 중 먼저 필요한 항목에 대해 설정해준다.
185 static void __init setup_machine_fdt(phys_addr_t dt_phys) 186 { 187 void *dt_virt = fixmap_remap_fdt(dt_phys); 188 const char *name; 189 190 if (!dt_virt || !early_init_dt_scan(dt_virt)) { 191 pr_crit("\n" 192 "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n" 193 "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n" 194 "\nPlease check your bootloader.", 195 &dt_phys, dt_virt); 196 197 while (true) 198 cpu_relax(); 199 } 200 201 name = of_flat_dt_get_machine_name(); 202 if (!name) 203 return; 204 205 pr_info("Machine model: %s\n", name); 206 dump_stack_set_arch_desc("%s (DT)", name); 207 } | cs |
dt_phys 주소를 가상메모리 영역에 remap 해주기 위해 fixmap_remap_fdt()를 사용하여 가상 메모리 주소를 구한다. 함수 내에서 __fixmap_remap_fdt()함수를 호출하여 가상메모리 주소를 구한다.
line 201 :
fixmap 영역에 dt가 mapping이 완료됨에 따라 of_flat_dt_get_machine_name()으로 name을 구한다.
위 과정을 통해 부팅과정 중 초기에 필요한 dt 정보를 fixmap 영역에 mapping하여 사용한다.
line 187에서 remap을 위한 가상 메모리 주소를 어떻게 remap하여 구하는지 알아보자.
[arch/arm64/mm/mmu.c - fixmap_remap_fdt()] 935 void *__init fixmap_remap_fdt(phys_addr_t dt_phys) 936 { 937 void *dt_virt; 938 int size; 939 940 dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO); 941 if (!dt_virt) 942 return NULL; 943 944 memblock_reserve(dt_phys, size); 945 return dt_virt; 946 } | cs |
__fixmap_remap_fdt() 함수에서 __fix_to_vir(FIX_FDT)를 이용해 dt를 로딩할 block의 Base Address를 구한다. FIX_FDT는 FDT 형태의 디바이스 트리를 먼저 사용하기 위한 매핑이다. line 914. 에서 dt_virt_base에서 offset을 더하여 dt_virt를 계산하게 되는데 offset은 dtb 사이즈가 Swapper block에 대해 맞춰주기 위해 계산되어진 값이다.
[arch/arm64/mm/mmu.c - __fixmap_remap_fdt] 878 void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot) 879 { 881 const u64 dt_virt_base = __fix_to_virt(FIX_FDT); 882 int offset; 883 void *dt_virt; 884 ... 892 BUILD_BUG_ON(MIN_FDT_ALIGN < 8); 893 if (!dt_phys || dt_phys % MIN_FDT_ALIGN) 894 return NULL; 895 ... 906 BUILD_BUG_ON(dt_virt_base % SZ_2M); 907 908 BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT != 909 __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT); 910 912 offset = dt_phys % SWAPPER_BLOCK_SIZE; 914 dt_virt = (void *)dt_virt_base + offset; 916 /* map the first chunk so we can read the size from the header */ 917 create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), 918 dt_virt_base, SWAPPER_BLOCK_SIZE, prot); 919 920 if (fdt_magic(dt_virt) != FDT_MAGIC) 921 return NULL; 922 923 *size = fdt_totalsize(dt_virt); 924 if (*size > MAX_FDT_SIZE) 925 return NULL; 928 if (offset + *size > SWAPPER_BLOCK_SIZE) 929 create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base, 930 round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot); 931 932 return dt_virt; 933 } | cs |
dt의 base address를 구했다면 그 주소에 맞게 mapping을 시켜준다.
line 917 ~ 921 :
dt의 사이즈는 모르는 상태에서 SWAPPER_BLOCK_SIZE만큼 선 매핑을 진행한다. 그 이후 정확히 dt 가 맞는 fdt_magic으로 확인해 준다. 그 이후 dt 가 맞다면 전체 mapping을 시작한다.
line 923 :
dt의 header는 앞서 매핑하였기에 header에 정의된 전체 dt의 사이즈를 구한다. 전체 사이즈를 이용해 전체 mapping을 시작한다. fdt의 header는 아래와 같이 fdt_header 구조체로 정의되어 있다.
57 struct fdt_header { 58 fdt32_t magic; /* magic word FDT_MAGIC */ 59 fdt32_t totalsize; /* total size of DT block */ 60 fdt32_t off_dt_struct; /* offset to structure */ 61 fdt32_t off_dt_strings; /* offset to strings */ 62 fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ 63 fdt32_t version; /* format version */ 64 fdt32_t last_comp_version; /* last compatible version */ 65 66 /* version 2 fields below */ 67 fdt32_t boot_cpuid_phys; /* Which physical CPU id we're 68 booting on */ 69 /* version 3 fields below */ 70 fdt32_t size_dt_strings; /* size of the strings block */ 71 72 /* version 17 fields below */ 73 fdt32_t size_dt_struct; /* size of the structure block */ 74 }; | cs |
line 928 ~ 930 :
setup_machine_fdt()
-> line 190 : [early_init_dt_scan]
early_ini_dt_scan() 함수는 early dt를 검증하고 node를 스캔하기 위한 함수이다.
1239 bool __init early_init_dt_scan(void *params) 1240 { 1241 bool status; 1242 1243 status = early_init_dt_verify(params); 1244 if (!status) 1245 return false; 1246 1247 early_init_dt_scan_nodes(); 1248 return true; 1249 } | cs |
line 1243 : early_init_dt_verify
내부에서 전달된 dt params의 유효성을 검증한다. fdt_magic(), fdt_version() 등을 이용해 fdt_header에 선언된 인자가 알맞은 값인지 검증하는 로직을 수행한다.
line 1247 : early_init_dt_scan_nodes()
of_scan_flat_dt()를 이용해 dt를 스캔한다. 여기서는 ealry에 필요한 내역만 scan을 진행한다. of_scan_flat_dt() 함수에서 ealry 에 필요한 dt 노드를 스캔한다.
1226 void __init early_init_dt_scan_nodes(void) 1227 { 1229 /* Retrieve various information from the /chosen node */ 1230 of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); 1231 1232 /* Initialize {size,address}-cells info */ 1233 of_scan_flat_dt(early_init_dt_scan_root, NULL); 1234 1235 /* Setup memory, calling early_init_dt_add_memory_arch */ 1236 of_scan_flat_dt(early_init_dt_scan_memory, NULL); 1237 } | cs |
스캔을 위한 콜백함수는 early_init_dt_scan_chosen() / ealry_init_dt_scan_root() / early_init_dt_scan_memory() 이렇게 3개의 함수이다.
하나씩 살펴보도록 하자.
setup_machine_fdt()
-> line 190 : [early_init_dt_scan]
-> early_init_dt_scan_chosen()
이 함수는 chosen 노드에서 정보를 검색하기 위한 용도로 사용된다.
1079 int __init early_init_dt_scan_chosen(unsigned long node, const char *uname, 1080 int depth, void *data) 1081 { 1082 int l; 1083 const char *p; 1084 1085 pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname); 1086 1087 if (depth != 1 || !data || 1088 (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0)) 1089 return 0; 1090 1091 early_init_dt_check_for_initrd(node); 1092 1093 /* Retrieve command line */ 1094 p = of_get_flat_dt_prop(node, "bootargs", &l); 1095 if (p != NULL && l > 0) 1096 strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE)); 1097 1098 /* 1099 * CONFIG_CMDLINE is meant to be a default in case nothing else 1100 * managed to set the command line, unless CONFIG_CMDLINE_FORCE 1101 * is set in which case we override whatever was found earlier. 1102 */ 1103 #ifdef CONFIG_CMDLINE 1104 #if defined(CONFIG_CMDLINE_EXTEND) 1105 strlcat(data, " ", COMMAND_LINE_SIZE); 1106 strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE); 1107 #elif defined(CONFIG_CMDLINE_FORCE) 1108 strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE); 1109 #else 1110 /* No arguments from boot loader, use kernel's cmdl*/ 1111 if (!((char *)data)[0]) 1112 strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE); 1113 #endif 1114 #endif /* CONFIG_CMDLINE */ 1115 1116 pr_debug("Command line is: %s\n", (char*)data); 1117 1118 /* break now */ 1119 return 1; 1120 } | cs |
line 1087 ~ 1089 :이 함수는 of_scan_flat_dt() 내부에서 iterator로 호출되면서 dt node의 이름이 전달된다. 이때 이름으로 chosen 노드인지 확인하기 위한 조건문이다.
line 1091 : early_init_dt_check_for_initrd()
initrd_start, initrd_end를 설정해 주기 위한 함수이다. initrd는 램디스크로 초기 루트 파일시스템으로 사용하기 위한 용도이다.
line 1094 : of_get_flat_dt_prop()
chosen 노드에서 bootargs 속성을 찾아 전역변수 boot_command_line에 저장한다.
setup_machine_fdt()
-> line 190 : [early_init_dt_scan]
-> early_init_dt_scan_root()
top level address와 size cells를 가져오기 위한 함수이다.
cell은 dt에서 사용하는 사이즈 단위이다. dt_root_size_cells 와 dt_root_addr_cells를 설정해 준다. 설정해주기 위해 dt에서 #size-cells와 #address-cells 속성을 가져와 설정해준다.
986 /** 987 * early_init_dt_scan_root - fetch the top level address and size cells 988 */ 989 int __init early_init_dt_scan_root(unsigned long node, const char *uname, 990 int depth, void *data) 991 { 992 const __be32 *prop; 993 994 if (depth != 0) 995 return 0; 996 997 dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT; 998 dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT; 999 1000 prop = of_get_flat_dt_prop(node, "#size-cells", NULL); 1001 if (prop) 1002 dt_root_size_cells = be32_to_cpup(prop); 1003 pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells); 1004 1005 prop = of_get_flat_dt_prop(node, "#address-cells", NULL); 1006 if (prop) 1007 dt_root_addr_cells = be32_to_cpup(prop); 1008 pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells); 1009 1010 /* break now */ 1011 return 1; 1012 } | cs |
setup_machine_fdt()
-> line 190 : [early_init_dt_scan]
-> early_init_dt_scan_memory()
메모리 memblock에 메모리를 등록해주기 위한 함수입니다. 메모리 노드를 찾아 분석을 하게됩니다.
1025 int __init early_init_dt_scan_memory(unsigned long node, const char *uname, 1026 int depth, void *data) 1027 { 1028 const char *type = of_get_flat_dt_prop(node, "device_type", NULL); 1029 const __be32 *reg, *endp; 1030 int l; 1031 bool hotpluggable; 1032 1033 /* We are scanning "memory" nodes only */ 1034 if (type == NULL) { 1035 /* 1036 * The longtrail doesn't have a device_type on the 1037 * /memory node, so look for the node called /memory@0. 1038 */ 1039 if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0) 1040 return 0; 1041 } else if (strcmp(type, "memory") != 0) 1042 return 0; 1043 1044 reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l); 1045 if (reg == NULL) 1046 reg = of_get_flat_dt_prop(node, "reg", &l); 1047 if (reg == NULL) 1048 return 0; 1049 1050 endp = reg + (l / sizeof(__be32)); 1051 hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL); 1052 1053 pr_debug("memory scan node %s, reg size %d,\n", uname, l); 1054 1055 while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { 1056 u64 base, size; 1057 1058 base = dt_mem_next_cell(dt_root_addr_cells, ®); 1059 size = dt_mem_next_cell(dt_root_size_cells, ®); 1060 1061 if (size == 0) 1062 continue; 1063 pr_debug(" - %llx , %llx\n", (unsigned long long)base, 1064 (unsigned long long)size); 1065 1066 early_init_dt_add_memory_arch(base, size); 1067 1068 if (!hotpluggable) 1069 continue; 1070 1071 if (early_init_dt_mark_hotplug_memory_arch(base, size)) 1072 pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n", 1073 base, base + size); 1074 } 1075 1076 return 0; 1077 } | cs |
line 1028 :
탐색하는 device tree node로 부터 "device_type" 속성을 가져온다.
line 1034 ~ 1042 :
탐색하려는 device tree node가 device_type이 null인지 아니면 memory인지 확인한다. 이 함수의 목적이 memory를 스캔하기 위함이기에 node 자체가 memory가 아니라면 스캔을 계속 진행할 이유가 없다.
line 1055 ~ 1074:
dt_mem_next_cell() 함수로 순차적으로 dt_root_addr_cells와 dt_root_size_cells를 이용해 base와 size를 구하게 된다. 그 후 base와 size의 값을 early_init_dt_add_memory_arch()에 전달하여 메모리 블럭을 추가해 준다.
'Linux > Kernel Analysis' 카테고리의 다른 글
[ARMv8] aarch64 프로세서 상태 레지스터(PSTATE) (0) | 2018.09.08 |
---|---|
[커널분석] parse_early_param() (0) | 2018.09.08 |
void __init early_fixmap_init(void) (작성중...) (0) | 2018.08.21 |
[Linux] Writing udev rules, udev 규칙 작성법에 관하여.. (1) | 2015.10.31 |
[Linux Kernel] 프로세스 상태 분석 (0) | 2014.07.17 |