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

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

**Linux uses DT data for three major purposes:

  1. platform identification, 平台识别信息
  2. runtime configuration, and 运行时配置信息
  3. 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值