设备树:
详解Zephyr设备树(DeviceTree)与驱动模型
来自 <https://www.cnblogs.com/jayant97/articles/17209392.html>
设备树文件有*.dts、*.dtsi两种,其中,图3、图4是图1的xp_cortex_m7.dts引用头文件xpm7.dtsi内容,*.dts文件是一种ASCII文本对Device Tree的描述,一个*.dts文件对应一个ARM的machine,由于一个SOC可能有多个不同的电路板,而每个电路板拥有一个 *.dts。这些dts势必会存在许多共同部分,为了减少代码的冗余,设备树将这些共同部分提炼保存在*.dtsi文件中,供不同的dts共同使用。*.dtsi的使用方法,类似于C语言的头文件,在dts文件中需要进行include *.dtsi文件。当然,dtsi本身也支持include 另一个dtsi文件。
类似于sram0:memory@40000000{}这样的子节点,其中40000000是一种新的设备树概念即单元地址,使用@符号将子节点与单元地址衔接
下面了解一下设备树节点中的各种属性,设备树常用属性有以下几种:
compatible:代表的硬件设备的名称,推荐的格式是"vendor,device",“vendor”部分是供应商名字,可以在 dts/bindings/vendor-prefixes.txt查询,“device”部分为模块对应驱动的名字,构建系统使用 compatible 属性为节点找到 正确的绑定,作为驱动和设备(设备节点)的匹配依据,以图4为例,compatible = “arm,pl011”;参数为arm公司的pl011 驱动,而根目录下compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965";会发现没有ti这样的公司,也没有lm3s6965这样 的驱动,实际上这里的"ti,lm3s6965"为zephyrproject/zephyr/dts/arm目录下ti文件夹的lm3s6965.dtsi,而lm3s6965.dtsi 内完成了类似图4这样的驱动设备匹配工作。
label:标签属性与节点标签不是一个内容,标签属性依据设备驱动模型而来,例如UART、I2C等,设置label为I2C_0,则会 调用device_get_binding("I2C_0")并返回一个指向设备结构的指针,该结构可以传递给I2C API函数, 如 i2c_transfer(), 生成的 C 头文件将包含该字符串的宏。
reg:用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,常见设置模式为(address, length)。
status:描述设备的状态信息,okay代表设备正常运行,disable代表该设备尚未运行,fail代表表明设备不可操作,fail-sss代 表含义和“fail”相同,后面的 sss 部分是检测到的错误内容。
model:描述板子的型号或者芯片型号,仅仅是给人看的,无其他特殊用途。
#address-cells:决定了子节点 reg 属性中地址信息所占用的字长(u32)。
#size-cells:决定了子节点 reg 属性中长度信息所占用的字长(u32)。
name:用于记录节点名字,已废弃,不建议使用。
ranges:格式 <local地址, parent地址, size>, 表示将local地址向parent地址的转换,比如对于#address-cells和#size-cells 都为1的话,以<0x0 0x10 0x20>为例,表示将local的从0x0~(0x0 + 0x20)的地址空间映射到parent的0x10~(0x10 + 0x20),其中,local地址的个数取决于当前含有ranges属性的节点的#address-cells属性的值,size取决于当前含有ranges 属性的节点的#size-cells属性的值。而parent地址的个数取决于当前含有ranges属性的节点的parent节点的 #address-cells的值。对于含有ranges属性的节点的子节点来说,子节点reg都是基于local地址的,ranges属性值为空 的话,表示1:1映射。
*.dts和*.dtsi是从是从目标的架构、SoC、板和应用程序目录中收集的,*.dts通过c预处理器来include *.dtsi,此外,c预处理器也会处理合并设备树扩展文件*.overlay,并在dts、dtsi、overlay文件中实现宏扩展,预处理器最终输出文件为build/zephyr/zephyr.dts.pre。
gen_defines.py解析处理zephyr.dts.pre生成包含预处理宏的devicetree_unfixed.h头文件。devicetree.h包含device tree_unfixed.h,源代码通过包含devicetree.h来访问设备树预处理宏。gen_defines.py将最终设备树信息写入zephyr.dts
上述表示方式表示:中断号为0xA,中断优先级为8.
上述表示有2个中断控制器,PIC及GIC,第一个中断连接到中断控制器PIC总线上,中断号为0xA,优先级为8.
我们无需关心devicetree_generated.h文件本身的内容,因为它不是给人看的,需要使用一套宏函数来将其读出。在需要操作DeviceTree的文件中包含以下头文件:
#include<zephyr/devicetree.h>
这里给出一个示例:
/{
zephyr,user {
test-gpios = <&gpio0 17 0>;
};
};
#include<zephyr/drivers/gpio.h>// 自己想要操作的节点的id,这里想要操作的节点是zephyr,user
#define NODE_ID DT_PATH(zephyr_user)
// 获取到zephyr,user节点的test-gpios属性,并把它作为gpio specifier,读入GPIO驱动。
Static const struct gpio_dt_spec test_io=GPIO_DT_SPEC_GET(NODE_ID, test_gpios);
// 实际代码
Int main()
{
// 判断设备(这里是gpio控制器)是否已初始化完毕
// 一般情况下,在application运行前,zephyr驱动就已经把控制器初始化好了
if(!device_is_ready(test_io.port)) {
return;
}
// 重新配置IO
// 如果DeviceTree里写好了,这里也可以不配
gpio_pin_configure_dt(&test_io, GPIO_OUTPUT_INACTIVE);
// 操作IOgpio_pin_set_dt(&test_io,1);
gpio_pin_set_dt(&test_io,0);
return0;
}
获得节点id的方式还有很多:通过父节点找子节点、通过子节点找父节点等等。详细不多赘述,可参考:
https://docs.zephyrproject.org/latest/build/dts/api-usage.html#node-identifiers
DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), clock_frequency) /* 宏展开为 1 */
DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), not_a_property) /* 宏展开为 0 */
Devicetree API的参考手册:
通用API及设备相关的特殊API接口,特殊API接口提供了与外设相关的特定的一些API操作。
整数与字符串示例:
DT_PROP(DT_PATH(soc, i2c_40002000), clock_frequency) /* 宏展开为 100000, */
#define I2C1 DT_NODELABEL(i2c1)
DT_PROP(I2C1, status) /* 宏展开为 "okay" */
数组示例:
假设dts为
foo:foo@1234 {
a = <1000 2000 3000>; /* array */
b = [aa bb cc dd]; /* uint8-array */
c = "bar", "baz"; /* string-array */
};
则C代码中可以写作:
Zephyr标准驱动
Zephyr是一个跨平台的操作系统,自然少不了对各类标准硬件的跨平台支持。