Device tree analysis Source code analysis <devicetree>-1. Basic structure

  • linux-v5.6

References:

1. DTB format

According to the FLATTENED DEVICETREE (DTB) FORMAT chapter  of the device tree specification , the composition of DTB is as follows:

 

insert image description here

 Header (struct ftd_header): used to indicate the offset address of each part, the size of the entire file, version number, etc.;
memory reservation block (memory reservation block): store the memory that needs to be reserved declared in the dts file information;
node block (structure block): the information of each node will be placed in the structure block;
strings block (strings block): store string information;
 

Compilation and viewing tools

The method of using the dtc tool is: dtc –I dts –O dtb –o xxx.dtb xxx.dts, the dtb file corresponding to the dts file can be generated and the
Linux terminal executes ftddump –h

Example of node information in Device Tree

insert image description here

  • dt_struct stores node value and name related information, such as: "A string" and node1
  • dt_string stores the property name, such as: "a-string-property"
  • You can add a lable to a device node, and then you can access this lable in the form of &lable, which is referenced through a phandle (pointer handle). For example, node1 in the figure is a lable, the child node child-node@0 of node@0 refers to the node@1 node through &node1, after being compiled by the DTC tool, &node1 will become a special integer number n, assuming n If the value is 1, then two attributes are automatically generated under the node@1 node. The attributes are as follows:
    linux,phandle = <0x00000001>;
    phandle = <0x00000001>;
    a-reference in the child-node@0 of node@0 -to-something=<&node1> would become a-reference-to-something=<0x00000001>. Here 0x00000001 is a phandle value (unique integer value), and the referenced node can be found indirectly through this special number in the subsequent kernel
     

The head of the Device Tree file structure
dtb first stores the structure information of fdt_header, followed by the filling area, the filling size is off_dt_struct – sizeof(struct fdt_header), and the filling value is 0. Then there is the relevant information of the struct fdt_property structure. Finally the dt_string part
insert image description here

1.1 header device tree header information

The first is struct fdt_header, which is defined as follows:

/* scripts/dtc/libfdt/fdt.h */
struct fdt_header { uint32_t magic; //The fixed start number of dtb file 0xd00dfeed uint32_t totalsize; //The size of dtb file uint32_t off_dt_struct; //Address offset value of structure block area , Calculate uint32_t off_dt_strings from the beginning of the file ; //The address offset value of the strings block area, calculate uint32_t off_mem_rsvmap from the beginning of the file; //The address offset value of the memory reservation block area, calculate uint32_t version from the beginning of the file; //Device tree data The version of the structure uint32_t last_comp_version; //The lowest version of the device tree data structure uint32_t boot_cpuid_phys; //The system boots the physical ID of the CPU, and its value should be the same as the reg under the CPU node in the device tree file The attribute values ​​are equal uint32_t size_dt_strings; //The number of bytes in the structure block area uint32_t size_dt_struct; //The number of bytes in the strings block area };










All members are 32-bit integers and stored in big-endian mode; the starting addresses and sizes of the memory reservation block, structure block, and strings block can be known from the header.

Let's take the dtb file of Raspberry Pi 4b as an example, first use the fdtdump tool to dump the dtb file:

 fdtdump -sd bcm2711-rpi-4-b.dtb > dtb_dump

header information

View the dtb_dump file, the header information is as follows:

bcm2711-rpi-4-b.dtb: found fdt at offset 0
/dts-v1/;
// magic:               0xd00dfeed
// totalsize:           0xa721 (42785)
// off_dt_struct:       0x48
// off_dt_strings:      0x98dc
// off_mem_rsvmap:      0x28
// version:             17
// last_comp_version:   16
// boot_cpuid_phys:     0x0
// size_dt_strings:     0xe45
// size_dt_struct:      0x9894

 View the corresponding hexadecimal data:

00000000: d0 0d fe ed 00 00 a7 21 00 00 00 48 00 00 98 dc
00000010: 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00
00000020: 00 00 0e 45 00 00 98 94

 It was found that it is indeed stored in big endian mode.

 1.2 memory reservation block

The memory area reserved by the device tree is used to store and protect some important data, which is related to the implementation of a specific platform and will not be discussed in detail in this article; the corresponding data structure is as follows:

struct fdt_reserve_entry {
        fdt64_t address;
        fdt64_t size;
};

The member is a 64-bit integer, which records the start address and size of the reserved memory area.

Assuming you want to reserve the highest 1M of 64M memory for your own use, add the following sentence in the dts file: /memreserve/ 0x33f00000 0x100000

/memreserve/ 0x33f00000 0x100000;
/ {
    name = "sample"
    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";
    };
};

 1.3 structure block

Struct Block is composed of a series of continuous slices, each slice starts with a 32-bit token , the token is stored in big endian order, and some tokens are followed by additional data (extra data). All tokens are 32bit aligned, and those less than 32bit will be filled with 0 in front.

