因爲工做需求,不得不看內核源碼。有關於 dts 的相關解析知識跟你們分享下,這塊也是博主看了好長時間的源碼才串起來了。作個博客記錄下,順便複習一遍,也對有須要的人是個參考。node
關於dts我就不詳解介紹了,網上博客一大堆。咱們知道在內核啓動的過程當中,因爲dts的加入,我們先是將dts解析成樹型結構,而後放在內存中,等待內核後面註冊devices和driver的時候再來鋪開調用。架構
那麼dts是在哪塊被解析的呢?又是怎麼進行解析的呢。內核的啓動是在 start_kernel() 函數中進行啓動的,在 start_kernel() 函數中調用了 setup_arch(&command_line) 架構相關的函數。由於咱們分析的是64位的系統,因此 setup_arch() 在 arch/arm64/kernel/setup.c 中。咱們進入到這個函數中,涉及到dts的有三個函數,分別是 setup_machine_fdt(__fdt_pointer); arm64_memblock_init(); unflatten_device_tree(); 接下來咱們就分別分析下這三個函數,在他們裏面是怎樣進行dts的建立及解析的。ide
A、首先是 setup_machine_fdt(),咱們進到函數裏,它位於 arch/arm64/kernel/setup.c 中。函數源碼以下函數
static void __init setup_machine_fdt(phys_addr_t dt_phys) { void *dt_virt = fixmap_remap_fdt(dt_phys); if (!dt_virt || !early_init_dt_scan(dt_virt)) { pr_crit("\n" "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\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); while (true) cpu_relax(); } dump_stack_set_arch_desc("%s (DT)", of_flat_dt_get_machine_name()); }
在這個函數中,咱們再追進去看看 fixmap_remap_fdt() 和 early_init_dt_scan()。spa
fixmap_remap_fdt() 源碼以下命令行
void *__init fixmap_remap_fdt(phys_addr_t dt_phys) { void *dt_virt; int size; dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO); if (!dt_virt) return NULL; memblock_reserve(dt_phys, size); return dt_virt; }
咱們能夠看出這個函數是爲 fdt 創建地址映射,在該函數的最後,順便就調用 memblock_reserve 保留了該段內存debug
early_init_dt_scan() 源碼以下
指針
bool __init early_init_dt_scan(void *params) { bool status; status = early_init_dt_verify(params); if (!status) return false; early_init_dt_scan_nodes(); return true; }
咱們能夠看出在這個函數裏又調用了 early_init_dt_verify(params) 和 early_init_dt_scan_nodes() 函數,再追進去看看這兩個函數分別實現了什麼orm
early_init_dt_verify(params) 源碼以下內存
bool __init early_init_dt_verify(void *params) { if (!params) return false; /* check device tree validity */ if (fdt_check_header(params)) return false; /* Setup flat device-tree pointer */ initial_boot_params = params; of_fdt_crc32 = crc32_be(~0, initial_boot_params, fdt_totalsize(initial_boot_params)); return true; }
經過源碼,咱們能夠不難看出這個函數主要是檢驗頭部,判斷設備樹有效性而且設置設備樹的指針。
early_init_dt_scan_nodes() 源碼以下
void __init early_init_dt_scan_nodes(void) { /* Retrieve various information from the /chosen node */ of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); /* 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); }
經過源碼,咱們能夠看出這個函數作了三個事:a> 從設備樹中讀取chosen節點的信息,包括命令行boot_command_line,initrd location及size;b> 獲得根節點的{size,address}-cells信息;c> 讀出設備樹的系統內存設置。
那麼經過源碼的閱讀,咱們能夠看出這個 setup_machine_fdt() 函數主要是爲了輸入設備樹(DTB)首地址,以便在後面進行調用。
B、下面是 arm64_memblock_init() 這個函數,它位於 arch/arm64/mm/init.c 中。函數源碼以下
void __init arm64_memblock_init(void) { const s64 linear_region_size = -(s64)PAGE_OFFSET; BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1)); memstart_addr = round_down(memblock_start_of_DRAM(), ARM64_MEMSTART_ALIGN); 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 (memory_limit != (phys_addr_t)ULLONG_MAX) { memblock_mem_limit_remove_map(memory_limit); memblock_add(__pa_symbol(_text), (u64)(_end - _text)); } memblock_reserve(__pa_symbol(_text), _end - _text); early_init_fdt_scan_reserved_mem(); /* 4GB maximum for 32-bit only capable devices */ if (IS_ENABLED(CONFIG_ZONE_DMA)) arm64_dma_phys_limit = max_zone_dma_phys(); else arm64_dma_phys_limit = PHYS_MASK + 1; high_memory = __va(memblock_end_of_DRAM() - 1) + 1; dma_contiguous_reserve(arm64_dma_phys_limit); memblock_allow_resize(); }
咱們能夠看到在裏面調用了 early_init_fdt_scan_reserved_mem() 這個函數。咱們再追進去看看裏面作了什麼事,源碼以下
void __init early_init_fdt_scan_reserved_mem(void) { int n; u64 base, size; if (!initial_boot_params) return; /* Process header /memreserve/ fields */ for (n = 0; ; n++) { fdt_get_mem_rsv(initial_boot_params, n, &base, &size); if (!size) break; early_init_dt_reserve_memory_arch(base, size, 0); } of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); fdt_init_reserved_mem(); }
在裏面分別調用了 __fdt_scan_reserved_mem() 和 fdt_init_reserved_mem()。
__fdt_scan_reserved_mem() 源碼以下
/** * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory */ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, int depth, void *data) { static int found; const char *status; int err; if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) { if (__reserved_mem_check_root(node) != 0) { pr_err("Reserved memory: unsupported node format, ignoring\n"); /* break scan */ return 1; } found = 1; /* scan next node */ return 0; } else if (!found) { /* scan next node */ return 0; } else if (found && depth < 2) { /* scanning of /reserved-memory has been finished */ return 1; } status = of_get_flat_dt_prop(node, "status", NULL); if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0) return 0; err = __reserved_mem_reserve_reg(node, uname); if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL)) fdt_reserved_mem_save_node(node, uname, 0, 0); /* scan next node */ return 0; }
咱們能夠看到這個函數是用來解析reserved-memory節點的內存的,status = of_get_flat_dt_prop(node, "status", NULL) 這句是用來獲取設備樹屬性status,若是是okay則向下,err = __reserved_mem_reserve_reg(node, uname) 這個函數則是用來 判斷reg屬性,若是沒有此節點則保留該節點而後再繼續繼續掃描下一個節點節點。
總得來講,a> early_init_fdt_scan_reserved_mem() 這個函數是用來分析dts中的節點,從而進行保留內存的動做;b>fdt_init_reserved_mem() 函數則是用來預留reserved-memory節點的內存的。
C、最後是 unflatten_device_tree() 函數,它位於 drivers/of/fdt.c 中,函數源碼以下
void __init unflatten_device_tree(void) { __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false); /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ of_alias_scan(early_init_dt_alloc_memory_arch); }
在這個函數中調用了 __unflatten_device_tree() 和 of_alias_scan(),追進去看看分別作了什麼事
static void *__unflatten_device_tree(const void *blob, struct device_node *dad, struct device_node **mynodes, void *(*dt_alloc)(u64 size, u64 align), bool detached) { int size; void *mem; pr_debug(" -> unflatten_device_tree()\n"); if (!blob) { pr_debug("No device tree pointer\n"); return NULL; } pr_debug("Unflattening device tree:\n"); pr_debug("magic: %08x\n", fdt_magic(blob)); pr_debug("size: %08x\n", fdt_totalsize(blob)); pr_debug("version: %08x\n", fdt_version(blob)); if (fdt_check_header(blob)) { pr_err("Invalid device tree blob header\n"); return NULL; } /* First pass, scan for size */ size = unflatten_dt_nodes(blob, NULL, dad, NULL); if (size < 0) return NULL; size = ALIGN(size, 4); pr_debug(" size is %d, allocating...\n", size); /* Allocate memory for the expanded device tree */ mem = dt_alloc(size + 4, __alignof__(struct device_node)); if (!mem) return NULL; memset(mem, 0, size); *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); pr_debug(" unflattening %p...\n", mem); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); if (be32_to_cpup(mem + size) != 0xdeadbeef) pr_warning("End of tree marker overwritten: %08x\n", be32_to_cpup(mem + size)); if (detached && mynodes) { of_node_set_flag(*mynodes, OF_DETACHED); pr_debug("unflattened tree is detached\n"); } pr_debug(" <- unflatten_device_tree()\n"); return mem; }
在這個函數調用了兩次 unflatten_dt_nodes() 函數,第一次scan是爲了獲得保存全部node和property所須要的內存size;第二次調用纔是具體填充每個struct device_node和struct property結構體。
總得來講,a> unflatten_device_tree() 函數是爲了解析dtb文件,DTS節點信息被解析出來,將DTB轉換成節點是device_node的樹狀結構;b> of_alias_scan() 是在設置內核輸出終端,以及遍歷「/aliases」節點下的全部的屬性並掛入相應鏈表。
那麼在執行完以上三個函數以後,dts將會在被展開成樹狀結構分佈在內存中,等待後續的devices 和 driver 註冊的時候再來調用。如下是他們的關係圖
setup_arch() | |------> setup_machine_fdt(__fdt_pointer) | 輸入設備樹(DTB)首地址 | |------> fixmap_remap_fdt() | 爲fdt創建地址映射,在該函數的最後,順便就調用memblock_reserve 保留了該段內存 | |------> early_init_dt_scan() |------> early_init_dt_verify() | 檢驗頭部,判斷設備樹有效性而且設置設備樹的指針 | |------> early_init_dt_scan_nodes() | |---->of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line) | 從設備樹中讀取chosen節點的信息,包括命令行 boot_command_line,initrd location及size | |----> of_scan_flat_dt(early_init_dt_scan_root, NULL) | 獲得根節點的{size,address}-cells信息 | |----> of_scan_flat_dt(early_init_dt_scan_memory, NULL) | 讀出設備樹的系統內存設置 | |--------> arm64_memblock_init() | |------> early_init_fdt_scan_reserved_mem() | 分析dts中的節點,從而進行保留內存的動做 | |------> __fdt_scan_reserved_mem() | 解析reserved-memory節點的內存 | |----> status = of_get_flat_dt_prop(node, "status", NULL) | 獲取設備樹屬性status,若是是okay則向下 | |----> err = __reserved_mem_reserve_reg(node, uname) | 判斷reg屬性,若是沒有此節點則保留該節點; | 繼續掃描下一個節點節點 | |------> fdt_init_reserved_mem() | 預留reserved-memory節點的內存 | |--------> unflatten_device_tree() | 解析dtb文件,將DTB轉換成節點是device_node的樹狀結構 | |----> __unflatten_device_tree() | 第一次scan是爲了獲得保存全部node和property所須要的內存size; | 第二次調用纔是具體填充每個struct device_node和struct property結構體 | |----> of_alias_scan() | 設置內核輸出終端,以及遍歷「/aliases」節點下的全部的屬性並掛入相應鏈表 | | | 在執行完unflatten_device_tree()後,DTS節點信息被解析出來,保存到allnodes鏈表中,allnodes在後面會被用到 |