• 【Linux驱动开发】设备树详解(三)设备树Kernel解析



    活动地址:CSDN21天学习挑战赛

    【Linux驱动开发】设备树详解(一)设备树基础介绍
    【Linux驱动开发】设备树详解(二)设备树语法详解
    【Linux驱动开发】设备树详解(三)设备树Kernel解析

     

    img
    个人主页:董哥聊技术
    我是董哥,嵌入式领域新星创作者
    创作理念:专注分享高质量嵌入式文章,让大家读有所得!
    img

    5、设备树的加载流程

    我们知道,dts文件经过dtc工具编译为dtb,内核加载并解析dtb文件,最终获得设备树的信息。

    那么Linux如何加载``dtb文件,并生成对应节点的呢?

     

    5.1 设备树地址设置

    我们一般通过Bootloader引导启动Kernel,在启动Kernel之前,Bootloader必须将dtb文件的首地址传输给Kernel,以供使用。

    1. Bootloaderdtb二进制文件的起始地址写入r2寄存器中
    2. Kernel在第一个启动文件head.S/head-common.S中,读取r2寄存器中的值,获取dtb文件起始地址
    3. 跳转入口函数start_kernel执行C语言代码

     

    5.2 获取设备树中的平台信息——machine_desc

    dts文件中,在根节点中有一个compatible属性,该属性的值是一系列的字符串,比如compatible = “samsung,smdk2440”“samsung,smdk2410,samsung,smdk24xx”;,该属性就是告诉内核要选择什么样的machine_desc,因为machine_desc结构体中有一个dt_compat成员,该成员表示machine_desc支持哪些单板,所以内核会把compatible中的字符串与dt_compat进行依次比较。

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

     

    5.3 获取设备树的配置信息

    在前面,我们也知道设备树中的chosen属性,用于传输固件和Linux之间的数据,包含一些启动参数,那么我们该如何解析出来呢?

    1. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
    2. 确定根节点的这2个属性的值: #address-cells, #size-cells
    3. 存入全局变量: dt_root_addr_cells, dt_root_size_cells
    4. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);

     

    5.4 设备树节点解析

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

    每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点,
    每一个属性都以TAG(FDT_PROP, 0x00000003)开始

     

    • 设备树中的每一个节点,都会被转换为device_node结构体
     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
            };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

     

    • device_node转换为platform_device

    那么多的device_node,哪些会被转化为platform_device呢?

    1. 根节点下的子节点,且该子节点必须包含compatible属性;
    2. 如果一个节点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一,那么它的子结点(需含compatile属性)也可以转换为platform_device。
    struct platform_device {
    	const char	*name;
    	int		id;
    	bool		id_auto;
    	struct device	dev;
    	u32		num_resources;
    	struct resource	*resource;
    
    	const struct platform_device_id	*id_entry;
    	char *driver_override; /* Driver name to force a match */
    
    	/* MFD cell pointer */
    	struct mfd_cell *mfd_cell;
    
    	/* arch specific additions */
    	struct pdev_archdata	archdata;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    转换完成之后,

    • 设备树中的reg/irq等属性,都存放在了platform_device->resource结构体中
    • 设备树中的其他属性,都存在在了platform_device.dev->of_node结构体中

     

    • C代码获取设备树属性

    转换完成之后,内核提供了一些API来直接获取设备树中对应的属性。如:

    • of_property_read_u32_index:获取设备树中某个属性的值
    • of_property_read_string:获取设备树中某个属性的字符串的值
    • of_get_address:获取设备树中的某个节点的地址信息

    整体总结下来,有几个类别

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

     

    上述总结下来,流程为dts->dtb->device_node->platform_device

    image-20220805090415993

     

    6、设备树调试

    • 查看原始的dtb文件
    ls /sys/firmware/fdt 
    hexdump -C /sys/firmware/fdt
    
    • 1
    • 2

     

    • 查看设备树信息
    ls /sys/firmware/devicetree
    ls /proc/device-tree
    
    • 1
    • 2

    以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件

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

     

    • 查看所有硬件信息
    ls /sys/devices/platform 
    
    • 1

    系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的。

     

    7、 参考地址

    [1]:https://elinux.org/Device_Tree_Usage

    [2]:https://www.kernel.org/doc/Documentation/devicetree/usage-model.txt

    [3]:https://blog.csdn.net/zj82448191/article/details/109195364

    ​ 

    img
  • 相关阅读:
    2023年【北京市安全员-B证】考试试卷及北京市安全员-B证模拟考试题
    【C#项目实战】控制台游戏 勇士斗恶龙(2)——游戏场景的设置以及玩家战斗逻辑
    【C语言】C的编译过程&&预处理
    [Kogel.Subscribe.Mssql]SQL Server增量订阅,数据库变更监听
    ORB-SLAM2 | Prometheus_px4 | OpenCV 3.4.9
    软件测试(三)测试分类
    基于图像识别的自动驾驶汽车障碍物检测与避障算法研究
    力扣45-跳跃游戏2——贪心算法&顺藤摸瓜&顺瓜摸藤
    计算机网络总结
    SpringBoot多数据源以及事务处理
  • 原文地址:https://blog.csdn.net/dong__ge/article/details/126214540