//Describe the node
struct fdt_node_header {     fdt32_t tag;//The corresponding token, such as FDT_BEGIN_NODE,     char name[0]; // The node name is stored in the structure block as a string ending with '\0' as additional data                            //32 -bits alignment, insufficient bits are filled with 0x0 };




//Description properties use fdt_property to describe
struct fdt_property {     fdt32_t tag; //tag is a property, the value is FDT_PROP;     fdt32_t len;//len is the length of the property value, including '\0', unit: byte)     fdt32_t nameoff ;//The offset address of the name storage location relative to off_dt_strings (string block)     char data[0];//Attribute value, stored in the form of a string ending with '\0' structure block 32-bits alignment };




Among them, tag has five types of tokens:

#define FDT_BEGIN_NODE  0x1             /* Start node: full name */
#define FDT_END_NODE    0x2             /* End node */
#define FDT_PROP        0x3             /* Property: name off,size, content */


#define FDT_NOP         0x4             /* nop */
#define FDT_END         0x9

  • 0x00000001 //FDT_BEGIN_NODE, indicating the beginning of a note node, indicating that the following content is node information. FDT_BEGIN_NODE is followed by the node name, and the node name is a string ending with "\0". If the name contains an address, the string should also contain the address (32-bit alignment, not satisfied with 0).
  • 0x00000002 //FDT_END_NODE, indicating the end of a note node. FDT_END_NODE has no additional information, so it is immediately followed by the next token other than FDT_PROP.
  • 0x00000003 //FDT_PROP, indicating the start of a property. It is followed by the extra data describing the attribute information, and its extra data is stored in the following C structure, which is followed by the attribute name and a string ending with "\0".
  • 0x00000004 //FDT_NOP, special data, will be ignored when parsing the device tree. This token has no extra data, so it is followed by the next token. If you want to remove a note or attribute from dtb, you can use it to overwrite the note or attribute, so that you don't have to move other data in the dtb file
  • 0x00000009 //FDT_END, indicating the end of the struct block area. A dtb file should contain only one FDT_END token, and the FDT_END token should be the last token. There is no extra data, and the content of the string block is directly followed.

insert image description here

Case 1:

Let's take a node in the Raspberry Pi 4b device tree for illustration:

 / {
    memory@0 {
                device_type = "memory";
                reg = <0 0 0>;
    };

......
};

 The corresponding hexadecimal data is as follows:

000071f0: 00 00 00 01 6d 65 6d 6f 72 79 40 30 00 00 00 00  ....memory@0....
00007200: 00 00 00 03 00 00 00 07 00 00 04 8d 6d 65 6d 6f  ............memo
00007210: 72 79 00 00 00 00 00 03 00 00 00 0c 00 00 01 69  ry.............i
00007220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02  ................

  • 00 00 00 01:FDT_BEGIN_NODE   fdt_node_header.tag
  • 6d 65 6d 6f 72 79 40 30 00: memory@0 (including the trailing '\0'), the following 00 00 00 is 32-bits to complete   fdt_node_header.name
  • 00 00 00 03:FDT_PROP
  • 00 00 00 07: len, indicating that the length of the attribute value is 7 (memory plus the last '\0', exactly 7 bytes)
  • 00 00 04 8d : nameoff , which means that the offset of the name of the attribute in the strings block is 0x048d. From the header, the starting address of the strings block is 0x98dc , so the starting address of the name is 0x9d69 . View the address data: 00009d60: 65 2d 6d 65 74 68 6f 64 00 64 65 76 69 63 65 5f  e-method. device_ 00009d70: 74 79 70 65 00 63 70 75 2d 72 65 6c 65 61 73 65   type .cpu -release              found 64 65 76 69 63 65 5f 74 79 70 65 00 is exactly device_type (including the trailing '\0')
  • 6d 65 6d 6f 72 79 00 00 : The attribute value memory (including the trailing '\0', the length is 7), the following 00 is 32-bits padding
  • 00 00 00 03:FDT_PROP
  • 00 00 00 0c : len, the attribute value length is 12 "reg = <0 0 0>;"
  • 00 00 01 69: nameoff, the starting address of the name reg is 0x9a45 (0x98dc+0x0169), view the address data: 00009a40: 6e 67 65 73 00 72 65 67 00 69 6e 74 65 72 72 75 nges.reg.interru Found that 72 65 67 00 is exactly reg (including the trailing '\0')
  • 00 00 00 00 00 00 00 00 00 00 00 00: The value of reg is an integer, an integer of 32 bits, without the trailing '\0'
  • 00 00 00 02:FDT_END_NODE

Case 2 

led {
    compatible = "jz2440_led";
    pin = <S3C2410_GPF(5)>;
};

00000140:35 32 30 30 00 00 00 00 00 00 00 02 00 00 00 01

00000150:6c 65 64 00 00 00 00 03 00 00 00 0b 00 00 00 06

00000160: 6a 7a 32 34 34 30 5f 6C 65 64 00 00 00 00 00 03

00000170:00 00 00 04 00 00 00 45 00 05 00 05 00 00 00 02

00000180:00 00 00 02 00 00 00 09 ....

