DTS的建立解析(一)

        因爲工做需求,不得不看內核源碼。有關於 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在後面會被用到
   |
相關文章
相關標籤/搜索