[uboot] (Fan outside) uboot fdt introduction

The following examples are based on the project X project tiny210 (s5pv210 platform, armv7 architecture).

It is recommended to first read "[[uboot] (番外) uboot relocation introduction" and "[uboot] (Chapter 4) uboot process - uboot compilation process"

=================================================================================

Because learning the uboot driver module, it is necessary to first understand the uboot fdt. So here's the first thing to learn about fdt.

I. Introduction

FDT, flatted device tree, flat device tree. People familiar with Linux should be familiar with this concept. Simply understood to store part of the device information structure into the device tree file. Uboot finally compiles its device tree into a dtb file, and uses the parsing of the dtb to obtain board-level device information during use. The dtb of uboot is the same as the dtb of the kernel. This part of the recommendation directly refers to the wok's dtb article Device Tree (a): background introduction Device Tree (two): basic concept Device Tree (three): code analysis

For uboot's fdt, you can refer to doc/README.fdt-control.

Second, dtb introduction

1, dtb structure introduction

The structure is as follows
DTB header
alignment gap
memory reserve map
alignment gap
device-tree structure
alignment gap
device-tree string

The dtb header structure is as follows:

The structure is as follows
magic
totalsize
off_dt_struct
off_dt_strings
off_mem_rsvmap
version
……

Where magic is a fixed value, 0xd00dfeed (big endian) or 0xedfe0dd0 (small endian). Take s5pv210-tiny210.dtb as an example: Execute the "hexdump -C s5pv210-tiny210.dtb | more" command