 00 00 00 01  6c 65 64 00  :   

FDT_BEGIN_NODE led\0

00 00 00 03 00 00 00 0b 00 00 00 06   6a 7a 32 34 34 30 5f 6C 65 64 00 00

FDT_PROP length offset jz2440_led\0 (the last 00 of 11bytes: 32bits is filled)

00 00 00 03 00 00 00 04 00 00 00 45 00 05 00 05                         00 00 00 02

FDT_PROP length offset S3C2410_GPF(5) pin definition FDT_END_NODE

00 00 00 02                           00 00 00 09

root node(/) FDT_END_NODE  FDT_END

1.4 string block

Immediately after the structure block (FDT_END).

Store the attribute names used in the struct block. These attribute names may have a high repetition rate, which saves space. Strings in this region are simply concatenated without byte alignment

1.5 Zero-length arrays

A zero-length array is generally used as the last member of a structure to access a section of memory after the structure object (usually dynamically allocated), see an example on the GNU C official website:

/* https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html */
struct line {
  int length;
  char contents[0];
};

struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
thisline->length = this_length;

contents is just a mark, and the size of sizeof (struct line) is the same as sizeof (int); when using it, you can use thisline->contents to access the memory of this_length size that you apply for later;

Since the name of the device tree node node and the length of the property value are not fixed, the fdt_node_header and fdt_property structures are implemented using zero-length arrays.

Two, dtb data analysis

The first chapter introduces the overall composition of the dtb file, including the composition of the header. The structure block area is composed of tag, node name, property len, property nameoff and property value. The strings block is composed of various property names. This chapter Introduce how the linux kernel finds and obtains these contents.

The entry function started by the kernel is stext , which will assign the dtb address (physical address, stored in the x21 register) from the bootloader to the __fdt_pointer variable :

ENTRY(stext)
--> __primary_switch
    --> __primary_switched
        --> str_l   x21, __fdt_pointer, x5          // Save FDT pointer

In this way, the start_kernel executed later can know the physical address of the dtb file in the memory at this time, so as to map the dtb file and perform data analysis .

2.1 tag

Use the fdt_next_tag function to get the current tag value and find the offset of the next tag.

/* scripts/dtc/libfdt/fdt.c 
 * fdt: virtual address of dtb file
 * startoffset: offset of current tag
 * nextoffset: offset of next tag to be searched
 */

/*

off_dt_struct: dt_struct Offset

*/

#define fdt_off_dt_struct(fdt)        (fdt_get_header(fdt, off_dt_struct))


 

/*

field: ftd_header_t.off_dt_struct

*/

#define fdt_get_header(fdt, field) \
    (fdt32_to_cpu( ((const struct fdt_header *)(fdt))->field))


 

const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
{
    unsigned absoffset = offset + fdt_off_dt_struct(fdt);

   

    /**/

    if ((absoffset < offset)
        || ((absoffset + len) < absoffset)
        || (absoffset + len) > fdt_totalsize(fdt))
        return NULL;

    if (fdt_version(fdt) >= 0x11)
        if (((offset + len) < offset)
            || ((offset + len) > fdt_size_dt_struct(fdt)))
            return NULL;

/*

(const char *)fdt + fdt_off_dt_struct(fdt) + offset;

*/

    return _fdt_offset_ptr(fdt, offset);
}


uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
{         int offset = startoffset; //Start from 0

        *nextoffset = -FDT_ERR_TRUNCATED;


        /* (const char *)fdt + fdt_off_dt_struct(fdt) + offset
         * Get the current tag first address here : ftd + structure block offset +offset
         */
        tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); // FDT_TAGSIZE=32bits
        if (!tagp)
                return FDT_END; /* premature end */


        /* fdt32_to_cpu(x) Get the 32bit value of big endian mode
         * --> (FDT_FORCE uint32_t)CPU_TO_FDT32(x)
         * --> ((EXTRACT_BYTE(x, 0 ) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \
         * (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3))
         * ie tagp[0]<<24 + tagp [1]<<16 + tagp[2]<<8 + tagp[3]
         * That is, the tag value of 32bits size is obtained
         */

        tag = fdt32_to_cpu(*tagp);


        /* skip the current tag byte, tag is 32bits */
        offset += FDT_TAGSIZE;
        * nextoffset = -FDT_ERR_BADSTRUCTURE;


        switch (tag) {         case FDT_BEGIN_NODE:                 /* If it is FDT_BEGIN_NODE, you also need to skip the node name */                 do {                         p = fdt_offset_ptr(fdt, offset++, 1);// move one byte at a time                 } while (p && ( *p != '\0' )); // Find the current node value end position                 break;





        case FDT_PROP:
                /* If it is FDT_PROP, you also need to skip the length of len, nameoff and property value*/
                lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); //take out len
                ​​offset += sizeof(struct fdt_property ) - FDT_TAGSIZE 
                        + fdt32_to_cpu(*lenp); //Skip the whole block of FDT_PROP, point to the next TAG
                break;

        case FDT_END: ​​//No additional data
        case FDT_END_NODE:
        case FDT_NOP:
                break;

        default:
                return FDT_END;
        }


        /* Check whether the current offset is legal: parameter 1-the beginning position of this search, parameter 2-the length of the characters to be searched*/ if ( !
        fdt_offset_ptr (fdt, startoffset, offset - startoffset)  )
                return FDT_END; /* premature end */


        /* The device tree has a 32-bits alignment requirement, and the aligned nextoffset is the offset of the next tag 
         * (((offset) + (FDT_TAGSIZE) - 1) & ~((FDT_TAGSIZE) - 1))
         * --- ----------- --------------
         * Guaranteed Carry Clear extra bits
         */
        *nextoffset = FDT_TAGALIGN(offset);


        /* Return the current tag value*/
        return tag ;
}

2.2 node

Use the fdt_next_node function to find the next node:

int fdt_next_node (const void *fdt, int offset, int * depth )
{         int nextoffset = 0;         uint32_t tag;         /* of_scan_flat_dt The offset passed in when obtaining the first node node is -1, and          *fdt_check_node_offset_ check is not performed at this time          * /         if (offset >= 0)                 /* fdt_check_node_offset_ mainly performs some checks:                  * 1. Whether the offset is >= 0, this sentence is actually a repeated judgment in this situation                  * 2. Whether the offset is 32-bits aligned                  * 3. The current tag Whether it is FDT_BEGIN_NODE                  * 4. If the check is passed, return the value of the offset parameter                  */                 if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)













                        return nextoffset;

        do {                 offset = nextoffset;                 /* Get the current tag value and the offset of the next tag (nextoffset) */                 tag = fdt_next_tag(fdt, offset, &nextoffset);


                switch (tag) {
                case FDT_PROP:
                case FDT_NOP:
                        break;

                case FDT_BEGIN_NODE:
                        if (depth)  //The depth of the search, when encountering a child node depth plus one
                                (*depth)++; 
                        break;

                case FDT_END_NODE:
                        /* FDT_END_NODE is followed by either FDT_BEGIN_NODE,
                         * or FDT_END, when FDT_END returns directly ((--(*depth) is -1)
                         */
                        if (depth && ((--(*depth)) < 0) )
                                return nextoffset;
                        break;

                case FDT_END:
                        ​​/* When the tag is FDT_END, the offset will exceed the structure block area,
                         * nextoffset is -FDT_ERR_BADSTRUCTURE, which is -11
                         */
                        if ((nextoffset >= 0)
                            || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth) )
                                return -FDT_ERR_NOTFOUND;
                        else
                                return nextoffset;
                }
        } while (tag != FDT_BEGIN_NODE);

        /* Return FDT_BEGIN_NODE or FDT_END, which can be distinguished by depth value*/
        return offset;
}

With the starting offset of the node , the corresponding virtual address can be calculated and converted to the fdt_node_header structure to obtain the node name, such as the fdt_get_name function:

const char *fdt_get_name(const void *fdt, int nodeoffset, int *len)
{
    const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset);
    nameptr = nh->name;
    /* 因name以'\0'结尾,故可直接使用strlen计算长度 */
    *len = strlen(nameptr);
}

About the node name :

  • For device tree version0~3: node name is the full path name starting with '/' and ending with '\0'
  • For device tree version16 and above: the node name is just the name of the node itself ending with '\0' ( the name of the root node is '\0' )

 of_scan_flat_dt

int __init of_scan_flat_dt(int (*it)(unsigned long node,
                     const char *uname, int depth,
                     void *data),
               void *data)
{
    const void *blob = initial_boot_params;
    const char *pathp;
    int offset, rc = 0, depth = -1;

    if (!blob)
        return 0;

    for (offset = fdt_next_node(blob, -1, &depth);
         offset >= 0 && depth >= 0 && !rc;
         offset = fdt_next_node(blob, offset, &depth)) {

        pathp = fdt_get_name(blob, offset, NULL);
        if (*pathp == '/')
            pathp = kbasename(pathp);
        rc = it(offset, pathp, depth, data);
    }
    return rc;
}
 

2.3 property

Use the nextprop_function to find the next property in node.

static int nextprop_(const void *fdt, int offset)
{
        uint32_t tag;
        int nextoffset;

        do {
                tag = fdt_next_tag(fdt, offset, &nextoffset);

                switch (tag) {
                case FDT_END:
                        if (nextoffset >= 0)
                                return -FDT_ERR_BADSTRUCTURE;
                        else
                                return nextoffset;

                case FDT_PROP:
                        return offset;
                }
                offset = nextoffset;
        } while (tag == FDT_NOP);

        return -FDT_ERR_NOTFOUND;
}

The fdt_first_property_offset and fdt_next_property_offset functions both call nextprop_ to obtain the first property and the next property in the node; with the property initial offset, you can obtain other members of the fdt_property structure (except the property name) just like the node operation.

The property name can be obtained through the fdt_get_string function:

const char *fdt_get_string(const void *fdt, int stroffset, int *lenp)
{
        int32_t totalsize = fdt_ro_probe_(fdt);
        /* stroffset: nameoff
         * absoffset: property name字符串在dtb中的绝对偏移
         */
        uint32_t absoffset = stroffset + fdt_off_dt_strings(fdt);
        size_t len;
        const char *s, *n;

        len = totalsize - absoffset;

        if (fdt_magic(fdt) == FDT_MAGIC) {
                if (stroffset < 0)
                        goto fail;
                if (fdt_version(fdt) >= 17) {
                        if (stroffset >= fdt_size_dt_strings(fdt))
                                goto fail;
                        if ((fdt_size_dt_strings(fdt) - stroffset) < len)
                                len = fdt_size_dt_strings(fdt) - stroffset;
                }
        } 
        /* 获取property name的虚拟地址 */
        s = (const char *)fdt + absoffset;
        /* 获取从s第一次出现'\0'字符的地址 */
        n = memchr(s, '\0', len);
        if (!n) {
                /* missing terminating NULL */
                err = -FDT_ERR_TRUNCATED;
                goto fail;
        }

        if (lenp)
                *lenp = n - s;
        return s;
}

 of_get_flat_dt_prop

const void *__init of_get_flat_dt_prop(unsigned long node, const char *name,
                       int *size)
return: return property value

size: attribute value length, 32bits aligned

Example 1:

        prop = of_get_flat_dt_prop (node, "phandle", &len);
        if (!prop)
            prop = of_get_flat_dt_prop (node, "linux,phandle", &len);
        if (prop)
            rmem->phandle = of_read_number(prop, len/4) ; //len=4, just read a 32bit

linux,phandle=<128> //The attribute value length is 4 bytes

Example 2:

chosen {

bootargs = "earlycon=sprd_serial,0x70100000,115200n8 loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw androidboot.hardware=sc9830";

linux,initrd-start = <0x85500000>; //The attribute value length is 4 bytes

linux,initrd-end = <0x855a3212>;

};

    prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
    if (!prop)
        return;
    start = of_read_number(prop, len/4);

    prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
    if (!prop)
        return;

View of_read_number, read a 32-bit number, so the length of the attribute value must be divided by 4

static inline u64 of_read_number(const __be32 *cell, int size)
{
    u64 r = 0;
    while (size--)
        r = (r << 32) | be32_to_cpu(*(cell++));
    return r;
}

3. Kernel processing flow

After knowing the methods of obtaining various information of dtb, let's look at the specific processing flow of the kernel on the device tree.

3.1 Important data structures

The kernel uses device_node and property structures to record node and attribute information under node.

/* include/linux/of.h */
struct device_node {
        /* node名称,取"/"与"@"之间的字符 */
        const char *name;
        phandle phandle;
        /* node路径全称 */
        const char *full_name;
        /* Firmware device相关 */
        struct fwnode_handle fwnode;

        /* node下的第一个property */
        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;
        struct  device_node *child;
        struct  device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
        struct  kobject kobj;
#endif
        unsigned long _flags;
        void    *data;
#if defined(CONFIG_SPARC)
        unsigned int unique_id;
        struct of_irq_controller *irq_trans;
#endif
};

struct property {
        char    *name;
        int     length;
        void    *value;
        /* 同一个node下的property会形成一个单链表 */
        struct property *next;
#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
};

3.2 Analysis process

The approximate calling path is as follows:

start_kernel
--> setup_arch(&command_line);
    --> setup_machine_fdt(__fdt_pointer);
        /* acpi与dt有着类似的功能,但主要用于提高电源效率 */
        if (acpi_disabled)
                unflatten_device_tree();

The following mainly analyzes the two functions setup_machine_fdt and unflatten_device_tree.

3.2.1 setup_machine_fdt

