0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linu设备树及其语法介绍

CHANBAEK 来源:嵌入式攻城狮 作者:安迪西 2023-04-14 11:38 次阅读

1. 什么是设备树

设备树的本质也是操作寄存器,只不过寄存器的相关信息放在了设备树中,配置寄存器时需要使用OF函数从设备树中读取寄存器数据后再进行配置

Linux 3.x之前是没有设备树的,Linux通过内核源码中arch/arm/mach-xxx和arch/arm/plat-xxx文件夹里的板级描述文件来描述ARM架构中的板级信息。 随着ARM硬件种类增多,与板子相关的设备文件也越来越多,导致内核越来越大,而实际上这些硬件板级信息与内核并无相关关系

2011年,Linus发现该问题后,引入了PowerPC等架构已经采用的设备树机制,将板级信息从内核中分离开来,用一个专属的文件格式(.dts文件)来描述。 设备树的作用就是描述硬件平台的硬件资源,它可被bootloader传递到内核,内核可以从设备树中获取硬件信息。 设备树描述硬件资源时有两个特点:

  • 以树状结构描述硬件资源
  • 可以像头文件那样,一个设备树文件可以引用另一个设备树文件,实现代码重用

图片

DTS、DTSI、DTB、DTC文件的区别及定义:

图片

DTC工具源码在Linux内核的scripts/dtc/Makefile文件中:

hostprogs-y:= dtc 
always:= $(hostprogs-y) 

dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o srcpos.o checks.o util.o 
dtc-objs+= dtc-lexer.lex.o dtc-parser.tab.o 
......

由上可见DTC工具依赖于dtc.c, flattree.c, fstree.c等文件,最终编译并链接出DTC这个主机文件。 若要编译DTS文件,只需要进入到Linux源码根目录下,然后执行如下命令:

make all   #编译Linux源码中的所有东西,包括zImage、.ko驱动模块以及设备树
make dtbs  #只是编译设备树

基于ARM架构的SOC有很多,一种SOC又可制作出多款板子,每个板子都有对应的DTS文件,那么如何确定编译哪一个DTS文件呢? 以I.MX6ULL芯片的板子为例,打开arch/arm/boot/dts/Makefile,有如下内容:

dtb-$(CONFIG_SOC_IMX6UL) += \\ 
    imx6ul-14x14-ddr3-arm2.dtb \\ 
    imx6ul-14x14-ddr3-arm2-emmc.dtb \\ 
    ...... 
dtb-$(CONFIG_SOC_IMX6ULL) += \\ 
    imx6ull-14x14-ddr3-arm2.dtb \\ 
    imx6ull-14x14-ddr3-arm2-adc.dtb \\ 
    ......

选中I.MX6ULL后(即CONFIG_SOC_IMX6ULL=y),所有使用该SOC的板子对应的.dts文件都会被编译为.dtb。 若新做一个板子,只需要新建一个对应的.dts文件,再将对应的.dtb文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树时就会将对应的.dts编译为二进制的.dtb文件

2. 设备树语法介绍

2.1 设备树代码分析

关于i. MX6ULL已有的设备树文件,大致有以下几种

imx6ull-14x14-evk-emmc.dts文件:在/arch/arm/boot/dts/文件夹中,描述了emmc板子的usdhc信息。 该文件通过头文件的形式包含了另一个设备树文件

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

&usdhc2 {
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    pinctrl-0 = <&pinctrl_usdhc2_8bit>;
    pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
    pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
    bus-width = <8>;
    non-removable;
    status = "okay";
};

imx6ull-14x14-evk.dts文件:在/arch/arm/boot/dts/文件夹中,包含了根节点、子节点及其他追加内容

#include 
#include "imx6ull.dtsi"

/ {
    model = "Freescale i.MX6 ULL 14x14 EVK Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    chosen {
        stdout-path = &uart1;
    };

    memory {
        reg = <0x80000000 0x20000000>;
    };

    reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        linux,cma {
            compatible = "shared-dma-pool";
            reusable;
            size = <0x14000000>;
            linux,cma-default;
        };
    };

    backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm1 0 5000000>;
        brightness-levels = <0 4 8 16 32 64 128 255>;
        default-brightness-level = <6>;
        status = "okay";
    };

    pxp_v4l2 {
        compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
        status = "okay";
    };

    regulators {
        compatible = "simple-bus";
        ......
    };
    ......
};

