精华内容
下载资源
问答
  • 一开接触设备树,我就不知道设备树与驱动的关系,设备树是在内核3.0以后才有的。不过3.0以前的和3.0以后的驱动其实变化不大。 驱动的开发方法可以分为三种:传统方法,总线方法,设备树方法。 这篇文章围绕点亮一...

    一开接触设备树,我就不知道设备树与驱动的关系,设备树是在内核3.0以后才有的。不过3.0以前的和3.0以后的驱动其实变化不大。

        驱动的开发方法可以分为三种:传统方法,总线方法,设备树方法。

        这篇文章围绕点亮一颗LED来说明这三种方法。

        驱动流程分为5个部分:1、分配  2、设置  3、注册file_operation  4、入口  5、出口

    一、传统方法

        传统方法就是简单粗暴方法,就是将IO设置,驱动放在同一个文件(drv.c)。

    drv.c:定义引脚信息:

    drv.c:驱动中的open函数:

    drv.c:驱动中的close函数:

    drv.c:file_operations结构体:

    为上层应用提供open(),write(),read(),close()

     

    drv.c:入口函数-申请设备号,创建设备节点:

    其中register_chrdev():申请设备号。

           class_create():创建一个设备类。

           device_create():创建一个设备节点,路径:/dev/led

     

    drv.c:出口函数-注销设备,注销设备节点:

    其中unregister_chrdev():通过主设备号,注销设备号

           device_destroy():注销设备节点

           class_destroy():注销设备类

     

    传统方法的优点:操作简单

                      缺点:不易扩展,每次修改需要重新编译驱动。

    二、总线方法

    在kernel 3.0之前都是采用总线的方法实现驱动与设备之间的联系。把驱动跟设备分开。

    其主要理解几个结构体:

    ①跟设备有关的结构体:struct platform_device{};

    ②跟驱动有关的结构体:struct platform_driver{};

        还要理解其中包含的两个结构体:struct device_driver{};      struct platform_device_id{};

    要记住一句话:在match的时候:优先匹配id_table的name,不匹配。再匹配driver的name

    其中

    dev.c:进行设备的引脚定义

    dev.c:注册platform_device结构体

    dev.c:入口函数-注册设备

    dev.c:出口函数-注销设备

    对于驱动来说,需要稍微修改一下:

    drv.c:增加probe函数和remove函数,其中函数platform_get_resource()就是过去设备中资源,比如引脚定义。

    drv.c :入口函数和出口函数的修改

    platform总线中的match()函数是设备与驱动匹配的函数,我们看看这个函数的实现,就是匹配设备与驱动的名字一不一致。函数如下:

    总线方法的优点:易扩展

                      缺点:代码冗余多,需要重新编译设备

    三、设备树方法

    在kernel 3.0以及之后的版本,都是采用设备树的方法实现驱动与设备之间的联系。将设备改为设备树实现,解决了总线方法中代码冗余多的问题。

    设备树方法只需要在总线方法的基础上稍微修改一下。

    dts:添加设备节点:

    drv: probe修改,其中通过函数of_property_read_s32()获取设备树的资源。

    总线方法的优点:易扩展,不需要重新编译(替换设备树),无冗余代码
     

    展开全文
  • 一开接触设备树,我就不知道设备树与驱动的关系,设备树是在内核3.0以后才有的。不过3.0以前的和3.0以后的驱动其实变化不大。     驱动的开发方法可以分为三种:传统方法,总线方法,设备树方法。 &...

        一开接触设备树,我就不知道设备树与驱动的关系,设备树是在内核3.0以后才有的。不过3.0以前的和3.0以后的驱动其实变化不大。

        驱动的开发方法可以分为三种:传统方法总线方法设备树方法

        这篇文章围绕点亮一颗LED来说明这三种方法。

        驱动流程分为5个部分:1、分配  2、设置  3、注册file_operation  4、入口  5、出口

    一、传统方法

        传统方法就是简单粗暴方法,就是将IO设置,驱动放在同一个文件(drv.c)。

    drv.c:定义引脚信息:

    drv.c:驱动中的open函数:

    drv.c:驱动中的close函数:

    drv.c:file_operations结构体:

    为上层应用提供open(),write(),read(),close()

     

    drv.c:入口函数-申请设备号,创建设备节点:

    其中register_chrdev():申请设备号。

           class_create():创建一个设备类。

           device_create():创建一个设备节点,路径:/dev/led

     

    drv.c:出口函数-注销设备,注销设备节点:

    其中unregister_chrdev():通过主设备号,注销设备号

           device_destroy():注销设备节点

           class_destroy():注销设备类

     

    传统方法的优点:操作简单

                      缺点:不易扩展,每次修改需要重新编译驱动。

    二、总线方法

    在kernel 3.0之前都是采用总线的方法实现驱动与设备之间的联系。把驱动跟设备分开。

    其主要理解几个结构体:

    ①跟设备有关的结构体:struct platform_device{};

    ②跟驱动有关的结构体:struct platform_driver{};

        还要理解其中包含的两个结构体:struct device_driver{};      struct platform_device_id{};

    要记住一句话:在match的时候:优先匹配id_table的name,不匹配。再匹配driver的name

    其中

    dev.c:进行设备的引脚定义

    dev.c:注册platform_device结构体

    dev.c:入口函数-注册设备

    dev.c:出口函数-注销设备

    对于驱动来说,需要稍微修改一下:

    drv.c:增加probe函数和remove函数,其中函数platform_get_resource()就是过去设备中资源,比如引脚定义。

    drv.c :入口函数和出口函数的修改

    platform总线中的match()函数是设备与驱动匹配的函数,我们看看这个函数的实现,就是匹配设备与驱动的名字一不一致。函数如下:

    总线方法的优点:易扩展

                      缺点:代码冗余多,需要重新编译设备

    三、设备树方法

    在kernel 3.0以及之后的版本,都是采用设备树的方法实现驱动与设备之间的联系。将设备改为设备树实现,解决了总线方法中代码冗余多的问题。

    设备树方法只需要在总线方法的基础上稍微修改一下。

    dts:添加设备节点:

    drv: probe修改,其中通过函数of_property_read_s32()获取设备树的资源。

    总线方法的优点:易扩展,不需要重新编译(替换设备树)无冗余代码

                      缺点:稍微复杂

    展开全文
  • 1 设备树的说明在写完嵌入式驱动总结后,对于设备树相关语法和使用一直都想进行系统描述,但是因为最近比较忙碌,所以一直拖到现在才完成初版,对于整个嵌入式Linux驱动开发中,设备树语法和构建是其中比较核心...

    1 设备树的说明

    在写完嵌入式驱动总结后,对于设备树相关的语法和使用一直都想进行系统的描述,但是因为最近比较忙碌,所以一直拖到现在才完成初版,对于整个嵌入式Linux驱动开发中,设备树语法和构建是其中比较核心的部分,是需要比较系统的去学习掌握的。本文参考设备树说明文档<Devicetree Specification Release v0.2>, 在结合日常驱动开发中积累的经验,总结完成的一篇设备树语法的说明。对于驱动的编写来说,设备树语法的了解自然必不可少,但大多数情况下我们模仿厂商的实现,在结合芯片手册就可以增加自己需要的功能,不过如何来添加设备节点,保证添加的有效性,这时候就需要掌握理和解设备树语法。随着设备树逐渐成为嵌入式驱动开发中的主流,并逐渐取代寄存器的访问方式,设备树对于驱动开发越来越重要,另外如果在本章了解中对于设备树中有疑惑不理解的部分,这很正常,可以先大概浏览下,做到心中有着概念,在带着疑问去学习驱动的内容,当你困惑的时候,可以回来系统的理解设备树的语法(不理解不要纠结),不要在缺少积累的时候去钻牛角尖,这也是嵌入式学习的重要经验。

    1.1 设备树综述

    对SOC构造和嵌入式硬件有了解的话,芯片一般由内核(Cortex-A), 以及通过系统总线(AHB, AXI等)挂载的GPIO,I2C,SPI,PWM,Ethernet等外设模块构成。而对于具体的外设模块,如I2C外设,又支持访问多个器件来满足不同功能的需求。而设备树,就是基于系统总线作为主干,将芯片SOC和各类外设以及外部器件用数据结构的形式组合起来描述硬件结构的文件,是对硬件模型的抽象,总结来说,设备树就是对硬件结构的抽象描述。

    e5c9089db1245a711cc93237036b26a6.png

    上面就是比较常见的基于SOC构建的嵌入式应用系统,包含芯片和外围的设备,虽然总线可能不指一条,外设模块的设备连接情况也会更加复杂,但都没有树结构模型,而设备树也是按照如此模型进行设计的,理解这一点,也可以更深刻的知道设备树的实现思路。

    在对嵌入式整个硬件框架有一定了解后,下面还要理解几个名词。

    DTS 设备树的源码文件,用于描述设备硬件情况的抽象

    DTSI 和C语言的#include类似,也是描述设备树的源码文件,另外DTS同样被#include包含

    DTB 基于DTS源码编译的二进制文件,用于内核调用读取设备树信息的文件。

    DTC 用于编译DTS到DTB的工具,由内核编译时使用make dtbs编译设备树二进制文件过程中生成。

    基于以上信息,我们理解DTS/DTSI是基于DTS语法实现的设备描述文件,DTB则是用于内核解析,需要下载的文件,下面正式开始设备树语法的讲解。

    1.2 设备树语法

    在上一小节,我们将设备树的概念有基本的认知,下面更重要的就是DTS语法了,这里我么结合实际的代码区理解设备树语法。

    1.2.1 #include语法

    DTS中#include语法和C语言中类似,支持将包裹的文件直接放置在#include位置从而访问到其它文件的数据,如官方设备树内使用的

    #include <dt-bindings/input/input.h>
    #include "imx6ull.dtsi"
    

    另外,也可以用来包含dts文件,如下

    #include "imx6ull-14x14-evk.dts"

    1.2.2 节点描述

    对于设备树来说,都是有根节点开始,在添加不同的设备节点描述的,以比较简单的LED设备树为例:

    /{  //根节点
        //......
        led { //节点名(子节点) <name> 
           compatible = "gpio-led";  //节点属性
           pinctrl-names = "default";
           pinctrl-0 = <&pinctrl_gpio_leds>;
           led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
           status = "okay";
        };
     
        gpio_keys: gpio_keys@0 {  //节点名(子节点) <label>:<name>[@<unit_address>]          
          compatible = "gpio-keys";  //节点属性
          pinctrl-names = "default";
          pinctrl-0 = <&pinctrl_gpio_keys>;
          #address-cells = <1>;
          #size-cells = <0>;
          autorepeat;
     
          key1@1 {   //节点名(子节点) <name>[@<unit_address>] 
             label = "USER-KEY1"; //节点属性 key-value键值对
             linux,code = <114>;
             gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
             gpio-key,wakeup;
        };
      };
    }

    1. 设备树文件都由根节点开始,每个设备只有一个根节点(如果包含多个文件,根节点则会合并),其它所有设备都作为子节点存在,由节点名和一组节点属性构成。

    2. 节点属性都是有key-value的键值对来描述,并以 ; 结束

    3. 节点间可以嵌套形成父子关系,这样可以方便描述设备间的关系

    4. 节点名支持<name>[@<unit_address>]的格式,其中后面的unit_address可选,一般为设备地址,这是为了用于保证节点是唯一的标识,当然用其它数字也可以。

    同时,节点名也支持<label>:<name>[@<unit_address>]的格式, 这里的label就是节点的标签别名,我们可以&<label>来直接访问节点。如对于gpio_keys: gpio_keys@0 可以通过&gpio_keys来访问clock@ gpio_keys@0,后面我们就将用到这个说明。

    5. 在设备树中查找节点需要完整的节点路径,对于项目来说,直接修改官方的dts文件是不推荐的,如果自己建立路径,又过于复杂,因此设备树提供通过标注引用<label>的方式,允许我们在其它文件中修改已存在的节点,或者添加新的节点,对于节点的合并原理,包含以下原则:

    a. 不同的属性信息进行合并

    b. 相同的属性信息进行覆写

    基于这种原则,我们可以通过如下的代码,在已有节点添加更新新的数据,如使用如下代码在gpio_keys: gpio_keys@0中增加节点。

    &gpio_key{
       key2@2{
          label="usr-key2”;
          //.....
        }
    }

    上面就是节点相关的信息,下面就开始深入节点内部,讲述节点内部如何基于属性来定义设备的说明。

    6. 在驱动中可以同/<node-1>/<node-2>/.../<node-n>的方式访问到指定设备节点

    如上面的访问key1@1节点即为

    /gpio_keys@0/key1@1

    方式即可访问到指定key1@1节点

    1.2.3 节点属性

    在上一章我们理解了设备树的节点间关系,并讲述了如何添加节点或修改已经存在节点的方法,进一步我们就要抽丝剥茧,讲述属性的说明。

    在我们上一章节中,讲到属性是key-value的键值对,这就分两部分讲解设备树的说明。

    1.2.3.1 常见value类型

    其中value中常见的几种数据形式如下:

    1. 空类型

    ranges;

    空类型,仅需要键值,用来表示真假类型, 或者值可选的类型

    2. 字符串<string>

    compatible = "simple-bus";

    这里”simple-blus”就是属性中对应的字符串值。

    3. 字符串表<stringlist>

    compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";

    值也可以为字符串列表,中间用,号隔开,这样既可以支持多个字符串的匹配

    4. 无符号整型<u32>/<u64>

    无符号的整型数值

    offset = <0x38>;

    5. 可编码数组<prop-encoded-array>

    支持编码的多无符号整数的数组,如reg可以通过

    #address-cells指定地址单元的数目,#size-cells指定长度单元的数目

    reg = <0x020ac000 0x4000>;

    可以通过&<label>的方式,即可引用其它节点的数据,用于后续的处理

    clocks = <&clks IMX6UL_CLK_PLL3_USB_OTG>;

    1.2.3.2 常用key属性

    1. compatible

    <stringlist>字符串列表类型

    compatible属性是值是由特定编程模型的一个或多个字符串组成,用于将驱动和设备连接起来,我们在驱动中也是通过compatible来选择设备树中指定的硬件,是非常重要的属性。compatible的格式一般为:

    “[<manufacturer>,]<model>”

    compatible = "arm,cortex-a7";
    compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
    compatible = "gpio-led";

    在该模型中,

    Manufacturer表示厂商,可选

    Model表示指定型号,一般和模块对应的驱动名称一致(当然不一致也不影响实际功能)

    在驱动中使用platform_driver中of_match_table里即可使用.compatible用来匹配该对应设备节点,另外匹配时严格的字符串匹配的,所以驱动内的匹配值和设备树中的value要保持一致。

    spidev: icm20608@0 {
        compatible = "alientek,icm20608";
        spi-max-frequency = <8000000>;
        reg = <0>;
    };
     
    /* 设备树匹配列表 */
    static const struct of_device_id icm20608_of_match[] = {
        { .compatible = "alientek,icm20608" },
        { /* Sentinel */ }
    };
     
    /* SPI驱动结构体 */  
    static struct spi_driver icm20608_driver = {
        .probe = icm20608_probe,
        .remove = icm20608_remove,
        .driver = {
                .owner = THIS_MODULE,
                .name = SPI_ICM_NAME,
                .of_match_table = icm20608_of_match, 
               },
    };

    参考上述结构,即可看到通过of_math_table指定设备树匹配列表,找到指定的节点去访问。

    2. model

    <string>字符串类型

    指定设备商信息和模块的具体信息,也有用于模块功能说明,和compatible类似,但仅支持单个字符串模式。

    model = "Freescale i.MX6 ULL 14x14 EVK Board";

    3. status

    <string>字符串类型

    指示设备的运行状态,目前支持的状态列表如下:

    37d880b7344de5b9257a5c58c87d102f.png

    4. #address-cell和#size-cell

    <u32>无符号整型

    #address-cells和#size-cells可以用在任何拥有子节点的设备中,用于描述子节点中”reg”对应属性内部值的信息,其中

    #address-cells 用来描述字节点中”reg”对应属性中描述地址列表中cell数目

    #size-cells 用来描述字节点中”reg”对应属性中描述长度列表中cell数目

    #address-cells和#size-cells属性不是从devicetree的祖先继承的。它们需要明确定义,如果未定义,对于设备树则默认按照地址cell为2个,长度cell为1个去解析reg的值。

    soc { 
       #address-cells = <1>; 
       #size-cells = <1>; 
       serial 
       { 
         compatible = "ns16550"; 
         reg = <0x4600 0x100>; 
         clock-frequency = <0>; 
         interrupts = <0xA 0x8>; 
         interrupt-parent = <&ipic>; 
        }; 
    };

    如这里reg就要被解析为address-1位,值为0x4600, size-1位,值为0x100。

    5. reg

    <pro-encode-array> 可编码数组类型

    由任意长度的地址和长度构成,描述设备在父设备地址空间中的总线范围,通过#address-cells和#size-cells变量去解析,另外如果#size-cells的长度位0,则reg中后面关于长度的部分应该去除,reg的举例如下:

    #address-cells = <1>;          //指定address的范围长度
    #size-cells = <0>;             //指定size的范围长度
    
    ethphy0: ethernet-phy@0 {
        compatible = "ethernet-phy-ieee802.3-c22";
        reg = <0>;                 //实际reg对应的寄存器地址和范围
    };

    6. ranges

    <empty>或者<child-bus-address, parent-bus-address, length>类型

    ranges 非空时是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度

    这三部分组成。

    child-bus-address:子总线地址空间的物理地址

    parent-bus-address:父总线地址空间的物理地址

    length:子地址空间的长度

    如果 ranges 属性值为空值,说明不需要进行地址转换。

    7. name和device_type

    <string>字符串类型

    分别表示节点名称或者设备树类型属性,这两个属性在新版本中已经被废弃,这里就不再讨论,可以通过《Devicetree Specifification Release v0.2》的2.3章节查看。

    除上述的标准属性外,设备树也支持其它属性如

    clock-frequency    //指定模块的时钟频率
    label              //指定可读的标签,用于开发者查看的属性
    current-speed:     //串口的波特率

    等,此外也支持自定义的属性键值对来实现符合自己驱动应用的需求。

    1.3 设备树在驱动中的应用

    上面描述的基本都是设备树语法的部分,不过对于驱动来说,如何从设备树中提取有效的设备信息,从而在驱动中脱离对硬件寄存器的直接访问,如何把设备树用于嵌入式驱动开发中,这部分内容也相当重要,对于嵌入式Linux设备,语法树是在/sys/firmware/devicetree下,可使用

    ls /sys/firmware/devicetree/base/

    来查看当前根节点下的设备树文件,如下:

    79ef572730bfa08100ca7d36589d7665.png

    对于驱动来说,可以使用内核提供访问设备树的函数用于匹配节点的接口来访问设备树。

    1.1.1 内核设备树访问函数

    内核访问设备树的函数主要包含获取节点的函数和获取节点内部属性的函数,这些函数都定义在内核include/linux/of.h中

    //根据节点路径获取设备节点信息
    struct device_node *of_find_node_by_path(const char *path)
    struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
    //根据设备属性获取设备节点信息
    struct device_node *of_find_node_by_name(struct device_node *from, const char *name)
    struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
    struct device_node *of_find_compatible_node(struct device_node *from, 
                              const char *type, const char *compat)
    //根据匹配的of_device_id表格获取node节点(在框架中常用的匹配方式)
    static inline struct device_node *of_find_matching_node_and_match(
                              struct device_node *from,
                              const struct of_device_id *matches,
                              const struct of_device_id **match);
    

    根据这些信息,我们就可以实现如下代码,找到设备树内的指定节点,代码如下:

    /*获取设备节点*/
    nd = of_find_node_by_path("/usr_gpios/beep");
    if(nd == NULL){
       return -EINVAL;
    }
    else{
       printk(KERN_INFO"node find by path okn");
    }
     
    nd = of_find_compatible_node(NULL, NULL, "gpio-beep");
    if(nd == NULL){
        return -EINVAL;
    }
    else{
       printk(KERN_INFO"beep node find by compatible okn");
    }

    在获取设备节点后,我们可以通过内核提供的接口对节点内的key-value键值对进一步读取,具体接口如下:

    //提取通用属性的接口
    struct property *of_find_property(const struct device_node *np, 
                               const char *name, int *lenp);
    int of_property_read_u32_index(const struct device_node *np, 
                               const char *propname, u32 index, u32 *out_value);
    int of_property_read_string(struct device_node *np, 
                               const char *propname, const char **out_string);
    //用于获取硬件信息的接口
    int of_get_named_gpio(struct device_node *np,
                               const char *propname, int index);
    int of_get_gpio(struct device_node *np, int index);

    在了解这些代码后,就可以实现如下代码访问设备树内的参数属性,具体如下

    proper = of_find_property(nd, "name", NULL);
    if(proper != NULL)
        printk(KERN_INFO"%s:%sn", proper->name, (char *)proper->value);
    ret = of_property_read_string(nd, "status", &pStr);
    if(pStr != NULL)
        printk(KERN_INFO"status:%sn", pStr);

    而在部分框架中,也对上述接口进一步封装,如platform_device_driver中需要提供的of_device_id就是更进一步的调用接口,通过

    static const struct of_device_id key_of_match[] = {
        { .compatible = "usr-gpios" },
        { /* Sentinel */ }
    };

    结构,也能实现对设备树的匹配,这在很多驱动框架中都是十分常用的,需要在实践中总结理解。

    1.4 总结

    至此,对设备树的语法进行了比较全面的讲解,当然这里面还有很多不完善的地方,如对中断控制器和中断相关的语法目前尚未说明,另外很多部分的理解受水平限制有遗漏或者错误的地方,如果有发现,请及时反馈。当然在实际驱动开发中,熟悉这些知识还是不够的,日常打交道还有很多是芯片厂商或者方案商定义的具有特定功能的自定义属性键值对,这就需要长期的积累了。不过理解了设备树语法的原理,反过来去理解这些自定义属性,是清晰明了的。这篇文章只能算是对设备树语法的入门指引,如果希望深入去掌握嵌入式驱动开发,还是配合着实际产品的硬件框架,在实际任务的维护或者修改设备树,再结合参考资料中提到的文档和本文的说明,带着目的去学习,才是高效且快速的方式。

    参考资料:

    1. <Devicetree Specification Release v0.2>

    2. imx6ull设备树文件

    展开全文
  • 引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。...

    inux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。比如在ARM Linux内,一个.dts(device tree source)文件对应一个ARM的machine,一般放置在内核的"arch/arm/boot/dts/"目录内,比如exynos4412参考板的板级设备树文件就是"arch/arm/boot/dts/exynos4412-origen.dts"。这个文件可以通过$make dtbs命令编译成二进制的.dtb文件供内核驱动使用。

    基于同样的软件分层设计的思想,由于一个SoC可能对应多个machine,如果每个machine的设备树都写成一个完全独立的.dts文件,那么势必相当一些.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的.dtsi文件。这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件, 这样就是整个设备树的管理更加有序。我这里用`Linux4.8.5源码自带的dm9000网卡为例来分析设备树的使用和移植。这个网卡的设备树节点信息在"Documentation/devicetree/bindings/net/davicom-dm9000.txt"有详细说明,其网卡驱动源码是"drivers/net/ethernet/davicom/dm9000.c"

    b3cd03f9b6868a35f4549325097a4cdb.png

    设备树框架

    设备树用树状结构描述设备信息,它有以下几种特性

    1. 每个设备树文件都有一个根节点,每个设备都是一个节点。
    2. 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
    3. 每个设备的属性都用一组key-value对(键值对)来描述。
    4. 每个属性的描述用;结束

    所以,一个设备树的基本框架可以写成下面这个样子

    /{                                  //根节点   
    node1{                          //node1是节点名,是/的子节点      
      key=value;                  //node1的属性       
      ...        node2{                      //node2是node1的子节点            
        key=value;              //node2的属性           
        ...        
      }   
      }                               //node1的描述到此为止   
      node3{       
        key=value;       
        ...   
      }
      }

    节点名

    理论个节点名只要是长度不超过31个字符的ASCII字符串即可,此外Linux内核还约定设备名应写成形如<name>[@<unit_address>]的形式,其中name就是设备名,unit_address就是设备地址,如果有应该写上,下面就是典型节点名的写法

    dc0920a7d09dc7774c117a8b1f796257.png

    Linux中的设备树还包括几个特殊的节点,比如chosen,chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,比如bootloader传递内核启动参数给内核

    1ffef17e2e7afb87bff4d62219af3858.png

    引用

    当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。这样就可以实现类似函数调用的效果。编译设备树的时候,相同的节点的不同属性信息都会被合并到设备节点中,而相同的属性会被覆盖,使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。

    aa46b10702316efa16e2c36701cc23a0.png

    下面的例子中就是直接引用了dtsi中的一个节点,并向其中添加/修改新的属性信息

    3e8ace059990b0c2b9a24a6796434c1a.png

    KEY

    在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的"compatible"这个属性查找设备节点。Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr""gpio""clock""power""regulator" 等等。

    compatible

    设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。dm9000驱动中就是使用下面这个函数通过设备节点中的"compatible"属性提取相应的信息,所以二者的字符串需要严格匹配。

    bde588d64f561792b3142d68312e3a9f.png

    86b1e5e654dda92a8b472f714205ea6e.png

    address

    (几乎)所有的设备都需要与CPU的IO口相连,所以其IO端口信息就需要在设备节点节点中说明。常用的属性有

    • #address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量
    • #size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量

    有了这两个属性,子节点中的"reg"就可以描述一块连续的地址区域。下例中,父节点中指定了"#address-cells = <2>" "#size-cells = <1>",则子节点dev-bootscs0中的reg中的前两个数表示一个地址,最后的0x4表示地址跨度是0x4

    a9171a7886433c3c2b3f19be64964f81.png

    interrupts

    一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断号。常用的属性有

    • interrupt-controller 一个空属性用来声明这个node接收中断信号
    • #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>
    • interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
    • interrupts,一个中断标识符列表,表示每一个中断输出信号

    这里,在我板子上的dm9000的的设备节点中,"interrupt-parent"使用了exynos4x12-pinctrl.dtsi(被板级设备树的exynos4412.dtsi包含)中的gpx0节点的引用,而在gpx0节点中,指定了"#interrupt-cells = <2>;",所以在dm9000中的属性"interrupts = <6 4>;"表示指定gpx0中的属性"interrupts"中的"<0 22 0>",通过查阅exynos4412的手册知道,对应的中断号是EINT[6]。

    4f4ef2d5180e76d5524c3d9b611be26b.png

    61422bb9f0e442bcec01c64359e6d9f3.png

    gpio

    gpio也是最常见的IO口,常用的属性有

    • "gpio-controller",用来说明该节点描述的是一个gpio控制器
    • "#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 属性 = <&引用GPIO节点别名 GPIO标号 工作模式>

    d224a30a2a60d9de581e2cd177b9c5cb.png

    驱动自定义key

    针对具体的设备,有部分属性很难做到通用,需要驱动自己定义好,通过内核的属性提取解析函数进行值的获取,比如dm9000节点中的下面这句就是自定义的节点属性,用以表示配置EEPROM不可用。

    13eaeb730805550825e3f0dd0b9b4128.png

    VALUE

    dts描述一个键的值有多种方式,当然,一个键也可以没有值

    字符串信息

    7f61bf779c4dd5ce8b2df1b3362cca14.png

    32bit无符号整型数组信息

    1e0faa3cd76cbe8ae8d3dc8c00876c0f.png

    二进制数数组

    51909b48a3f55a7f94c8648dd4c90130.png

    字符串哈希表

    f71b9146b5e1e2ee75dadbdc891c240a.png

    混合形式

    上述几种的混合形式

    设备树/驱动移植

    设备树就是为驱动服务的,配置好设备树之后还需要配置相应的驱动才能检测配置是否正确。比如dm9000网卡,就需要首先将示例信息挂接到我们的板级设备树上,并根据芯片手册和电路原理图将相应的属性进行配置,再配置相应的驱动。需要注意的是,dm9000的地址线一般是接在片选线上的,我这里用的exynos4412,接在了bank1,所以是"<0x50000000 0x2 0x50000004 0x2>"最终的配置结果是:

    d9fb4c29fc6e6b980bb0249420b53db4.png

    勾选相应的选项将dm9000的驱动编译进内核。

    make menuconfig
    [*] Networking support  --->  
     Networking options  --->     
      <*> Packet socket   
     <*>Unix domain sockets        
     [*] TCP/IP networking        
     [*]   IP: kernel level autoconfiguration
      Device Drivers  --->    
      [*] Network device support  --->        
        [*]   Ethernet driver support (NEW)  --->            
          <*>   DM9000 supportFile systems  --->    
            [*] Network File Systems (NEW)  --->        
              <*>   NFS client support        
              [*]     NFS client support for NFS version 3        
               [*]       NFS client support for the NFSv3 ACL protocol extension        
               [*]   Root file system on NFS

    执行make uImage;make dtbs,tftp下载,成功加载nfs根文件系统并进入系统,表示网卡移植成功

    468cec70a95af0e903aebf6e094db776.png

    b9a43e98942ec35fbee7a8fa79ed84cc.png

    另外还有一些关于c++ Linux后台服务器开发的一些知识点分享:Linux,Nginx,MySQL,Redis,P2P,K8S,Docker,TCP/IP,协程,DPDK,webrtc,音视频等等视频。

    喜欢的朋友可以后台私信【1】获取学习视频

    6254018c2b8269d7735f57b23d19c8b1.png
    展开全文
  • 整理自:百问网+正点原子前言之前分享笔记:【Linux笔记】总线设备驱动模型中在...什么是设备树设备树简单理解就是描述设备信息(资源)一棵树。设备树(Device Tree)用代码体现如下:这些代码被保存在....
  • 在底层硬件基础上,操作系统覆盖一层驱动,屏蔽底层硬件操作,通过特定软件接口去操作底层硬件,用户在用户空间可以很容易把软件设计目标放在策略需求上,可以很方便屏蔽掉底层实现,从而很好完成客户...
  • 在“Step by Step为HPS添加UART外设”章节,我们讲解了如何使用SoC EDS软件为创建好包含HPSQsys系统添加UART外设并生成相应的设备树(dts)文件。在“基于Linux应用程序HPS配置FPGA”章节,我们也提到了使用开发...
  • 设备树源文件扩展名为.dts,但是我们在移植 Linux 时候却一直在使用.dtb 文件,那么 DTS 和DTB 这两个文件是什么关系呢?DTS 是设备树源码文件,DTB 是将DTS 编译以后得到二进制文件,Linux 内核和 uboot 只能 ...
  • 2020年12月02日 11:26内核驱动不仅可以将驱动编译到内核中,还可以动态编译内核驱动。本文档介绍如何以模块方式编译内核驱动。 要动态编译内核,首先需要将内核源码编译通过,内核编译请参考使用手册第五章...
  • 本篇文章主要介绍CCF子系统下驱动程序开发,本章涉及如下三个方面内容:一、clk注册注销接口说明二、clk驱动开发流程三、clk使用流程四、依据clk provider用途封装注册接口一、clk注册注销接口说明 clk...
  • 前面我们已经完成了CCF子系统分析,也说明了如何实现CCF驱动,本章为该专栏最后一篇文章, 本章我们将实现一个虚拟gpio clk gate驱动。本章大概分为如下几个章节:一、 本次驱动开发涉及知识点二、clk ...
  • 在之前分析I2C模块时候,我们知道I2C驱动模块抽象出i2c adapter、i2c driver、i2c device三个部分,分别对应于i2c控制器、i2c驱动、i2c设备。而spi模块也是抽象出了spi master、spi device、spi driver三个模块...
  • 本章的内容大概分为如下几部分:一、实现虚拟中断控制器的可行性说明二、中断控制器驱动的开发流程三、虚拟中断控制器驱动设计说明一、实现虚拟中断控制器的可行性说明在进行虚拟中断控制器的设计之前,我们先说明下...
  • Linux中I2C驱动框架分析I2C核心(i2c_core)I2C核心维护了i2c_bus结构体,提供了I2C总线驱动和设备驱动的注册、注销方法,维护了I2C总线的驱动、设备链表,实现了设备驱动的匹配探测。此部分代码由Linux内核提供。I2...
  • 在前面两章我们分析了gpio子系统内容,主要包括gpio子系统内部架构,以及gpio子系统其他内核子系统关联等内容,主要主要介绍如何实现一个gpio控制器驱动。一般来说,gpio控制器驱动一般都是soc厂商实现,...
  • uboot的设备驱动的

    2019-02-10 22:18:45
    dm_init_and_scan中通过平台的方式或者设备树的方式,绑定设备与驱动之间的关系;   dm_scan_platdata 函数是搜索使用宏U_BOOT_DEVICE定义的设备进行驱动匹配,也就是bind子节点   dm_scan_fdt 对设备树中的...
  • smdk2440是依次后者包含前者的关系,也只有到了最后的开发板级别,我们才能在此基础上面做出具体的项目产品,想一想,我们只有一个arm核、一个soc芯片可以做什么?答案是什么也做不了,因为它不满足一个微型计算机...
  • 内核设备树简介-1

    2017-11-17 19:50:22
    引入设备树之后,驱动代码只负责处理驱动的逻辑,关于设备的具体信息存放到设备树文件中。 如果只是发生硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者是需要修改设备树文件信息,不需要改写驱动代码。 一:...
  • 参考资料:<devicetree-...设备树中有一颗存在中断树,它描述了该硬件平台中中断连接层级关系 中断源中断控制器物理连接是用interrupt-parent这个属性来描述,如果某个中断源没有这个属性,那么
  • 以cpsw为例学习linux设备树

    千次阅读 2015-09-17 19:08:55
    对于设备数语法网上已经有很多了,但是懂了语法之后可能还是不太理解具体设备树工作原理,这里以tiCPSW为例说明设备树与内核的驱动之家微妙关系。 cpsw就是ti公司Common Platform Ethernet Switch ...
  • Linux存储IO栈(3)-- 设备驱动模型

    千次阅读 2016-09-07 17:11:02
    * 使用统一机制来表达设备与驱动之间的关系,规范设备驱动的编写,核心代码复用。 * 将系统中的设备结构组织,并且通过sysfs将其呈现在用户空间——包括所有的总线和内部连接。 * 支持设备的热拔插机制。 * ...
  • 转载于 : ...对于设备数语法网上已经有很多了,但是懂了语法之后可能还是不太理解具体设备树工作原理,这里以tiCPSW为例说明设备树与内核的驱动之家微妙关系
  • 转载于 : ...对于设备数语法网上已经有很多了,但是懂了语法之后可能还是不太理解具体设备树工作原理,这里以tiCPSW为例说明设备树与内核的驱动之家微妙关系。 cpsw就...
  • 1、模块、外部编译2、字符设备框架(函数接口和结构体的关系)3、字符设备框架、platform框架4、设备树、led驱动、蜂鸣器驱动5、内核中断子系统,按键驱动,中断上下半部。6、adc驱动,内核的IO模型(阻塞、非阻塞、...
  • 对于设备数语法网上已经有很多了,但是懂了语法之后可能还是不太理解具体设备树工作原理,这里以tiCPSW为例说明设备树与内核的驱动之家微妙关系。 cpsw就是ti公司Common Platform Ethernet Switch 缩写...
  • 对于设备数语法网上已经有很多了,但是懂了语法之后可能还是不太理解具体设备树工作原理,这里以tiCPSW为例说明设备树与内核的驱动之家微妙关系。cpsw就是ti公司Common Platform Ethernet Switch 缩写,...
  • 本文转载自:http://blog.sina.com.cn/s/blog_4ba5b45e0102e6vp.html ... 最近在学习设备驱动程序,编写完之后进行编译之前,要在Kconfig和Makefile文件里面添加一些内容,参考了 其他源码,发现源码目录下
  • 关于设备驱动和设备管理,Linux主要有四种内核成分 设备类型:在所有Unix系统中为了统一普通设备操作所采用分类。... sysfs: 表示系统中设备树的一个文件系统。 1.设备类型

空空如也

空空如也

1 2 3 4 5
收藏数 86
精华内容 34
关键字:

设备树与驱动的关系