设备树是设备的纯粹描述,不会去改变设备的状态,也不能设置设备初值。应该由驱动程序读取这些描述信息进而控制设备。
先导知识
简述启动过程
设备树 dtb 由 kernel 工程编译生成;
在启动 uboot 时传入 dtb 地址,由 uboot 解析后获取存储器信息以进行系统初始化,接着 uboot 会传递 dtb 地址给 kernel;
设备树源文件
源文件有 dts、dtsi 等,dts 可以引用 dtsi 文件,最后由 dtc 编译生成 dtb 文件;dts/dtsi 兼容c语言的一些语法,能使用宏定义,也能包含 .h 文件;
源文件路径: linux-3.10/arch/arm/boot/dts/xxx.dts
文档路径: linux-3.10/Documentation/devicetree/bindings
BSP 中设备树的属性和子节点含义必须存档在 binding 文档中,当我们发现设备树中的一些属性不能理解时,在该目录下查看相应的文档都能找到答案。
设备树原理
linux3.x 之前我们需要为每一个设备编写设备模块和驱动模块,并将它们注册到系统,如果两者 compatible 字段匹配,就可以正常关联;
现在只需要提供一份包含设备信息的设备树文件,系统会遍历树中的节点,将设备信息注册到系统(还未生成设备),此时已经可用于匹配;在驱动中我们通过 of 系列 API 从设备树中读取配置并使用,一般也在驱动中生成设备。of API 见附录;
最终的结果是,驱动匹配后会在 /sys 目录下创建关于设备和驱动的虚拟文件。通常 i2c、spi、usb 等总线节点会生成到 platform 总线下,总线下的设备会生成到对应总线下。可以在 drivers 目录下搜索设备树中的 compatible 字段,查看驱动的具体实现。
#address-cells 、#size-cells、reg 三者的关系
格式如下:
父节点的 #address-cells 和 #size-cells 决定子节点的 reg 的 address 和 lenth 字段的长度,cell 的单位为 32bit;
reg 的长度为 (address-cells+size-cells) 的倍数;
1
2
3
4
5
6
#address-cells // 子节点 reg 的 address 为 n1 个 32bit 的整型数据
#size-cells // 长度为 n2 个 32bit 整型数据,如果为 0,则没有 lenth 字段
node{
reg = <address1 lenth1 [address2 lenth2] [address3 lenth3]...>;
}
示例1:
有两块内存:
第一块起始地址 0x30000000,大小 0x04000000;
第二块内存起始地址 0x00000000 大小 4096;
1
2
3
4
5
6
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x30000000 0x04000000 0x00000000 4096>;
};
示例2:
有一块内存:
地址范围:0x4000000~0x8000000,多个 32bit 位表示时使用大端模式;
1
2
3
4
5
6
7
#address-cells = <2>;
#size-cells = <2>;
memory@40000000 {
device_type = "memory";
reg = <0x0 0x40000000 0x0 0x40000000>;
};
预定义的属性
-
“compatible” 属性用来匹配驱动,一般用 ”厂商芯片,控制器型号” 表示;
-
“model” 属性用来表示设备的型号,填写主板名,整个 dts 中只需要一个;
-
“#address-cells”、”#size-cells”、”reg”、”ranges”、”dma-ranges” 属性都是和地址有关的;
-
“reg” 属性用来表示节点地址资源的,格式是二元组(地址,大小),常见的就是寄存器的起始地址及大小;
-
‘ranges’ 属性是 cpu 使用的地址,格式是三元组(子总线地址,父总线地址,大小);
-
‘dma-ranges’ 属性是 dma 使用的地址,格式是三元组(子总线地址,父总线地址,大小);
-
‘phandle’ 属性是专门为方便引用节点设计的;
1 2 3 4 5 6 7
intc1:intrxxx{ phandle = <1> } //两种引用方法 interrupt-parent = <1> //引用 phandle 值 interrupt-parent = <&intc1> //引用节点标签
-
‘status’ 属性用来表示节点的状态的,常用 okay 和 disabled;
设备树框架
第一部分:根和内存
cpus 和 memory 节点是必要的;这里 cpus 节点未指出;memory 节点描述 RAM 信息;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ {
model = "主板名";
compatible = "SOC名"; //必选,用于匹配BSP中的支持的SOC列表,以判断内核是否支持该主板
// platform总线
#address-cells = <A>; //必选, 地址 cell 数量(单位 cell 为 32bit)
#size-cells = <S>; //必选, 空间大小 cell 位数(单位 cell 为 32bit)
// 内存
memory {
device_type = "memory"; //device_type值一定为"memory"
reg = <address size......> //必选, 描述地址范围
};
};
第二部分:chosen
1
2
3
4
5
6
7
8
/{
...
chosen {
bootargs = "内核启动参数"; //必选
linux,stdout-path = "console" //可选
};
...
};
第三部分:设备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
标号:设备类型@地址 { // 设备名字, 基本名通常为设备类型, 扩展名通常为设备在总线上的地址
name = "设备(总线)类型"; // 可选,name属性值必须和基本名一致
device_type = "芯片名"; // 可选,设备采用芯片名
compatible = "兼容芯片","兼容产品"; // 必选, 用于匹配驱动
// io内存(寄存器)地址
#address-cells = <A>; // 可选, 同"根和内存"相同名称属性
#size-cells = <S>; // 可选, 同"根和内存"相同名称属性
reg = <地址(端口号) 大小......>; // 可选, 同"根和内存"相同名称属性
// 中断(详细参考 Documentation/devicetree/bindings/interrupt-controller/interrupts.txt 文档)
// 只有一个中断父设备时(必选)
interrupt-parent = <&父设备标号>;
interrupts = <irqno trigger>, <......>;
// 有多个中断父设备时(必选)
interrupts-extended = <&父设备标号 irqno trigger>, <.........>;
};
设备和驱动匹配的条件
-
匹配设备树上的设备
驱动 设备树 匹配条件 compatible compatible 必须 type device_type 驱动存在 type 属性时 name name 驱动存在 name 属性时 -
匹配代码添加的设备
驱动 设备 匹配条件 name name 必须
设备中断(非中断控制器)
interrupt-parent、interrupts 的组合或 interrupts-extended 在整个设备树中只允许出现一次;
-
父设备(中断控制器)中的中断编号 irqno
-
触发条件 trigger,目前只用到 32bit 中的 [0:3]
(bit0) 1 = low-to-high edge triggered
(bit1) 2 = high-to-low edge triggered
(bit2) 4 = active high level-sensitive
(bit3) 8 = active low level-sensitive
第四部分:中断控制器
1
2
3
4
5
6
7
8
9
10
11
12
标号:设备类型@地址 {
...
interrupt-controller; // 必选
interrupt-parent = <&父设备标号>; // 必选
#interrupt-cells = < 1>; // 必选
interrupts = <irqno>, <...>; // 必选
或
#interrupt-cells = < 2>; // 必选
interrupts = <irqno trigger>, <......>; // 必选
...
};
第五部分:[可选]别名
1
2
3
aliases {
serial0 = &uart1;
};
附录
设备树的操作接口源文件
在 linux-3.10/include/linux/
目录下有一些 of 开头的文件,这些是设备树操作的接口头文件:
文件名 | 功能描述 |
---|---|
of.h | 提供设备树的一般处理函数,大部分的功能函数都在该文件中 |
of_address.h | 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值) |
of_device.h | 设备相关的函数, 比如 of_match_device、of_device_register |
of_dma.h | 设备树中DMA相关属性的函数 |
of_fdt.h | dtb文件的相关操作函数,我们一般不使用 |
of_gpio.h | GPIO相关的函数 |
of_graph.h | GPU相关驱动中用到的函数, 从设备树中获得GPU信息 |
of_iommu.h | IOMMU相关函数 |
of_irq.h | 中断相关的函数 |
of_mdio.h | mdio相关的函数 |
of_mtd.h | mtd相关的函数 |
of_net.h | net相关的函数 |
of_pci.h | pci相关的函数 |
of_platform.h | 把device_node转换为platform_device时用到的函数 |
of_reserved_mem.h | reserved_mem的相关函数 |
一般设备树操作接口
-
用来查找在 dtb 中的根节点
1
unsigned long __init of_get_flat_dt_root(void);
-
根据 deice_node 结构的 full_name 参数,在全局链表 of_allnodes 中,查找合适的 device_node
1
struct device_node *of_find_node_by_path(const char *path);
示例:
1 2
struct device_node *cpus; cpus=of_find_node_by_path("/cpus");
-
若 from=NULL,则在全局链表 of_allnodes 中根据 name 查找合适的 device_node
1
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
示例:
1 2
struct device_node *np; np = of_find_node_by_name(NULL,"firewire");
-
根据设备类型查找相应的 device_node
1
struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
示例:
1 2
struct device_node *tsi_pci; tsi_pci= of_find_node_by_type(NULL,"pci");
-
根据 compatible 字符串查找 device_node
1
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible);
-
根据节点属性的 name 查找 device_node
1
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name);
-
根据 phandle 查找 device_node
1
struct device_node *of_find_node_by_phandle(phandle handle);
-
根据 alias 的 name 获得设备 id 号
1
int of_alias_get_id(struct device_node *np, const char *stem);
-
device node 计数增加/减少
1 2
struct device_node *of_node_get(struct device_node *node); void of_node_put(struct device_node *node);
-
根据 property 结构的 name 参数,在指定的 device node 中查找合适的 property
1
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);
-
根据 property 结构的 name 参数,返回该属性的属性值
1
const void *of_get_property(const struct device_node *np, const char *name,int *lenp);
-
根据 compat 参数与 device node 的 compatible 匹配,返回匹配度
1
int of_device_is_compatible(const struct device_node *device,const char *compat);
-
获得父节点的 device node
1
struct device_node *of_get_parent(const struct device_node *node);
-
将 matches 数组中 of_device_id 结构的 name 和 type 与 device node 的 compatible 和 type 匹配,返回匹配度最高的 of_device_id 结构
1
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node);
-
根据属性名 propname,读出属性值中的第 index 个 u32 数值给 out_value
1
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);
-
根据属性名 propname,读出该属性的数组中 sz 个属性值给 out_values
1 2 3
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz); int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz); int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz);
-
根据属性名 propname,读出该属性的 u64 属性值
1
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value);
-
根据属性名 propname,读出该属性的字符串属性值
1
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string);
-
根据属性名 propname,读出该字符串属性值数组中的第 index 个字符串
1
int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output);
-
读取属性名 propname 中,字符串属性值的个数
1
int of_property_count_strings(struct device_node *np, const char *propname);
-
读取该设备的第 index 个 irq 号
1
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
-
读取该设备的第 index 个 irq 号,并填充一个 irq 资源结构体
1
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r);
-
获取该设备的 irq 个数
1
int of_irq_count(struct device_node *dev);
-
根据 device_node 查找返回该设备对应的 platform_device 结构
1
struct platform_device *of_find_device_by_node(struct device_node *np);
-
根据 device node,bus id 以及父节点创建该设备的 platform_device 结构
1 2
struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent); static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id, void *platform_data, struct device *parent);
-
遍历 of_allnodes 中的节点挂接到 of_platform_bus_type 总线上,由于此时 of_platform_bus_type 总线上还没有驱动,所以此时不进行匹配
1
int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent);
-
遍历 of_allnodes 中的所有节点,生成并初始化 platform_device 结构
1
int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent);