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

    文章

    9054

    浏览量

    366827
  • 寄存器
    +关注

    关注

    31

    文章

    5322

    浏览量

    120018
  • 源码
    +关注

    关注

    8

    文章

    633

    浏览量

    29140
  • Linu
    +关注

    关注

    0

    文章

    26

    浏览量

    19809
  • 设备树
    +关注

    关注

    0

    文章

    38

    浏览量

    3110
收藏 人收藏

    评论

    相关推荐

    Linux 设备详解

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

    Linux 设备详解

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

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

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

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

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

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

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

    如何修改内核设备

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

    ARM Device Tree设备

    近期在学校如何写linux的设备驱动,这片文章告诉我们为什么要引进设备Device Tree,以及举例说明设备是怎样写的
    发表于 11-17 18:16 22次下载

    决策介绍

    关于决策介绍,是一些很基础的介绍,不过是英文介绍
    发表于 09-18 14:55 0次下载

    ARM嵌入式Linux设备介绍及应用

      设备机制从Linux内核3.2版本左右开始采用,其不仅可以定义ARMSoC内部内存映射外设,还可以定义整个板卡,下面就以ToradexColibriVF61计算机模块搭配ColibriEvaBoard为例来展示设备
    发表于 09-16 11:53 9次下载
    ARM嵌入式Linux<b class='flag-5'>设备</b><b class='flag-5'>树</b><b class='flag-5'>介绍</b>及应用

    详细解答Linux设备语法的原理构造

    Linux内核从3.x开始引入设备的概念,用于实现驱动代码与设备信息相分离。在设备出现以前,所有关于
    发表于 05-15 10:53 1564次阅读
    详细解答Linux<b class='flag-5'>设备</b><b class='flag-5'>树</b><b class='flag-5'>语法</b>的原理构造

    Petalinux工程中设备介绍

    设备是 Petalinux kernel 的关键组件,接下来以 2020.1 版本为例,为大家介绍一下在Xilinx Petalinux 工程中的设备
    的头像 发表于 02-20 16:32 6819次阅读
    Petalinux工程中<b class='flag-5'>设备</b><b class='flag-5'>树</b>的<b class='flag-5'>介绍</b>

    基于关键词的GCC抽象语法消除冗余算法

    GCC( GNU Compiler Collection)编译器编译¢语言源程序所生成的抽象语法文本中包含大量与源代码无关的冗余信息,若直接进行解析,会严重影响分析效率,降低分析精确度,同时
    发表于 05-07 10:30 210次下载

    设备的传递及kernel 对设备的解析

    当 U-Boot 将设备加载到内存指定位置后,ARM 内核的 SoC 以通用寄存器 r2 来传递 dtb 在内存中的地址。kernel 获取到该地址后对 dtb 文件做进一步的处理。 设备
    的头像 发表于 07-29 11:19 2375次阅读
    <b class='flag-5'>设备</b><b class='flag-5'>树</b>的传递及kernel 对<b class='flag-5'>设备</b><b class='flag-5'>树</b>的解析

    zynq开发中的设备

    在zynq开发中经常会修改设备,每次遇到这种情况都有点发愁,今天把设备相关的知识点总结一下,希望以后遇到设备
    的头像 发表于 05-25 11:29 2111次阅读
    zynq开发中的<b class='flag-5'>设备</b><b class='flag-5'>树</b>

    如何修改内核设备

    如何修改内核设备
    的头像 发表于 12-14 14:06 772次阅读
    如何修改内核<b class='flag-5'>设备</b><b class='flag-5'>树</b>