一、设备树的描述
对于设备树,其描述的信息可以分成三部分;在内核中,对设备树的处理也会分成三部分:
Linux uses DT data for three major purposes:
1) platform identification, (平台识别信息)
2) runtime configuration, and (运行时配置信息)
3) device population. (设备信息)
二、内核head.S对dtb的简单处理
u-boot把一些参数,把设备树文件传给内核,那么内核如何处理设备树文件呢?从内核的第一个启动文件head.S着手,分析其对dtb的处理。(详细的head.s的分析可参见:嵌入式Linux完全开发手册 - 移植Linux内核 - Linux内核启动概述)
bootloader启动内核时,会设置r0,r1,r2三个寄存器,由这三个寄存器将参数传给内核:
r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;
这里额外介绍下machine id的作用。假设一个内核镜像uImage可以支持多种单板:
head.S分析如下:
a. [ __lookup_processor_type ] 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
b. [ __vet_atags ] 判断是否存在可用的ATAGS或DTB;
c. [ __create_page_tables ] 创建页表, 即创建虚拟地址和物理地址的映射关系;
d. [ __enable_mmu ] 使能MMU, 以后就要使用虚拟地址了
e. [ __mmap_switched ] 上述函数里将会调用__mmap_switched
f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
g. 调用C函数start_kernel
head.S/head-common.S :
把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
把bootloader传来的r2值, 赋给了C变量: __atags_pointer // dtb首地址
由此可见,head.S对dtb的处理还是比较简单的,只是将u-boot传来的R2寄存器里的值,也就是DTB的首地址赋给了内核中的C变量:__atags_pointer 。
三、内核对设备树中平台信息的处理(选择machine_desc)
platform identification, (平台识别信息)
上面的介绍中,已经知道在以前u-boot的ATAG传参中,会传入machine id 由此来匹配machine desc;当使用dtb传参时,u-boot并不传递machine id了,内核machine desc 的依赖于dtb 根节点下的compatible属性,内核根据该属性来找到合适的machine desc。
作为内核的使用者,我们已经列出清单,期望在内核中找到支持某个单板的machine desc 。
对于内核,每个machine desc需表明能支持哪些单板;
compatible属性对machine desc的查找
a. 设备树根节点的compatible属性列出了一系列的字符串,表示它兼容的单板名,从"最兼容"到次之。
b. 内核中有多个machine_desc, 其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板
c. 使用compatile属性的值, 跟每一个machine_desc.dt_compat 比较,成绩为"吻合的compatile属性值的位置",成绩越低越匹配, 对应的machine_desc即被选中
分析下代码的调用流程:
从上面的分析已经知道,内核在启动后,将dtb文件的首地址保存到变量__atags_pointer中,然后调用start_kernel,接下来从start_kernel开始分析:
函数调用过程:
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
// 判断是否有效的dtb, drivers/of/ftd.c
early_init_dt_verify(phys_to_virt(dt_phys)
initial_boot_params = params; //将dtb的地址保存在全局变量里
// 找到最匹配的machine_desc, drivers/of/ftd.c
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score best_data &#61; data; best_score &#61; score; } } machine_desc &#61; mdesc; 四、内核对设备树中运行时配置信息的处理 runtime configuration, (运行时配置信息) 运行时的三种配置信息总结如下&#xff1a; a. /chosen节点中bootargs属性的值, 存入全局变量&#xff1a; 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); 函数调用过程: start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c mdesc &#61; setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c early_init_dt_scan_nodes(); // drivers/of/ftd.c /* Retrieve various information from the /chosen node */ of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); /* Initialize {size,address}-cells info */ of_scan_flat_dt(early_init_dt_scan_root, NULL); /* Setup memory, calling early_init_dt_add_memory_arch */ of_scan_flat_dt(early_init_dt_scan_memory, NULL); 5 五、内核对设备树中设备信息的处理 device population. (设备信息) 内核启动后&#xff0c;会将dtb中的所有节点转换为device_node&#xff0c;了解了device_node结构体和properties结构体&#xff0c;就可知道大致情况。 ➢每一个节点都转换为一个device_node结构体: struct device_node { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL" const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL" phandle phandle; const char *full_name; // 节点的名字, node-name[&#64;unit-address] struct fwnode_handle fwnode; struct property *properties; // 节点的属性 struct property *deadprops; /* removed properties */ struct device_node *parent; // 节点的父亲 struct device_node *child; // 节点的孩子(子节点) struct device_node *sibling; // 节点的兄弟(同级节点) #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif }; ➢device_node结构体中有properties, 用来表示该节点的属性,每一个属性对应一个property结构体: struct property { char *name; // 属性名字, 指向dtb文件中的字符串 int length; // 属性值的长度 void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储 struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif }; a. 在DTB文件中, 每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点, 每一个属性都以TAG(FDT_PROP, 0x00000003)开始 b. 这些device_node构成一棵树, 根节点为: of_root 跟踪下代码&#xff1a; 函数调用过程: start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c arm_memblock_init(mdesc); // arch/arm/kernel/setup.c early_init_fdt_reserve_self(); /* Reserve the dtb region */ // 把DTB所占区域保留下来, 即调用: memblock_reserve early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0); early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve unflatten_device_tree(); // arch/arm/kernel/setup.c __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c /* First pass, scan for size */ size &#61; unflatten_dt_nodes(blob, NULL, dad, NULL); /* Allocate memory for the expanded device tree */ mem &#61; dt_alloc(size &#43; 4, __alignof__(struct device_node)); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); populate_node np &#61; unflatten_dt_alloc(mem, sizeof(struct device_node) &#43; allocl, __alignof__(struct device_node)); np->full_name &#61; fn &#61; ((char *)np) &#43; sizeof(*np); populate_properties pp &#61; unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property)); pp->name &#61; (char *)pname; pp->length &#61; sz; pp->value &#61; (__be32 *)val; 对于驱动开发者&#xff0c;设备树中还有一个备受关注的信息&#xff1a;platform_device。 dts -> dtb -> device_node -> platform_device 在上述&#xff0c;我们已经知道dts中的节点信息终将被转换为 device_node ,但并不是所有device_node都会被转换成platform_device&#xff0c;需满足&#xff1a; 根节点下含有compatile属性的子节点 如果一个结点的compatile属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device 转换规则&#xff1a; platform_device中含有resource数组, 它来自device_node的reg, interrupts属性; platform_device.dev.of_node指向device_node, 可以通过它获得其他属性&#xff1b; 总结下&#xff1a; a. 内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device b. 并非所有的device_node都会转换为platform_device 只有以下的device_node会转换: b.1 该节点必须含有compatible属性 b.2 根节点的子节点(节点必须含有compatible属性) b.3 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性): 这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus" 示例: 比如以下的节点, /mytest会被转换为platform_device, 因为它兼容"simple-bus", 它的子节点/mytest/mytest&#64;0 也会被转换为platform_device /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver; /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。 类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver; /spi/flash&#64;0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。 / { mytest { compatile &#61; "mytest", "simple-bus"; mytest&#64;0 { compatile &#61; "mytest_0"; }; }; i2c { compatile &#61; "samsung,i2c"; at24c02 { compatile &#61; "at24c02"; }; }; spi { compatile &#61; "samsung,spi"; flash&#64;0 { compatible &#61; "winbond,w25q32dw"; spi-max-frequency &#61; <25000000>; reg &#61; <0>; }; }; }; 函数调用过程&#xff1a; a. of_platform_default_populate_init (drivers/of/platform.c) 被调用到过程: start_kernel // init/main.c rest_init(); pid &#61; kernel_thread(kernel_init, NULL, CLONE_FS); kernel_init kernel_init_freeable(); do_basic_setup(); do_initcalls(); for (level &#61; 0; level do_initcall_level(level); // 比如 do_initcall_level(3) for (fn &#61; initcall_levels[3]; fn do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数 b. of_platform_default_populate_init (drivers/of/platform.c) 生成platform_device的过程: of_platform_default_populate_init of_platform_default_populate(NULL, NULL, NULL); of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL) for_each_child_of_node(root, child) { rc &#61; of_platform_bus_create(child, matches, lookup, parent, true); // 调用过程看下面 dev &#61; of_device_alloc(np, bus_id, parent); // 根据device_node节点的属性设置platform_device的resource if (rc) { of_node_put(child); break; } } c. of_platform_bus_create(bus, matches, ...)的调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点): dev &#61; of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus节点的platform_device结构体 if (!dev || !of_match_node(matches, bus)) // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点 return 0; for_each_child_of_node(bus, child) { // 取出每一个子节点 pr_debug(" create child: %pOF\n", child); rc &#61; of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 处理它的子节点, of_platform_bus_create是一个递归调用 if (rc) { of_node_put(child); break; } } d. I2C总线节点的处理过程: /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver; platform_driver的probe函数中会调用i2c_add_numbered_adapter: i2c_add_numbered_adapter // drivers/i2c/i2c-core-base.c __i2c_add_numbered_adapter i2c_register_adapter of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c for_each_available_child_of_node(bus, node) { client &#61; of_i2c_register_device(adap, node); client &#61; i2c_new_device(adap, &info); // 设备树中的i2c子节点被转换为i2c_client } e. SPI总线节点的处理过程: /spi节点一般表示spi控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver; platform_driver的probe函数中会调用spi_register_master, 即spi_register_controller: spi_register_controller // drivers/spi/spi.c of_register_spi_devices // drivers/spi/spi.c for_each_available_child_of_node(ctlr->dev.of_node, nc) { spi &#61; of_register_spi_device(ctlr, nc); // 设备树中的spi子节点被转换为spi_device spi &#61; spi_alloc_device(ctlr); rc &#61; of_spi_parse_dt(ctlr, spi, nc); rc &#61; spi_add_device(spi); } 最后&#xff0c;便是platform_device跟platform_driver的匹配过程了&#xff0c;具体的过程分析可移步&#xff1a; 1. 驱动程序分层分离概念-总线设备驱动模型。 2. 字符设备驱动-总线设备驱动模型写法。