DTS的创建解析(一)

        由于工作需求,不得不看内核源码。有关于dts的相关解析知识跟大家分享下,这块也是博主看了好长时间的源码才串起来了。做个博客记录下,顺便复习一遍,也对有需要的人是个参考。

        关于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的创建及解析的。

        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()


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 保留了该段内存


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() 函数,再追进去看看这两个函数分别实现了什么


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在后面会被用到
   |


猜你喜欢

转载自blog.51cto.com/12810168/2103567
DTS