简介
参考qemu调试内核 搭建调试环境。
使用的设备树文件是
arch/arm/boot/dts/vexpress-v2p-ca9.dts
关于设备树文件的选定可以参考U-boot&设备树&Linux 文章。我们从 setup_machine_fdt函数开始分析。
我选定的Linux版本为
VERSION = 4PATCHLEVEL = 19SUBLEVEL = 19EXTRAVERSION =NAME = "People's Front"
setup_machine_fdt
这个函数传入设备树的物理地址,通过of_flat_dt_match_machine函数查找到最合适的struct machine_desc 。 关于of_flat_dt_match_machine匹配,最终会调用到of_fdt_is_compatible函数。此函数将会通过匹配 compatible = “arm,vexpress,v2p-ca9”, “arm,vexpress”; 中的字符串来确定是否符合,得到的 score越小越好。
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
unflatten_device_tree
遍历每一个节点,填充struct device_node 结构体。将在设备树镜像里面各个节点平面化成为立体的树结构,转化后的节点我们一般称作 设备节点(device_node)。 但是这个时候还并没有和平台设备搭上关系,所有的东西仅仅只是设备树上面描述的内容。下面一步就是需要将设备节点(device_node)转化为平台设备(platform_device)。
关于这两个设备之间的转化,要从 of_platform_default_populate_init 入手。
of_platform_default_populate_init
本段的主要目的是讲解,内核读取设备树镜像,生成对应的device_node,struct platform_device
此函数为入口。
arch_initcall_sync(of_platform_default_populate_init); # 内核的初始化段,执行一次就会被释放
1 创建保留的mem node
# drivers/of/platform.cstatic const struct of_device_id reserved_mem_matches[] = {{ .compatible = "qcom,rmtfs-mem" },{ .compatible = "qcom,cmd-db" },{ .compatible = "ramoops" },{}};for_each_matching_node(node, reserved_mem_matches)of_platform_device_create(node, NULL, NULL);
2. 创建 “/firmware”
这些操作可以不用分析,作用不大。了解就行。
node = of_find_node_by_path("/firmware");if (node) {of_platform_populate(node, NULL, NULL, NULL);of_node_put(node);}
3. 填充其他的节点
前面填充了特殊的节点,现在填充替他通用的节点。
of_platform_default_populate(NULL, NULL, NULL);
of_platform_default_populate
默认的填充函数。
const struct of_device_id of_default_bus_match_table[] = {{ .compatible = "simple-bus", },{ .compatible = "simple-mfd", },{ .compatible = "isa", },{} /* Empty terminated list */};# root = NULL# lookup = NULL# parent = NULLint of_platform_default_populate(struct device_node *root,const struct of_dev_auxdata *lookup,struct device *parent)of_platform_populate(root, of_default_bus_match_table, lookup,parent);
1.Populate platform_devices from device tree data
这里就是最重要的函数,使用设备树数据填充平台设备。
# 记住,这里的 matches 还是 of_default_bus_match_table 这个数组int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)# 找到设备树根节点root = root ? of_node_get(root) : of_find_node_by_path("/");# 遍历每一个子节点for_each_child_of_node(root, child)rc = of_platform_bus_create(child, matches, lookup, parent, true);
1.1 of_platform_bus_create
- struct device_node 这个结构体很重要,
# drivers/of/platform.cCreate a device for a node and its children.of_platform_bus_create# /* Make sure it has a compatible property */of_get_property(bus, "compatible", NULL)/* Skip nodes for which we don't want to create devices */# 跳过我们不想创建的节点of_match_node(of_skipped_node_table, bus)# 检查节点是否已经填充过了of_node_check_flag(bus, OF_POPULATED_BUS)# Given a device node, lookup the preferred Linux name# 这里主要是给节点起名字,然后将节点数据读取出来auxdata = of_dev_lookup(lookup, bus);# 这个函数就要开始读取镜像里面的数据来填充数据了。dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);# 递归查找下一个节点for_each_child_of_node(bus, child)rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
1.1.1.1 of_platform_device_create_pdata
# drivers/of/platform.cof_platform_device_create_pdata - Alloc, initialize and register an of_device# 此函数 Allocate and initialize an of_devicestruct platform_device *dev = of_device_alloc()# 设置平台设备类型dev->dev.bus = &platform_bus_type;# 填充平台设备数据dev->dev.platform_data = platform_data;
数据填充总结
of_platform_default_populate_init 函数通过初始化函数修饰作为入口, 调用of_platform_default_populate函数来填充非特殊的平台设备。
然后由of_platform_default_populate调用of_platform_populate。来创建遍历每一个root下面的子节点,通过调用of_platform_bus_create函数来创建
当前节点以及其子节点的设备。
of_platform_bus_create 函数通过 of_platform_device_create_pdata 来 初始化 struct platform_device。填充dev->dev.bus 以及 dev->dev.platform_data两个数据。
然后递归调用of_platform_bus_create。
那么最重要的两个函数
- of_platform_bus_create 递归调用自身来遍历所有的子节点
- of_platform_bus_create 调用 of_platform_device_create_pdata 来创建 struct platform_device 并且填充数据
平台设备和驱动的匹配
前面我们讲述了平台设备的创建,最终通过of_platform_device_create_pdata函数创建了 struct platform_device并 填充了dev->dev.bus 以及 dev->dev.platform_data两个数据。
首先我们需要明确一件事,设备和驱动之间存在着一个虚拟的总线。这个总线一头挂载着设备,一头挂载着驱动。那么产生了以下的几个问题。
1. 驱动以及设备是以什么样的方式挂载到虚拟总线上面的?2. 驱动和设备的匹配过程?
platform_driver_register
这个函数就是平台驱动的注册函数
# include/linux/platform_device.hplatform_driver_register__platform_driver_register# drivers/base/platform.cint __platform_driver_register(struct platform_driver *drv,struct module *owner)drv->driver.owner = owner;drv->driver.bus = &platform_bus_type;# 最重要的就是这个 platform_drv_probe 函数drv->driver.probe = platform_drv_probe;drv->driver.remove = platform_drv_remove;drv->driver.shutdown = platform_drv_shutdown;# 然后调用驱动注册函数driver_register(&drv->driver);
platform_driver_register
