本文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 {} };
即:如果一个结点的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 ; 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" )) { 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); 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 ; 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 ; while (of_address_to_resource(np, num_reg, &temp_res) == 0 ) num_reg++; num_irq = of_irq_count(np); 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); } 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; 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; 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 ; 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); if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC) return of_irq_parse_oldworld(device, index, out_irq); addr = of_get_property(device, "reg" , NULL ); res = of_parse_phandle_with_args(device, "interrupts-extended" , "#interrupt-cells" , index, out_irq); if (!res) return of_irq_parse_raw(addr, out_irq); 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); p = of_irq_find_parent(device); if (p == NULL ) return -EINVAL; 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); if ((index + 1 ) * intsize > intlen) { res = -EINVAL; goto out; } 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++); 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的匹配过程。