&cpu0 {
    arm-supply = <®_arm>;
    soc-supply = <®_soc>;
    dc-supply = <®_gpio_dvfs>;
};

&clks {
    assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    assigned-clock-rates = <786432000>;
};
......
&wdog1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_wdog>;
    fsl,wdog_b;
};

imx6ull.dtsi文件:是设备树的头文件,其格式与设备树基本相同

#include 
#include "imx6dl-pinfunc.h"
#include "imx6qdl.dtsi"

/ {
    aliases {
        i2c3 = &i2c4;
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a9";
            device_type = "cpu";
            ......
        };

        cpu@1 {
            compatible = "arm,cortex-a9";
            device_type = "cpu";
            reg = <1>;
            next-level-cache = <&L2>;
        };
    };

    reserved-memory {
        ......
    };

    soc {
        ......
        ocram: sram@00905000 {
            compatible = "mmio-sram";
            reg = <0x00905000 0x1B000>;
            clocks = <&clks IMX6QDL_CLK_OCRAM>;
        };
        ......
    };
};
......
&vpu_fsl {
    iramsize = <0>;
};

2.2 DTS语法介绍

设备树采用树形结构来描述板子上的设备信息,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键-值对

node-name@unit-address{
    属性1 = ...
    属性2 = ...
    子节点...
}

节点名称:node-name用于指定节点名称,应使用字母开头,并能描述设备类别(根节点用斜杠表示)

单元地址:@unit-address用于指定单元地址,@为分隔符,后面是实际的单元地址,它的值与节点reg属性的第一个地址一致,若没有reg属性值,则可以省略单元地址

节点属性:节点的大括号{ }中包含的内容是节点属性, 一个节点可以包含多个属性信息,设备树最主要的内容就是编写节点的属性。 属性包括自定义属性和标准属性

  • model属性:用于指定设备的制造商和型号,多个字符串使用“,”分隔开
  • compatible属性:由一个或多个字符串组成,是用来查找节点的方法之一
  • status属性:用于指示设备的操作状态,通过status可以禁用或启用设备
  • reg属性:描述设备资源在其父总线定义的地址空间内的地址,通常用于表示一块寄存器的起始地址和长度
  • #address-cells 和 #size-cells:这两个属性同时存在,前者决定了子节点reg属性中地址信息所占用的字长,后者决定了长度信息所占的字长
  • ranges属性:是一个地址映射/转换表,由子地址、父地址和地址空间长度这三部分组成
    • child-bus-address:子总线地址空间的物理地址, 由父节点的#address-cells 确定此物理地址所占用的字长
    • parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长
    • length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长
      特殊节点:aliases子节点、chosen子节点
//为其他节点起一个别名
aliases {
    i2c3 = &i2c4;
};
//该节点位于根节点下,它不代表实际硬件,主要用于给内核传递参数
//下面代码表示系统标准输出stdout使用串口uart1
chosen {
    stdout-path = &uart1;
};

3. 设备树OF函数

内核提供了一系列函数用于从设备节点获取节点中定义的属性,这些函数以of_开头,称为OF函数。 在编写设备树版的驱动时,在进行硬件配置方面,就是要用这些OF函数,将寄存器地址等信息从设备树文件中获取出来,然后进行配置

图片

3.1 查找节点的OF函数

设备都是以节点的形式挂到设备树上的,因此要获取这个设备的其他属性信息,必须先获取到这个设备的节点。 内核使用device_node结构体来描述一个节点,此结构体定义在文件include/linux/of.h中,定义如下:

struct device_node {
    const char *name; /* 节点名字 */
    const char *type; /* 设备类型 */
    phandle phandle;
    const char *full_name; /* 节点全名 */
    struct fwnode_handle fwnode;

    struct property *properties; /* 属性 */
    struct property *deadprops; /* removed 属性 */
    struct device_node *parent; /* 父节点 */
    struct device_node *child; /* 子节点 */
    struct device_node *sibling;
    struct kobject kobj;
    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
};

of_find_node_by_name:通过节点名字查找指定的节点

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
//from:开始查找的节点,NULL表示从根节点开始查找整个设备树
//name:要查找的节点名字
//返回值:找到的节点,NULL表示查找失败

of_find_node_by_type:通过device_type属性查找指定的节点

struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
//from:开始查找的节点,NULL表示从根节点开始查找整个设备树
//type:要查找的节点对应的type字符串,即device_type属性值
//返回值:找到的节点,NULL表示查找失败

