简介
参考qemu调试内核 搭建调试环境。
使用的设备树文件是
arch/arm/boot/dts/vexpress-v2p-ca9.dts
关于设备树文件的选定可以参考U-boot&设备树&Linux 文章。我们从 setup_machine_fdt函数开始分析。
我选定的Linux版本为
VERSION = 4
PATCHLEVEL = 19
SUBLEVEL = 19
EXTRAVERSION =
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.c
static 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 = NULL
int 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.c
Create 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.c
of_platform_device_create_pdata - Alloc, initialize and register an of_device
# 此函数 Allocate and initialize an of_device
struct 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.h
platform_driver_register
__platform_driver_register
# drivers/base/platform.c
int __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