文章目录
- 一、设备树语法
- 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.3.7 地址转换
- 1.3.8 中断
- 1.4 设备特定数据
- 1.5 特殊节点
- 二、设备树加载过程
- 2.1 整体流程
- 2.2 dtb结构
- 2.3 property
- 2.4 device_node
- 2.5 platform_device
- 三、设备树与驱动关系
- 3.1 总线
- 3.2 驱动端
- 3.3 设备端
声明:本文是以前整理的笔记,部分内容来源于网络,侵删。
一、设备树语法
1.1 简介
Device Tree设备树是描述单板资源以及设备的一种文本文件,格式是dts,包含的头文件格式是dtsi。
1.2 基本数据格式
设备树是一个包含节点和属性的简单树状结构。属性就是键-值对,而节点可以同时包含属性和子节点。例如,以下就是一个 .dts 格式的简单树:
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
这棵树显然是没什么用的,因为它并没有描述任何东西,但它确实体现了节点的一些属性:
- 一个单独的根节点:“/”
- 两个子节点:“node1”和“node2”
- 两个 node1 的子节点:“child-node1”和“child-node2”
- 一堆分散在树里的属性。
属性是简单的键-值对,它的值可以为空或者包含一个任意字节流。虽然数据类型并没有编码进数据结构,但在设备树源文件中任有几个基本的数据表示形式。
- 文本字符串(无结束符)可以用双引号表示:string-property = “a string”
- ‘Cells’是32位无符号整数,用尖括号限定:cell-property = <0xbeef 123 0xabcd1234>
- 二进制数据用方括号限定:binary-property = [0x01 0x23 0x45 0x67]
- 不同表示形式的数据可以使用逗号连在一起:mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>;
- 逗号也可用于创建字符串列表:string-list = “red fish”, “blue fish”;
1.3 一个例子
1. / {
2. compatible = "acme,coyotes-revenge";
3. #address-cells = <1>;
4. #size-cells = <1>;
5. interrupt-parent = <&intc>;
6.
7. cpus {
8. #address-cells = <1>;
9. #size-cells = <0>;
10. cpu@0 {
11. compatible = "arm,cortex-a9";
12. reg = <0>;
13. };
14. cpu@1 {
15. compatible = "arm,cortex-a9";
16. reg = <1>;
17. };
18. };
19.
20. serial@101f0000 {
21. compatible = "arm,pl011";
22. reg = <0x101f0000 0x1000 >;
23. interrupts = < 1 0 >;
24. };
25.
26. serial@101f2000 {
27. compatible = "arm,pl011";
28. reg = <0x101f2000 0x1000 >;
29. interrupts = < 2 0 >;
30. };
31.
32. gpio@101f3000 {
33. compatible = "arm,pl061";
34. reg = <0x101f3000 0x1000
35. 0x101f4000 0x0010>;
36. interrupts = < 3 0 >;
37. };
38.
39. intc: interrupt-controller@10140000 {
40. compatible = "arm,pl190";
41. reg = <0x10140000 0x1000 >;
42. interrupt-controller;
43. #interrupt-cells = <2>;
44. };
45.
46. spi@10115000 {
47. compatible = "arm,pl022";
48. reg = <0x10115000 0x1000 >;
49. interrupts = < 4 0 >;
50. };
51.
52. external-bus {
53. #address-cells = <2>
54. #size-cells = <1>;
55. ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
56. 1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
57. 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
58.
59. ethernet@0,0 {
60. compatible = "smc,smc91c111";
61. reg = <0 0 0x1000>;
62. interrupts = < 5 2 >;
63. };
64.
65. i2c@1,0 {
66. compatible = "acme,a1234-i2c-bus";
67. #address-cells = <1>;
68. #size-cells = <0>;
69. reg = <1 0 0x1000>;
70. interrupts = < 6 2 >;
71. rtc@58 {
72. compatible = "maxim,ds1338";
73. reg = <58>;
74. interrupts = < 7 3 >;
75. };
76. };
77.
78. flash@2,0 {
79. compatible = "samsung,k8f1315ebm", "cfi-flash";
80. reg = <2 0 0x4000000>;
81. };
82. };
83. };
1.3.1 根节点
compatible 指定了系统的名称。它包含了一个“<制造商>,<型号>”形式的字符串。重要的是要指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突。由于操作系统会使用 compatible的值来决定如何在机器上运行,所以正确的设置这个属性变得非常重要。
2.3.2 CPU
每个cpu节点的compatible属性是一个“<制造商>,<型号>”形式的字符串,并指定了确切的cpu,就像顶层的compatible属性一样。
1.3.3 节点名称
每个节点必须有一个“<名称>[@<设备地址>]”形式的名字。
<名称> 就是一个不超过31位的简单ascii字符串。通常,节点的命名应该根据它所体现的是什么样的设备。
如果该节点描述的设备有一个地址的话就还应该加上设备地址(unit-address)。通常,设备地址就是用来访问该设备的主地址,并且该地址也在节点的reg属性中列出。
同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称(例如serial@101f1000和serial@101f2000)。
1.3.4 设备
每个设备节点都拥有一个compatible属性。compatible属性是操作系统用来决定使用哪个设备驱动来绑定到一个设备上的关键因素。
compatible是一个字符串列表,之中第一个字符串指定了这个节点所表示的确切的设备,该字符串的格式为:"<制造商>,<型号>"。剩下的字符串的则表示其它与之相兼容的设备。
1.3.5 status
device tree中的status标识了设备的状态,使用status可以去禁止设备或者启用设备,看下设备树规范中的status可选值:
- “okay” 表示设备正在运行
- “disabled” 表示该设备目前尚未运行,但将来可能会运行
- “fail” 表示设备无法运行。 在设备中检测到严重错误,确实如此没有修理就不可能投入运营
- “fail-sss” 表示设备无法运行。 在设备中检测到严重错误,它是没有修理就不可能投入运营。 值的sss部分特定于设备并指示检测到的错误情况
1.3.6 编址
可编址设备使用以下属性将地址信息编码进设备树:
- reg
- #address-cells
- #size-cells
每个可编址设备都有一个元组列表的 reg,元组的形式为:reg = <地址1 长度1 地址2 长度2 地址3 长度3 …>。每个元组都表示一个该设备使用的地址范围。每个地址值是一个或多个32位整型数列表,称为cell。同样,长度值也可以是一个cell列表或者为空。
由于地址和长度字段都是可变大小的变量,那么父节点的#address-cells和#size-cells属性就用来声明各个子节点的cell的数量。换句话说,正确解释一个reg属性需要用到父节点的#address-cells和#size-cells的值。
在cpu节点中,#address-cells设置为1,#size-cells设置为0。这意味着子节点的reg 值是一个单一的uint32,这是一个不包含大小字段的地址,为这两个cpu分配的地址是0和1。cpu节点的#size-cells为0是因为只为每个cpu分配一个单独的地址。
与cpu节点里单一地址值不同,内存映射设备应该分配给内存映射设备一个地址范围。#size-cells声明每个子节点的reg元组中长度字段的大小。在接下来的例子中,每个地址值是1 cell(32 位),每个长度值也是1 cell,这是典型的32位系统。64位的机器则可以使用值为2的#address-cells和#size-cells来获得在设备树中的64位编址。
非内存映射设备没有被映射到处理机总线上。虽然这些设备可以有一个地址范围,但他们并不是由CPU直接访问。取而代之的是,父设备的驱动程序会代表CPU执行简介访问。
1.3.7 地址转换
地址还只是设备节点的本地地址,我们还没有描述如何将这些地址映射成CPU可使用的地址。
根节点始终描述的是CPU视角的地址空间。根节点的子节点已经使用的是CPU的地址域,所以它们不需要任何直接映射。例如,serial@101f0000设备就是直接分配的0x101f0000 地址。
那些非根节点直接子节点的节点就没有使用CPU地址域。为了得到一个内存映射地址,设备树必须指定从一个域到另一个域地址转换地方法,而ranges属性就为此而生。
ranges是一个地址转换列表。ranges表中的每一项都是一个包含子地址、父地址和在子地址空间中区域大小的元组。每个字段的值都取决于子节点的#address-cells、父节点的#address-cells和子节点的#size-cells。以本例中的外部总线来说,子地址是2 cell、父地址是1 cell、区域大小也是1 cell。那么三个ranges被翻译为:
- 从片选0开始的偏移量0被映射为地址范围:0x10100000…0x1010ffff
- 从片选1开始的偏移量0被映射为地址范围:0x10160000…0x1016ffff
- 从片选2开始的偏移量0被映射为地址范围:0x30000000…0x10000000
另外,如果父地址空间和子地址空间是相同的,那么该节点可以添加一个空的ranges属性。一个空的ranges属性意味着子地址将被1:1映射到父地址空间。
你还应该注意到在i2c@1,0节点中并没有ranges属性。不同于外部总线,这里的原因是i2c总线上的设备并没有被内存映射到CPU的地址域。相反,CPU将通过i2c@1,0设备间接访问rtc@58设备。缺少ranges属性意味着这个设备将不能被出他的父设备之外的任何设备直接访问。
1.3.8 中断
描述中断连接需要四个属性:
- interrupt-controller - 一个空的属性定义该节点作为一个接收中断信号的设备。
- #interrupt-cells - 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中cell的个数(类似于#address-cells和#size-cells)。
- interrupt-parent - 这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的phandle。那些没有interrupt-parent的节点则从它们的父节点中继承该属性。
- interrupts - 一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。
这个机器只有一个中断控制器:interrupt-controller@10140000。
中断控制器节点上添加了‘inc:’标签,该标签用于给根节点的interrupt-parent属性分配一个phandle。这个interrupt-parent将成为本系统的默认值,因为所有的子节点都将继承它,除非显示覆写这个属性。
#interrupt-cells是2,所以每个中断指示符都有2个cell。本例使用一种通用的模式,也就是用第一个cell 来编码中断线号;然后用第二个cell编码标志位,比如高电平/低电平有效,或者边缘/水平触发。对于任何给定的中断控制器,请参考该控制器的binding文档以了解指示符如何编码。
1.4 设备特定数据
除了通用属性以外,一个节点中可以添加任何属性和子节点。只要遵循一些规则,可以添加任何操作系统所需要的数据。
首先,新的设备特定属性的名字都应该使用制造商前缀,以避免和现有标准属性名相冲突。
其次,属性和子节点的含义必须存档在binding文档中,以便设备驱动程序的程序员知道如何解释这些数据。
Linux源码目录/Documentation/devicetree/bindings
1.5 特殊节点
引用一个特定的节点通常使用全路径,如/external-bus/ethernet@0,0,aliases 节点可以用于指定一个设备全路径的别名。例如:
1. aliases {
2. ethernet0 = ð0;
3. serial0 = &serial0;
4. };
chosen节点并不代表一个真正的设备,只是作为一个为固件和操作系统之间传递数据的地方,比如引导参数。chosen节点里的数据也不代表硬件。通常,chosen节点在.dts源文件中为空,并在启动时填充。
1. chosen {
2. bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
3. };
memory节点用来描述硬件内存布局的。
memory@0 {
device_type = "memory";
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};
二、设备树加载过程
2.1 整体流程
dtb->property->device_node->platform_device
2.2 dtb结构
可以通过fdtdump –sd xxx.dtb文件查看结构
Device Tree源文件的结构分为header、fill_area、dt_struct及dt_string四个区域。
fill_area区域填充数值0。
header在Linux内核中使用struct fdt_header结构体描述。struct fdt_header结构体定义在scripts\dtc\libfdt\fdt.h文件中。
struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};
节点信息使用struct fdt_node_header结构体描述。属性信息使用struct fdt_property结构体描述。各个结构体信息如下:
struct fdt_node_header {
fdt32_t tag;
char name[0];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[0];
};
tag是标识node的起始结束等信息的标志位,name指向node名称的首地址。tag的取值如下:
#define FDT_BEGIN_NODE 0x1 /* Start node: full name */
#define FDT_END_NODE 0x2 /* End node */
#define FDT_PROP 0x3 /* Property: name off, size, content */
#define FDT_NOP 0x4 /* nop */
#define FDT_END 0x9
FDT_BEGIN_NODE和FDT_END_NODE标识node节点的起始和结束,FDT_PROP标识node节点下面的属性起始符,FDT_END标识Device Tree的结束标识符。因此,对于每个node节点的tag标识符一般为FDT_BEGIN_NODE,对于每个node节点下面的属性的tag标识符一般是FDT_PROP。
描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROP;len为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址。
例如:compatible =“samsung,goni”, “samsung,s5pv210”;compatible是属性名称,“samsung,goni”, "samsung,s5pv210"是属性值。compatible属性名称字符串存放的区域是dt_string。“samsung,goni”, "samsung,s5pv210"存放的位置是fdt_property.data后面。因此fdt_property.data指向该属性值。fdt_property.tag的值为属性标识,len为属性值的长度(包括‘\0’,单位:字节),此处len = 29。nameoff为compatible字符串的位置相对于off_dt_strings的偏移地址,即&compatible = nameoff +off_dt_strings。
2.3 property
kernel会根据Device Tree的结构解析出kernel能够使用的struct property结构体。kernel根据Device Tree中所有的属性解析出数据填充struct property结构体。struct property结构体描述如下:
struct property {
char *name; /* property full name */
int length; /* property value length */
void *value; /* property value */
struct property *next; /* next property under the same node */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr; /* 属性文件,与sysfs文件系统挂接 */
};
总的来说,kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表。
2.4 device_node
Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:
struct device_node {
const char *name; /* node的名称,取最后一次“/”和“@”之间子串 */
const char *type; /* device_type的属性名称,没有为<NULL> */
phandle phandle; /* phandle属性值 */
const char *full_name; /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
struct fwnode_handle fwnode;
struct property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
struct property *deadprops; /* removed properties */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling; /* 姊妹节点,与自己同等级的node */
struct kobject kobj; /* sysfs文件系统目录体现 */
unsigned long _flags; /* 当前node状态标志位,见/include/linux/of.h line124-127 */
void *data;
};
/* flag descriptions (need to be visible even when !CONFIG_OF) */
#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
#define OF_DETACHED 2 /* node has been detached from the device tree*/
#define OF_POPULATED 3 /* device already created for the node */
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */
解析流程
kernel的C语言阶段的入口函数是init/main.c/start_kernel()函数,在early_init_dt_scan_nodes()中会做以下三件事:
- (1) 扫描/chosen或者/chose@0节点下面的bootargs属性值到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中;
- (2) 扫描根节点下面,获取{size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中;
- (3) 扫描具有device_type = “memory”属性的/memory或者/memory@0节点下面的reg属性值,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。
Device Tree的解析首先从unflatten_device_tree()开始,代码列出如下:
/**
* unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens the device-tree passed by the firmware, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
*/
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, &of_root,
early_init_dt_alloc_memory_arch);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}
/**
* __unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens a device-tree, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
* @blob: The blob to expand
* @mynodes: The device_node tree created by the call
* @dt_alloc: An allocator that provides a virtual address to memory
* for the resulting tree
*/
static void __unflatten_device_tree(const void *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
unsigned long size;
int start;
void *mem;
/* 省略部分不重要部分 */
/* First pass, scan for size */
start = 0;
size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
size = ALIGN(size, 4);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
memset(mem, 0, size);
/* Second pass, do actual unflattening */
start = 0;
unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
}
分析以上代码,在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。
__unflatten_device_tree()代码列出如下:
/**
* unflatten_dt_node - Alloc and populate a device_node from the flat tree
* @blob: The parent device tree blob
* @mem: Memory chunk to use for allocating device nodes and properties
* @poffset: pointer to node in flat tree
* @dad: Parent struct device_node
* @nodepp: The device_node tree created by the call
* @fpsize: Size of the node path up at the current depth.
* @dryrun: If true, do not allocate device nodes but still calculate needed
* memory size
*/
static void * unflatten_dt_node(const void *blob,
void *mem,
int *poffset,
struct device_node *dad,
struct device_node **nodepp,
unsigned long fpsize,
bool dryrun)
{
const __be32 *p;
struct device_node *np;
struct property *pp, **prev_pp = NULL;
const char *pathp;
unsigned int l, allocl;
static int depth;
int old_depth;
int offset;
int has_name = 0;
int new_format = 0;
/* 获取node节点的name指针到pathp中 */
pathp = fdt_get_name(blob, *poffset, &l);
if (!pathp)
return mem;
allocl = ++l;
/* version 0x10 has a more compact unit name here instead of the full
* path. we accumulate the full path size using "fpsize", we'll rebuild
* it later. We detect this because the first character of the name is
* not '/'.
*/
if ((*pathp) != '/') {
new_format = 1;
if (fpsize == 0) {
/* root node: special case. fpsize accounts for path
* plus terminating zero. root node only has '/', so
* fpsize should be 2, but we want to avoid the first
* level nodes to have two '/' so we use fpsize 1 here
*/
fpsize = 1;
allocl = 2;
l = 1;
pathp = "";
} else {
/* account for '/' and path size minus terminal 0
* already in 'l'
*/
fpsize += l;
allocl = fpsize;
}
}
/* 分配struct device_node内存,包括路径全称大小 */
np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
/* 填充full_name,full_name指向该node节点的全路径名称字符串 */
np->full_name = fn = ((char *)np) + sizeof(*np);
if (new_format) {
/* rebuild full path for new format */
if (dad && dad->parent) {
strcpy(fn, dad->full_name);
fn += strlen(fn);
}
*(fn++) = '/';
}
memcpy(fn, pathp, l);
/* 节点挂接到相应的父节点、子节点和姊妹节点 */
prev_pp = &np->properties;
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}
/* 处理该node节点下面所有的property */
for (offset = fdt_first_property_offset(blob, *poffset);
(offset >= 0);
(offset = fdt_next_property_offset(blob, offset))) {
const char *pname;
u32 sz;
if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
offset = -FDT_ERR_INTERNAL;
break;
}
if (pname == NULL) {
pr_info("Can't find property name in list !\n");
break;
}
if (strcmp(pname, "name") == 0)
has_name = 1;
pp = unflatten_dt_alloc(&mem, sizeof(struct property),
__alignof__(struct property));
if (!dryrun) {
/* We accept flattened tree phandles either in
* ePAPR-style "phandle" properties, or the
* legacy "linux,phandle" properties. If both
* appear and have different values, things
* will get weird. Don't do that. */
/* 处理phandle,得到phandle值 */
if ((strcmp(pname, "phandle") == 0) ||
(strcmp(pname, "linux,phandle") == 0)) {
if (np->phandle == 0)
np->phandle = be32_to_cpup(p);
}
/* And we process the "ibm,phandle" property
* used in pSeries dynamic device tree
* stuff */
if (strcmp(pname, "ibm,phandle") == 0)
np->phandle = be32_to_cpup(p);
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)p;
*prev_pp = pp;
prev_pp = &pp->next;
}
}
/* with version 0x10 we may not have the name property, recreate
* it here from the unit name if absent
*/
/* 为每个node节点添加一个name的属性 */
if (!has_name) {
const char *p1 = pathp, *ps = pathp, *pa = NULL;
int sz;
/* 属性name的value值为node节点的名称,取“/”和“@”之间的子串 */
while (*p1) {
if ((*p1) == '@')
pa = p1;
if ((*p1) == '/')
ps = p1 + 1;
p1++;
}
if (pa < ps)
pa = p1;
sz = (pa - ps) + 1;
pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
__alignof__(struct property));
if (!dryrun) {
pp->name = "name";
pp->length = sz;
pp->value = pp + 1;
*prev_pp = pp;
prev_pp = &pp->next;
memcpy(pp->value, ps, sz - 1);
((char *)pp->value)[sz - 1] = 0;
}
}
/* 填充device_node结构体中的name和type成员 */
if (!dryrun) {
*prev_pp = NULL;
np->name = of_get_property(np, "name", NULL);
np->type = of_get_property(np, "device_type", NULL);
if (!np->name)
np->name = "<NULL>";
if (!np->type)
np->type = "<NULL>";
}
old_depth = depth;
*poffset = fdt_next_node(blob, *poffset, &depth);
if (depth < 0)
depth = 0;
/* 递归调用node节点下面的子节点 */
while (*poffset > 0 && depth > old_depth)
mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
fpsize, dryrun);
if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
pr_err("unflatten: error %d processing FDT\n", *poffset);
/*
* Reverse the child list. Some drivers assumes node order matches .dts
* node order
*/
if (!dryrun && np->child) {
struct device_node *child = np->child;
np->child = NULL;
while (child) {
struct device_node *next = child->sibling;
child->sibling = np->child;
np->child = child;
child = next;
}
}
if (nodepp)
*nodepp = np;
return mem;
}
通过以上函数处理就得到了所有的struct device_node结构体
2.5 platform_device
转换流程
static int __init customize_machine(void) // arch\arm\kernel\Setup.c
{
/*
* customizes platform devices, or adds new ones
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
of_iommu_init();
if (machine_desc->init_machine) //如果存在machine_desc->init_machine则调用
machine_desc->init_machine();
#ifdef CONFIG_OF
else
of_platform_populate(NULL, of_default_bus_match_table,
NULL, NULL);
#endif
return 0;
}
arch_initcall(customize_machine);
//kernel启动会通过下面的函数调用按顺序执行.init.data代码段中的函数
--> start_kernel // init/main.c
----> rest_init();
------> pid = kernel_thread(kernel_init, NULL, CLONE_FS);
--------> kernel_init
----------> kernel_init_freeable();
------------> do_basic_setup()
--------------> do_initcalls();
----------------> do_initcall_level(int level)
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
/* 获取根节点 */
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
/* 为根节点下面的每一个节点创建platform_device结构体 */
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc) {
of_node_put(child);
break;
}
}
/* 更新device_node flag标志位 */
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* 只有包含"compatible"属性的node节点才会生成相应的platform_device结构体 */
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
return 0;
}
/* 省略部分代码 */
/*
* 针对节点下面得到status = "ok" 或者status = "okay"或者不存在status属性的
* 节点分配内存并填充platform_device结构体
*/
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
/* 递归调用节点解析函数,为子节点继续生成platform_device结构体,前提是父节点
* 的“compatible” = “simple-bus”,也就是匹配of_default_bus_match_table结构体中的数据
*/
for_each_child_of_node(bus, child) {
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,void *platform_data,struct device *parent)
{
struct platform_device *dev;
dev = of_device_alloc(np, bus_id, parent);
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
}
总的来说,当of_platform_populate()函数执行完毕,kernel就为DTB中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者"arm,amba-bus"的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并通过of_device_add()注册设备。
三、设备树与驱动关系
3.1 总线
总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。而总线在linux系统中也是属于设备,所以总线也要注册,同时要先有总线而后才能注册设备和驱动,所以总线要先注册。
对于依附在USB、PCI、I2C、SPI等物理总线来 这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。
相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。
platform总线相关代码:driver\base\platform.c 文件
相关结构体定义:include\linux\platform_device.h 文件中
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
最重要的是match函数,正是此成员函数确定了platform_device和platform_driver之间如何匹配的
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv)) // 设备树风格
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv)) // ACPI风格
return 1;
/* Then try to match against the id table */
if (pdrv->id_table) // 匹配ID表
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); // 匹配名字
}
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
static const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches)
return NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);//找到compatible节点
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}
return score;
}
3.2 驱动端
platform_driver结构体
struct platform_driver {
struct device_driver driver; // 设备驱动结构体
const struct platform_device_id *id_table;
int (*probe)(struct platform_device *); // probe函数,在设备和驱动匹配时调用
int (*remove)(struct platform_device *); // remove函数,在移除设备时调用
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
bool prevent_deferred_probe;
};
device_driver结构体
struct device_driver {
struct module *owner; // THIS_MODULE
const char *name; // 设备驱动的名字,
struct bus_type *bus; // 总线类型,有platform,IIC,SPI等
const struct of_device_id *of_match_table;
int (*probe) (struct device *dev); // probe函数,这个接口调用真正的 probe 函数
int (*remove) (struct device *dev); // remove函数,这个接口调用真正的 remove 函数
struct driver_private *p; // 私有数据指针
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct acpi_device_id *acpi_match_table;
void (*shutdown) (struct device *dev); // 电源管理相关
int (*suspend) (struct device *dev, pm_message_t state); // 电源管理相关
int (*resume) (struct device *dev); // 电源管理相关
const struct attribute_group **groups;
const struct dev_pm_ops *pm; // 电源管理相关
};
驱动端probe过程
#define platform_driver_register(pdrv) __platform_driver_register(pdrv, THIS_MODULE) (include\linux\platform_device.h) // 平台驱动注册函数
|-> pdrv->driver.bus = &platform_bus_type; (drivers\base\platform.c)
|-> platform_bus_type.name = "platform" // 总线的名字
|-> platform_bus_type.match = platform_match// match函数
pdrv->driver.probe = platform_drv_probe; // probe 函数
pdrv->driver.remove = platform_drv_remove; // remove函数
|-> driver_register(struct device_driver *drv) (drivers\base\driver.c)
|-> bus_add_driver(struct device_driver *drv) (drivers\base\bus.c) // 添加驱动到总线上
|-> driver_attach(struct device_driver *drv) (drivers\base\dd.c)
|-> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) (drivers\base\bus.c)
|-> __driver_attach(struct device *dev, void *data) (drivers\base\dd.c)
|-> driver_match_device(struct device_driver *drv, struct device *dev) (drivers\base\base.h) // 驱动和设备匹配
|-> return drv->bus->match ? drv->bus->match(dev, drv) : 1
|-> platform_match(struct device *dev, struct device_driver *drv) (drivers\base\platform.c) // 匹配函数
|-> strcmp(pdev->driver_override, drv->name) // 通常不设置
|-> platform_match_id(pdrv->id_table, pdev) // 匹配ID表
|-> strcmp(pdev->name, pdrv->id_table->name)
|-> strcmp(pdev->name, drv->name) // 最后匹配名字,以上三种匹配方式只要有一个成功,返回1
|-> driver_probe_device(drv, dev) (drivers\base\dd.c) // 驱动和设备匹配成功后,执行probe函数
|-> really_probe(drv, dev) (drivers\base\dd.c) // 执行真正的 probe 函数
|-> if (dev->bus->probe) { // 没有定义
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) { // 执行 device_driver 里面的 probe 函数
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
|-> if (dev->bus->remove) // remove 函数
dev->bus->remove(dev);
else if (drv->remove) // 在这里调用真正的自己定义的remove函数
drv->remove(dev);
|-> platform_drv_probe(struct device *_dev) (drivers\base\platform.c)
|-> struct platform_driver *drv = to_platform_driver(_dev->driver) // 找到 platform_driver 结构体
|-> #define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver)) (include\linux\platform_device.h)
|-> struct platform_device *dev = to_platform_device(_dev)
|-> #define to_platform_device(x) container_of((x), struct platform_device, dev)
|-> if (drv->probe) {
ret = drv->probe(dev); // 执行真正的probe函数,也就是在 platform_driver 中自己定义的,并将 pdev 传入进去
if (ret)
dev_pm_domain_detach(_dev, true);
} else {
/* don't fail if just dev_pm_domain_attach failed */
ret = 0;
}
3.3 设备端
platform_device结构体
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
//如果为IO资源,resource将代表reg属性下的一组值,如果为中断资源,表示中断号的开始值和结束值
struct resource {
resource_size_t start; //device_node的reg属性的address值对应的cpu地址
resource_size_t end;//device_node的reg属性的address+size值对应的cpu地址的结尾
const char *name;//如果device_node有reg-names,等于reg-names对应的值,否则为device_node的full_name
unsigned long flags;//代表resource的类型,\include\linux\Ioport.h中定义了各种IORESOURCE类型
struct resource *parent, *sibling, *child;
};
device结构体
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
};
设备端match调用过程
platform_device_register(struct platform_device *pdev) (drivers\base\platform.c) //平台设备注册函数
|-> platform_device_add(pdev) (drivers\base\platform.c)
|-> pdev->dev.bus = &platform_bus_type
|-> platform_bus_type.name = "platform" // 总线的名字
platform_bus_type.match = platform_match// match函数
|-> device_add(&pdev->dev) (drivers\base\core.c)
|-> bus_probe_device(dev) (drivers\base\bus.c)
|-> device_initial_probe(dev) (drivers\base\dd.c)
|-> __device_attach(dev, true) (drivers\base\dd.c)
|-> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver) (drivers\base\bus.c)
|-> __device_attach_driver(struct device_driver *drv, void *_data) (drivers\base\dd.c)
|-> driver_match_device(drv, dev) (drivers\base\base.h)
|-> return drv->bus->match ? drv->bus->match(dev, drv) : 1
... ... // 与驱动注册后面一致
|-> driver_probe_device(drv, dev) (drivers\base\dd.c) // 驱动和设备匹配成功后,执行probe函数
... ... // 与驱动注册后面一致