1 设备树的说明

在写完嵌入式驱动总结后,对于设备树相关的语法和使用一直都想进行系统的描述,但是因为最近比较忙碌,所以一直拖到现在才完成初版,对于整个嵌入式Linux驱动开发中,设备树语法和构建是其中比较核心的部分,是需要比较系统的去学习掌握的。本文参考设备树说明文档<Devicetree Specification Release v0.2>, 在结合日常驱动开发中积累的经验,总结完成的一篇设备树语法的说明。对于驱动的编写来说,设备树语法的了解自然必不可少,但大多数情况下我们模仿厂商的实现,在结合芯片手册就可以增加自己需要的功能,不过如何来添加设备节点,保证添加的有效性,这时候就需要掌握理和解设备树语法。随着设备树逐渐成为嵌入式驱动开发中的主流,并逐渐取代寄存器的访问方式,设备树对于驱动开发越来越重要,另外如果在本章了解中对于设备树中有疑惑不理解的部分,这很正常,可以先大概浏览下,做到心中有着概念,在带着疑问去学习驱动的内容,当你困惑的时候,可以回来系统的理解设备树的语法(不理解不要纠结),不要在缺少积累的时候去钻牛角尖,这也是嵌入式学习的重要经验。

1.1 设备树综述

对SOC构造和嵌入式硬件有了解的话,芯片一般由内核(Cortex-A), 以及通过系统总线(AHB, AXI等)挂载的GPIO,I2C,SPI,PWM,Ethernet等外设模块构成。而对于具体的外设模块,如I2C外设,又支持访问多个器件来满足不同功能的需求。而设备树,就是基于系统总线作为主干,将芯片SOC和各类外设以及外部器件用数据结构的形式组合起来描述硬件结构的文件,是对硬件模型的抽象,总结来说,设备树就是对硬件结构的抽象描述。

上面就是比较常见的基于SOC构建的嵌入式应用系统,包含芯片和外围的设备,虽然总线可能不指一条,外设模块的设备连接情况也会更加复杂,但都没有树结构模型,而设备树也是按照如此模型进行设计的,理解这一点,也可以更深刻的知道设备树的实现思路。

在对嵌入式整个硬件框架有一定了解后,下面还要理解几个名词。

DTS 设备树的源码文件,用于描述设备硬件情况的抽象

DTSI 和C语言的#include类似,也是描述设备树的源码文件,另外DTS同样被#include包含

DTB 基于DTS源码编译的二进制文件,用于内核调用读取设备树信息的文件。

DTC 用于编译DTS到DTB的工具,由内核编译时使用make dtbs编译设备树二进制文件过程中生成。

基于以上信息,我们理解DTS/DTSI是基于DTS语法实现的设备描述文件,DTB则是用于内核解析,需要下载的文件,下面正式开始设备树语法的讲解。

1.2 设备树语法

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

1.2.1 #include语法

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

#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"

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

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

1.2.2 节点描述

对于设备树来说,都是有根节点开始,在添加不同的设备节点描述的,以比较简单的LED设备树为例:

/{  //根节点//......led { //节点名(子节点) <name> compatible = "gpio-led";  //节点属性pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_leds>;led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;status = "okay";};gpio_keys: gpio_keys@0 {  //节点名(子节点) <label>:<name>[@<unit_address>]          compatible = "gpio-keys";  //节点属性pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_keys>;#address-cells = <1>;#size-cells = <0>;autorepeat;key1@1 {   //节点名(子节点) <name>[@<unit_address>] label = "USER-KEY1"; //节点属性 key-value键值对linux,code = <114>;gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;gpio-key,wakeup;};};
}

1. 设备树文件都由根节点开始,每个设备只有一个根节点(如果包含多个文件,根节点则会合并),其它所有设备都作为子节点存在,由节点名和一组节点属性构成。

2. 节点属性都是有key-value的键值对来描述,并以 ; 结束

3. 节点间可以嵌套形成父子关系,这样可以方便描述设备间的关系

4. 节点名支持<name>[@<unit_address>]的格式,其中后面的unit_address可选,一般为设备地址,这是为了用于保证节点是唯一的标识,当然用其它数字也可以。

同时,节点名也支持<label>:<name>[@<unit_address>]的格式, 这里的label就是节点的标签别名,我们可以&<label>来直接访问节点。如对于gpio_keys: gpio_keys@0 可以通过&gpio_keys来访问clock@ gpio_keys@0,后面我们就将用到这个说明。

5. 在设备树中查找节点需要完整的节点路径,对于项目来说,直接修改官方的dts文件是不推荐的,如果自己建立路径,又过于复杂,因此设备树提供通过标注引用<label>的方式,允许我们在其它文件中修改已存在的节点,或者添加新的节点,对于节点的合并原理,包含以下原则:

a. 不同的属性信息进行合并

b. 相同的属性信息进行覆写

基于这种原则,我们可以通过如下的代码,在已有节点添加更新新的数据,如使用如下代码在gpio_keys: gpio_keys@0中增加节点。

&gpio_key{key2@2{label="usr-key2”;//.....}
}

上面就是节点相关的信息,下面就开始深入节点内部,讲述节点内部如何基于属性来定义设备的说明。

6. 在驱动中可以同/<node-1>/<node-2>/.../<node-n>的方式访问到指定设备节点

如上面的访问key1@1节点即为

/gpio_keys@0/key1@1

方式即可访问到指定key1@1节点

1.2.3 节点属性

在上一章我们理解了设备树的节点间关系,并讲述了如何添加节点或修改已经存在节点的方法,进一步我们就要抽丝剥茧,讲述属性的说明。

在我们上一章节中,讲到属性是key-value的键值对,这就分两部分讲解设备树的说明。

1.2.3.1 常见value类型

其中value中常见的几种数据形式如下:

1. 空类型

ranges;

空类型,仅需要键值,用来表示真假类型, 或者值可选的类型

2. 字符串<string>

compatible = "simple-bus";

这里”simple-blus”就是属性中对应的字符串值。

3. 字符串表<stringlist>

compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";

值也可以为字符串列表,中间用,号隔开,这样既可以支持多个字符串的匹配

4. 无符号整型<u32>/<u64>

无符号的整型数值

offset = <0x38>;

5. 可编码数组<prop-encoded-array>

支持编码的多无符号整数的数组,如reg可以通过

#address-cells指定地址单元的数目,#size-cells指定长度单元的数目

reg = <0x020ac000 0x4000>;

可以通过&<label>的方式,即可引用其它节点的数据,用于后续的处理

clocks = <&clks IMX6UL_CLK_PLL3_USB_OTG>;

1.2.3.2 常用key属性

1. compatible

<stringlist>字符串列表类型

compatible属性是值是由特定编程模型的一个或多个字符串组成,用于将驱动和设备连接起来,我们在驱动中也是通过compatible来选择设备树中指定的硬件,是非常重要的属性。compatible的格式一般为:

“[<manufacturer>,]<model>”

compatible = "arm,cortex-a7";
compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
compatible = "gpio-led";

在该模型中,

Manufacturer表示厂商,可选

Model表示指定型号,一般和模块对应的驱动名称一致(当然不一致也不影响实际功能)

在驱动中使用platform_driver中of_match_table里即可使用.compatible用来匹配该对应设备节点,另外匹配时严格的字符串匹配的,所以驱动内的匹配值和设备树中的value要保持一致。

spidev: icm20608@0 {compatible = "alientek,icm20608";spi-max-frequency = <8000000>;reg = <0>;
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = SPI_ICM_NAME,.of_match_table = icm20608_of_match, },
};

参考上述结构,即可看到通过of_math_table指定设备树匹配列表,找到指定的节点去访问。

2. model

<string>字符串类型

指定设备商信息和模块的具体信息,也有用于模块功能说明,和compatible类似,但仅支持单个字符串模式。

model = "Freescale i.MX6 ULL 14x14 EVK Board";

3. status

<string>字符串类型

指示设备的运行状态,目前支持的状态列表如下:

4. #address-cell和#size-cell

<u32>无符号整型

#address-cells和#size-cells可以用在任何拥有子节点的设备中,用于描述子节点中”reg”对应属性内部值的信息,其中

#address-cells 用来描述字节点中”reg”对应属性中描述地址列表中cell数目

#size-cells 用来描述字节点中”reg”对应属性中描述长度列表中cell数目

#address-cells和#size-cells属性不是从devicetree的祖先继承的。它们需要明确定义,如果未定义,对于设备树则默认按照地址cell为2个,长度cell为1个去解析reg的值。

soc { #address-cells = <1>; #size-cells = <1>; serial { compatible = "ns16550"; reg = <0x4600 0x100>; clock-frequency = <0>; interrupts = <0xA 0x8>; interrupt-parent = <&ipic>; };
};

如这里reg就要被解析为address-1位,值为0x4600, size-1位,值为0x100。

5. reg

<pro-encode-array> 可编码数组类型

由任意长度的地址和长度构成,描述设备在父设备地址空间中的总线范围,通过#address-cells和#size-cells变量去解析,另外如果#size-cells的长度位0,则reg中后面关于长度的部分应该去除,reg的举例如下:

#address-cells = <1>;          //指定address的范围长度
#size-cells = <0>;             //指定size的范围长度ethphy0: ethernet-phy@0 {compatible = "ethernet-phy-ieee802.3-c22";reg = <0>;                 //实际reg对应的寄存器地址和范围
};

6. ranges

<empty>或者<child-bus-address, parent-bus-address, length>类型

ranges 非空时是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度

这三部分组成。

child-bus-address:子总线地址空间的物理地址

parent-bus-address:父总线地址空间的物理地址

length:子地址空间的长度

如果 ranges 属性值为空值,说明不需要进行地址转换。

7. name和device_type

<string>字符串类型

分别表示节点名称或者设备树类型属性,这两个属性在新版本中已经被废弃,这里就不再讨论,可以通过《Devicetree Specifification Release v0.2》的2.3章节查看。

除上述的标准属性外,设备树也支持其它属性如

clock-frequency    //指定模块的时钟频率
label              //指定可读的标签,用于开发者查看的属性
current-speed:     //串口的波特率

等,此外也支持自定义的属性键值对来实现符合自己驱动应用的需求。

1.3 设备树在驱动中的应用

上面描述的基本都是设备树语法的部分,不过对于驱动来说,如何从设备树中提取有效的设备信息,从而在驱动中脱离对硬件寄存器的直接访问,如何把设备树用于嵌入式驱动开发中,这部分内容也相当重要,对于嵌入式Linux设备,语法树是在/sys/firmware/devicetree下,可使用

ls /sys/firmware/devicetree/base/

来查看当前根节点下的设备树文件,如下:

对于驱动来说,可以使用内核提供访问设备树的函数用于匹配节点的接口来访问设备树。

1.1.1 内核设备树访问函数

内核访问设备树的函数主要包含获取节点的函数和获取节点内部属性的函数,这些函数都定义在内核include/linux/of.h中

//根据节点路径获取设备节点信息
struct device_node *of_find_node_by_path(const char *path)
struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
//根据设备属性获取设备节点信息
struct device_node *of_find_node_by_name(struct device_node *from, const char *name)
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat)
//根据匹配的of_device_id表格获取node节点(在框架中常用的匹配方式)
static inline 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);

根据这些信息,我们就可以实现如下代码,找到设备树内的指定节点,代码如下:

/*获取设备节点*/
nd = of_find_node_by_path("/usr_gpios/beep");
if(nd == NULL){return -EINVAL;
}
else{printk(KERN_INFO"node find by path okn");
}nd = of_find_compatible_node(NULL, NULL, "gpio-beep");
if(nd == NULL){return -EINVAL;
}
else{printk(KERN_INFO"beep node find by compatible okn");
}

在获取设备节点后,我们可以通过内核提供的接口对节点内的key-value键值对进一步读取,具体接口如下:

//提取通用属性的接口
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value);
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
//用于获取硬件信息的接口
int of_get_named_gpio(struct device_node *np,const char *propname, int index);
int of_get_gpio(struct device_node *np, int index);

在了解这些代码后,就可以实现如下代码访问设备树内的参数属性,具体如下

proper = of_find_property(nd, "name", NULL);
if(proper != NULL)printk(KERN_INFO"%s:%sn", proper->name, (char *)proper->value);
ret = of_property_read_string(nd, "status", &pStr);
if(pStr != NULL)printk(KERN_INFO"status:%sn", pStr);

而在部分框架中,也对上述接口进一步封装,如platform_device_driver中需要提供的of_device_id就是更进一步的调用接口,通过

static const struct of_device_id key_of_match[] = {{ .compatible = "usr-gpios" },{ /* Sentinel */ }
};

结构,也能实现对设备树的匹配,这在很多驱动框架中都是十分常用的,需要在实践中总结理解。

1.4 总结

至此,对设备树的语法进行了比较全面的讲解,当然这里面还有很多不完善的地方,如对中断控制器和中断相关的语法目前尚未说明,另外很多部分的理解受水平限制有遗漏或者错误的地方,如果有发现,请及时反馈。当然在实际驱动开发中,熟悉这些知识还是不够的,日常打交道还有很多是芯片厂商或者方案商定义的具有特定功能的自定义属性键值对,这就需要长期的积累了。不过理解了设备树语法的原理,反过来去理解这些自定义属性,是清晰明了的。这篇文章只能算是对设备树语法的入门指引,如果希望深入去掌握嵌入式驱动开发,还是配合着实际产品的硬件框架,在实际任务的维护或者修改设备树,再结合参考资料中提到的文档和本文的说明,带着目的去学习,才是高效且快速的方式。

参考资料:

  1. <Devicetree Specification Release v0.2>

2. imx6ull设备树文件

