-
内核之解析DTS设备树文件并创建设备的过程
在这里,我分析的是内核源码来自谷歌官方Android7.1.2源码包经过打补丁包"SC60_Android7.1.2_Quectel_SDK_r270060_20180731.tar.gz"后得到的.
本文分析时使用的工具是"SourceInsight".
分析过程中抓大放小,仅分析主干流程,细枝末节的东西未深究,如果有同志愿意分享自己分析的心得,或者进行了更深层次的分析,还望贴出自己博客链接在评论栏,或者把相关资料发到我的邮箱"uestc_ganlin@163.com",谢谢~
-
主干的分析的过程
搜索有关宏定义DT_MACHINE_START的所有文件发现:该宏定义是在"kernel\msm-3.18\arch\arm\include\asm\mach"目录的"arch.h",除此之外,其他文件都为这个宏定义的使用文件,形如在目录"kernel\msm-3.18\arch\arm\mach-xxx"下,名为"xxx.c"的文件.这里我用的是"kernel\msm-3.18\arch\arm\mach-msm\board-8953.c"来切入,进行后续分析:
DT_MACHINE_START(MSM8953_DT,
"Qualcomm Technologies, Inc. MSM 8953 (Flattened Device Tree)")
.init_machine = msm8953_init,
.dt_compat = msm8953_dt_match,
MACHINE_END
发现其实主要做了两件是,一个是定义msm8953的设备树匹配表dt_compat为msm8953_dt_match,另一个是定义msm8953的初始化函数init_machine为msm8953_init.
先来看看msm8953的设备树匹配表msm8953_dt_match:
static const char *msm8953_dt_match[] __initconst = {
"qcom,msm8953",
"qcom,apq8053",
NULL
};
发现msm8953_dt_match是一个char*类型的指针数组,其中每个元素指向一个字符串,譬如"qcom,msm8953".
这个字符串我猜测的是用来指明要匹配的DTS文件的,因为所有的DTS文件都在"kernel\msm-3.18\arch\arm\boot\dts\"目录下,所以这里指明的相关文件应该就是"kernel\msm-3.18\arch\arm\boot\dts\qcom"目录下的"msm8953.dtsi"文件;
或者查看该DTS文件中根节点定义的compatible属性,发现该属性定义为 "qcom,msm8953",看到这里,也可能因为他跟该DTS文件中定义的compatible属性一致,所以才能匹配的上.但不论是前者那种我自己猜的理解方式("qcom,msm8953"是指明目录和文件的),还是后者的理解("qcom,msm8953"是通过DTS文件中定义的compatible属性进行匹配的)都能够说明该字符串的用意,貌似后者的理解更为合理一些,呵呵~
接下来继续分析msm8953的初始化函数msm8953_init:
static void __init msm8953_init(void)
{
board_dt_populate(NULL);
}
简单分析后不难看出msm8953_init是初始化设备的入口函数,而 msm8953_dt_match是设备树匹配的关键数据结构数组.
继续看msm8953_init函数中调用的板级设备树建立函数board_dt_populate,切换到定义处所在文件"kernel\msm-3.18\arch\arm\mach-msm\board-dt.c":
void __init board_dt_populate(struct of_dev_auxdata *adata)
{
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
*/
of_platform_populate(of_find_node_by_path("/soc"),
of_default_bus_match_table, adata, NULL);
}
其中两个关键点,一个该函数的参数是of_device_id结构体类型的数组定义的默认总线匹配表of_default_bus_match_table:
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 */
};
如代码所示,这是一个of_device_id结构体类型的数组,其中为每一个元素的compatible赋值,不难理解,这个就真的是会和DTS中对应的compatible属性进行匹配了.
另一个关键点是该函数调用的平台设备建立函数of_platform_populate,在文件"kernel\msm-3.18\drivers\of\platform.c":
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;
}
核心工作是用for_each_child_of_node函数遍历根设备节点下的所有的子节点,并执行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;
/* 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);
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_get_property函数检查该平台总线设备节点否具有compatible属性,通过后开始执行of_platform_device_create_pdata函数真正的创建平台设备内容,到这里其实就已经算是完成了一个设备节点从匹配识别到最终创建出设备内容的完整流程了,但是了解DTC文件的朋友知道,这个设备节点下往往还会有其他的设备的子节点,然后子节点的子节点还有设备节点......等等,以此类推组成的一个庞大的树形分支的设备树结构,那怎么办得呢?往下看就知道了,其实很简单,管它有多少个设备的节点,都调用到我们这里把设备内容真正的创建出来不就解决了吗?是的,的确如此,接下来再使用for_each_child_of_node函数遍历当前平台总线设备节点的所有子节点,递归式的执行of_platform_bus_create函数进行每一个平台总线设备的创建,其中每完成一个创建,调用of_node_put函数释放当前节点的内存.分析到这里,我对递归的用法算是理解的更深刻一点了.
接下来在看看具体如何创建平台设备内容的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))
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);
of_msi_configure(&dev->dev, dev->dev.of_node);
of_reserved_mem_device_init(&dev->dev);
if (of_device_add(dev) != 0) {
of_reserved_mem_device_release(&dev->dev);
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_device_is_available函数检查设备节点是否有效,然后执行of_device_alloc函数分配设备内存,有了设备后,紧接着对设备的一部分内容进行初始化巴拉巴拉,最终调用of_device_add函数往内核中添加设备.
再看看是如何分配设备内容的of_device_alloc函数:
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);
}
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;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
首先调用platform_device_alloc函数为定义的platform_device结构体类型的设备的分配内存,接下来分别调用of_address_to_resource和of_irq_count两个函数统计该设备使用的地址和中断资源个数,如果有上述资源的存在则分别调用of_address_to_resource和of_irq_to_resource_table两个函数对设备进行地址和中断资源分配,完成上述设备初始化工作后紧接着更进一步初始化,比如调用of_node_get函数初始化设备所属设备树节点,最终执行dev_set_name或者of_device_make_bus_id函数来设置设备的名字或者设置设备总线ID.
到此为止,所有主干内容分析完毕,如果你把所有的标红的内容整理出来,你会知道此次分析的源码所做的核心事件是什么,只要是我自己亲自进行的代码分析,我都会用心的记录下来,愿同行小伙伴自己做源码分析时也这样记录,并分享链接到评论,大家互相学习,谢谢~