The setup_machine_fdt function is used to parse some necessary attributes for system startup in advance .

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{         int size;         /* In order to support subsequent modification of fdt by early_init_dt_scan, the device tree physical address          * is mapped here in a readable and writable manner. For details, see comiit e112b032a72c7(arm64: map FDT as RW for          * early_init_dt_scan())          * Since the memory management subsystem has not been initialized at this time, fixmap region is used, and the related commit is          * 61bd93ce801bb(arm64: use fixmap region for permanent FDT mapping)          */         void *dt_virt = fixmap_remap_fdt (dt_phys, &size , PAGE_KERNEL);         const char *name;








        if (dt_virt)
                memblock_reserve(dt_phys, size);

        if (!dt_virt || ! early_init_dt_scan (dt_virt)) {                 /* invalid dtb, print Error and enter cpu_relax */         }

        /* Early fixups are done, map the FDT as read-only now 
         * remap the FDT as read-only
         */
        fixmap_remap_fdt (dt_phys, &size, PAGE_KERNEL_RO);
}

The early_init_dt_scan function is implemented as follows:

bool __init early_init_dt_scan(void *params)
{                       
        /* 1. 检查device tree有效性
         * 2. 将全局变量initial_boot_params赋值为dt_virt
         */
        status = early_init_dt_verify(params);
        /* 做一些预先的扫描工作 */
        early_init_dt_scan_nodes();
}

early_init_dt_scan_node function:

void __init early_init_dt_scan_nodes(void)
{
        int rc = 0;

        /* of_scan_flat_dt function is mainly used to traverse all nodes from the root and call back the incoming function;
         * Input the parameters of the callback function:
         * node: current scanning node
         * uname: node name
         * depth: node nesting depth
         * data: Private data, the second formal parameter of of_scan_flat_dt
         */

        /* Obtain the following attribute values ​​from the /chosen or /chosen@0 node:
         * linux, initrd-start, linux, initrd-end
         * bootargs, this attribute is the command line parameter passed in by the bootloader, and is assigned to boot_command_line
         */
        rc = of_scan_flat_dt (early_init_dt_scan_chosen, boot_command_line);
        if (!rc)
                pr_warn("No chosen node found, continuing without\n");

        /* Obtain the {size,address}-cells information under the "/" node (the depth of the root node is 0), *
         assign the virtual address values ​​​​of the attributes named #size-cells and #address-cells to
         * global variables dt_root_size_cells and dt_root_addr_cells
         */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);

        /* Scan the node whose device_type is "memory", and
         parse the value of "linux, usable-memory" * or "reg" attribute into each base and size, and finally add it to the memory management system:
         * early_init_dt_scan_memory
         * --> early_init_dt_add_memory_arch
         * --> memblock_add(base, size)
         */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

The more important functions involved in the above callback function are the of_get_flat_dt_prop function:

/* 在node下寻找属性名称与name相同的属性,并返回:
 *      1、找到的property的虚拟地址
 *      2、找到的property value的长度
 */
const void *__init of_get_flat_dt_prop(unsigned long node, const char *name,
                                       int *size)
{
        return fdt_getprop(initial_boot_params, node, name, size);
}

3.2.2 unflatten_device_tree

unflatten_device_tree starts to fully scan the dtb file, fills the relevant information into the device_node and property structures, and creates a device_nodes tree.

oid __init unflatten_device_tree(void)
{
        /* initial_boot_params: 在early_init_dt_scan-->early_init_dt_verify中赋值为
         *                      设备树首地址(虚拟地址)
         * of_root: 经过该函数处理后会指向根节点
         * early_init_dt_alloc_memory_arch: 该函数使用的内存分配器
         */
        __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);

        unittest_unflatten_overlay_base();
}

Mainly implemented in the __unflatten_device_tree function:

void *__unflatten_device_tree(const void *blob,
                              struct device_node *dad,
                              struct device_node **mynodes,
                              void *(*dt_alloc)(u64 size, u64 align),
                              bool detached)
{
    /* 读取dtb头部信息,并进行合法检查
     * 其中有一项是dtb的totalsize不能大于INT_MAX,即不能大于2GB
     */
    if (fdt_check_header(blob)) {
        pr_err("Invalid device tree blob header\n");
        return NULL;
    }
    /* 第一次调用unflatten_dt_nodes,仅创建device_node和property结构体,并计算
     * 总共需要的内存大小
     */
    size = unflatten_dt_nodes(blob, NULL, dad, NULL);
    /* 进行4字节对齐 */
    size = ALIGN(size, 4);
    /* Allocate memory for the expanded device tree */
    mem = dt_alloc(size + 4, __alignof__(struct device_node));
    memset(mem, 0, size);
    /* 设置魔数 */
    *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
    /* 第二次调用,进行实际的填充工作*/
    unflatten_dt_nodes(blob, mem, dad, mynodes);
    if (be32_to_cpup(mem + size) != 0xdeadbeef)
        pr_warn("End of tree marker overwritten: %08x\n",
                be32_to_cpup(mem + size));
    return mem;
}

