此篇博客有很多参考其他文章的内容,由于参考内容繁杂,不一一标注角标了,在末尾会贴上所有参考博客的link,如有侵权,请联系本人处理,谢谢。
深入,并且广泛
-沉默犀牛
上一篇文章已经详细的分析了两个问题:
1.如何根据Device Tree的信息,找到最适合的machine_desc
2.如何将DTB转换成节点是device_node的树状结构
那么为什么要做这两件事情呢?
我们现在要做的事情是把DTS中描述的节点(status = okay的)注册到kernel中,DTS的信息我们的kernel没法直接识别啊,就得通过上述的2把DTS中描述的每一个节点,转换成device_node结构体,我们可以认为DTS中写的每一个节点在这里都被解析为一个device_node。(该结构体中有parent、child、sibling成员,通过这些成员把device_node给连接成了树状结构)好,现在我们有了完全代表了DTS信息,又能被kernel识别的device_node,现在就要把这些device_node一个个的创建起来(比如有的device_node要注册到platform总线上,有的要注册到i2c总线…),这个创建的过程,就需要我们上述1中的machine_desc了!在定义machine_desc结构体的时候,会定义一个回调函数:xxxx_init(大部分情况)就是这个xxxx_init把device_node都注册起来。
再补充上面一段话的三个点
1.xxxx_init函数只是注册了platform总线上的device_node,那其他的呢?比如i2c上的device呢?在注册i2c总线时,调用qup_i2c_probe(),这个接口会添加i2c适配器,适配器添加完成后会调用of_i2c_register_devices()接口来i2c总线节点的子节点,然后调用i2c_new_device(),生成i2c设备。
2.我在定义回调函数后面写了个大部分情况,也就代表有的时候不会定义这个回调函数,那么kernel怎么注册device_node呢?事实上kernel会检测machine_desc有没有init函数,如果有,则调用;如果没有,则调用of提供的接口直接注册所有platform的device,这里就有一个问题了:这个machine_desc如果没有定义init函数,那我还要它干嘛呢?我去看(根据DTS选择最适配的machine_desc)那一段代码时发现,如果没有找到合适的machine_desc的话,有一行注释:does not return! 这似乎意味着,找不到合适的machine_desc也没啥关系,kernel还是会继续运行下去,platform上的device还是可以注册上。如果这一点的理解有误,请留言纠正,大恩不言谢!
3.DTS中的节点都转换成了device_node,但是DTS中描述的节点并不都是device啊,比如cpus node,memory node,choose node等。对于这些节点的处理,我会在下一篇文章中说明。
经过前面的介绍和解释,我们明确了这一篇文章的主题:
那些platform总线下的device_node如何注册到platform总线上的?
进入kernel的入口后,会按照如下的调用流程start_kernel
->rest_init
->kernel_init
->kernel_init_freeable
->
do_basic_setup
->do_initcalls
,do_initcalls
函数中,在do_initcalls函数中,kernel会依次执行各个initcall函数。
在上述的调用流程中,会调用到调用customize_machine
,也正是这个函数,检测了machine_desc是否有xxxx_init函数,代码如下:
static int __init customize_machine(void)
{
if (machine_desc->init_machine) //如果有init_machine()函数,则执行
machine_desc->init_machine();
else //否则直接调用of提供的接口来注册device
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
return 0;
}
arch_initcall(customize_machine);
以高通msm8953为例,看看它有没有定义init_machine函数:
DT_MACHINE_START(MSM8953_DT,
"Qualcomm Technologies, Inc. MSM8953 (Flattened Device Tree)")
.init_machine = msm8953_init, //这里定义了init函数
.dt_compat = msm8953_dt_match, //这个参数就是之前匹配machine_desc用的
//这个参数将于DTS中的compatible属性值来比较
MACHINE_END
再看看这个函数的内容:
static void __init msm8953_init(void)
{
board_dt_populate(NULL);
}
void __init board_dt_populate(struct of_dev_auxdata *adata)
{
//传入NULL参数表示从root node开始scan
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
/* Explicitly parent the /soc devices to the root node to preserve
* the kernel ABI (sysfs structure, etc) until userspace is updated
*/
//从soc节点开始scan
of_platform_populate(of_find_node_by_path("/soc"),
of_default_bus_match_table, adata, NULL);
}
看来of_platform_populate()
这个函数就是主要起作用的函数了,仔细分析一下:
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;
//之前传入的NULL,就是从/节点下开始,即根节点
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %s\n", root->full_name);
for_each_child_of_node(root, child) { //这里面是一个for循环,如果root节点下面有child,就执行一遍
rc = of_platform_bus_create(child, matches, lookup, parent, true); //重要函数
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
看来of_platform_bus_create()
这个函数是个重要函数,仔细分析一下:
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;
//确保device node有compatible属性的代码
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %s, already populated\n",
__func__, bus->full_name);
return 0;
}
//在传入的lookup table寻找和该device node匹配的附加数据
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
/*ARM公司提供了CPU core,除此之外,它设计了AMBA的总线来连接SOC内的各个block。
符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals。
如果一个device node的compatible属性值是arm,primecell的话,
可以调用of_amba_device_create来向amba总线上增加一个amba device。*/
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;
}
//如果不是ARM Primecell Peripherals,那么我们就需要向platform bus上增加一个platform device了
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
// 一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉。
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;
}
具体增加platform device的代码在of_platform_device_create_pdata()
中,代码如下:
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)) //check status属性,和是否已经注册过
return NULL;
// of_device_alloc除了分配struct platform_device的内存,还分配了该platform device需要的resource的内存
//这里就根据struct device_node创建了struct platform_device
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
//设定platform_device 中的其他成员
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_dma_configure(&dev->dev, dev->dev.of_node);
of_msi_configure(&dev->dev, dev->dev.of_node);
of_reserved_mem_device_init_by_idx(&dev->dev, dev->dev.of_node, 0);
if (of_device_add(dev) != 0) { //把这个platform device加入统一设备模型系统中
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;
}
到这里我们就很清楚device_node是如何最终成为platform_device了。