0%

Linux设备树解析

一.平台识别

平台识别主要的内容就是根据设备树的根节点campatiable字段匹配最合适的mach_desc

  1. 首先得知道bootloader是怎么把设备树传递给kernel。具体的汇编代码在arch\arm\kernel\head-common.S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data

ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b

mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b

ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
cmp r7, #0
strne r0, [r7] @ Save control register values
b start_kernel
ENDPROC(__mmap_switched)

可以看出bootloader通过r1传递machine id,r2传递ATAG(kernel支持旧的以tag list方式传递)或者DTB,同时在head-common.S里kernel还定义了__machine_arch_type和__atags_pointer,以方便后续的C代码使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	.align	2
.type __mmap_switched_data, %object
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
#ifdef CONFIG_CPU_CP15
.long cr_alignment @ r7
#else
.long 0 @ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp
.size __mmap_switched_data, . - __mmap_switched_data

2.第一步通过 b start_kernel跳转到C环境执行,设备树的处理在arch\arm\kernel\setup.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;

setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", mdesc->name);

......
}

从上面代码可以看出kernel现在首选设备树,只有设备树设置失败之后才会尝试传统的__machine_arch_type寻machine 描述符,使用tag lists来进行运行时参数传递。那接下来就分析setup_machine_fdt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* setup_machine_fdt - Machine setup when an dtb was passed to the kernel
* @dt_phys: physical address of dt blob
*
* If a dtb was passed to the kernel in r2, then use it to choose the
* correct machine_desc and to setup the system.
*/
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;

#ifdef CONFIG_ARCH_MULTIPLATFORM
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
MACHINE_END

mdesc_best = &__mach_desc_GENERIC_DT;
#endif
//确定传进的DTB是否合法
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
return NULL;

//遍历mach_desc找到与设备树的compatible最匹配的
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

//查找失败的处理
if (!mdesc) {
const char *prop;
int size;
unsigned long dt_root;

early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");

dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("]\n\n");

dump_machine_table(); /* does not return */
}

/* We really don't want to do this, but sometimes firmware provides buggy data */
if (mdesc->dt_fixup)
mdesc->dt_fixup();

//扫描设备树
early_init_dt_scan_nodes();

/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;

return mdesc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用DT_MACHINE_START和MACHINE_END定义一个一个描述符,然后编译器会把这些machine描述符放到arch.info.init section
//__arch_info_begin就指向arch.info.init section的起始,__arch_info_end就指向arch.info.init section的末尾
static const void * __init arch_get_next_mach(const char *const **match)
{
//这里的mdesc是static的,意味着arch_get_next_mach函数执行完之后mdesc并不会被销毁
static const struct machine_desc *mdesc = __arch_info_begin;
const struct machine_desc *m = mdesc;

if (m >= __arch_info_end)
return NULL;

//每次进入arch_get_mach函数,m都会赋为更新后的mdesc,直到m到达__arch_info_end
mdesc++;
*match = m->dt_compat;

//返回值保存了当前处理的machine_desc,而match保存着设备树里的compatiable属性值
return m;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const void * __init of_flat_dt_match_machine(const void *default_match,
const void * (*get_next_compat)(const char * const**))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
unsigned long dt_root;
unsigned int best_score = ~1, score = 0;

dt_root = of_get_flat_dt_root();
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;//分最小意味着越匹配
}
}
if (!best_data) {//如果没有匹配到
const char *prop;
int size;

pr_err("\n unrecognized device tree list:\n[ ");

prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
if (prop) {
while (size > 0) {
printk("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
}
printk("]\n\n");
return NULL;
}
//如果model属性没有,就打印compatiable属性值
pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

return best_data;
}
1
#define of_compat_cmp(s1, s2, l)	strcasecmp((s1), (s2))

setup_machine_fdt首先会调用early_init_dt_verify进行参数(DTB)的校验(magic,etc…),校验成功后会把传给DTB的指针赋给initial_boot_params,这个指针之后会用到。然后调用of_flat_dt_match_machine从根节点开始,调用arch_get_next_mach不断的寻找下一个mdesc,然后调用of_flat_dt_match匹配,最终调用到of_compat_cmp,简单的比较根节点compatible的值是否与mdesc.dt_compat值是否相等即可。由于我使用的是IMX6UL,IMX6UL的mdesc_best定义:arch\arm\mach-imx\mach-imx6ul.c

1
2
3
4
5
6
7
8
9
10
11
12
13
static const char *imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
1
2
3
4
5
6
7
8
9
10
11
12
//arch\arm\include\asm\mach\arch.h
#define MACHINE_END \
};

#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,

#endif

可以看出kernel使用DT_MACHINE_START和MACHINE_END定义一个mdesc,然后编译器会在mdesc放到一个.arch.info.init的段里,形成一个machine描述符列表,这个列表里存放的相应成员在定义时已经初始化过了。struct machine_desc定义如下,现在只需要关注nr、dt_offset成员即可:

1
2
3
4
5
6
7
8
9
10
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */

unsigned int nr_irqs; /* number of IRQs */
......
}

二.运行时参数配置(Runtime configuration)

在boot早期阶段,参数传递主要发生在early_init_dt_scan_nodes函数完成,主要完成三个工作。

  • 扫描 /chosen node,保存运行时参数(bootargs,bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来)到boot_command_line,此外,还通过early_init_dt_check_for_initrd处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中 。
  • 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 ,memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示
  • 扫描DTB中的memory node

具体的代码在early_init_dt_scan_nodes,路径是drivers\of\fdt.c

1
2
3
4
5
6
7
8
9
10
11
void __init early_init_dt_scan_nodes(void)
{
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);

/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

上面已经完成了DTB的一些设置工作,接下来就是展开设备树进行解析,展开设备树目的就是把DTB解析成struct device_node保存在of_root全局变量里。解析函数是__unflatten_device_tree

三.设备生成(Device population)

struct device_node结构体成员如下,路径include\linux\of.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct device_node {
const char *name;//设备节点名字
const char *type;//对应device_type property
phandle phandle;//编译器自动生成,对应着node的phandle
const char *full_name;//从"/"开始,某个节点的full path,比如gpio0 = "/soc/aips-bus@02000000/gpio@0209c000";
struct fwnode_handle fwnode;//固件相关的handle

struct property *properties;//节点的property
struct property *deadprops;//如果需要删除某些属性,kernel不是真正的删除,而是挂到deadprops上
struct device_node *parent;//parent、child以及sibling形成一棵树将所有的device node连接起来
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;//用于在/sys目录下生成相应用户文件
unsigned long _flags;//可参考include\linux\of.h里的flag descriptions
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};

在__unflatten_device_tree中,主要调用unflatten_dt_node进行解析,第一次的解析主要是为了扫描得出设备树转换成struct device_node所需要的空间(size),然后调用dt_alloc(early_init_dt_alloc_memory_arch)向系统申请分配内存空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//路径:drivers\of\fdt.c
static void __unflatten_device_tree(void *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
......
/* First pass, scan for size */
start = 0;
size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
size = ALIGN(size, 4);

pr_debug(" size is %lx, allocating...\n", size);

/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
memset(mem, 0, size);

*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

pr_debug(" unflattening %p...\n", mem);

/* Second pass, do actual unflattening */
start = 0;
unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
//设备树结束处赋值0xdeadbeef,为了后边检查是否有数据溢出
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));

pr_debug(" <- unflatten_device_tree()\n");
......
}

接下来第二次调用unflatten_dt_node进行真正的解析工作,unflatten_dt_node,unflatten_dt_node的实参为unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);blob是DTB的首地址,mem就是申请的内存空间指针,start保存着偏移量,NULL代表着父节点(因为我们从root node开始处理的),mynodes是个二级指针保存着最终组织好的device_node,0代表fpsize为0,false代表着dryrun。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
static void * unflatten_dt_node(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 = 0;
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 '/'.
*/
//若是节点路径名则(*pathp)== "/"
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;
}
}

//分配一个设备节点device_node结构,*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小
np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
//full_name保存完整的节点名,即包括各级父节点的名称
np->full_name = fn = ((char *)np) + sizeof(*np);
//若new_format=1,表示pathp保存的是节点名,而不是节点路径名,所以需要加上父节点的name
if (new_format) {
/* rebuild full path for new format */
if (dad && dad->parent) {
strcpy(fn, dad->full_name);
#ifdef DEBUG
if ((strlen(fn) + l + 1) != allocl) {
pr_debug("%s: p: %d, l: %d, a: %d\n",
pathp, (int)strlen(fn),
l, allocl);
}
#endif
fn += strlen(fn);
}
*(fn++) = '/';
}
memcpy(fn, pathp, l);

//prev_pp指向节点的属性链表
prev_pp = &np->properties;
//若父亲节点不为空,则设置该节点的parent
if (dad != NULL) {
//指向dad node
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}
/* process properties */
//处理该node节点下面所有的property
for (offset = fdt_first_property_offset(blob, *poffset);
(offset >= 0);
(offset = fdt_next_property_offset(blob, offset))) {
const char *pname;
u32 sz;

//从节点的strings block中noff偏移处,得到该属性的name
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;
}
//如果有名称为name的property,则变量has_name为1
if (strcmp(pname, "name") == 0)
has_name = 1;
//为该属性分配一个属性结构,即struct property
pp = unflatten_dt_alloc(&mem, sizeof(struct property),
__alignof__(struct property));
//处理属性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. */
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;
//property插入该节点的属性链表np->properties
*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
*/
//如果该节点没有"name"的属性,则为该节点生成一个name属性,插入该节点的属性链表,注意V0.3的设备树规范已经弃用了name property,page 17
if (!has_name) {
const char *p1 = pathp, *ps = pathp, *pa = NULL;
int sz;

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;
pr_debug("fixed up name for %s -> %s\n", pathp,
(char *)pp->value);
}
}
//填充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;
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;