of_find_compatible_node:根据device_type和compatible这两个属性查找指定的节点

struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
//from:开始查找的节点,NULL表示从根节点开始查找整个设备树
//type:要查找的节点对应的device_type属性值,为NULL时表示忽略掉该属性
//compatible:要查找的节点所对应的compatible属性列表
//返回值:找到的节点,NULL表示查找失败

of_find_matching_node_and_match:通过of_device_id匹配表来查找指定的节点

struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                          const struct of_device_id *matches,
                                           const struct of_device_id **match)
//from:开始查找的节点,NULL表示从根节点开始查找整个设备树
//matches:of_device_id匹配表,也就是在此匹配表里面查找节点
//match:找到的匹配的of_device_id
//返回值:找到的节点,NULL表示查找失败

of_find_node_by_path:通过路径来查找指定的节点

inline struct device_node *of_find_node_by_path(const char *path)
//path:带有全路径的节点名
//返回值:找到的节点,NULL表示查找失败

3.2 查找父/子节点的OF函数

of_get_parent:用于查找父节点

struct device_node *of_get_parent(const struct device_node *node)
//node:要查找的父节点的节点
//返回值:找到的父节点

of_get_next_child:用迭代的方式查找子节点

struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
//node:父节点
//prev:前一个子节点,即从哪一个子节点开始迭代的查找下一个子节点,若为NULL,表示从第一个子节点开始
//返回值: 找到的下一个子节点

3.3 提取属性值的OF函数

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,内核中使用结构体property表示属性,定义在include/linux/of.h中,内容如下:

struct property {
    char *name;              /* 属性名字 */
    int length;              /* 属性长度 */
    void *value;             /* 属性值 */
    struct property *next;   /* 下一个属性 */
    unsigned long _flags;
    unsigned int unique_id;
    struct bin_attribute attr;
};

of_find_property:查找指定的属性

property *of_find_property(const struct device_node *np, const char *name, int *lenp)
//np:设备节点
//name:属性名字
//lenp:属性值的字节数
//返回值:找到的属性

of_property_count_elems_of_size:获取属性中元素的数量

int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
//np:设备节点
//proname:需要统计元素数量的属性名字
//elem_size:元素长度
//返回值:得到的属性元素数量

of_property_read_u32_index:从属性中获取指定标号的u32类型数据值

int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
//np:设备节点
//proname:要读取的属性名字
//index:要读取的值标号
//out_value:读取到的值
//返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在, 
//       -ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表大小

of_property_read_u8_array:读取属性中u8类型的数组数据(类似的还有u16、u32 和 u64)

int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//np:设备节点
//proname:要读取的属性名字
//out_value:读取到的数组值
//sz:要读取的数组元素数量
//返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在, 
//       -ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太

of_property_read_u8:读取只有一个整形值的属性(类似的还有u16、u32和u64)

int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
//np:设备节点
//proname:要读取的属性名字
//out_value:读取到的数组值
//返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在, 
//       -ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小

of_property_read_string:读取属性中字符串值

int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
//np:设备节点
//proname:要读取的属性名字
//out_string:读取到的字符串值
//返回值:0,读取成功,负值,读取失败

of_n_addr_cells:获取#address-cells 属性值

int of_n_addr_cells(struct device_node *np)
//np:设备节点
//返回值:获取到的#address-cells属性值

of_n_size_cells:获取#size-cells 属性值

int of_n_size_cells(struct device_node *np)
//np:设备节点
//返回值:获取到的#size-cells属性值

3.4 其他常用OF函数

of_device_is_compatible:查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性

int of_device_is_compatible(const struct device_node *device, const char *compat)
//device:设备节点
//compat:要查看的字符串
//返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串; 
//        正数,节点的 compatible 属性中包含 compat 指定的字符串

of_get_address:获取地址相关属性

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
//dev:设备节点
//index:要读取的地址标号
//size:地址长度
//flags:参数,比如IORESOURCE_IO、IORESOURCE_MEM等
//返回值:读取到的地址数据首地址,表示读取失败

of_translate_address:将设备树读取到的地址转换为物理地址

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
//dev:设备节点
//in_addr:要转换的地址
//返回值:得到的物理地址,为OF_BAD_ADDR表示转换失败

of_address_to_resource:将reg属性值,转换为resource结构体类型

int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
//dev:设备节点
//index:地址资源标号
//r:得到的 resource 类型的资源值
//返回值: 0,成功;负值,失败