The unflatten_dt_nodes function is called twice. In commit dfbd4c6eff35f(drivers/of: Split unflatten_dt_node()), unflatten_dt_node is divided into populate_properties(), populate_node() and unflatten_dt_node().

  • populate_properties(): Called by populate_node, create a struct property structure for all properties under the current node, and fill in information
  • populate_node(): Called by unflatten_dt_node(), create a struct device_node structure and a struct property structure for the node at the current offset, and fill in the information
  • unflatten_dt_node(): Parses and fills all nodes and attribute information in dtb.

The first is the populate_properties() function:

static void populate_properties(const void *blob,
                                int offset,
                                void **mem,
                                struct device_node *np,
                                const char *nodename,
                                bool dryrun)
{
        struct property *pp, **pprev = NULL;
        int cur;
        bool has_name = false;

        /* np下所有的property会形成一个单链表,
         * np->properties指向其中第一个property
         */
        pprev = &np->properties;
        /* 查找np下所有的property,最终都会调用到2.3小节的nextprop_()函数
         * 返回的cur为找到的property的offset值
         */
        for (cur = fdt_first_property_offset(blob, offset);
             cur >= 0;
             cur = fdt_next_property_offset(blob, cur)) {
                const __be32 *val;
                const char *pname;
                u32 sz;

                /* 通过offset获取property的value、value size以及name值 */
                val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
                /* 此处不是向内存管理器申请内存,而是为property划分内存区域
                 * 函数实现:
                 *      *mem = PTR_ALIGN(*mem, align);
                 *      res = *mem;
                 *      *mem += size;   //mem指向该property后面的地址
                 *      return res;     //返回对齐后的地址
                 */
                pp = unflatten_dt_alloc(mem, sizeof(struct property),
                                        __alignof__(struct property));
                /* dryrun有两种情况:
                 *      1:传入的mem为NULL,即第一次调用unflatten_dt_nodes的情况
                 *      0: 传入的mem指向一块已分配的内存,即第二次调用情况
                 * 第一次调用只是为了获取size,不做具体填充操作
                 */
                if (dryrun)
                        continue;

                /* We accept flattened tree phandles either in
                 * ePAPR-style "phandle" properties, or the
                 * legacy "linux,phandle" properties.  If both
                 * appear and have different values, things
                 * will get weird. Don't do that.
                 */
                if (!strcmp(pname, "phandle") ||
                    !strcmp(pname, "linux,phandle")) {
                        if (!np->phandle)
                                np->phandle = be32_to_cpup(val);
                }
                /* And we process the "ibm,phandle" property
                 * used in pSeries dynamic device tree
                 * stuff
                 */
                if (!strcmp(pname, "ibm,phandle"))
                        np->phandle = be32_to_cpup(val);

                pp->name   = (char *)pname;
                pp->length = sz;
                pp->value  = (__be32 *)val;
                *pprev     = pp;
                /* 指向property链表的下一个成员 */
                pprev      = &pp->next;
        }

        /* 设备树的16及以上版本没有name为"name"的property,此处重新进行创建
         */
        if (!has_name) {
                const char *p = nodename, *ps = p, *pa = NULL;
                int len;

                while (*p) {
                        if ((*p) == '@')
                                pa = p;
                        /* 注:设备树16及以上版本的nodename中不含'/'字符 */
                        else if ((*p) == '/')
                                ps = p + 1;
                        p++;
                }

                if (pa < ps)
                        pa = p;
                len = (pa - ps) + 1;
                /* 在struct property后面多划分了len大小的内存区域,用于存放pp->value,
                 * 也就是这里的"nodename",及node的名称(取'/'和'@'之间的字符)
                 */
                pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
                                        __alignof__(struct property));
                if (!dryrun) {
                        pp->name   = "name";
                        pp->length = len;
                        pp->value  = pp + 1;
                        *pprev     = pp;
                        pprev      = &pp->next;
                        memcpy(pp->value, ps, len - 1);
                        ((char *)pp->value)[len - 1] = 0;
                        pr_debug("fixed up name for %s -> %s\n",
                                 nodename, (char *)pp->value);
                }
        }
    if (!dryrun)
                *pprev = NULL;
}

Then the populate_node() function:

