设备树学习(三)(内核对设备树的处理,重要部分)

it2024-01-25  76

前言:这篇文章主要分析内核对设备树的处理,即怎么生成了最后的设备节点。

**Linux uses DT data for three major purposes:

platform identification, 平台识别信息runtime configuration, and 运行时配置信息device population. 设备信息**

一、head.S对设备树文件的简单处理

bootloader启动内核时,会设置r0,r1,r2三个寄存器, r0一般设置为0; r1一般设置为machine id (在使用设备树时该参数没有被使用);(machine id会在内核中用来选择匹配的machine_desc,匹配原则是id与machine_desc.nb相等); r2一般设置ATAGS或DTB的开始地址

内核第一个文件head.S/head-common.S会进行如下操作: 把bootloader传来的r1值, 赋给了C变量: __machine_arch_type 把bootloader传来的r2值, 赋给了C变量: __atags_pointer // dtb首地址 最后会调用C函数start_kernel

二、对设备树中平台信息的处理(选择machine_desc)

我们知道当不使用设备树的时候,uboot会使用r1寄存器传递machine id,然后内核会把该ID与内核中的所有的machine_desc.nb匹配,但是如果使用设备树,id就不会被使用,这时内核怎么选择machine_desc呢? 在设备树文件中,在根节点中有一个compatible属性,该属性的值是一系列的字符串,比如compatible = “samsung,smdk2440”“samsung,smdk2410,samsung,smdk24xx”;该属性就是告诉内核要选择什么样的machine_desc,因为machine_desc结构体中有一个dt_compat成员,该成员表示machine_desc支持哪些单板,所以内核会把compatible中的字符串与dt_compat进行比较,但是compatible中是一系列的字符串,所以匹配是有最优解的,即所有的machine_desc中的dt_compat与compatible进行比较后,匹配的compatible中的字符串越靠前的dt_compat所对应的machine_desc即为最优解。下面给出图示: 下面是代码的实现部分,主要是比较的过程,遍历所有的machine_desc,然后作比较,得到最优解:

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 early_init_dt_verify(phys_to_virt(dt_phys) // 判断是否有效的dtb, drivers/of/ftd.c initial_boot_params = params; mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/ftd.c while ((data = get_next_compat(&compat))) { score = of_flat_dt_match(dt_root, compat); if (score > 0 && score < best_score) { best_data = data; best_score = score; } } machine_desc = mdesc; arch_get_next_mach是每调用一次,返回下一个machine_desc,至于该函数为什么能实现,是因为所有的machine_desc有一个段属性值,在编译的时候,一样的段属性会放到一块。如下图,所以就能在arch_info_begin 到arch_info_end中找到所有的machine。如下图:

三、对设备树运行时配置信息的处理

设备树我们知道只是起一个设备信息的传输作用,所以内核会将设备树中的信息保存在变量中,储存起来。

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);

四、dtb文件转化为device_node

dtb文件会在内存中一直存在着,不会被内核或者应用程序占用,我们需要使用的时候可以直接使用dtb文件。dtb文件的内容会被解析生成多个device_node,然后这些device_node构成一棵树, 根节点为: of_root

1.上一章我们学习了dtb文件的格式,dtb文件中有如下规则:

每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点, 每一个属性都以TAG(FDT_PROP, 0x00000003)开始 所以可以根据这个规则解析dtb文件,生成device_node结构体。但是内核如何去解析文件,这里不是重点,我们需要知道的是dts文件中的所有的内容保存在device_node结构体中的什么成员中。

2.每一个节点都转换为一个device_node结构体 这里给出两个结构体,根据这两个结构体,就能大概看出来dts文件中的内容与结构体成员的对应关系了。

struct device_node { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL" const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL" phandle phandle; const char *full_name; // 节点的名字, node-name[@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 };

上面结构体已经能很清楚的知道每个成员代表着dts文件中的什么属性了,这里我们有一个现成的例子,dts文件如下,我们画出该dts文件解析后生成的device_node;

/ { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; memory { /* /memory */ device_type = "memory"; reg = <0x30000000 0x4000000 0 4096>; }; /* cpus { cpu { compatible = "arm,arm926ej-s"; }; }; */ chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; }; led { compatible = "jz2440_led"; pin = <S3C2410_GPF(5)>; }; };

图片太大,建议放大查看。

五、device_node转化为platform_device

首先,这里有两个问题: 1 .那么多的device_node,哪些会被转化为platform_device呢?

根节点下的子节点,且该子节点必须包含compatible属性;如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device。

2.spi,i2c等节点怎么处理? spi或者i2c控制器的节点会被转化为platform_device,但是这些节点下的子节点则由相应的总线驱动决定生成什么。 下图是一个简单的例子,来描述i2c,spi及根节点下的子节点及特殊节点的子节点会被怎么处理: 解释了上面两个问题之后,我们来说一下device_node最后转化为platform_device的什么部分。 我们知道设备树中的属性会有reg/irq等,就会转化为platform_device结构体中的resource【】数组,然后其他的部分就全部在platform_device.dev.of_node结构体成员中,想要得到文件中的任何信息,就从该结构体成员获得。

六、platform_device跟platform_driver的匹配

匹配其实之前我总结在了第一章中,但是当时只总结了三种办法,漏掉了优先级最高的,这里我们把它加上,优先级排序为 0.1.2.3

七、内核中设备树的操作函数

include/linux/目录下有很多of开头的头文件: a. 处理DTB of_fdt.h // dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)

b. 处理device_node of.h // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数) of_address.h // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值) of_match_device(从matches数组中取出与当前设备最匹配的一项) of_dma.h // 设备树中DMA相关属性的函数 of_gpio.h // GPIO相关的函数 of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息 of_iommu.h // 很少用到 of_irq.h // 中断相关的函数 of_mdio.h // MDIO (Ethernet PHY) API of_net.h // OF helpers for network devices. of_pci.h // PCI相关函数 of_pdt.h // 很少用到 of_reserved_mem.h // reserved_mem的相关函数

c. 处理 platform_device of_platform.h // 把device_node转换为platform_device时用到的函数, // 比如of_device_alloc(根据device_node分配设置platform_device), // of_find_device_by_node (根据device_node查找到platform_device), // of_platform_bus_probe (处理device_node及它的子节点) of_device.h // 设备相关的函数, 比如 of_match_device

八、在根文件系统中查看设备树(有助于调试)

a. /sys/firmware/fdt // 原始dtb文件

hexdump -C /sys/firmware/fdt

b. /sys/firmware/devicetree // 以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件

c. /sys/devices/platform // 系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的 对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性

d. /proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base

最新回复(0)