0%

Linux设备树device_node转换成platform_device

本文Linux代码版本是:ARM Linux 4.1.15,源码阅读工具:Vscode

Linux设备树解析一文中已经对设备树的整个解析过程进行了详细的分析,那接下来就以IMX6UL为例分析device_node是如何转换成platform_device。

device_node转换成platform_device是与machine相关的,我们首先看customize_machine函数。在customize_machine主要完成的就是自定义平台设备或者添加新设备。

1
2
3
4
5
6
7
static int __init customize_machine(void)
{
......
if (machine_desc->init_machine)
machine_desc->init_machine();
......
}

machine_desc->init_machine,即mdesc被定义在machine相关的文件,我们以 ARM+IMX6UL为例,代码路径见:arch\arm\mach-imx\mach-imx6ul.c,我们可以看到.init_machine被绑定到imx6ul_init_machine。

1
2
3
4
5
6
7
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
static void __init imx6ul_init_machine(void)
{
struct device *parent;
......
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
......
}

生成platform_device相关的代码见of_platform_populate。从imx6ul_init_machine可以看出,of_platform_populate的实参第一个参数为NULL,代表着从根节点root开始生成platform_device,第二个参数为of_default_bus_match_table,表示match table,符合这个table的device node是成为platform_device的必要条件。我们看看of_default_bus_match_table的定义:

1
2
3
4
5
6
7
8
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 */
};

即:如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device。第三个参数是NULL代表没有auxdata table,第四个参数也为NULL代表从最顶层开始,即从root节点开始。继续往下走,看of_platform_populate的实现:drivers\of\platform.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;

for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}
of_node_set_flag(root, OF_POPULATED_BUS);

of_node_put(root);
return rc;
}

首先根据传入的root是否为空,我们传入的root为空,因此调用of_find_node_by_path从device_node里找到代表根节点的device_node,然后根据根节点来遍历device tree中的节点了。得到一个子节点之后,调用of_platform_bus_create函数。

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
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;

/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}

auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}

if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}

dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;

for_each_child_of_node(bus, child) {
pr_debug(" create child: %s\n", child->full_name);
//递归创建符合条件的节点下的所有子节点,因为一个device node可能是一个bridge device
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;
}

of_platform_bus_create里首先会通过strict和of_get_property确定节点是否有compatible属性,strict传入的时候被设置成了true,那只用关注of_get_property从节点是否能获取到compatible属性即可。如果节点没有compatible属性的话,会直接返回,调过创建platform_device的过程。接下来调用of_dev_lookup查找在传入lookup table寻找和该device node匹配的附加数据 ,由于IMX6UL传入的lookup为NULL,因此调过此阶段。接下来调用of_device_is_compatible检查节点compatible属性值是arm,primecell,如果是的话就进行后一步的动作(ARM公司提供了CPU core,除此之外,它设计了AMBA的总线来连接SOC内的各个block。符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals。如果一个device node的compatible属性值是arm,primecell的话,可以调用of_amba_device_create来向amba总线上增加一个amba device。)。但我们的IMX6UL设备树里是没有使用到arm,primecell这个属性值。继续往下走,调用了of_platform_device_create_pdata

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
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;
//检查节点是否有status property,如果没有"status"属性,那还好说直接返回true。如果有"status"属性,
//而它的值又不是"okay"或"ok",那么不好意思,返回false,否则还是返回true。所以"status"属性就是用来检测 //是否可用
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;

//分配内存
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;

dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_dma_configure(&dev->dev, dev->dev.of_node);

//添加
if (of_device_add(dev) != 0) {
of_dma_deconfigure(&dev->dev);
platform_device_put(dev);
goto err_clear_flag;
}

return dev;

err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}

可以看到of_platform_device_create_pdata是创建platform_device的地方。首先会调用of_device_is_available检查节点是否有status属性,从我们的IMX6UL dts可以看到,pwm8等节点是有status属性的,并且其值是”okay”。

1
2
3
4
5
&pwm8 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm8>;
status = "okay";
};

of_device_is_available最终调用了__of_device_is_available,我们看看里面做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static bool __of_device_is_available(const struct device_node *device)
{
const char *status;
int statlen;

if (!device)
return false;

status = __of_get_property(device, "status", &statlen);
if (status == NULL)
return true;

if (statlen > 0) {
if (!strcmp(status, "okay") || !strcmp(status, "ok"))
return true;
}

return false;
}

可以看出__of_device_is_available首先检查节点是否有status属性,如果有的话,再判断该节点的值是否是”okay”或者”ok”,如果是,就返回true,否则返回false。of_platform_device_create_pdata起初还会判断通过of_node_test_and_set_flag判断节点是否已经生成过了,如果已经生成,就直接返回NULL。继续往下走,调用到了of_device_alloc,该函数的主要作用就是分配和初始化一个platform_device。

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
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;

dev = platform_device_alloc("", -1);
if (!dev)
return NULL;

/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);

/* Populate the resource table */
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}

dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
//把irq转换成中断resource
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %s\n",
np->name);
}

dev->dev.of_node = of_node_get(np);
dev->dev.parent = parent ? : &platform_bus;

if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);

return dev;
}

在of_device_alloc函数里,首先会调用platform_device_alloc分配struct platform_device的内存。内存申请完了之后,还会调用of_address_to_resource去统计IO和中断resource。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
const __be32 *addrp;
u64 size;
unsigned int flags;
const char *name = NULL;

addrp = of_get_address(dev, index, &size, &flags);
if (addrp == NULL)
return -EINVAL;