static bool populate_node(const void *blob,
                          int offset,
                          void **mem,
                          struct device_node *dad,
                          struct device_node **pnp,
                          bool dryrun)
{
        struct device_node *np;
        const char *pathp;
        unsigned int l, allocl;

        /* 获取offset处的node的name和name字符串的长度(不含结尾的'\0') */
        pathp = fdt_get_name(blob, offset, &l);
        if (!pathp) {
                *pnp = NULL;
                return false;
        }

        /* 将结尾的'\0'长度加上 */
        allocl = ++l;

        /* np为存放当前node数据的首地址
         * 在struct device_node大小后面多划分allocl大小的内存,用于存放np->full_name
         */
        np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
                                __alignof__(struct device_node));
        if (!dryrun) {
                char *fn;
                of_node_init(np);
                np->full_name = fn = ((char *)np) + sizeof(*np);
                /* 将name字符串的值拷贝到划分的内存处 */
                memcpy(fn, pathp, l);
                /* 如果当前节点有父节点 */
                if (dad != NULL) {
                        np->parent = dad;
                        np->sibling = dad->child;
                        dad->child = np;
                }
        }
        /* 分配struct property结构体,并进行填充(dryrun == 0时) */
        populate_properties(blob, offset, mem, np, pathp, dryrun);
        if (!dryrun) {
                /* 获取当前node下name属性,并将value值赋值给np->name('/'和'@'之间的字符)
                 * 注:version 16及以上的node name不是全路径名,只是name本身,所以不会含有'/'
                 */
                np->name = of_get_property(np, "name", NULL);
                if (!np->name)
                        np->name = "<NULL>";
        }
        /* 将填充好的np赋值到pnp指向的地址,如果是根节点情况(offset == 0),
         * 则nps[depth+1]指向根节点('/')
         */
        *pnp = np;
        return true;
}

And finally the unflatten_dt_nodes() function:

static int unflatten_dt_nodes(const void *blob,
                              void *mem,
                              struct device_node *dad,
                              struct device_node **nodepp)
{
        struct device_node *root;
        int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH   64
        /* 题外话:
         *      nps是一个数组,数组成员为struct device_node *类型的指针
         *      如果写为struct device_node (*nps)[FDT_MAX_DEPTH];
         *      那么nps是一个指针,指向由struct device_node类型数据组成的数组
         */
        struct device_node *nps[FDT_MAX_DEPTH];
        void *base = mem;
        bool dryrun = !base;

        /* unflatten_device_tree()函数中传入的nodepp为&of_root(全局变量) */
        if (nodepp)
                *nodepp = NULL;

        /* 如果有父节点,需要设置depth为1,
         * unflatten_device_tree()函数中传入的dad为NULL
         */
        if (dad)
                depth = initial_depth = 1;

        root = dad;
        nps[depth] = dad;

        for (offset = 0;
             offset >= 0 && depth >= initial_depth;
             offset = fdt_next_node(blob, offset, &depth)) {
                /* 嵌套深度大于64的node将不会被处理 */
                if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
                        continue;
                /* 没有定义CONFIG_OF_KOBJ,并且当前noden节点的status属性值为disable,
                 * 则跳过对该节点的处理
                 * 注:CONFIG_OF_KOBJ在commit b56b5528f5b3c(of: make kobject and
                 *     bin_attribute support configurable)加入,当sysfs和OF_DYNAMIC
                 *     使能时配置该选项;
                 */
                if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
                    !of_fdt_device_is_available(blob, offset))
                        continue;
                /* 分配struct device_node结构体,并进行填充(dryrun == 0时) */
                if (!populate_node(blob, offset, &mem, nps[depth],
                                   &nps[depth+1], dryrun))
                        /* 如果获取到的node name为NULL,则提前返回size */
                        return mem - base;
                if (!dryrun && nodepp && !*nodepp)
                        /* of_root指向根节点 */
                        *nodepp = nps[depth+1];
                if (!dryrun && !root)
                        /* root指向根节点 */
                        root = nps[depth+1];
        }

        /*
         * Reverse the child list. Some drivers assumes node order matches .dts
         * node order
         */
        if (!dryrun)
                reverse_nodes(root);

        /* 将所有struct device_node,struct property,包含紧跟的一些name的size的
         * 的总和进行返回(unflatten_dt_alloc()函数划分的区域大小)
         */
        return mem - base;
}

From the comments, the reverse_nodes() function will reverse the child linked list of the device tree. Let's take a simple device tree as an example:

/ {
    leds {
        act {
            gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
        };

        pwr {
            label = "PWR";
            gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
            default-state = "keep";
            linux,default-trigger = "default-on";
        };
    };

    wifi_pwrseq: wifi-pwrseq {
        compatible = "mmc-pwrseq-simple";
        reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
    };

};

After the device tree is parsed by populate_node(), the structure relationship is as follows:

- /节点:
  - parent:NULL
  - sibling:NULL
  - child:wifi_pwrseq
- led节点:
  - parent:/
  - sibling:NULL
  - child:pwr
- act节点:
  - parent:led
  - sibling:NULL
  - child:NULL
- pwr节点:
  - parent:led
  - sibling:act
  - child:NULL
- wifi_pwrseq节点:
  - parent:/
  - sibling:led
  - child:NULL

It can be found that the order of child and sibling in the tree structure is indeed opposite to that in the actual dts. 

Guess you like

Origin blog.csdn.net/y13182588139/article/details/125827699