//mem返回整个设备树所分配的内存大小,即设备树占的内存空间
return mem;
}

经过第二次unflatten_device_tree,我们已经建立起了struct device_node,就可以通过of_root全局变量得到解析后的设备树对应的device_node,还有一步就是对设备树里的alias进行扫描,代码路径为:drivers\of\base.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
struct property *pp;

//of_aliases和of_chosen都是全局变量,保存着aliases和chosen节点对应的device_node
of_aliases = of_find_node_by_path("/aliases");
of_chosen = of_find_node_by_path("/chosen");
if (of_chosen == NULL)
of_chosen = of_find_node_by_path("/chosen@0");

if (of_chosen) {
/* linux,stdout-path and /aliases/stdout are for legacy compatibility */
const char *name = of_get_property(of_chosen, "stdout-path", NULL);
if (!name)
name = of_get_property(of_chosen, "linux,stdout-path", NULL);
if (IS_ENABLED(CONFIG_PPC) && !name)
name = of_get_property(of_aliases, "stdout", NULL);
if (name)
of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
}

if (!of_aliases)
return;

//alias节点的所有property,这里以IMX6UL的某一个DTS举例
/*
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
...
}


flexcan1: can@02090000 {
compatible = "fsl,imx6ul-flexcan", "fsl,imx6q-flexcan";
reg = <0x02090000 0x4000>;
interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_CAN1_IPG>,
<&clks IMX6UL_CLK_CAN1_SERIAL>;
clock-names = "ipg", "per";
stop-mode = <&gpr 0x10 1 0x10 17>;
status = "disabled";
};
下面以can0举例
*/
for_each_property_of_node(of_aliases, pp) {
//property name都放在strings block里,这里pp-name就是"can0"
const char *start = pp->name;
const char *end = start + strlen(start);
struct device_node *np;
struct alias_prop *ap;
int id, len;

/* Skip those we do not want to proceed */
if (!strcmp(pp->name, "name") ||
!strcmp(pp->name, "phandle") ||
!strcmp(pp->name, "linux,phandle"))
continue;

//根据property can0的值找到其这个值对应的device_node
np = of_find_node_by_path(pp->value);
if (!np)
continue;

/* walk the alias backwards to extract the id and work out
* the 'stem' string */
//由于我们以can0举例,那么start指向字符'c',end指向字符'\0'
while (isdigit(*(end-1)) && end > start)
end--;
len = end - start;

//最终end指向字符can0里的字符‘0’,len就为3,然后将end指向的字符转换成十进制的数字0,赋值给id
if (kstrtoint(end, 10, &id) < 0)
continue;

/* Allocate an alias_prop with enough space for the stem */
ap = dt_alloc(sizeof(*ap) + len + 1, 4);
if (!ap)
continue;
memset(ap, 0, sizeof(*ap) + len + 1);
//ap-alias指向can0
ap->alias = start;
of_alias_add(ap, np, id, start, len);
}
}

of_alias_scan最后一步调用of_alias_add

1
2
//aliases_lookup是一个全局的链表
LIST_HEAD(aliases_lookup);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void of_alias_add(struct alias_prop *ap, struct device_node *np,
int id, const char *stem, int stem_len)
{
//np就是flexcan1对应的device_node
ap->np = np;
//id为0
ap->id = id;
//拷贝之后ap-stem值为can0
strncpy(ap->stem, stem, stem_len);
ap->stem[stem_len] = 0;
// 将这个ap加入到全局aliases_lookup链表中
list_add_tail(&ap->link, &aliases_lookup);
pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
ap->alias, ap->stem, ap->id, of_node_full_name(np));
}

经过of_alias_scan之后,把alias里所有的property加入全局链表aliases_lookup,供之后驱动里使用,比如有多个spi,那么设备树里就可以定义spi0,spi1….等,在of_alias_scan的时候会自动给相应节点的device_node分配id,供kernel其他地方调用of_alias_get_id获取对应id。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int of_alias_get_id(struct device_node *np, const char *stem)
{
struct alias_prop *app;
int id = -ENODEV;

mutex_lock(&of_mutex);
list_for_each_entry(app, &aliases_lookup, link) { // 遍历全局链表aliases_lookup
if (strcmp(app->stem, stem) != 0) //target stem
continue;

if (np == app->np) { // 判断这个alias_prop指向的device_node是不是跟传入的匹配
id = app->id; //如果匹配就退出
break;
}
}
mutex_unlock(&of_mutex);

return id;
}

四.总结

本节围绕设备树三大功能:平台识别、运行期配置、设备生成三大功能结合具体的代码进行分析,后续还会分析device_node转换成platform_device的过程。