linux spidev 应用_嵌入式Linux设备树语法总结相关推荐

  1. 【Linux驱动开发】设备树详解(二)设备树语法详解

    ​ 活动地址:CSDN21天学习挑战赛 [Linux驱动开发]设备树详解(一)设备树基础介绍 [Linux驱动开发]设备树详解(二)设备树语法详解 [Linux驱动开发]设备树详解(三)设备树Kern ...

  2. Linux设备树语法详解【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备 ...

  3. Linux源码编译-编译哪些设备树

    Linux源码编译-编译哪些设备树 Fang XS. 1452512966@qq.com 如果有错误,希望被指出 编译内核过程中编译了那些设备树文件 编译源码前都会make xxxdefconfig ...

  4. i.MX6ULL终结者Linux设备树DTS设备树语法结构

    文章目录 1 dtsi头文件 2 设备节点信息 3 设备节点及label的命名 4 标准属性 5 根节点compatible属性 6 在设备节点中添加或修改内容 一般情况下,我们不会从头编写一个完整的 ...

  5. linux spi 工作模式,嵌入式linux系统的开发——SPI Bit-banging方法的实现

    前言 SPI(Serial Peripheral Interface)是一种应用广泛的通信总线,通常微处理器上会集成SPI模块以支持该通信协议,输出正确的信号的时序,并保证时序间同步,实现与外部SPI ...

  6. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

  7. 嵌入式linux的发展历程,嵌入式Linux论文(历史发展分类及应用)

    嵌入式Linux 一.嵌入式Linux简介 1.1 嵌入式Linux历史 随着社会的发展,信息化技术的成熟和数字化产品的普及,让以计算机技术.芯片技术和软件技术为核心的嵌入式系统再度成为当前研究和应用 ...

  8. linux 直流电机驱动设计,嵌入式Linux直流电机驱动.PPT

    嵌入式Linux直流电机驱动.PPT 嵌入式Linux直流电机驱动实验 开发平台中直流电机驱动的实现 S3C2410芯片自带定时器,所以控制部分省去了三角波产生电路.脉冲调制电路和PWM信号延迟及信号 ...

  9. 设备树语法,加载过程和与驱动的关系

    文章目录 一.设备树语法 1.1 简介 1.2 基本数据格式 1.3 一个例子 1.3.1 根节点 2.3.2 CPU 1.3.3 节点名称 1.3.4 设备 1.3.5 status 1.3.6 编 ...

最新文章

  1. R语言ggplot2可视化使用ggsave将可视化图像结果保存为SVG文件实战
  2. logstsh xpack 认证_ElasticSearch Kibana 和Logstash 安装x-pack记录
  3. 面试再问值传递与引用传递,把这篇文章砸给他!
  4. php 根据坐标计算范围内,php计算经纬度是否在区域内
  5. Python可以调用Gpu吗_python可以开发app吗
  6. php如何接入微信支付接口,PHP实现微信支付(jsapi支付)流程的方法
  7. NIHCC发布迄今世界最大的CT医学影像数据集(附下载)
  8. 数据结构与算法课程作业--奇数个数的数的查找方法-异或
  9. 《学习之道》第十章方法空间能力是可以后天形成的
  10. javascript中typeof、undefined 和 null
  11. 如何设置excel表格表头冻结_Excel如何固定表头,Excel冻结首行首列或指定行
  12. win10计算机远程连接命令,详细教你win10设置远程桌面连接命令
  13. 【react学习笔记】为什么页面只展示空标签
  14. DT财经:2018北京城市大数据活跃报告
  15. 百万邮做邮件营销的邮箱配置
  16. push_back、emplace_back、std::move
  17. FANUC机器人外部电缆连接示意图(一)
  18. 女友闹别扭不用担心,这个撩妹黑科技轻松哄好
  19. 更改Linux系统的主机名(hostname)两种实用的方法
  20. 苹果6plus自动时间不准 修复苹果自动设置时间不准教程(图文)

热门文章

  1. 专栏推荐丨Oracle Database 21c 专栏
  2. 详述MySQL事务的实现原理
  3. 读者福利:复盘2018上半年精选文章,还有礼品等着你!
  4. 云小课|大数据时代的隐私利器-GaussDB(DWS)数据脱敏
  5. 什么是全场景AI计算框架MindSpore?
  6. 【DevCloud · 敏捷智库】如何进行需求优先级管理?
  7. 华为云提供多场景本地数据上云方案,数据上云不再愁
  8. frc机器人比赛主题_RCC机器人比赛
  9. 迷宫问题c语言报告,c语言写的迷宫问题
  10. 高等组合学笔记(十五):容斥原理,错排问题