zoukankan      html  css  js  c++  java
  • Linux 设备树详解【转】

    转自:http://www.pianshen.com/article/428276673/;jsessionid=D90FC6B215155680E0B89A6D060892D4

    本文基于天嵌E9V3开发板,详解设备树的规则和用法。

    一、基本概念

    DTS即Device Tree Source,是一个文本形式的文件,用于描述硬件信息,包括CPU的数量和类别、内存基地址和大小、中断控制器、总线和桥、外设、时钟和GPIO控制器等。
    DTB即Device Tree Blob,是一个二进制形式的文件,由linux内核识别,为其中的设备匹配合适的驱动程序。
    DTC即Device Tree Compiler,将适合人类阅读和编辑的DTS文件编译成适合机器处理的DTB文件。
    编译内核的时候会同时使用DTC 将DTS编译成DTB,天嵌E9V3使用的DTS文件e9v3-sabresd.dts位于/arch/arm/boot/dts目录下。
    在这里插入图片描述
    如上图所示,bootloader读取dtb文件放入RAM中,并将存放地址告诉linux内核,内核启动以后从该地址读取相应的设备信息,匹配平台和设备驱动。

    二、E9V3设备树总览

    linux中的一个dts文件对应一个machine, 不同的machine可能使用相同的SOC,只是对外设的使用不同,这些不同的dts文件势必包含很多相同的内容,为了简化,可以把公用的部分提炼为dtsi文件。
    e9v3-sabresd.dts包含dtsi的结构如下:
    在这里插入图片描述

    列出各个文件中的节点,如下图所示,是不是有点像有很多分支的树?
    在这里插入图片描述

    三、设备树编写规则

    Device Tree的编写规则可参考文档<<devicetree-specification-v0.2.pdf>>, 以下简称spec,下载链接为:
    https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.2

    设备树由一个一个的节点组成,每个设备树有且仅有一个根节点,节点可以包含子节点。

    1、节点名称
    基本的节点名格式如下:
    node-name@unit-address
    其中node-name由字母、数字和一些特殊字符构成的字符串,长度不超过31个字符,可自定义,但为了可读性,spec中规定了一些约定成熟的名称,比如cpus, memory, bus,clock等。
    unit-address为节点的地址,通常为寄存器的首地址,比如imx6q datasheet中uart1的寄存器地址范围为0202_0000~0202_3FFF,在定义uart1节点时,对应的unit-address为0202_0000:
    uart1: serial@02020000 {

    }
    有些节点没有对应的寄存器,则unit-address可省略,节点名只由node-name组成,比如cpus:
    cpus {

    }
    根节点的名称比较特殊,由一个斜杠组成:
    /{

    }

    2、label标签

    三、设备与驱动的匹配

    linux内核启动以后,先解析并注册dts中的设备,然后再注册驱动,比较驱动中的compatible 属性和设备中的compatible 属性,或者比较两者的name属性,如果一致则匹配成功。
    1、解析dtb
    在start_kernel() --> setup_arch(0 --> unflatten_device_tree() --> __unflatten_device_tree()函数中扫描dtb,并转换成节点是device_node的树状结构。
    注:代码基于linux4.1.15内核(下同)

    static void __unflatten_device_tree()
    {
        ...
    	/* First pass, scan for size */
    	start = 0;
    	size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
    	size = ALIGN(size, 4);
        ...
    	/* Second pass, do actual unflattening */
    	start = 0;
    	unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
       ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. 注册dts设备

    imx6q_init_machine() --> of_platform_populate()。
    在of_platform_populate()中循环扫描根节点下的各节点:

    int of_platform_populate()
    {
        ...
    	for_each_child_of_node(root, child) {
    		rc = of_platform_bus_create(child, matches, lookup, parent, true);
         }
         ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    static int of_platform_bus_create()
    {
        ...
    	/* Make sure it has a compatible property */
    	if (strict && (!of_get_property(bus, "compatible", NULL))) {
    		pr_debug("%s() - skipping %s, no compatible prop
    ",
    			 __func__, bus->full_name);
    		return 0;
    	}
    
    	auxdata = of_dev_lookup(lookup, bus);
    	if (auxdata) {
    		bus_id = auxdata->name;
    		platform_data = auxdata->platform_data;
    	}
        ...
    	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    	if (!dev || !of_match_node(matches, bus))
    		return 0;
        如果节点有子节点,则递归调用of_platform_bus_create()扫描节点的子节点:
    	for_each_child_of_node(bus, child) {
    		pr_debug("   create child: %s
    ", child->full_name);
    		rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
    		if (rc) {
    			of_node_put(child);
    			break;
    		}
    	}
    	of_node_set_flag(bus, OF_POPULATED_BUS);
    	return rc;
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    最终调用of_platform_device_create_pdata() —> of_device_add() 注册设备并添加到对应的链表中。

    3、注册驱动
    Linux注册驱动的函数为driver_register(),或者其包装函数如platform_driver_register(),而driver_register()或者其包装函数一般在驱动的初始化函数xxx_init()中调用。
    驱动初始化函数xxx_init()被调用的路劲为:
    start_kernel() --> rest_init() --> Kernel_init() --> kernel_init_freeable() --> do_basic_setup() --> do_initcalls:
    在这里插入图片描述

    简而言之,在start_kernel()中调用driver_register()注册驱动程序。

    4、匹配设备
    追踪driver_register()函数,driver_register() --> bus_add_driver() --> driver_attach() --> __driver_attach:

    static int __driver_attach(struct device *dev, void *data)
    {
    	struct device_driver *drv = data;
    	if (!driver_match_device(drv, dev))
    		return 0;
    
    	if (dev->parent)	/* Needed for USB */
    		device_lock(dev->parent);
    	device_lock(dev);
    	if (!dev->driver)
    		driver_probe_device(drv, dev);
    	device_unlock(dev);
    	if (dev->parent)
    		device_unlock(dev->parent);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    driver_match_device()中寻找匹配的设备,如果匹配成功则执行驱动的probe函数。
    driver_match_device()最终会调用平台的匹配函数platform_match():

    static int platform_match(struct device *dev, struct device_driver *drv)
    {
    	struct platform_device *pdev = to_platform_device(dev);
    	struct platform_driver *pdrv = to_platform_driver(drv);
    
    	/* When driver_override is set, only bind to the matching driver */
    	if (pdev->driver_override)
    		return !strcmp(pdev->driver_override, drv->name);
    
    	/* Attempt an OF style match first */
    	if (of_driver_match_device(dev, drv))
    		return 1;
    
    	/* Then try ACPI style match */
    	if (acpi_driver_match_device(dev, drv))
    		return 1;
    
    	/* Then try to match against the id table */
    	if (pdrv->id_table)
    		return platform_match_id(pdrv->id_table, pdev) != NULL;
    
    	/* fall-back to driver name match */
    	return (strcmp(pdev->name, drv->name) == 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    从代码中可以看出, platform_match()会采用多种方法进行匹配:

    1. of_driver_match_device将根据驱动程序of_match_table中的compatible属性,与设备中的compatible属性进行比对。
    2. 其次调用acpi_driver_match_device()进行匹配。
    3. 如果前2种方法都没有匹配的,最后比对设备和驱动的name字符串是否一致。

    以GPIO-key为例,设备和驱动匹配示意图如下:
    在这里插入图片描述

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/ethercat_i7/article/details/83786670
  • 相关阅读:
    Linux Apache服务(一)
    mysql基础
    linux面试题一
    linux shell(2)
    linux基础汇总2
    python列表生成式、生成器,迭代器与二分法
    python的函数
    python函数
    java 翁老师学习笔记-持续更
    责任链模式
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/11755702.html
Copyright © 2011-2022 走看看