Parsing dtb === ###### tags: `Linux Kernel Study` `dts` `dtb` `device tree` `init` u-boot會透過通用暫存器傳遞dtb的起始位址 在kernel啟動初始階段將dtb的起始位址保存到__atags_pointer, 因此__atags_pointer指向dtb在記憶體中的起始位址。 然後跳轉到 start_kernel() (arch/arm/kernel/head-common.S) (arch/arm/kernel/head.S) ## struct machine_desc 用來描述platform架構的硬體資訊, 以vexpress-a9來說, 該結構體定義在: arch/arm/include/asm/mach/arch.h kernel初始化階段根據dtb選擇machine_desc來基於對不同的platform進行初始化 ## Flow **start_kernel**() (init/main.c)   +--**setup_arch**() (arch/arm/kernel/setup.c)     +-- mdesc = **setup_machine_fdt**(__atags_pointer) (arch/arm/kernel/devtree.c)     |  +-- **of_flat_dt_match_machine**() (/drivers/of/fdt.c)     |  |  +-- **arch_get_next_mach**()     |  |  |  called by callback get_next_compat()     |  |  |      |  |  +-- **of_flat_dt_match**()     |  +-- **early_init_dt_scan_nodes**() (/drivers/of/fdt.c)     |     +-- **of_scan_flat_dt**(early_init_dt_scan_chosen, boot_command_line)     |     |  +-- **early_init_dt_scan_chosen**()     |     +-- **of_scan_flat_dt**(early_init_dt_scan_root, NULL);     |     |  +--**early_init_dt_scan_root**()     |     +-- **of_scan_flat_dt**(early_init_dt_scan_memory, NULL);     |     |  +-- **early_init_dt_scan_memory**()     +-- **unflatten_device_tree**() ## setup_arch ```c= void __init setup_arch(char **cmdline_p) { const struct machine_desc *mdesc; ...... /* 傳入dtb的起始位址__atags_pointer獲取platform的machine_desc結構體*/ mdesc = setup_machine_fdt(__atags_pointer); /* 不是dtb格式的話用atags獲得machine_desc*/ if (!mdesc) mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); /* 以上兩者都沒有的話, 報err*/ if (!mdesc) { early_print("\nError: invalid dtb and unrecognized/unsupported machine ID\n"); early_print(" r1=0x%08x, r2=0x%08x\n", __machine_arch_type, __atags_pointer); if (__atags_pointer) early_print(" r2[]=%*ph\n", 16, phys_to_virt(__atags_pointer)); dump_machine_table(); } /* 保存machine_desc與其name*/ machine_desc = mdesc; machine_name = mdesc->name; dump_stack_set_arch_desc("%s", mdesc->name); ...... /* 創建device node tree*/ unflatten_device_tree(); ...... } ``` ## setup_machine_fdt ```c= /** * setup_machine_fdt - Machine setup when an dtb was passed to the kernel * @dt_phys: physical address of dt blob * * If a dtb was passed to the kernel in r2, then use it to choose the * correct machine_desc and to setup the system. */ const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { const struct machine_desc *mdesc, *mdesc_best = NULL; ...... if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) return NULL; /* * iterate all machine_desc尋找匹配的machine_desc, * callback arch_get_next_mach用來獲取下一個mdesc */ mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); if (!mdesc) { ...... dump_machine_table(); /* does not return */ } /* We really don't want to do this, but sometimes firmware provides buggy data */ if (mdesc->dt_fixup) mdesc->dt_fixup(); /** 獲取dtb的一些資訊*/ early_init_dt_scan_nodes(); /* Change machine number to match the mdesc we're using */ __machine_arch_type = mdesc->nr; return mdesc; } ``` ## of_flat_dt_match_machine ```c= /** * of_flat_dt_match_machine - Iterate match tables to find matching machine. * * @default_match: A machine specific ptr to return in case of no match. * @get_next_compat: callback function to return next compatible match table. * * Iterate through machine match tables to find the best match for the machine * compatible string in the FDT. */ const void * __init of_flat_dt_match_machine(const void *default_match, const void * (*get_next_compat)(const char * const**)) { const void *data = NULL; const void *best_data = default_match; const char *const *compat; unsigned long dt_root; unsigned int best_score = ~1, score = 0; dt_root = of_get_flat_dt_root(); /* iterate all machine_desc, 找到最合適的 struct machine_desc*/ while ((data = get_next_compat(&compat))) { score = of_flat_dt_match(dt_root, compat); if (score > 0 && score < best_score) { best_data = data; best_score = score; } } if (!best_data) { ...... } pr_info("Machine model: %s\n", of_flat_dt_get_machine_name()); return best_data; } ``` ## early_init_dt_scan_nodes 分別獲得dts中 /chosen (u-boot傳送的boot args), {size,address}-cells info, 以及 memory node的資訊 ```c= void __init early_init_dt_scan_nodes(void) { int rc = 0; /* Retrieve various information from the /chosen node */ rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); if (!rc) pr_warn("No chosen node found, continuing without\n"); /* Initialize {size,address}-cells info */ of_scan_flat_dt(early_init_dt_scan_root, NULL); /* Setup memory, calling early_init_dt_add_memory_arch */ of_scan_flat_dt(early_init_dt_scan_memory, NULL); } ``` #### {size,address}-cells 在 dts中 address-cells代表用多少個cell表示base address, size-cells表示該空間有多大, 如 /arch/arm/boot/dts/vexpress-v2p-ca9.dts address-cells與size-cells皆為1, 因此可以看到在memory node reg的部分表示addr與size各使用一個cell, 0x60000000 為 memery的base address 0x40000000 為 memory的大小, 為 1 GB ```c= /dts-v1/; #include "vexpress-v2m.dtsi" / { model = "V2P-CA9"; ...... compatible = "arm,vexpress,v2p-ca9", "arm,vexpress"; ...... #address-cells = <1>; #size-cells = <1>; chosen { }; ...... memory@60000000 { device_type = "memory"; reg = <0x60000000 0x40000000>; }; ``` ## of_scan_flat_dt 在early_init_dt_scan_nodes呼叫三次of_scan_flat_dt,分別傳入callback early_init_dt_scan_chosen, early_init_dt_scan_root, early_init_dt_scan_memory 以分別獲取dtb的/chosen, {size,address}-cells, memory資訊 of_scan_flat_dt會iterate dtb的節點, 並呼叫callback it, callback return 1則停止iterate, 回傳0,代表不是這個節點, 繼續iterate ```c= /** * of_scan_flat_dt - scan flattened tree blob and call callback on each. * @it: callback function * @data: context data pointer * * This function is used to scan the flattened device-tree, it is * used to extract the memory information at boot before we can * unflatten the tree */ int __init of_scan_flat_dt(int (*it)(unsigned long node, const char *uname, int depth, void *data), void *data) { const void *blob = initial_boot_params; /*blob指向dtb起始?*/ const char *pathp; int offset, rc = 0, depth = -1; if (!blob) return 0; for (offset = fdt_next_node(blob, -1, &depth); /* 從根node開始iterate, 呼叫callback*/ offset >= 0 && depth >= 0 && !rc; /** rc=0繼續for迴圈*/ offset = fdt_next_node(blob, offset, &depth)) { /*繼續下一個節點*/ pathp = fdt_get_name(blob, offset, NULL); /*獲取節點名稱*/ rc = it(offset, pathp, depth, data); /* 呼叫 callback, return 1則停止for迴圈*/ } return rc; } ``` ## unflatten_device_tree 將 dtb 轉換為樹狀結構,每個節點保存在struct device_node中 #### struct device_node 該結構用來保存 device_tree節點 ```c= struct device_node { const char *name; phandle phandle; const char *full_name; struct fwnode_handle fwnode; struct property *properties; struct property *deadprops; /* removed properties */ struct device_node *parent; struct device_node *child; struct device_node *sibling; #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) unsigned int unique_id; struct of_irq_controller *irq_trans; #endif }; ``` device_node內嵌的property結構描述節點的屬性 ```c= struct property { char *name; int length; void *value; struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif }; ``` ```c= /dts-v1/; #include "vexpress-v2m.dtsi" / { /* root節點 (struct device_node)*/ /* 這些是struct property*/ model = "V2P-CA9"; ...... compatible = "arm,vexpress,v2p-ca9", "arm,vexpress"; ...... #address-cells = <1>; #size-cells = <1>; chosen { }; ...... memory@60000000 { /* 節點node :struct device_info*/ /* 這些是struct property*/ device_type = "memory"; reg = <0x60000000 0x40000000>; }; ``` ![](https://i.imgur.com/HWSDelR.png)