of_iomap:用于直接内存映射

void __iomem *of_iomap(struct device_node *np, int index)
//np:设备节点
//index:reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0
//返回值:经过内存映射后的虚拟内存首地址,NULL表示内存映射失败
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM
    ARM
    +关注

    关注

    134

    文章

    8651

    浏览量

    361780
  • 寄存器
    +关注

    关注

    30

    文章

    5028

    浏览量

    117719
  • 源码
    +关注

    关注

    8

    文章

    573

    浏览量

    28586
  • Linu
    +关注

    关注

    0

    文章

    25

    浏览量

    19755
  • 设备树
    +关注

    关注

    0

    文章

    35

    浏览量

    3045
收藏 人收藏

    评论

    相关推荐

    Linu内核学习视频

    最近发现一个非常好的Linu内核学习视频,是陈莉君老师讲的,非常细致,很好入门,想学习内核的同学可以看一下哈。pan点baidu点com/s/1hqxlqio
    发表于 04-28 13:52

    Linux 设备详解

    .dts的设备文件,在内核使用前需要转换一次,主要是把繁复的语法形式及属性值转换成字节数据(特殊的数据结构),而非符号。.dts文件转换后是.dtb的二进制文件。3、节点3.1、命名节点的命名以字母、数字
    发表于 10-19 15:53

    Linux 设备详解

    .dts的设备文件,在内核使用前需要转换一次,主要是把繁复的语法形式及属性值转换成字节数据(特殊的数据结构),而非符号。.dts文件转换后是.dtb的二进制文件。3、节点3.1、命名节点的命名以字母、数字
    发表于 11-02 13:46

    Linux 设备详解

    .dts的设备文件,在内核使用前需要转换一次,主要是把繁复的语法形式及属性值转换成字节数据(特殊的数据结构),而非符号。.dts文件转换后是.dtb的二进制文件。3、节点3.1、命名节点的命名以字母、数字
    发表于 11-29 17:58

    设备(Device Tree)

    设备介绍设备是一个描述设备硬件资源的文件,该文件是由节点组成的树形结构。如下:/ {nod
    发表于 03-14 14:36

    深入探究Linux的设备

    新版本linux设备讲解!!ppt- 深入探究Linux的设备_2017.8.14.pdf
    发表于 07-03 08:03

    深入探究Linux的设备

    新版本linux设备讲解!!ppt- 深入探究Linux的设备_2017.8.14.pdf
    发表于 07-09 00:15

    语法在开发过程中的相关应用和具体使用场景

    【技术学院】开发者的进阶之路:用语法来实现预编译
    发表于 05-14 06:19

    迅为iMX6开发板- 设备内核-设备相关文件简要分析

    在支持设备的内核源码中,设备用来取代来老版本内核中的平台文件。本文档主要介绍在 iMX6 开发板(iMX6Q,iMX6D,iMX6PLU
    发表于 10-21 10:51

    iMX6开发板设备文件分析

    在支持设备的内核源码中,设备用来取代来老版本内核中的平台文件。本文档主要介绍在 iMX6 开发板(iMX6Q,iMX6D,iMX6PLU
    发表于 12-30 07:29

    介绍主时钟

    什么是 时钟树下面介绍主时钟二. HSE时钟 (外部高速时钟)HSE时钟无源晶振接线方法三.HSI时钟 (内部高速...
    发表于 08-06 06:11

    介绍时钟的构成

    RCC是reset clock control的简称(即复位和时钟控制器),本文将详细介绍时钟的构成,通过理解时钟我们可以更加的理解STM32的所有时钟来源和关系。如下图,是STM32的时钟
    发表于 08-12 07:48

    什么是设备?由什么组成?设备怎么使用?

    什么是设备?由什么组成?设备怎么使用?
    发表于 03-04 07:04

    【米尔-TIAM62开发板-接替335x-试用评测】+(三)手把手创建Uboot设备与内核设备实战

    这一数据结构进行了深入的研究和学习。设备是一种特殊的语法格式,用于描述嵌入式系统中的硬件信息。这种数据结构允许我们在不直接访问硬件的情况下,通过软件来识别和控制硬件设备。 首先,我了
    发表于 11-28 09:54

    如何修改内核设备

    本文档介绍了内核设备的位置和包含关系 1.内核设备位置 文件 备注 dts longan/device/config/chips/t50
    发表于 12-14 13:42