This is my study note of device tree.

基本知识

基本名词

  • DT: 设备树
  • FDT(Flatten device tree): 开发设备树
  • dts: 设备树源码
    1
    路径:/linux/kernel/arch/arm(or arm64)/boot/dts
  • dtsi:通用的设备树源码(重复的部分)
  • dtb:编译设备树后得到的文件
  • dtc:编译设备树的编译器

编译设备树

1
2
dtc路径:/linux/kernel/scripts/dtc
如果没有dtc文件,则:进入 /linux/kernel/.config文件中,将CONFIG_DTC设置为y
  • 编译命令
    1
    dtc -I dts -O dtb -o xxx.dtb xxx.dts
  • 反编译命令
    1
    dtc -I dtb -O dts -o xxx.dts xxx.dtb

语法

  • 版本号

    1
    /dts-v1/;
  • 结点

    1
    2
    3
    4
    5
    6
    7
    /{
    // 根结点名为“/”
    led:led@19121
    {
    // 标签(别名):名称@设备地址
    };
    }
  • 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #address-cells = <1>;
    #size-cells = <1>; // 用于描述子节点的地址和长度信息
    child_node
    {
    reg = <0x02 0x01>; // 描述地址信息,比如寄存器地址(地址在前,长度在后)
    };
    status = "" // 属性值为字符串,描述设备状态,有"okay disabled fail fail-sss(sss表示错误内容)"四种状态
    compatiable = " "; // 用于与驱动进行匹配,匹配成功后会执行驱动的probe
    model = " "; // 用来描述一些信息,比如设备名字等
    device_type = "memory"; // 只用于声明cpu结点或memory结点
  • 自定义属性

    1
    属性名 = <>;                     // 自定义属性名和参数
  • 特殊结点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    aliases                         // 用于定义别名,例如mmc0是&sdmc0的别名,适合排序定义别名
    {
    mmc0 = &sdmc0;
    serial0 = "/simple@125/serial@5455";
    };

    chosen // 用于uboot给内核传递参数,重点是bootargs参数,chosen结点必须是根节点的子节点
    {

    };
  • 控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    gpio-controller;               // GPIO控制器
    #gpio-cells = <x>; // 表示后续使用gpio时需要输入x个值

    ngpios = <x>; // 表示有x个gpio可以使用
    gpio-line-names = " "," "; // x个gpio的名字
    gpio-reserved-ranges=<m n>; // 保留的gpio,m为起始,n为长度
    gpio-ranges = <&x m n q>; // 把n后q个gpio映射到m后q个gpio上

    intertrrupt-controller; // 中断控制器
    #interrupt-cells = <x>; // 表示后续调用interrupts时需要输入x个值

    interrupt-parent = <&结点名> // 引用某个结点,如果当前结点没有指明引用的结点,则使用当前结点的父节点所引用的结点
    interrupts = <x x> // 根据cells来确定输入的参数,确定管脚、中断方式等
    interrupt-extended = <&结点1 x x>, <&结点2 x x>; // 表示引入多组中断
  • 时钟

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    时钟分为生产者和消费者:
    1.生产者
    #clock-cells = <x>; // x为0时表示只有一路时钟输出,大于等于1时表示多路时钟输出
    clock-ouput-names = "x", "x"; // 输出时钟名字
    clock-frequency = <x>; // 指定时钟大小
    assigned-clocks = <&x a>,<&y b>;
    assigned-clock-rates=<m>,<m>; // assign-clocks和assigned-clock-rates一般成对使用,m表示a,b对应的时钟大小
    clock-indices = <>,<>; // 用于指定clock-output-names的索引号
    assigned-clock-parents = <&x m>; // 用于设置时钟的父时钟

    2.消费者属性
    clocks = <& x>; // 用于指定时钟源
    clocks-names = " "; // 用于指定时钟名字
  • CPU结点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    1.cpus结点
    cpu的全部布局在此结点下描述
    2.cpu-map结点
    主要用在描述大小核架构处理器中,cpu-map结点的父节点必须是cpus结点,子节点必须是一个或者多个cluster和socket结点。
    3.socket结点
    描述CPU插槽,主板上有几个CPU插槽就有几个socket结点。socket的子节点必须是一个或者多个cluster结点,socket结点的命名方式必须是socketN,N=0,1,2...
    4.cluster结点
    用于描述CPU的集群,cluster结点的命名方式必须是clusterN,子结点必须为一个或者多个cluster结点或者多个core结点。
    5.core结点
    用来描述一个cpu,如果只有一个cpu则,core节点就是cpus结点的子节点。
    6.thread结点
    用于描述处理的线程。
  • pinctrl设置复用关系

    1
    2
    3
    4
    5
    1.客户端
    pinctrl-names = " "; // 表示设备的状态
    pintrl-0 = <&pintrl_hog_1>; // 表示第0个状态对应的配置
    2.服务端
    根据不同厂商进行不同配置

