DTS creation analysis (1)

        Due to work requirements, I have to look at the kernel source code. I will share with you the relevant analytical knowledge about dts . This is also the source code that the blogger has read for a long time before stringing it together. Make a blog record, review it by the way, and it is also a reference for those in need.

        I will not explain in detail about dts, there are a lot of online blogs. We know that in the process of kernel startup, due to the addition of dts, we first parse dts into a tree structure, and then put them in memory, and wait for the devices and drivers to be registered behind the kernel before rolling out the call.

        So where is the dts parsed? How is it analyzed. The startup of the kernel is started in the start_kernel() function, and the setup_arch(&command_line) architecture-related functions are called in the start_kernel() function. Since we are analyzing a 64-bit system, setup_arch() is in arch/arm64/kernel/setup.c . We enter this function, there are three functions related to dts, namely setup_machine_fdt(__fdt_pointer); arm64_memblock_init(); unflatten_device_tree(); Next, we will analyze these three functions separately, and how to perform dts in them created and parsed.

        A. The first is setup_machine_fdt() , we enter the function, which is located in arch/arm64/kernel/setup.c . The function source code is as follows

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

        In this function, let's go after fixmap_remap_fdt() and early_init_dt_scan() .


The source code of fixmap_remap_fdt() is as follows

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;
}

        We can see that this function establishes an address mapping for fdt. At the end of the function, by the way, memblock_reserve is called to reserve this segment of memory


The source code of early_init_dt_scan() is as follows

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;
}

        We can see that the early_init_dt_verify(params) and early_init_dt_scan_nodes() functions are called again in this function, and then chase in to see what these two functions achieve respectively


The source code of early_init_dt_verify(params) is as follows

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;
}

        From the source code, we can easily see that this function mainly checks the header, judges the validity of the device tree and sets the pointer of the device tree .


The source code of early_init_dt_scan_nodes() is as follows

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);
}

        From the source code, we can see that this function does three things: a> Read the information of the chosen node from the device tree, including the command line boot_command_line, initrd location and size; b> Get the {size, address}- of the root node cells information; c> read out the system memory settings of the device tree.

        那么通过源码的阅读,我们可以看出这个 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();
}

        We can see that the function early_init_fdt_scan_reserved_mem() is called inside . Let's chase it in and see what's going on inside. The source code is as follows

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

        In it,   __fdt_scan_reserved_mem() and fdt_init_reserved_mem() are called respectively .


__fdt_scan_reserved_mem() The source code is as follows

/**
 * 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;
}

        We can see that this function is used to parse the memory of the reserved-memory node, status = of_get_flat_dt_prop(node, "status", NULL) This sentence is used to get the status of the device tree attribute, if it is OK, go down, err = __reserved_mem_reserve_reg(node, uname) This function is used to judge the reg attribute. If there is no such node, the node will be reserved and then continue to scan the next node node.

        In general, the a> early_init_fdt_scan_reserved_mem() function is used to analyze the nodes in the dts to reserve memory; the b>fdt_init_reserved_mem() function is used to reserve the memory of reserved-memory nodes.

      

         C. Finally, there is the unflatten_device_tree() function, which is located in drivers/of/fdt.c. The function source code is as follows

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);
}

        In this function, __unflatten_device_tree() and of_alias_scan() are called , and chase them to see what they have done.

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;
}

        In this function , the unflatten_dt_nodes() function is called twice. The first scan is to get the memory size required to save all nodes and properties; the second call is to specifically fill each struct device_node and struct property structure.

        In general, the a> unflatten_device_tree() function is to parse the dtb file, the DTS node information is parsed out, and the DTB is converted into a tree structure whose node is the device_node; b> of_alias_scan() is to set the kernel output terminal, and traverse " All attributes under the /aliases" node are linked to the corresponding linked list.

        Then after the above three functions are executed, dts will be expanded into a tree structure and distributed in memory, and will be called again when the subsequent devices and drivers are registered . The following is their relationship diagram

setup_arch()
    |
    |------> setup_machine_fdt(__fdt_pointer) 
        | Enter the first address of the device tree (DTB)
        |
        |------> fixmap_remap_fdt()
        | Create an address map for fdt. At the end of the function, by the way, memblock_reserve is called to reserve this segment of memory
        |
        |------> early_init_dt_scan()
            |------> early_init_dt_verify()
            | Check the header, determine the validity of the device tree and set the pointer of the device tree
            |
            |------> early_init_dt_scan_nodes()
                |
                |---->of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line)
                | Read the information of the chosen node from the device tree, including the command line boot_command_line, initrd location and size
                |
                |----> of_scan_flat_dt(early_init_dt_scan_root, NULL)
                | Get the {size, address}-cells information of the root node
                |
                |----> of_scan_flat_dt(early_init_dt_scan_memory, NULL)
                | Read the system memory settings of the device tree
                |
    |--------> arm64_memblock_init()
        |
        |------> early_init_fdt_scan_reserved_mem()
            | Analyze the nodes in the dts to perform memory-reserving actions
            |
            |------> __fdt_scan_reserved_mem()
                | Parse the memory of the reserved-memory node
                |
                |----> status = of_get_flat_dt_prop(node, "status", NULL)
                | Get the device tree attribute status, if it is OK, go down
                |
                |----> err = __reserved_mem_reserve_reg(node, uname)
                | Judge the reg attribute, if there is no such node, keep the node;
                | Continue to scan the next node node
                |
            |------> fdt_init_reserved_mem()
            | Reserve the memory of the reserved-memory node
            |
    |--------> unflatten_device_tree()
        | Parse the dtb file and convert the DTB into a tree structure whose node is device_node
        |
        |----> __unflatten_device_tree()
        | The first scan is to get the memory size required to save all nodes and properties;
        | The second call is to specifically fill each struct device_node and struct property structure
        |
        |----> of_alias_scan()
        | Set the kernel output terminal, and traverse all the attributes under the "/aliases" node and attach to the corresponding linked list
        |    
   |         
   | After executing unflatten_device_tree(), the DTS node information is parsed and saved in the allnodes linked list, allnodes will be used later
   |


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325673610&siteId=291194637
DTS