u-boot fdt的应用

简单的说,如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file。通过DTC(Device Tree Compiler),可以将这些适合人类阅读的Device Tree source file变成适合机器处理的Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。在系统启动的时候,boot program(例如:firmware、bootloader)可以将保存在flash中的DTB copy到内存(当然也可以通过其他方式,例如可以通过bootloader的交互式命令加载DTB,或者firmware可以探测到device的信息,组织成DTB保存在内存中),并把DTB的起始地址传递给client program(例如OS kernel,bootloader或者其他特殊功能的程序)。对于计算机系统(computer system),一般是firmware->bootloader->OS,对于嵌入式系统,一般是bootloader->OS。

1. u-boot对fdt(flattened device tree)的支持

实现:只要加入

#define CONFIG_OF_LIBFDT               /* Device Tree support */

重新编译u-boot,就可以实现对device tree的支持。

2、dtb在uboot中的位置

dtb可以以两种形式编译到uboot的镜像中。

    • dtb和uboot的bin文件分离

    • dtb集成到uboot的bin文件内部

      • 如何使能 
        需要打开CONFIG_OF_EMBED宏来使能。
      • 编译说明 
        在这种方式下,在编译uboot的过程中,也会编译dtb。
      • 最终位置 
        注意:最终dtb是包含到了uboot的bin文件内部的。 
        dtb会位于uboot的.dtb.init.rodata段中,并且在代码中可以通过__dtb_dt_begin符号获取其符号。 
        因为这种方式不够灵活,文档上也不推荐,所以后续也不具体研究,简单了解一下即可。
    • 另外,也可以通过fdtcontroladdr环境变量来指定dtb的地址 
      可以通过直接把dtb加载到内存的某个位置,并在环境变量中设置fdtcontroladdr为这个地址,达到动态指定dtb的目的。

 

u-boot中如何获取dtb

1、整体说明

在u-boot初始化过程中,需要对dtb做两个操作:

  • 获取dtb的地址,并且验证dtb的合法性
  • 因为我们使用的dtb并没有集成到uboot的bin文件中,也就是使用的CONFIG_OF_SEPARATE方式。因此,在relocate uboot的过程中并不会去relocate dtb。因此,这里我们还需要自行为dtb预留内存空间并进行relocate。关于uboot relocate的内容请参考《[uboot] (番外篇)uboot relocation介绍》。
  • relocate之后,还需要重新获取一次dtb的地址。

对应代码common/board_f.c

static init_fnc_t init_sequence_f[] = {
...
#ifdef CONFIG_OF_CONTROL
    fdtdec_setup, // 获取dtb的地址,并且验证dtb的合法性
#endif
...
    reserve_fdt, // 为dtb分配新的内存地址空间
...
    reloc_fdt, // relocate dtb
...
}

后面进行具体函数的分析。

2、获取dtb的地址,并且验证dtb的合法性(fdtdec_setup)

对应代码如下: 
lib/fdtdec.c

int fdtdec_setup(void)
{
#if CONFIG_IS_ENABLED(OF_CONTROL) // 确保CONFIG_OF_CONTROL宏是打开的

# ifdef CONFIG_OF_EMBED
    /* Get a pointer to the FDT */
    gd->fdt_blob = __dtb_dt_begin; 
// 当使用CONFIG_OF_EMBED的方式时,也就是dtb集成到uboot的bin文件中时,通过__dtb_dt_begin符号来获取dtb地址。

# elif defined CONFIG_OF_SEPARATE
    /* FDT is at end of image */
    gd->fdt_blob = (ulong *)&_end;
//当使用CONFIG_OF_SEPARATE的方式时,也就是dtb追加到uboot的bin文件后面时,通过_end符号来获取dtb地址。
# endif

    /* Allow the early environment to override the fdt address */
    gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
                        (uintptr_t)gd->fdt_blob);
// 可以通过环境变量fdtcontroladdr来指定gd->fdt_blob,也就是指定fdt的地址。
#endif

// 最终都把dtb的地址存储在gd->fdt_blob中
    return fdtdec_prepare_fdt();
// 在fdtdec_prepare_fdt中检查fdt的合法性
}

/* fdtdec_prepare_fdt实现如下 */
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;
// 判断dtb是否存在,以及是否有四个字节对齐。
// 然后再调用fdt_check_header看看头部是否正常。fdt_check_header主要是检查dtb的magic是否正确。
    }
    return 0;
}

验证dtb的部分可以参考《[kernel 启动流程] (第四章)第一阶段之——dtb的验证》。

3、为dtb分配新的内存地址空间(reserve_fdt)