dtb部分

dtb文件格式

dtb文件分为四个部分:

  • 头部
  • 内存阅读块
  • 结构块
    1
    2
    3
    4
    0x00000001表示属性开始
    0x00000002表示结点结束
    0x00000003表示结点开始
    0x00000009表示根结点结束
  • 字符串块
    1
    00表示一个字符串的结束。
  • 自由空间(不一定存在,用于内存对齐)

Alt text

1
2
3
4
5
6
7
8
其中,magic一定是0xd00dfeed
totalsize表示文件大小,以16进制表示
off_dt_struct表示结构块在dtb中的偏移地址
size_dt_struct表示结构块的大小
off_dt_struct+size_dt_struct表示结构块终止的位置
off_dt_string表示字符串块在dtb中的偏移地址
size_dt_string表示字符串块的大小
off_mem_rsvmap表示内存阅读块的偏移地址

dtb展开为device_node

设备树传递给内核的流程:
Alt text

  • device_node结构体
    1
    位于include/linux/of.h的头文件中
  • device_node转换成platform_device
    1
    2
    3
    4
    5
    并不是所有结点都会被转换,只有满足要求的结点被转换。被转换的结点可以在:"sys/bus/platform/devices"下查看。
    转换规则:
    1.根节点下包含compatiable属性的子节点
    2.节点中compatiable属性包含simple-bus、simple-mfd、isa其中之一的结点下包含compatiable属性的子节点。
    3.如果结点的compatiable属性包含arm、primecell值,则对应的结点不会被转换

of操作函数

结点查找

  • of_find_node_by_name 通过结点名字查找指定结点
  • of_find_node_by_path 通过结点路径查找结点
  • of_find_compatible_node 通过compatiable属性查找指定的结点
  • of_find_matching_node_and_match 通过compatiable属性列表查找指定结点
  • of_get_pareny 获取指定结点的父节点
  • of_get_next_child 查找子节点,第二个参数设置为NULL的话,从第一个结点开始

属性查找

  • of_find_property 查找节点下指定的属性
  • of_property_count_elems_of_size 获取属性中元素的数量
  • of_property_read_u32_index 从指定的属性中获取指定标号的u32类型的数据
  • of_property_read_u64_index 从指定的属性中获取指定标号的u64类型的数据
  • of_property_read_variable_u8_array 读取数组函数
  • of_property_read_variable_u16_array 读取数组函数
  • of_property_read_variable_u32_array 读取数组函数
  • of_property_read_variable_u64_array 读取数组函数
  • of_property_read_string 读取字符串函数

ranges属性

1
2
1.ranges = <child-bus-address parent-bus-address len>;
2.ranges;

中断操作

  • irq_of_parse_and_map 从interrupts属性中获取到对应的中段号
  • irqd_get_trigger_type 从中断属性中获取对应的中断标志
  • irq_get_irq_data 返回irq_data结构体
  • platform_get_irq 获取中断号