@:dts$ hexdump -C s5pv210-tiny210.dtb | more
00000000  d0 0d fe ed 00 00 5a cc  00 00 00 38 00 00 58 14  |......Z....8..X.|
00000010  00 00 00 28 00 00 00 11  00 00 00 10 00 00 00 00  |...(............|

You can see that the first 4 bytes of dtb are 0xd00dfeed, which is magic.In summary, we only need to extract the first four bytes of data on the address of the dtb to be verified, and compare it with 0xd00dfeed (big endian) or 0xedfe0dd0 (small endian). If it matches, it means that the corresponding dtb to be verified is a legal one. Dtb.

2, the location of dtb in uboot

Dtb can be compiled into the uboot image in two forms.

  • Dtb and uboot bin file separation

    • How to enable Need to open the CONFIG_OF_SEPARATE macro to enable.
    • Compilation instructions In this way, uboot compile and dtb compile are separate, Mr. into uboot bin file, and then generate dtb file. Refer to "[uboot] (Chapter 4) uboot Process - uboot Compilation Process" for details.
    • Final positionDtb will eventually be appended to the back of the uboot bin file. This is the last part of uboot.img. Therefore, the address of dtb can be obtained by the end address symbol of uboot, which is the _end symbol. Refer to "[uboot] (Chapter 4) uboot Process - uboot Compilation Process" for details.
  • Dtb integrated into the uboot bin file internal

    • How to enable Need to open the CONFIG_OF_EMBED macro to enable.
    • Compilation instructions In this way, dtb is also compiled during the process of compiling uboot.
    • Final positionNote: The final dtb is included inside the bin file of uboot. dtb will be located in the .dtb.init.rodata section of uboot, and its symbol can be obtained in the code via the __dtb_dt_begin symbol. Because this method is not flexible enough, it is not recommended on the document, so it is not specifically studied in the future. Simply understand it.
  • In addition, you can also specify the address of dtb through the fdtcontroladdr environment variable You can dynamically specify dtb by directly loading dtb to a location in memory and setting fdtcontroladdr to this address in the environment variable. Used in debugging.

Third, how to support fdt in uboot

1, related macros

  • CONFIG_OF_CONTROL Used to configure whether FDT is enabled. ./source/configs/tiny210_defconfig:312:CONFIG_OF_CONTROL=y

  • CONFIG_OF_SEPARATE, CONFIG_OF_EMBED Configure whether dtb is integrated into the bin file of uboot. Refer to the above for details. It is generally used in a separate way.

2, how to add a dtb

Take tiny210 as an example. For details, please refer to the git record of uboot in the project X project: 8a371676710cc0572a0a863255e25c35c82bb928 (1) Add the corresponding target dtb in the Makefile Arch/arm/dts/Makefile

dtb-$(CONFIG_TARGET_TINY210) += \
       s5pv210-tiny210.dtb

(2) Create a corresponding dts file Arch/arm/dts/s5pv210-tiny210.dts, note that the file name should be the same as the dtb name in the Makefile.

/dts-v1/;
/{
};

(3) Open the corresponding macro Configs/tiny210_defconfig

CONFIG_OF_CONTROL=y
CONFIG_OF_SEPARATE=y ## In fact, there is no need to match here, the arm default is to specify this way.

(4) Because the final compiled dtb may be more than one, here you need to specify a dtb for tiny210 Configs/tiny210_defconfig

CONFIG_DEFAULT_DEVICE_TREE="s5pv210-tiny210"

Compile and solve some compilation errors, you can find that the u-boot.dtb file is finally generated. You can view our dtb file by the following "hexdump -C u-boot.dtb | more" command, and get some of the contents as follows:

[email protected]:u-boot$ hexdump -C u-boot.dtb | more
00000000  d0 0d fe ed 00 00 01 a4  00 00 00 38 00 00 01 58  |...........8...X|
00000010  00 00 00 28 00 00 00 11  00 00 00 10 00 00 00 00  |...(............|
00000020  00 00 00 4c 00 00 01 20  00 00 00 00 00 00 00 00  |...L... ........|
00000030  00 00 00 00 00 00 00 00  00 00 00 01 00 00 00 00  |................|

Fourth, how to get dtb in uboot

1, the overall description

In the uboot initialization process, you need to do two operations on dtb:

  • Get the address of dtb and verify the legality of dtb
  • Because the dtb we use is not integrated into the uboot bin file, which is the CONFIG_OF_SEPARATE method used. Therefore, in the process of relocate uboot will not go to relocate dtb. Therefore, here we also need to reserve memory space for dtb and relocate. For the contents of uboot relocate, please refer to "[uboot] (Fan outside) uboot relocation introduction".
  • After relocate, you also need to re-acquire the address of dtb.

Corresponding code common/board_f.c

Static init_fnc_t init_sequence_f[] = {
...
#ifdef CONFIG_OF_CONTROL
    Fdtdec_setup, // get the address of dtb and verify the legality of dtb
#endif
...
    Reserve_fdt, // allocate a new memory address space for dtb
...
    Reloc_fdt, // relocate dtb
...
}

The analysis of specific functions is performed later.

2, get the address of dtb, and verify the legality of dtb (fdtdec_setup)

The corresponding code is as follows: Lib/fdtdec.c

Int fdtdec_setup(void)
{
#if CONFIG_IS_ENABLED(OF_CONTROL) // Make sure the CONFIG_OF_CONTROL macro is open

# ifdef CONFIG_OF_EMBED
    /* Get a pointer to the FDT */
    Gd->fdt_blob = __dtb_dt_begin;
// When using the CONFIG_OF_EMBED method, that is, when dtb is integrated into the uboot bin file, the dtb address is obtained by the __dtb_dt_begin symbol.

# elif defined CONFIG_OF_SEPARATE
    /* FDT is at end of image */
    Gd->fdt_blob = (ulong *)&_end;
//When using the CONFIG_OF_SEPARATE method, that is, when dtb is appended to the uboot bin file, the dtb address is obtained by the _end symbol.
# endif

    /* Allow the early environment to override the fdt address */
    Gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
                        (uintptr_t)gd->fdt_blob);
// You can specify gd->fdt_blob via the environment variable fdtcontroladdr, which specifies the address of the fdt.
#endif

// Finally, store the address of dtb in gd->fdt_blob
    Return fdtdec_prepare_fdt();
// Check the legitimacy of fdt in fdtdec_prepare_fdt
}

/* fdtdec_prepare_fdt is implemented as follows */
Int fdtdec_prepare_fdt(void)
{
    If (!gd->fdt_blob || ((uintptr_t)gd->fdt_blob & 3) ||
        Fdt_check_header(gd->fdt_blob)) {
        Puts("No valid device tree binary found - please append one to U-Boot binary, use u-boot-dtb.bin or define CONFIG_OF_EMBED. For sandbox, use -d <file.dtb>\n");
        Return -1;
// Determine if dtb exists and if there are four byte alignments.
// Then call fdt_check_header to see if the header is normal. Fdt_check_header is mainly to check if the magic of dtb is correct.
    }
    Return 0;
}

3, allocate a new memory address space for dtb (reserve_fdt)

Static int reserve_fdt(void)
{
#ifndef CONFIG_OF_EMBED
// When using the CONFIG_OF_EMBED method, that is, when dtb is integrated in uboot, the retate uboot process will also relocate dtb together, so there is no need to deal with it here.
// When using the CONFIG_OF_SEPARATE method, you need to relocate here.
    If (gd->fdt_blob) {
        Gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);
/ / Get the size of dtb

        Gd->start_addr_sp -= gd->fdt_size;
        Gd->new_fdt = map_sysmem(gd->start_addr_sp, gd->fdt_size);
// allocate a new memory space for dtb
        Debug("Reserving %lu Bytes for FDT at: %08lx\n",
              Gd->fdt_size, gd->start_addr_sp);
    }
#endif

    Return 0;
}

4、relocate dtb(reloc_fdt)

Static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED
// When using the CONFIG_OF_EMBED method, that is, when dtb is integrated in uboot, the retate uboot process will also relocate dtb together, so there is no need to deal with it here.
// When using the CONFIG_OF_SEPARATE method, you need to relocate here.
    If (gd->flags & GD_FLG_SKIP_RELOC)
// check the GD_FLG_SKIP_RELOC flag
        Return 0;
    If (gd->new_fdt) {
        Memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);
// relocate dtb space
        Gd->fdt_blob = gd->new_fdt;
/ / Switch gd->fdt_blob to the new address space of dtb
    }
#endif

    Return 0;
}

Five, common interface for dtb parsing in uboot

Gd->fdt_blob has been set to the address of dtb. Note that the interfaces provided by fdt are all based on gd->fdt_blob (the address of dtb).

1, interface function

The following is a brief description of the functions of several interfaces, and does not go into the implementation principle. Explain a few first, and continue to add afterwards. In addition, a node is represented by the offset address of the node in dtb. That is, the node variable node stores the offset address of the node.

  • Lib/fdtdec.c

    • Fdt_path_offsetint fdt_path_offset(const void *fdt, const char *path) Eg:node = fdt_path_offset(gd->fdt_blob, "/aliases"); Function: Get the offset of the path of a node under dtb. This offset represents this node.

    • Fdt_getpropconst void *fdt_getprop(const void *fdt, int nodeoffset, const char *name, int *lenp) Eg: mac = fdt_getprop(gd->fdt_blob, node, "mac-address", &len); Function: Get a string property value of the node node.

    • Fdtdec_get_int_array, fdtdec_get_byte_arrayint fdtdec_get_int_array(const void *blob, int node, const char *prop_name, u32 *array, int count) Eg: ret = fdtdec_get_int_array(blob, node, "interrupts", cell, ARRAY_SIZE(cell)); Function: Get the value of an integer array property of the node node.

    • Fdtdec_get_addrfdt_addr_t fdtdec_get_addr(const void *blob, int node, const char *prop_name) Eg:fdtdec_get_addr(blob, node, “reg”); Function: Get the address attribute value of the node node.

    • Fdtdec_get_config_int, fdtdec_get_config_bool, fdtdec_get_config_stringFunction: Get the shaping attribute, bool attribute, string, etc. under the config node.

    • Fdtdec_get_chosen_nodeint fdtdec_get_chosen_node(const void *blob, const char *name) Function: Get the offset of the name node under choose

    • Fdtdec_get_chosen_propconst char *fdtdec_get_chosen_prop(const void *blob, const char *name) Function: get the value of the name attribute under choose

  • Lib/fdtdec_common.c

    • Fdtdec_get_intint fdtdec_get_int(const void *blob, int node, const char *prop_name, int default_val) Eg: bus->udelay = fdtdec_get_int(blob, node, "i2c-gpio, delay-us", DEFAULT_UDELAY); Function: Get the value of an integer attribute of the node node.

    • Fdtdec_get_uintFunction: Get the value of an unsigned integer attribute of the node node.