This section describes how dtb is converted to device node device_node. In fact, each device node is a structure.
Reserve the memory space where the dtb is located
Before explaining the device node, let's think about a problem.
Q: we put dtb files in a certain part of memory through uboot. Don't we have to worry about this memory being used by other programs? Won't this memory be overwritten during kernel operation?
A: when explaining the device tree file earlier, you know that you can specify the starting address and size of the memory space to be reserved through memreserve in the dts file, so that the kernel will not use this memory.
In fact, even if / memreserve / is not used to set reserved memory, the area occupied by dtb will be reserved when the kernel starts.
The function calling process is as follows:
start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c arm_memblock_init(mdesc); // arch/arm/kernel/setup.c early_init_fdt_reserve_self(); /* Reserve the dtb region */ // Keep the area occupied by DTB, that is, call: memblock_reserve early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0); early_init_fdt_scan_reserved_mem(); // Call memblock according to the memreserve information in dtb_ reserve
First, start_ kernel -> setup_ Arch, and then call arm_. memblock_ Init function, the corresponding desc structure is passed in.
Then, the calling procedure is: arm_ memblock_ init->early_ init_ fdt_ reserve_ self->early_ init_ dt_ reserve_ memory_ arch. (FDT: flat device tree)
early_ init_ dt_ reserve_ memory_ The arch function passes in the starting address (converting the virtual address to the physical address through _pa) and size of the dtb, and then in early_ init_ dt_ reserve_ memory_ Calling memblock_ in arch function Reserve function to preserve this area.
After that, the DTB file will always be saved in memory, and the data in the DTB file can be used at any time in the future.
Processing memreserve information
After the DTB file is kept in memory, the memreserve information in the DTB file is processed.
Call early_init_fdt_scan_reserved_mem function, in which the memreserve information (fdt_get_mem_rsv) in the dtb file will be obtained first, and then through early_ init_ dt_ reserve_ memory_ arch->memblock_ Reserve preserves memory.
To sum up, at arm_ memblock_ In the init function, there are two functions to reserve the memory occupied by the dtb file and the memory reserved by memreserve in the dtb file.
Unflatten is called when memory retention is complete_ device_ Tree function (start_kernel - > setup_arch - > unflatten_device_tree).
unflatten means flat. There are many nodes in the dts file. These nodes need to be extracted one by one to construct a tree.
Analysis unflatten_ device_ Before the tree function, first look at the two structures, device_node and properties.
device_node description
The first is device_node structure, defined as follows:
struct device_node { const char *name; // From the name attribute in the node. If there is no such attribute, it will be set to "NULL" const char *type; // From device in node_ Type attribute, if not, set to "NULL" phandle phandle; const char *full_name; // Node name [@ unit address], such as full of LED node_ Name is led struct fwnode_handle fwnode; struct property *properties; // Properties of nodes struct property *deadprops; /* removed properties */ struct device_node *parent; // Parent node of node struct device_node *child; // Child node of node struct device_node *sibling; // Sibling node of node (peer node) #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif };
In the dts file, each brace represents a node. The root node is a brace. There are many child nodes in the root node, and these child nodes are also braces.
In the root node, the memory node, the chosen node, and the led node are sibling nodes with the same parent node -- the root node.
In device_ In the node structure, there is struct device_node *parent,struct device_node *child,struct device_node *sibling, which are parent node, child node and brother node respectively, can build a tree through these three members.
properties description
device_ In the node structure, there is also a member struct property *properties that records node properties, which is defined as follows:
struct property { char *name; // Property name, pointing to the string in the dtb file int length; // The attribute value is the length of the value void *value; // Attribute value, pointing to the location of value in the dtb file, and the data is still stored in big endian struct property *next; // Property pointer, which can point to the next property of the node #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 };
Through these two structures, you can easily feel how dtb files are converted into devices_ Node structure.
Structure jz2440 DTS device tree
Let's construct jz2440 DTS device tree, below is jz2440 DTS content.
// SPDX-License-Identifier: GPL-2.0 /* * SAMSUNG SMDK2440 board device tree source * * Copyright (c) 2018 weidongshan@qq.com * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb */ #define S3C2410_GPA(_nr) ((0<<16) + (_nr)) #define S3C2410_GPB(_nr) ((1<<16) + (_nr)) #define S3C2410_GPC(_nr) ((2<<16) + (_nr)) #define S3C2410_GPD(_nr) ((3<<16) + (_nr)) #define S3C2410_GPE(_nr) ((4<<16) + (_nr)) #define S3C2410_GPF(_nr) ((5<<16) + (_nr)) #define S3C2410_GPG(_nr) ((6<<16) + (_nr)) #define S3C2410_GPH(_nr) ((7<<16) + (_nr)) #define S3C2410_GPJ(_nr) ((8<<16) + (_nr)) #define S3C2410_GPK(_nr) ((9<<16) + (_nr)) #define S3C2410_GPL(_nr) ((10<<16) + (_nr)) #define S3C2410_GPM(_nr) ((11<<16) + (_nr)) /dts-v1/; /memreserve/ 0x33f00000 0x100000; / { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; memory { /* /memory */ device_type = "memory"; reg = <0x30000000 0x4000000 0 4096>; }; /* cpus { cpu { compatible = "arm,arm926ej-s"; }; }; */ chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; }; led { compatible = "jz2440_led"; pin = <S3C2410_GPF(5)>; }; };
The first is the root node. The node diagram of the root node is as follows:
The memory nodes are as follows:
The chosen node is as follows:
led nodes are as follows:
memory node, chosen node and led node are sibling nodes. They have a common parent node - root node.
code
In__ unflatten_ device_ In the tree function, unflatten is called twice_ dt_ Nodes function, the first time is to calculate the memory size of the device tree, and the second time is to fill and establish the tree.
When creating a device tree, the device node will be obtained in turn, and then the attributes in the device node will be obtained in turn to allocate space.
Finally, a root node named of is established_ Root's device tree.
The calling process of the function is as follows:
unflatten_device_tree(); // arch/arm/kernel/setup.c __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c /* First pass, scan for size */ size = unflatten_dt_nodes(blob, NULL, dad, NULL); /* Allocate memory for the expanded device tree */ mem = dt_alloc(size + 4, __alignof__(struct device_node)); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); populate_node np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node)); np->full_name = fn = ((char *)np) + sizeof(*np); populate_properties pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property)); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)val;
populate_node function, when the node has no name attribute and device_ When the type attribute is, the name and type of the node are "< null >";
One strange thing is that in the code, if no node has no name attribute, it should add a name attribute according to the name of the node. In this way, except for the root node, the name of other nodes should not be "< null >".
This will not be investigated first. If there are problems in the future, we will analyze them in detail.