设备树不是用来写驱动,而是用来给内核里的驱动程序指定硬件信息
1、设备树的语法
1.1 DTS文件格式
DTS(Device Tree Source)文件是一种描述硬件设备和设备树的源代码文件。它是用于描述硬件平台和设备树的一种规范格式,用于在Linux内核中配置和管理硬件设备。
DTS文件使用的是一种基于文本的语言,它采用了树状结构来描述硬件设备和设备之间的关系。DTS文件通常以.dts
为扩展名。
通过编写DTS文件,可以描述硬件设备的各种属性和连接关系,包括设备的寄存器地址、中断号、时钟源、GPIO引脚等。Linux内核在启动过程中会解析DTS文件,根据设备树的描述来进行硬件的初始化和驱动的匹配。这样可以实现对不同硬件平台的统一管理和配置。
DTS的文件布局
/dts-v1/; // 表示版本
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/{
[property definitions]
[child nodes]
};
1.2 node格式
设备树中的基本单元,被称为node
,其格式为:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
label 是标号,可以省略。label 的作用是为了方便地引用 node。例如:
/dts-v1/;
/ {
uart0: uart@fe001000 {
compatible="ns16550";
reg=<0xfe001000 0x100>;
};
};
可以使用下面 2 种方法来修改 uart@fe001000
这个 node
:
// 在根节点之外使用 label 引用 node:
&uart0 {
status = “disabled”;
};
或在根节点之外使用全路径:
&{/uart@fe001000} {
status = “disabled”;
};
1.3 properties 的格式
简单地说,properties
就是name=value
,value 有多种取值方式
//格式1
[label:] property-name = value;
//格式2(没有值)
[label:] property-name;
//Property取值只有3种:
//arrays of cells(1 个或多个 32 位数据, 64 位数据使用 2 个 32 位数据表示)
interrupts = <17 0xc>;//32bit
clock-frequency = <0x00000001 0x00000000>;//64bit
//string(字符串)
compatible = "simple-bus";
//bytestring(1 个或多个字节)
local-mac-address = [00 00 12 34 56 78]; // 每个 byte 使用 2 个 16 进制数来表示
local-mac-address = [000012345678]; // 每个 byte 使用 2 个 16 进制数来表示
//也可以是各种值的组合
compatible = "ns16550", "ns8250";
example = <0xf00f0000 19>, "a strange property format";
1.4 dts文件包含dtsi文件
在内核的 arch/arm/boot/dts
目录下就有能用的设备树模板,一般命名为 xxxx.dtsi
。i
表示include
,被别的文件引用的。
dtsi
文件跟 dts
文件的语法是完全一样的。
dts
中可以包含.h
头文件,也可以包含 dtsi
文件,在.h 头文件中可以定义一些宏。
1.5 常用的属性
1.5.1 #address-cells、#size-cells
cell 指一个 32 位的数值,
address-cells:address 要用多少个 32 位数来表示;
size-cells:size 要用多少个 32 位数来表示
下例中,address-cells 为 1,所以 reg 中用 1 个数来表示地址,即用 0x80000000 来表示地址;size-cells 为 1,所以 reg 中用 1 个数来表示大小,即用 0x20000000 表示.
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
1.5.2 compatible
compatible
表示“兼容”,对于某个 LED,内核中可能有 A、B、C 三个驱动都支持它,那可以这样
写:
led {
compatible = "A","B", "C";
};
内核启动时,就会为这个 LED 按这样的优先顺序为它找到驱动程序:A、B、C。
根节点下也有 compatible 属性,用来选择哪一个“machine desc”:一个内核可以支持 machine A,
也支持 machine B,内核启动后会根据根节点的 compatible 属性找到对应的 machine desc 结构体,执行其中的初始化函数。
compatible
的值,建议取这样的形式:manufacturer,model
,即“厂家名,模块名”。
1.5.3 model
model 属性与 compatible 属性有些类似,但是有差别。
compatible 属性是一个字符串列表,表示你的硬件兼容 A、B、C 等驱动;
model 用来准确地定义这个硬件是什么。
比如根节点中可以这样写:
/ {
compatible = "my,A","my,B";
model = "hardwordName";
};
//这里表示这个单板,可以兼容内核中的"A",也兼容内核中的"B",但是它是什么板,用model属性明确
1.5.4 status
dtsi
文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个 status 属性,设置为disabled
:
&uart1 {
status = "disabled";
};
//value可取值
//okay:设备正常运行
//disabled:设备不可操作,但是后面可以恢复正常工作
//fail:发生了严重错误,需要修复
//fail-sss:发生了严重错误,需要修复;sss表示错误信息
1.5.5 reg
reg
的本意是 register
,用来表示寄存器地址。
但是在设备树里,它可以用来描述一段空间。反正对于 ARM 系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。
reg 属性的值,是一系列的address size
,用多少个 32 位的数来表示 address 和 size,由其父节
点的#address-cells、#size-cells 决定。
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
1.5.6 interrupts
interrupts
表示设备的中断信息。interrupts
属性用于描述设备的中断线路,包括中断控制器的引脚号和中断触发方式。
interrupts
属性的值是一个整数数组,每个元素表示一个中断线路。数组的第一个元素表示中断控制器的引脚号,后续的元素表示中断触发方式和其他中断相关的属性。
示例:
interrupts = <0 2 0x04>;
//表示设备的中断控制器引脚号为0,中断触发方式为2(上升沿触发),其他中断相关属性为0x04
1.5.7 常用的结点
(1) dts
文件必须有一个根节点:
/dts-v1/;
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
};
根节点中必须有这些属性:
#address-cells
// 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述地址(address)
#size-cells
// 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述大小(size)
compatible
// 定义一系列的字符串, 用来指定内核中哪个 machine_desc 可以支持本设备
// 即这个板子兼容哪些平台
// uImage : smdk2410 smdk2440 mini2440 ==> machine_desc
model
// 咱这个板子是什么
// 比如有 2 款板子配置基本一致, 它们的 compatible 是一样的
// 那么就通过 model 来分辨这 2 款板子
(2) CPU
节点
一般不需要我们设置,在 dtsi 文件中都定义好了:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
.......
}
};
(3) memory
节点
芯片厂家不可能事先确定你的板子使用多大的内存,所以 memory 节点需要板厂设置,比如:
memory {
reg = <0x80000000 0x20000000>;
};
(4) chosen
节点
我们可以通过设备树文件给内核传入一些参数,这要在 chosen 节点中设置 bootargs 属性:
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
2、内核对设备树的处理
从源代码文件dts文件开始,设备树的处理过程为:
dts
在PC机上被编译为dtb文件u-boot
把dtb
文件传给内核- 内核解析
dtb
文件,把每一个节点都转为device_node
结构体 - 对于某些
device_node
结构体,会被转为platform_device
结构体
dtb里的每一个节点都会被转换为device_node
结构体
//包含了设备树节点的各种属性和信息
struct device_node {
const char *name; // 节点的名称
const char *type; // 节点的节点所代表的设备的类型(device_type)
struct device_node *parent; // 指向父节点的指针
struct device_node *child; // 指向子节点的指针
struct device_node *sibling; // 指向兄弟节点的指针
struct property *properties; // 指向节点属性的指针
struct property *deadprops; // 指向已删除属性的指针
struct node_ops *ops; // 节点操作的指针
void *data; // 指向节点的私有数据指针
struct list_head allnext; // 用于链表操作的指针
struct fwnode_handle fwnode; // 用于处理固件节点的句柄
struct fwnode_handle *secondary; // 用于处理辅助固件节点的指针
struct of_phandle_args phandle_args; // 用于处理phandle属性的参数
};
struct property {
const char *name;
unsigned int length;
void *value;
struct property *next;
struct kobject *kobj;
const struct of_device_id *of_match;
};
2.1 符合下列条件的节点会被转换为platform_device
(1) 根节点下含有compatile
属性的子节点
(2) 含有特定 compatile
属性节点的子节点
如果一个节点的 compatile
属性,它的值是这4者之一:simple-bus
,simple-mfd
,ias
,armba-bus
,那么它的子节点(也需要包含compatile属性)也可以转换为platform_device
。
注意:总线I2C、SPI节点下的子节点:不转换为platform_device。某个总线下的子节点。应该交给对应的总线驱动程序来处理,他们不应该被转换为platform_device。
2.2 device_node结构体如何转为platform_device结构体
(1) platform_device 中含有 resource 数组, 它来自 device_node 的 reg, interrupts 属性;
(2) platform_device.dev.of_node 指向device_node, 可以通过它获得其他属性;
3、platform_device 如何与 platform_driver 配对
从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个 platform_driver
时,它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的 probe 函数。
下图为platform_device与platform_driver 的匹配过程: