设备树由一系列被命名的节点(node)和属性(property)组成,节点本身包含子节点,属性是成对出现的 < name, value > 。
Documentation/devicetree/binding/arm/gic.text
内核源码目录下 make dtbs
使用 dtc 工具
./dtc -I dts -O dtb -o xxx.dtb xxx.dts
也可以用 dtc 工具把 dtb 文件编译成 dts 文件
./dtc -I dtb -O dts -o xxx.dts xxx.dtb
dtc [-I input-format] [-O output-format][-o output-filename
] [-V output_version] input_filename
参数说明
input-format:
- “dtb”: “blob” format
- “dts”: “source” format.
- “fs” format.
output-format:
- “dtb”: “blob” format
- “dts”: “source” format
- “asm”: assembly language file
output_version:
定义”blob”的版本,在dtb文件的字段中有表示,支持1 2 3和16,默认是3,
在16版本上有许多特性改变
##1.4 设备树在启动过程中的使用
bootloader在引导内核时,会预先读取.dtb到内存,并将设备树在内存中的地址传给内核。在ARM中通过 bootm 或 bootz 命令来进行传递。
bootm [kernel_addr] [initrd_address] [dtb_address]
其中kernel_addr为内核镜像的地址,
initrd为initrd的地址,若initrd_address为空,则用“-”来代替。
dtb_address为dtb所在的地址。
一个 dts 文件有且只有一个root节点 “/” ,root 节点下又可以有一系列的子节点,称root为“node1”和“node2”的parent节点,除了root节点外,每个节点有且仅有一个parent;其中子节点node1下还存在子节点“child-nodel1”和“child-node2”。
注意
dtsi中有一个根节点,dts在包含的话会不会有两个了?
编译器 DTC 在编译 dts 时,会对 node 进行合并操作。最终结果是dtb中有一个根节点。
/{
node1{
child-node1{
};
child-node2{
};
};
node2{
};
};
{} 中是描述该节点的属性,它可以是:
1. string
2. u32
3. 二进制数据
4. 也可以是空
2.1 skeleton
首先从 “骨架” 开始 skeleton.dtsi
每个平台的 dtsi 文件都包含这个骨架
/include/ “skeleton.dtsi”
骨架中的内容
/ {
// 以“/”根节点为parent的子节点中,reg的每个属性中存在一个 address 值
#address-cells = <1>;
// ... reg属性中存在一个 size 值. 则 reg 的组织形式是 reg =
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0 0>; };
};
主要用来描述由系统指定的 runtime parameter,它不用来描述任何硬件设备节点信息。原先通过 tag list 传递的一些 linux kernel 运行的参数,可以通过 chosen 来传递,如 uboot 阶段的命令行中的 bootargs 传递的参数,可以用这个节点来传递。它的父节点必须是 根节点。
例子
chosen {
bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk";
}
aliases 用来定义别名,类似 C++ 的引用。在 dtsi 文件中定义了别名后,在 dts 中,使用别名的效果和引用体是一样的。
例子
在 at91sam9g45.dtsi 文件中,根节点下有
aliases {
serial0 = &dbgu;
serial1 = &usart0;
...
};
dtsi 文件中使用的是 dbgu、usart0 …
dts 文件中使用 serial0、serial1 这些别名。
memory { device_type = "memory"; reg = <0 0>; };
device_type 必须是 memory, reg 指定 memory 的起点和大小
例子
usb_a9g20.dts 文件
memory {
reg = <0x20000000 0x4000000>;
};
memory 的起点是 0x20000000,大小是 0x4000000.
一般而言,在 .dts 中不对 memory 进行描述,而是通过 bootargs 中类似 521M@0x00000000 的方式传递给内核。
节点名称@单元地址
这个属性为 string list,用来跟驱动 匹配。优先级从左到右。
/ 节点也有这个属性,用来匹配 machine type。
compatible = "厂商,外设名字"
设备节点通过 interrupt-parent 来指定它所依附的中断控制器,当节点没有指定 interrupt-parent 时,则从 parent 节点中继承。
如果子节点使用到中断,则需用 interrupt 属性指定,该属性的数值长度受中断控制器中 #interrupt-cells 指定。当 #interrupt-cells >= 2 时,每个数字表示的意思由驱动决定。
例子
at91sam9g20.dtsi
/ {
model = "Atmel AT91SAM9G20 family SoC";
compatible = "atmel,at91sam9g20";
interrupt-parent = <&aic>;
...
aic: interrupt-controller@fffff000 {
#interrupt-cells = <2>;
compatible = "atmel,at91rm9200-aic";
interrupt-controller;
reg = <0xfffff000 0x200>;
};
usart0: serial@fffb0000 {
...
interrupts = <6 4>;
}
根节点指定中断控制器,
aic 中 interrupt-controller 指定了这个节点是中断控制器
中断控制器中指定了每个中断单元是 2 个元素。
usart0 中 interrupts 传递了一个中断 <6 4>
当两个中断 cell 时,一般第一个cell表示中断线的序号,第二个cell表示中断类型的 flag,对于不同的中断控制器,需要阅读对应的 binding document 来得知其说明符的格式。
地址转换表,这在 pcie 中使用比较常见。它表明该设备在 parent 节点中所对应的地址映射关系。ranges格式长度受当前节点 #address-cell、parent 节点的 #address-cell 和 #size-cells 所控制。格式如下
ranges = <当前节点#address-cell, parent 节点的 #address-cell, 当前节点#size-cells>
例子
rwid-axi {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
ebi@50000000 {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <1>;
ranges = <0 0 0x40000000 0x08000000
1 0 0x48000000 0x08000000
2 0 0x50000000 0x08000000
3 0 0x58000000 0x08000000>;
};
可以看到 ebi 的父节点中指定了 1 个地址单元
ebi 的节点中是 2 个地址单元
ebi 中的 size 单元的个数是 1 个
则
dtc在编译设备树时会将相同名称的节点进行合并,以最后的属性为准。
DTB(DT block) 由 3 部分组成:
头(header) | 结构块(device-tree structure) | 字符串块(string block) |
---|
struct boot_param_header {
__be32 magic; /* 设备树的幻数,固定为 0xd00dfeed */
__be32 totalsize; /* 整个DT block的大小 */
__be32 off_dt_struct; /* 结构块 在这个设备树中的偏移 */
__be32 off_dt_strings; /* 字符串块 的偏移 */
__be32 off_mem_rsvmap; /* 保留内存区的偏移,该区域不能被内核动态分配 */
__be32 version; /* 版本 */
__be32 last_comp_version; /* 向下兼容的版本号 */
/* version 2 fields below */
__be32 boot_cpuid_phys; /* 正在运行的物理 CPU 的id */
/* version 3 fields below */
__be32 dt_strings_size; /* 字符串块 大小 */
/* version 17 fields below */
__be32 dt_struct_size; /* 结构块 大小 */
};
设备树的结构块是一个线性化的结构体,是设备树的主体。
一个节点的组成
OF_DT_BEGIN_NODE | 节点的开始的标志 |
---|---|
节点的路径 / 节点的单元名(version<3以节点路径表示,version>=0x10以节点单元名表示) | |
四字节对齐 | |
OF_DT_PROP | 节点属性的开始标志 |
属性的字节长度 | |
属性名称在字符串中的偏移量 | |
属性值+4字节对齐 | |
子节点 | |
… | |
OF_DT_END_NODE | 节点结束的标志 |
不同节点的属性有相同的属性名称,因此,将属性名称提取出了一张表,而在结构块 的属性名字段保存名称在字符串块中的偏移
在头中可以看到还有一个 内存保留的 map,这些内存区域被保留用于 ARM 和 DSP 进行信息交互 或做 他用,总之是不会进入内存管理系统。
这个区域包括了若干的reserve memory描述符。没一个描述符是由 address 和 size 组成的,他们的数据类型都是 u64.
在以前的方式中,内核包含了对硬件的全部描述。
uboot 在加载一个二进制的内核镜像(uImage/zImage)时,会提供一些额外信息,成为 ATAGS,通过 r2 寄存器传给内核。ATAGS 中包含 内存大小和地址、命令行 等。
uboot 通过 r1 寄存器传递机器类型(machine type)。
uboot 的命令行中启动内核的命令: bootm <内核镜像的地址>
有了设备树后,内核和硬件描述分离。uboot 需要加载两个二进制文件,一个是内核镜像(uImage/zImage),一个是设备树文件。
uboot 通过 r2 寄存器来传递设备树的地址,r1 寄存器 不在用来存放机器类型信息,设备树中包含了。
uboot 的命令行中启动内核的命令:bootm
① kernel 入口处获取到 uboot 传过来的 .dtb 镜像的基地址
② 通过 early_init_dt_scan() 函数来获取 kernel 初始化时需要的 bootargs 和 cmd_line 等系统引导参数。
③ 调用 unflatten_device_tree 函数来解析dtb文件,构建一个由 device_node 结构连接而成的单向链表,并使用全局变量 of_allnodes 保存这个链表的头指针。
④ 内核调用OF的API接口,获取 of_allnodes 链表信息来初始化内核其他子系统、设备等。
设备树有关 API 包含在的源文件所在位置: drivers/of/
1 | of_get_flat_dt_root(void) | 查找dtb中的根节点 |
---|---|---|
2 | struct device_node *of_find_node_by_path(const char *path) | 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node |
3 | struct device_node *of_find_node_by_name(struct device_node *from,const char *name) | 若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node |
4 | struct device_node *of_find_node_by_type(struct device_node *from,const char *type) | 根据设备类型查找相应的device_node |
5 | struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible) | 根据compatible字符串查找device_node |
6 | struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name) | 根据节点属性的name查找device_node |
7 | struct device_node *of_find_node_by_phandle(phandle handle) | 根据phandle查找device_node |
8 | int of_alias_get_id(struct device_node *np, const char *stem) | 根据alias的name获得设备id号 |
9 | struct device_node *of_node_get(struct device_node *node)void of_node_put(struct device_node *node) device node | 计数增加/减少 |
10 | struct property *of_find_property(const struct device_node *np,const char *name,int *lenp) | 根据property结构的name参数,在指定的device node中查找合适的property |
11 | const void *of_get_property(const struct device_node *np, const char *name,int *lenp) | 根据property结构的name参数,返回该属性的属性值 |
12 | int of_device_is_compatible(const struct device_node *device,const char *compat) | 根据compat参数与device node的compatible匹配,返回匹配度 |
13 | struct device_node *of_get_parent(const struct device_node *node) | 获得父节点的device node |
14 | const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node) | 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构 |
15 | int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value) | 根据属性名propname,读出属性值中的第index个u32数值给out_value |
16 | 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,读出该属性的数组中sz个属性值给out_values |
17 | int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value) | 根据属性名propname,读出该属性的u64属性值 |
18 | int of_property_read_string(struct device_node *np, const char *propname,const char **out_string) | 根据属性名propname,读出该属性的字符串属性值 |
19 | int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output) | 根据属性名propname,读出该字符串属性值数组中的第index个字符串 |
20 | int of_property_count_strings(struct device_node *np, const char *propname) | 读取属性名propname中,字符串属性值的个数 |
21 | unsigned int irq_of_parse_and_map(struct device_node *dev, int index) | 读取该设备的第index个irq号 |
22 | int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) | 读取该设备的第index个irq号,并填充一个irq资源结构体 |
23 | int of_irq_count(struct device_node *dev) | 获取该设备的irq个数 |
24 | int of_address_to_resource(struct device_node *dev, int index,struct resource *r)const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags) | 获取设备寄存器地址,并填充寄存器资源结构体 |
25 | void __iomem *of_iomap(struct device_node *np, int index) | 获取经过映射的寄存器虚拟地址 |
26 | struct platform_device *of_find_device_by_node(struct device_node *np) | 根据device_node查找返回该设备对应的platform_device结构 |
27 | 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) | 根据device node,bus id以及父节点创建该设备的platform_device结构 |
28 | int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent) | 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配 |