relocate的内容请参考《[uboot] (番外篇)uboot relocation介绍》。 
common/board_f.c中

static int reserve_fdt(void)
{
#ifndef CONFIG_OF_EMBED
// 当使用CONFIG_OF_EMBED方式时,也就是dtb集成在uboot中的时候,relocate uboot过程中也会把dtb一起relocate,所以这里就不需要处理。
// 当使用CONFIG_OF_SEPARATE方式时,就需要在这里地方进行relocate
    if (gd->fdt_blob) {
        gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);
// 获取dtb的size

        gd->start_addr_sp -= gd->fdt_size;
        gd->new_fdt = map_sysmem(gd->start_addr_sp, gd->fdt_size);
// 为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)

relocate的内容请参考《[uboot] (番外篇)uboot relocation介绍》。 
common/board_f.c中

static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED
// 当使用CONFIG_OF_EMBED方式时,也就是dtb集成在uboot中的时候,relocate uboot过程中也会把dtb一起relocate,所以这里就不需要处理。
// 当使用CONFIG_OF_SEPARATE方式时,就需要在这里地方进行relocate
    if (gd->flags & GD_FLG_SKIP_RELOC)
// 检查GD_FLG_SKIP_RELOC标识
        return 0;
    if (gd->new_fdt) {
        memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);
// relocate dtb空间
        gd->fdt_blob = gd->new_fdt;
// 切换gd->fdt_blob到dtb的新的地址空间上
    }
#endif

    return 0;
}

 

 

boot中对FDT的支持

如果要使用tag,需boot和kernel同时支持,那么用FDT取代TAG也同样如此.

uboot中对FDT的支持:

1) uboot代码中已经对fdt有支持,可以通过开启CONFIG_OF_LIBFDT配置,实现FDT支持,如此会省掉一部分工作。例如:fdt调试命令可以直接使用。
2) 如前所述dtb文件被烧录到flash上。在Uboot引导内核启动之前会将dtb拷贝到内存上,并对dtb的内容进行修改,这一步是为了支持对环境变量的动态修改。

以下介绍u-boot中如何修改dts文件中定义的内容:重点观察example_plat_version.dts文件里chosen节点中bootargs参数的修改,关键函数fdt_chosen:

int fdt_chosen(void *fdt, int force)
{
  int nodeoffset;
  char *str;
  const char *path;
  nodeoffset= fdt_path_offset(fdt, "/chosen");
  if(nodeoffset<0)
  {
    nodeoffset=fdt_add_subnode(fdt,0,"chosen");
    if(nodeoffset<0)
    {
      return nodeoffset;
    }
  }
  str = getenv("bootargs");
  path = fdt_set_prop(fdt,nodeoffset,"bootargs",str,strlen(str)+1);
  return 0;
} 

上述函数的fdt_path_offset(fdt,”chosen”)获取的是dts文件中的:


chosen{
bootargs = "console=ttyS0,115200 mem=100M root=/dev/mtdblock0 init=/linuxrc";
};
要想修改bootargs则调用fdt_setprop_string;
Ps:其中fdt_setprop_string等接口是FDT提供的标准接口,boot和kernel源码中都可调用。
3)将dtb从flash拷贝到dram上。假设拷贝到dram=0x200100的地址,则uboot跳转到kernel时再通过cpu的通用寄存器r2告知系统这个地址,这一点与tag没有分别。至此完成boot下对fdt的支持。

Uboot下调试FDT

Uboot支持标准的fdt命令修改dtb的内容。

1 举例:常用调试命令
1>fdt list :列出系统中所有节点。即上面dts文件中的节点。

#fdt list
/{
#address-cells = <0x1000000>;
#size-cells = <0x1000000>;
compatible = "example,**";
model = "example plat evm Board";
chosen{
};
aliases{
};
memory{
};
cpus{
};
apb@80000000{
};
};
2>列出aliases节点:
#fdt list /aliases
aliases{
serial0 = "/apb@80000000/uart@8005000";
nand = "/apb@90000000/nand@9001000";
i2c0 = "/ahb@70000000/i2c@7001000";
};
3>查看boot分区信息:
#fdt list nand
nand@9001000{
compatible = "example,nand";
#address-cells = <0x1000000>;
#size-cells = <0x1000000>;
partition@0{
};
partition@1{
};
};
#fdt list nand/partition@0
partition@0{
reg = <0x0 0x40000>;
label = "uboot";
};
2 修改dtb举例:
#fdt list /memory
memory{
device_type = "memory";
reg = <0x2000,0xe007>;
};
#fdt set /memory reg <0x200000 0x6e00000>
#
#fdt list /memory
memory{
device_type = "memory";
reg = <0x2000,0xe006>;
};
1>修改前 reg= <0x2000 0xe007>(字节序反了):实际表示linux 系统内存从0x200000开始大小为0x7e00000
2>修改后 reg=<0x2000 0xe006>:实际表示linux 系统内存从0x200000开始大小为0x6e00000

 

fdt调试和验证的工具方法:

驱动开发时与设备注册、设备树相关的调试方法,彼此间没有优先级之分,每种方法不一定是最优解,但可以作为一种debug查找问题的手段,快速定位问题原因。例如在芯片验证时,不同时钟频率下系统启动情况摸底时,U-Boot fdt命令可以方便快捷的帮助我们完成这个实验。

#1. dtc工具

dtc可以使用宿主机提供的亦可以使用kernel提供的。这个工具是将已编译的dtb文件反汇编。

 

 

#2. U-Boot fdt command

驱动代码在debug期间,若希望更改外设模块的设备树属性时,在不改变存储设备中dtb文件的前提下,进入到U-Boot的命令行界面,通过U-Boot的fdt命令来实现。例如修改外设时钟源、修改外设时钟名、status属性等。为了使U-Boot支持fdt命令需要打开CONFIG_OF_LIBFDT。

 

 

 

 

1、fdtdec_setup 函数

 fdtdec_setup:如果u-boot中使用设备树,则需处理一些相关工作
第一篇文章查看.config配置文件知道关于设备树就有下面几个定义:

CONFIG_OF_CONTROL=y
CONFIG_OF_SEPARATE=y
CONFIG_OF_TRANSLATE=y
CONFIG_OF_LIBFDT=y

所以去掉宏定义之后的函数定义就是:

/* file: lib/fdtdec.c */
int fdtdec_setup(void)
{
int ret;

/* Allow the board to override the fdt address. */
gd->fdt_blob = board_fdt_blob_setup();

/* Allow the early environment to override the fdt address */
gd->fdt_blob = map_sysmem
(env_get_ulong("fdtcontroladdr", 16,
(unsigned long)map_to_sysmem(gd->fdt_blob)), 0);
ret = fdtdec_prepare_fdt();
if (!ret)
ret = fdtdec_board_setup(gd->fdt_blob);
return ret;
}

函数先是调用board_fdt_blob_setup来设置gd结构体的fdt_blob成员,简化宏定义之后函数如下:

/* file: lib/fdtdec.c */
__weak void *board_fdt_blob_setup(void)
{
void *fdt_blob = NULL;
/* FDT is at end of image */
fdt_blob = (ulong *)&_end;
return fdt_blob;
}

由前面的镜像组成可以知道u-boot设备树文件是放在u-boot.bin的末尾,所以取它的地址返回即可。 后面继续调用env_get_ulong从环境变量中获取u-boot设备树的地址,如果该环境变量没有被设置则返回原本的默认值(unsigned long)map_to_sysmem(gd->fdt_blob)。最终继续调用fdtdec_prepare_fdt函数来打印一些信息:

/* file: lib/fdtdec.c */
int fdtdec_prepare_fdt(void)
{
if (!gd->fdt_blob || ((uintptr_t)gd->fdt_blob & 3) ||
fdt_check_header(gd->fdt_blob)) {
#ifdef CONFIG_SPL_BUILD
puts("Missing DTB\n");
#else
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");
# ifdef DEBUG
if (gd->fdt_blob) {
printf("fdt_blob=%p\n", gd->fdt_blob);
print_buffer((ulong)gd->fdt_blob, gd->fdt_blob, 4,
32, 0);
}
# endif
#endif
return -1;
}
return 0;
}

节点node

{}包围起来的结构称之为节点,dts中最开头的/ {},称为根节点。
在节点中,以 key = value 代表节点属性。
树中每个表示一个设备的节点都需要一个 compatible 属性。

节点名 name

  • 节点名称:每个节点名格式为:<name>[@<unit_address>],其中:
    • :设备名,就是一个不超过31位的简单 ascii 字符串,节点的命名应该根据它所体现的是什么样的设备。
    • <unit_address> :设备地址,用来唯一标识一共节点。没有指定<unit_address>时,同级节点命名必须是唯一的;但只要<unit_address>不同,多个节点也可以使用一样的通用名称。

下面是典型节点名的写法:

/ {
        model = "Freescale i.MX23 Evaluation Kit";
        compatible = "fsl,imx23-evk", "fsl,imx23";

        memory {
                reg = <0x40000000 0x08000000>;
        };
        // 注意这里
        apb@80000000 {
                ...
        };
}

