看了韦老师的设备树视频,讲的很好!也在网上找到了根据老师讲课内容整理出来的博文,关于.dts转换成.dtb文件的内容可以参考这篇博文:https://blog.csdn.net/huanting_123/article/details/90142745
单板上电之后uboot引导内核启动的流程,也参考该作者的另一篇博文:https://blog.csdn.net/huanting_123/article/details/89931329,中间涉及汇编和好多看了头晕的代码,自己写起来有些吃力,等后面深入学习了再来分析。
后面会陆续讲到内核对设备树的处理,也可以参考上面的这位作者的博文,这里自己写一下自己的理解。只能是自己的一些浅显理解,深入代码内部的理解暂时还做不到。
8.3.2.对设备树中平台信息的处理(选择machine_desc)
可参考:
内核中有很多machine_desc结构体描述单板信息,其中有个字符串变量为dt_compat,它可以为单个字符串也可以为多个字符串,字符串内容为该machine_desc可以支持的单板信息。.dts中的根节点的compatiple中申明了该设备树可以运行的单板类型,可以是单个字符串或多个字符串,依次与dt_compat对比,排在前面的compatiple属性优先匹配。看下6ull的dts和machine_desc。
//.dts:
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
}
********************************************************
//arch/arm/mach-imx6ul.c
static const char * const imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};
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
函数调用过程:
8.3.3.对设备树中运行时配置信息的提取
可参考:https://blog.csdn.net/huanting_123/article/details/89978923
主要是讲内核如何提取几个特殊节点和属性:
a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
b. 确定根节点的这2个属性的值: #address-cells, #size-cells,存入全局变量: dt_root_addr_cells, dt_root_size_cells
c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);
8.3.4.dtb转换为device_node(unflatten)
内核先计算出整个树的大小并为其分配内存空间,然后将设备树中的每个node转化为device_node,再为每个device_node填入device_properties。
unflatten_device_tree(); //解压设备树
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); //执行解压
/* First pass, scan for size 计算设备树的大小*/
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
/* Allocate memory for the expanded device tree 为设备树分配内存*/
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes); //解压设备树节点
fdt_next_node(); //遍历设备树找节点
populate_node(); //填充设备树节点
|-->fdt_get_name(); //获取name属性
|-->np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node)); //为node分配内存
|-->of_node_init(np); //初始化节点
|-->np->full_name = fn = ((char *)np) + sizeof(*np);
//将device_node的full_name指向结构体结尾处,即将一个节点的unit name放置在一个struct device_node的结尾处。
|-->populate_properties(); //填充属性
||-->fdt_next_property_offset(); //
||-->pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property)); //为property分配内存
||-->pp->name = (char *)pname;
||-->pp->length = sz;
||-->pp->value = (__be32 *)val;
关于韦老师的那个node和特性的图是很好的,但是太长了,不贴出来了。
8.3.5.device_node转换为platform_device
一路转化过来就是:dts -> dtb -> device_node -> platform_device
两个问题:
并非所有的device_node都会转换为platform_device,只有以下的device_node会转换:
a.1 根节点不会转化成platform_device;
a.2 根节点的子节点(父节点)必须含有compatible属性才可以转化成platform_device;
a.3 孙节点可以转化为platform_device的前提是自身必须含有compatible属性而且父节点必须含有特殊的compatible属性,如"simple-bus","simple-mfd","isa","arm,amba-bus"。
举例:
/ {
mytest {
/*父节点中的"simple-bus"属性会导致其子节点也会构建出平台设备的设备端*/
compatile = "mytest", "simple-bus";
mytest@0 {
compatile = "mytest_0";
};
};
i2c {
compatile = "samsung,i2c";
at24c02 {
compatile = "at24c02";
};
};
};
(1)节点 /mytest 会被转换为 platform_device。由于它兼容 "simple-bus", 它的子节点 /mytest/mytest@0 也会被转换为platform_device. 也就是说一个节点的 compatible 属性中只要有 "simple-bus",其子节也会转换为 platform_device,若没有是不会转换的。
(2)/i2c 节点一般表示i2c控制器,它会被转换为platform_device,在内核中有对应的 platform_driver; 其子节点 /i2c/at24c02 节点不会被转换为 platform_device,它被如何处理完全由父节点的 platform_driver 决定, 一般是被创建为一个i2c_client。
b. 怎么转换?
首先看一个结构体platform_device,找到一篇很好的博文:https://www.cnblogs.com/downey-blog/p/10486568.html,引用:
“在老版本的内核中,platform_device部分是静态定义的,其实最主要的部分就是resources部分,这一部分描述了当前驱动需要的硬件资源,一般是IO,中断等资源。
在设备树中,这一类资源通常通过reg属性来描述,中断则通过interrupts来描述,所以,设备树中的reg和interrupts资源将会被转换成platform_device内的struct resources资源。
那么,设备树中其他属性是怎么转换的呢?答案是:不需要转换,在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node *of_node,linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构。
例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息.”
原来是这么个调用关系!
8.3.6.platform_device跟platform_driver的匹配
发现写的很不错的博文记录下:https://blog.csdn.net/richard_liujh/article/details/45825333
小结:uboot把设备树编译成的.dtb文件的地址传递给内核,内核提取.dtb根节点的compatible属性来匹配machine_desc,解析.dtb的chosen节点、memory节点、#address-cells和#size-cells属性,为其分配内存。再去遍历整个.dtb文件的节点,将其转换成device_nodes,填充到各个device_nodes的properties,最终构建出device-tree。再根据compatible属性的转化原则将某些device_nodes转化成platform_devices,不能转化成device_nodes的节点内核提供专门的处理函数。platform_devices被挂载到不同的设备总线上,等待platform_drivers的注册。到这里设备树到内核的转化过程已经清楚了,回归到leddrv.c中。