/* Get optional "reg-names" property to add a name to a resource */
of_property_read_string_index(dev, "reg-names", index, &name);

return __of_address_to_resource(dev, addrp, size, flags, name, r);
}

of_address_to_resource里首先调用of_get_address获得一些地址信息。

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
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
unsigned int *flags)
{
const __be32 *prop;
unsigned int psize;
struct device_node *parent;
struct of_bus *bus;
int onesize, i, na, ns;

/* Get parent & match bus type */
parent = of_get_parent(dev);
if (parent == NULL)
return NULL;
bus = of_match_bus(parent);
bus->count_cells(dev, &na, &ns);
of_node_put(parent);
if (!OF_CHECK_ADDR_COUNT(na))
return NULL;

/* Get "reg" or "assigned-addresses" property */
prop = of_get_property(dev, bus->addresses, &psize);
if (prop == NULL)
return NULL;
psize /= 4;

onesize = na + ns;
for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)
if (i == index) {
if (size)
*size = of_read_number(prop + na, ns);
if (flags)
*flags = bus->get_flags(prop);
return prop;
}
return NULL;
}

在of_get_address里,首先调用of_get_parent找到节点的pararent,然后调用of_match_bus去找到parent所在bus。

1
2
3
4
5
6
7
8
9
10
static struct of_bus *of_match_bus(struct device_node *np)
{
int i;

for (i = 0; i < ARRAY_SIZE(of_busses); i++)
if (!of_busses[i].match || of_busses[i].match(np))
return &of_busses[i];
BUG();
return NULL;
}

of_busses里定义各种bus,比如PCI,ISA等bus。在of_match_bus函数中,我们使用到的bus就是默认的,它的addresses成员为”reg”。我们回到of_get_address里,该调用of_get_property,of_get_property函数就是根据of_get_address得到的address信息,即”reg”属性,我们可以得到这样一个结论:设备树里的reg属性就是就是用来定义io地址地址信息的。而io地址的长度是通过of_get_address中的of_read_number去读取完成的,最后返回这个io地址。

​ 我们回到of_address_to_resource,继续往下走看到__of_address_to_resource,它的作用就是将io地址转换成struct resource资源信息。现在回到of_device_alloc函数里,继续往下走,前面已经统计了reg属性,接下来调用of_irq_count统计IQR。IRQ统计相关工作主要在of_irq_parse_one函数内进行。

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
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
struct device_node *p;
const __be32 *intspec, *tmp, *addr;
u32 intsize, intlen;
int i, res;

pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index);

/* OldWorld mac stuff is "special", handle out of line */
if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
return of_irq_parse_oldworld(device, index, out_irq);

/* Get the reg property (if any) */
addr = of_get_property(device, "reg", NULL);

/* Try the new-style interrupts-extended first */
//一般中断必须包含一个interrupts属性或者一个interrupts-extended属性。或者两者都有,如果两者都有在解析
//的时候会优先解析。interrupts-extended这个属性仅仅只能用于设备有多个中断父节点
//#interrupt-cells定义每个中断配置有几个成员
res = of_parse_phandle_with_args(device, "interrupts-extended",
"#interrupt-cells", index, out_irq);
if (!res)
return of_irq_parse_raw(addr, out_irq);

/* Get the interrupts property */
//获取该设备的interrupts属性,反正属性值的地址,及数据大小
intspec = of_get_property(device, "interrupts", &intlen);
if (intspec == NULL)
return -EINVAL;

intlen /= sizeof(*intspec);

pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen);

/* Look for the interrupt parent. */
p = of_irq_find_parent(device);
if (p == NULL)
return -EINVAL;

/* Get size of interrupt specifier */
//解析interrupt-cells 属性,取得一个interrupt有几个成员
tmp = of_get_property(p, "#interrupt-cells", NULL);
if (tmp == NULL) {
res = -EINVAL;
goto out;
}
intsize = be32_to_cpu(*tmp);

pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);

/* Check index */
if ((index + 1) * intsize > intlen) {
res = -EINVAL;
goto out;
}

/* Copy intspec into irq structure */
//这里根据我们传递的数组下标作位移,index是具体下标,intsize为每个interrupt成员数据个数,也就是整数个 //数。指针按这个offset进行位移后,就会指向我们需要的中断数据
intspec += index * intsize;
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++)
out_irq->args[i] = be32_to_cpup(intspec++);

/* Check if there are any interrupt-map translations to process */
res = of_irq_parse_raw(addr, out_irq);
out:
of_node_put(p);
return res;
}

of_irq_parse_one的作用就是通过遍历中断树找到某个有interrupts属性的节点中断控制器是哪个,并且返回相应的中断描述符供最终的解析IRQ 编号用。我们回到of_device_alloc函数内,之前我们已经统计了reg和irq,接下来调用kzalloc为IRQ和reg分配内存。然后调用of_irq_to_resource_table,这个函数最终调用of_irq_to_resource来完成IRQ number到IRQ resource的映射。我们可以对of_device_alloc做一个总结:它完成了对platform_device内存的分配以及还为platform_device找到了IO resource和IRQ resource

​ 我们回到of_platform_device_create_pdata,平台设备已经申请好了,然后对平台设备继续进行赋值操作,例如平台设备的总线赋值为平台总线,平台设备的私有数据赋值为platform_data等。of_platform_device_create_pdata的最后一步就是调用of_device_add把当前platform_device中的struct device成员注册到系统device中,并为其在用户空间创建相应的访问节点。至以上就是platform_device生成的全部过程。后面会再开文分析platform_device和platform_driver的匹配过程。