上面的节点名是apb,节点路径是/apb@80000000 ,这点要注意,因为根据节点名查找节点的API的参数是不能有"@xxx"这部分的。

Linux中的设备树还包括几个特殊的节点:比如chosen,chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,比如bootloader传递内核启动参数给内核

/include/ "zynq-7000.dtsi"

/ {
        model = "Zynq ZC702 Development Board";
        compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";

        ...

        chosen {
                bootargs = "console=ttyPS1,115200 earlyprintk";
        };
};

引用

当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便。所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。
标号引用常常还作为节点的重写方式,用于修改节点属性。

  • 格式:
    • 声明别名: 别名 : 节点名
    • 访问 : &别名

编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写(覆盖前值),使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。

/include/ "imx53.dtsi"

/ {
        model = "Freescale i.MX53 Automotive Reference Design Board";
        compatible = "fsl,imx53-ard", "fsl,imx53";

        memory {
                reg = <0x70000000 0x40000000>;
        };

        eim-cs1@f4000000 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "fsl,eim-bus", "simple-bus";
                reg = <0xf4000000 0x3ff0000>;

                lan9220@f4000000 {
                        compatible = "smsc,lan9220", "smsc,lan9115";
                        reg = <0xf4000000 0x2000000>;
                        phy-mode = "mii";
                        interrupt-parent = <&gpio2>; // 直接使用引用

                        vdd33a-supply = <&reg_3p3v>;
                };
        };

        regulators {
                compatible = "simple-bus";

                reg_3p3v: 3p3v {                     // 定义一个引用
                        compatible = "regulator-fixed";
                        regulator-name = "3P3V";
                };
        };

        ...
        // 引用一个节点,新增/修改其属性。
        &reg_3p3v {
            regulator-always-on;
        }

 

 

 

 

1.2 设备树语法

在上一小节,我们将设备树的概念有基本的认知,下面更重要的就是DTS语法了,这里我么结合实际的代码区理解设备树语法。

1.2.1 #include语法

DTS中#include语法和C语言中类似,支持将包裹的文件直接放置在#include位置从而访问到其它文件的数据,如官方设备树内使用的

#include "nuc9xx.dtsi"

另外,也可以用来包含dts文件,如下

#include "imx6ull-14x14-evk.dts"


解析:
(1) 根节点
compatible: 内核通过root节点"/"的compatible属性来判断它启动的是哪个machine。
#address-cells : 子结点(reg属性)需要多少个cell描述地址。
#size-cells : 子结点(reg属性)需要多少个cell描述长度。
interrupt-parent : 标示该节点属于哪个中断控制器,如果没有该属性,则依附于父节点。

(2) cpus节点
#address-cells : 同上
#size-cells : 同上

(3) cpu节点
@unit-address: 可选项,设备地址,节点名相同时可以通过这个来区分不同节点。unit-address地址也经常在其对应的reg属性中给出。
reg : region,描述设备地址
格式: reg = <address1 length1 [address2 length2] [address3 length3]>

(4) serial节点
compatible: 外设节点上的compatible属性用于驱动和设备的绑定(匹配)
reg: 外设基地址和偏移量 ,比如:reg = <0x101f1000 0x1000 >
interrupts: 中断号和标识(上升沿,下降沿等), 里面多少个值要根据中断控制器的#interrupt-cells属性来决定。而#interrupt-cells属性值要由中断控制器的类型决定。

中断控制器类型:
GIC: Generic Interrupt Controller(通用中断控制器)
中断类型,中断号,标识(上升沿,下降沿等)
VIC : Vectored Interrupt Controller(向量中断控制器)
中断号
NVIC:Nested Vectored Interrupt Controller(内嵌向量中断控制器)
中断号,中断优先级

参考: Documentation\devicetree\bindings\interrupt-controller

(5) interrupt-controller节点
compatible: 中断控制器类型,查看上面路径下的文件来获知。
reg:同上
interrupt-controller:空属性,用来声明这个节点接收中断信号。
#interrupt-cells:标识该控制器需要几个cell来描述中断,其实就是决定了interrupts属性需要几个cell

(6) external-bus节点
ranges: 地址转换表,每一行都包含子地址、父地址、在子地址空间内的区域大小。
ranges属性值为空的话,表示1:1映射。

ranges属性值的格式 <local地址, parent地址, size>
local地址的个数取决于当前含有ranges属性的节点的#address-cells属性的值。
parent地址的个数取决于父节点的#address-cells的值。
size取决于当前含有ranges属性的节点的#size-cells属性的值。

(7) rtc节点
reg: i2c设备地址




 

posted @ 2022-01-26 20:44  liujunhuasd  阅读(2524)  评论(0编辑  收藏  举报