【U-Boot驱动模型DM】

U-Boot驱动模型DM


1.DM简介

DM即driver module,也就是驱动模型。

原始需求

在2014年以前,uboot没有一种类似于linux kernel的设备驱动模型,随着uboot支持的设备越来越多,其一直受到如下问题困扰:

  • 设备初始化流程都独立实现,而且为了集成到系统,需要修改核心公共代码(如init_sequence)
  • 很多子系统只允许一个驱动,比如无法同时支持USB2.0和USB3.0
  • 子系统间的交互实现各异,开发难度大
  • 没有个统一的设备视图(如linux的/sys)

为了解决以上问题,Uboot借鉴了Linux的驱动模型架构,引入了Uboot的驱动模型(driver model :DM)。它的设计目标包括:

  • 提供统一设备驱动框架,降低设备驱动的开发复杂度
  • 提供设备树视图
  • 支持设备组
  • 支持设备lazy init
  • 支持设备驱动沙盒测试
  • 较小的系统开销(内存和CPU)

使用目的

1)提高代码的可重用性。,驱动和设备分离,同一类驱动代码可在不同平台上运行,避免重复造轮子。

2) 这种驱动模型为驱动的定义和访问接口提供了统一的方法。

3) 提高了驱动之间的兼容性以及访问的标准型。

基本概念

首先我们先区分两个概念,设备和驱动,也就是device和driver。device持有物理设备的资源信息,而driver实现对设备的管理。
以RTC芯片为例,硬件拓扑如下:

在这里插入图片描述

I2C控制器控制I2C总线,RTC连在I2C总线上。

RTC对应的device持有RTC的资源信息,包括I2C地址,位宽,这些信息要么是接口相关的,要么是跟硬件设计相关的;而RTC对应的driver则负责对RTC的管理,如将从RTC中读出来的时间值解析为用户可以识别的时间值,以及将时间编码后写入RTC芯片,driver和device结合起来才能实现RTC芯片的访问和管理。

其实RTC的资源信息不止I2C地址和寄存器位宽,还有寄存器定义,那么寄存器定义是否也需要放入device中呢?其实不必,当然放进去也可以。

其实硬件设备的资源信息是否要放入device中有一个关键的原则,如果该资源信息影响driver的通用性,就把它放到device中。

拿RTC来说,RTC I2C地址是跟硬件设计相关的,不同的硬件设计下I2C地址会不一样,这个信息就需要放在device中,那么针对不同的硬件设备平台,就只需要修改device中的信息即可,而不需要修改driver。寄存器位宽和寄存器定义都是跟硬件设计无关的,完全是RTC自身的参数,所以可以不放在device中,直接在driver中编码即可。其实对于哪些硬件资源信息需要放入device,并没有统一的规则,主要还是看需求而定。

把device和driver区分开以后,就可以正式开撸DM模型了,DM模型有几个关键的概念,把这几个关键概念搞明白就基本了解了DM模型,这几个概念分别是:uclass,device定义 ,device和driver绑定,设备层级关系。

2. DM 模型整体架构

2.1 U-Boot DM 的4要素

像 Linux Kernel 中的驱动三要素 device 、bus 、driver 一样,U-Boot DM 也有自己的三要素:udevice、uclass、driver。

1 udevice

是一个设备实例,对应linux中的device,描述某一个硬件设备所持有的资源。

udevice定义方式有三种:

  • 通过硬编码的方式

    硬编码的方式就是在代码中调用U_BOOT_DEVICE宏来定义设备资源,实际上是一个设备实例

  • 设备树的方式

    设备树的方式就是将设备描述信息写在对应的DTS文件中,DTS文件被编译成DTB文件,然后跟uboot 二进制合并在一起,uboot启动的时候,会解析DTB文件,将所有的设备描述信息解析出来,以设备树的方式定义设备资源信息是目前比较流行的方案。

  • 传参的方式

    参数的方式就是通过命令行或者接口将设备资源信息传递进来,非常灵活。

2 driver

某个udevice的driver(这个设备匹配的驱动)。定义了该设备的操作方法,向上提供操作接口。

3 uclass

是同一类设备(udevice)的抽象,提供管理同一类设备的抽象接口uclass_driver。Uclass可以看作抽象的udevice。

4 uclass_driver

uclass_driver是uclass(该抽象udevice)的驱动程序, 主要提供这一类udevices标准的操作接口,如绑定、激活、移除等。

uclass是DM模型的精华所在,uclass代表着一个类,同一类设备属于同一个uclass,拥有相同的uclass ID。还是拿RTC来说事,市面上RTC芯片很多,由不同的厂家生产,其内存寄存器定义甚至访问接口都不一样,所以RTC的driver肯定是不一样的,但是从功能的角度来说,他们都是用来记录时间的,所他们都属于rtc-class。
uclass从层级结构来讲,起到非常好的承上启下的作用,它既能屏蔽具体设备个体间的差异性,向用户提供统一的接口,又能为同一类的设备定义统一的处理函数,具体的设备驱动只需要实现这些处理函数即可,从而简化的设备驱动的开发。
从设备的角度来看,同一类的设备拥有相同的uclass ID,并全部挂在该uclass下。从驱动的角度来看,uclass driver实现通用的处理逻辑。

2.2 相互关系

下面举例gpio DM驱动模型来介绍它们之间的联系,如下:
在这里插入图片描述

1.udevice和driver的绑定

通过U_BOOT_DEVICE或者设备树定义的设备资源udevice,实际上代表着一个设备实例,该设备实例必须找到对应的driver才能实现设备管理,为设备实例查找driver的过程其实就是device和driver的绑定过程。那么device和driver如何才能绑定在一起呢?

如果是通过U_BOOT_DEVICE定义的设备实例,通过name来进行匹配具有相同名字的driver,如果是通过设备树定义的设备实例,则需要通过compatible来匹配compatible相同的driver。

一个device和driver匹配后,就会创建对应的struct udevice结构体,它会同时指向设备资源和driver,这样设备资源和driver就绑定在一起了。
如果是通过命令行或接口将设备资源信息传递进来的话,一般会指定对应的driver跟该设备实例绑定。如sf probe 0:1 100000命令,将总线号,片选,时钟通过命令行传递进去,内部处理逻辑会指定spi_flash_std驱动与该设备实例匹配。

2.udevice找到对应的uclass

udevice找到对应的uclass的方式主要是通过:udevice对应的driver的id和uclass对应的uclass_driver的id是否匹配。

3. 总体绑定流程

uclass和udevice都是动态生成的。

  • 在解析设备树中的设备或直接定义的平台设备的时候,会动态生成udevice。
  • 然后找到udevice对应的driver,通过driver中的uclass id得到uclass_driver id。
  • 从uclass链表中查找对应的uclass是否已经生成,没有生成的话则动态生成uclass。

3.DM四个主要组成部分详细介绍

后续以数据结构、如何定义、存放位置、如何获取四个部分进行说明。

3.1 uclass id

每一种uclass都有自己对应的ID号。定义于其uclass_driver中。其附属的udevice的driver中的uclass id必须与其一致。

所有uclass id定义于include/dm/uclass-id.h中.

列出部分id如下:

1.enum uclass_id {  
2.    /* These are used internally by driver model */  
3.    UCLASS_ROOT = 0,  
4.    UCLASS_DEMO,  
5.    UCLASS_TEST,  
6.    UCLASS_TEST_FDT,  
7.    UCLASS_TEST_FDT_MANUAL,  
8.    UCLASS_TEST_BUS,  
9.    UCLASS_TEST_PROBE,  
10.   UCLASS_TEST_DUMMY,  
11.        ......  
12.}  

3.2 uclass

  1. 数据结构

    1.struct uclass {  
    2.    void *priv;  // uclass的私有数据指针  
    3.    struct uclass_driver *uc_drv; // 对应的uclass driver  
    4.    struct list_head dev_head; // 链表头,连接所属的所有udevice  
    5.    struct list_head sibling_node; // 链表节点,用于把uclass连接到uclass_root链表上
    6.}; 
    
  2. 如何定义

    uclass是uboot自动生成。并且不是所有uclass都会生成,有对应uclass driver并且有被udevice匹配到的uclass才会生成。

    具体参考后面的uboot DM初始化一节。或者参考uclass_add实现。

  3. 存放位置

    所有生成的uclass都会被挂载gd->uclass_root链表上。

  4. 如何获取、API

    直接遍历链表gd->uclass_root链表并且根据uclass id来获取到相应的uclass。

    具体uclass_get->uclass_find实现了这个功能。

    1.int uclass_get(enum uclass_id id, struct uclass **ucp)  
    2.{  
    3.    struct uclass *uc;  
    4.  
    5.    /* Immediately fail if driver model is not set up */  
    6.    if (!gd->uclass_root)  
    7.        return -EDEADLK;  
    8.    *ucp = NULL;  
    9.    uc = uclass_find(id);  
    10.    if (!uc) {  
    11.        if (CONFIG_IS_ENABLED(OF_PLATDATA_INST))  
    12.            return -ENOENT;  
    13.        return uclass_add(id, ucp);  
    14.    }  
    15.    *ucp = uc;  
    16.  
    17.    return 0;  
    18.}  
    

3.3 uclass_driver

  1. 数据结构

    定义在include/dm/uclass.h

    1.struct uclass_driver {  
    2.    const char *name; // 该uclass_driver的名称  
    3.    enum uclass_id id; // 对应的uclass id  
    4./* 以下函数指针主要是调用时机的区别 */  
    5.    int (*post_bind)(struct udevice *dev); 
        // 在udevice被绑定到该uclass之后调用(bind相关函数指针在device_bind_common函数中调用)  
    6.    int (*pre_unbind)(struct udevice *dev);
        // 在udevice被解绑出该uclass之前调用 (在device_unbind函数中调用) 
    7.    int (*pre_probe)(struct udevice *dev); 
        // 在该uclass的一个udevice进行probe之前调用 (probe相关函数指针在device_probe函数中调用) 
    8.    int (*post_probe)(struct udevice *dev);
        // 在该uclass的一个udevice进行probe之后调用  
    9.    int (*pre_remove)(struct udevice *dev);
        // 在该uclass的一个udevice进行remove之前调用  (在device_remove函数中调用)
    10.    int (*child_post_bind)(struct udevice *dev); 
        // 在该uclass的一个udevice的一个子设备被绑定到该udevice之后调用  
    11.    int (*child_pre_probe)(struct udevice *dev); 
        // 在该uclass的一个udevice的一个子设备进行probe之前调用  
    12.    int (*child_post_probe)(struct udevice *dev);
        // 在该uclass的一个udevice的一个子设备进行probe之后调用  
    13.    int (*init)(struct uclass *class); // 安装该uclass的时候调用(在uclass_add中调用)  
    14.    int (*destroy)(struct uclass *class); // 销毁该uclass的时候调用  
    15.    int priv_auto;// 如果非零,它就是"uclass->priv"指针中分配的uclass私有数据的大小。  
    16.    int per_device_auto; 
        // uclass拥有的私有数据,保存在'dev->uclass_priv_'。如果该值是非零,将自动分配该值大小的空间。
    17.    int per_device_plat_auto;  	
        //uclass拥有的平台数据,保存在'dev->uclass_plat_'。如果该值是非零,将自动分配该值大小的空间
    18.    int per_child_auto;   
        //子设备(在这个uclass中一个parent的child)可以保存的parent私有数据。地址为'dev->parent_priv_'
    19.    int per_child_plat_auto;  
        // 子设备可以保存的parent平台数据。如果非零,将分配到子设备的'dev->parent_plat_'指针中。
    20.    uint32_t flags; // 这个uclass的标志
    21.};  
    
  2. 如何定义

    通过UCLASS_DRIVER宏来定义uclass_driver.

    以serial-uclass为例:

    1.UCLASS_DRIVER(serial) = {  
    2.    .id     = UCLASS_SERIAL,  
    3.    .name       = "serial",  
    4.    .flags      = DM_UC_FLAG_SEQ_ALIAS,  
    5.    .post_probe = serial_post_probe,  
    6.    .pre_remove = serial_pre_remove,  
    7.    .per_device_auto    = sizeof(struct serial_dev_priv),  
    8.};  
    

    UCLASS_DRIVER实现如下:

    #define UCLASS_DRIVER(__name)                       \  
        ll_entry_declare(struct uclass_driver, __name, uclass)  
       
    #define ll_entry_declare(_type, _name, _list)               \  
        _type _u_boot_list_2_##_list##_2_##_name __aligned(4)       \  
                __attribute__((unused,              \  
                   section(".u_boot_list_2_"#_list"_2_"#_name)))  
    

    最终得到一个如下结构体:

    1.struct uclass_driver  _u_boot_list_2_uclass_2_serial __aligned(4) \  
    2.__attribute__((unused,  section(".u_boot_list_2_uclass_2_serial"))) = {    
    3.    .id        = UCLASS_SERIAL,   // 设置对应的uclass id    
    4.    .name        = "serial",    
    5.    .flags        = DM_UC_FLAG_SEQ_ALIAS,       
    6.    .post_probe    = serial_post_probe,    
    7.    .pre_remove    = serial_pre_remove,    
    8.    .per_device_auto = sizeof(struct serial_dev_priv),    
    9.}   
    
  3. 存放位置

    通过上述,我们知道serial的uclass_driver结构体_u_boot_list_2_uclass_2_serial被存放到.u_boot_list_2_uclass_2_serial段中(section段内容)。通过查看u-boot.map得到如下

    1..u_boot_list_2_uclass_1  
    2.                0x23e368e0        0x0 drivers/built-in.o  
    3..u_boot_list_2_uclass_2_gpio  
    4.                0x23e368e0       0x48 drivers/gpio/built-in.o  
    5.                0x23e368e0             _u_boot_list_2_uclass_2_gpio  // gpio uclass driver的符号  
    6..u_boot_list_2_uclass_2_root  
    7.                0x23e36928       0x48 drivers/built-in.o  
    8.                0x23e36928              _u_boot_list_2_uclass_2_root // root uclass drvier的符号  
    9..u_boot_list_2_uclass_2_serial  
    10.                0x23e36970       0x48 drivers/serial/built-in.o  
    11.                0x23e36970          _u_boot_list_2_uclass_2_serial // serial uclass driver的符号  
    12..u_boot_list_2_uclass_2_simple_bus  
    13.                0x23e369b8       0x48 drivers/built-in.o  
    14.                0x23e369b8                _u_boot_list_2_uclass_2_simple_bus  
    15..u_boot_list_2_uclass_3  
    16.                0x23e36a00        0x0 drivers/built-in.o  
    17.                0x23e36a00                . = ALIGN (0x4)  
    

    最终,所有uclass driver结构体以列表的形式被放在.u_boot_list_2_uclass_1和.u_boot_list_2_uclass_3的区间中。
    这个列表简称uclass_driver table。

  4. 如何获取、API

    想要获取uclass_driver需要先获取uclass_driver table。

    (1) 可以通过以下宏来获取uclass_driver table

    1. struct uclass_driver *uclass =  
    2.    ll_entry_start(struct uclass_driver, uclass_driver); // 会根据.u_boot_list_2_uclass_driver _1的段地址来得到uclass_driver table的首地址  
    3. const int n_ents = ll_entry_count(struct uclass_driver, uclass_driver);//获取.u_boot_list_2_uclass_driver _3的段地址,减去.u_boot_list_2_uclass_driver _1的段地址得到uclass_driver table的长度  
    
    

    (2) 接着通过遍历这个uclass_driver table,得到相应的uclass_driver。

    有如下API(从driver table中获取指定id的uclass_driver):

    1.struct uclass_driver *lists_uclass_lookup(enum uclass_id id)    
    2.{  
    3.    struct uclass_driver *uclass =  
    4.        ll_entry_start(struct uclass_driver, uclass_driver);  
    5.    const int n_ents = ll_entry_count(struct uclass_driver, uclass_driver);  
    6.    struct uclass_driver *entry;  
    7.   
    8.    for (entry = uclass; entry != uclass + n_ents; entry++) {  
    9.        if (entry->id == id)  
    10.            return entry;  
    11.    }  
    12.   
    13.    return NULL;  
    14.}  
    
    

3.3 udevice

  1. 数据结构

    定义在include/dm/device.h

    1.struct udevice {  
    2.    const struct driver *driver;  //此设备使用的驱动程序  
    3.    const char *name;   //设备名称,通常为 FDT 节点名称  
    4.    void *plat_;     //此设备的配置数据  
    5.    void *parent_plat_;   //提供给父设备使用的配置数据  
    6.    void *uclass_plat_;    //提供给所属uclass使用的配置数据  
    7.    ulong driver_data;    //驱动程序数据  
    8.    struct udevice *parent; // 父设备  
    9.    void *priv_;  // 私有数据的指针  
    10.    struct uclass *uclass; // 所属uclass  
    11.    void *uclass_priv_;  // 提供给所属uclass使用的私有数据指针  
    12.    void *parent_priv_;   // 提供给其父设备使用的私有数据指针  
    13.    struct list_head uclass_node;  // 用于连接到其所属uclass的链表上  
    14.    struct list_head child_head;   // 链表头,连接其子设备  
    15.    struct list_head sibling_node;  //设备列表中的下一个设备  
    16.#if !CONFIG_IS_ENABLED(OF_PLATDATA_RT)  
    17.    u32 flags_;    //设备标识  
    18.#endif  
    19.    int seq_;  
    20.#if CONFIG_IS_ENABLED(OF_REAL)  
    21.    ofnode node_;  
    22.#endif  
    23.#if CONFIG_IS_ENABLED(DEVRES)  
    24.    struct list_head devres_head;  
    25.#endif  
    26.#if CONFIG_IS_ENABLED(DM_DMA)  
    27.    ulong dma_offset;  
    28.#endif  
    29.};  
    
    
  2. 如何定义

    (1) 通过宏U_BOOT_DRVINFO静态定义

    (2) 在dtb存在的情况下,由uboot解析dtb后动态生成,后续在“uboot DM的初始化”一节中具体说明。

  3. 存放位置

    • 连接到对应uclass中

      也就是会连接到uclass->dev_head中。

    • 连接到父设备的子设备链表中

      也就是会连接到udevice->child_head中,并且最终的根设备是gd->dm_root这个根设备。

  4. 如何获取、API

    从uclass中获取udevice。遍历uclass->dev_head,获取对应的udevice。有如下API

    1.int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // 通过索引从uclass中获取udevice  
    2.int uclass_get_device_by_name(enum uclass_id id, const char *name, struct udevice **devp);  // 通过设备名从uclass中获取udevice  
    3.int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp); // 通过序列从uclass中获取udevice  
    4.int uclass_get_device_by_of_offset(enum uclass_id id, int node, struct udevice **devp);  //通过设备偏移量从uclass中获取udevice  
    5.int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node,struct udevice **devp);  //通过设备树节点从uclass中获取udevice  
    6.int uclass_get_device_by_driver(enum uclass_id id, const struct driver *drv, struct udevice **devp);  //通过驱动从uclass中获取udevice  
    7.int uclass_first_device(enum uclass_id id, struct udevice **devp);  //获取uclass下的第一个设备  
    8.int uclass_next_device(struct udevice **devp);  //获取uclass中的下一个udevice  
    
    
    

    这些相关的API(uclass.c),主要作用就是根据uclass_id,查找对应的uclass,然后根据索引值或者名称,来查找到对应的udevice,之后调用device_probe函数激活设备。

3.4 driver

和uclass_driver方式是相似的。

  1. 数据结构

    include/dm/device.h

    1.struct driver {  
    2.    char *name; // device名称  
    3.    enum uclass_id id; // 标记driver属于哪个uclass的id  
    4.    const struct udevice_id *of_match; // 要匹配的compatible字符串列表,以及每个字符串的标识数据。  
    5.    int (*bind)(struct udevice *dev); // 绑定device到它的driver时被调用(在device_bind_common函数中调用)
    6.    int (*probe)(struct udevice *dev); // 探测一个device时被调用,例如:激活它(在device_probe函数中调用)
    7.    int (*remove)(struct udevice *dev); // 移除一个device时被调用,例如:停用它(在device_remove函数中调用)
    8.    int (*unbind)(struct udevice *dev); // 解除device和driver绑定时被调用(在device_unbind函数中调用)  
    9.    int (*of_to_plat)(struct udevice *dev); // 在probe设备之前调用,解码device tree数据(在device_probe函数中调用)
    10.    int (*child_post_bind)(struct udevice *dev); // 在目标设备的一个子设备被绑定之后,调用(在device_bind_common函数中调用)  
    11.    int (*child_pre_probe)(struct udevice *dev); // 在目标设备的一个子设备被probe之前,调用(在device_probe函数中调用)  
    12.    int (*child_post_remove)(struct udevice *dev); // 在目标设备的一个子设备被remove之后,调用(在device_remove函数中调用)  
    13.    int priv_auto; // 如果非零,它就是"udevice->priv"指针中分配的私有数据的大小。如果为0,则driver负责分配所需私有数据的空间。  
    14.    int plat_auto; // 如果非零,它就是"udevice->plat_"指针中分配的平台数据的大小。这通常只对设备树感知的驱动程序有用(那些带有of_match的驱动程序)  
    15.    int per_child_auto; // device包含的父设备的私有数据。如果需要,该值是非零,将自动分配该数据所需的空间,并保存到子节点的parent_priv_指针中。  
    16.    int per_child_plat_auto; // device包含的父设备的平台数据。如果非零,则分配该数据所需的空间,并保存到子节点的parent_plat_指针中。  
    17.    const void *ops; // driver的具体操作,这通常是一个由driver定义的函数指针列表,用于实现device所需的驱动程序函数。  
    18.    uint32_t flags; // driver flags - see DM_FLAGS_...  
    19.#if CONFIG_IS_ENABLED(ACPIGEN)  
    20.    struct acpi_ops *acpi_ops;  
    21.#endif  
    22.};  
    
    
  2. 如何定义

    通过U_BOOT_DRIVER宏来定义一个driver。

    以s5pv210为例:driver/serial/serial_s5p.c

    1.U_BOOT_DRIVER(serial_s5p) = {  
    2.    .name   = "serial_s5p",  
    3.    .id = UCLASS_SERIAL,  
    4.    .of_match = s5p_serial_ids,  
    5.    .of_to_plat = s5p_serial_of_to_plat,  
    6.    .plat_auto  = sizeof(struct s5p_serial_plat),  
    7.    .probe = s5p_serial_probe,  
    8.    .ops    = &s5p_serial_ops,  
    9.};  
    
    

    U_BOOT_DRIVER实现如下:

    1.#define U_BOOT_DRIVER(__name)                        \  
    2.    ll_entry_declare(struct driver, __name, driver)  
    3.   
    4.#define ll_entry_declare(_type, _name, _list)               \  
    5.    _type _u_boot_list_2_##_list##_2_##_name __aligned(4)       \  
    6.            __attribute__((unused,              \  
    7.            section(".u_boot_list_2_"#_list"_2_"#_name)))  
    
    
    

    最终得到如下一个结构体:

    1.struct driver _u_boot_list_2_driver_2_serial_s5p= {  
    2.    .name   = "serial_s5p",  
    3.    .id = UCLASS_SERIAL,  
    4.    .of_match = s5p_serial_ids,  
    5.    .of_to_plat = s5p_serial_of_to_plat,  
    6.    .plat_auto  = sizeof(struct s5p_serial_plat),  
    7.    .probe = s5p_serial_probe,  
    8.    .ops    = &s5p_serial_ops,  
    9.};  
    
    
  3. 存放位置

    通过上述,我们知道serial_s5p的driver结构体_u_boot_list_2_driver_2_serial_s5p被存放在.u_boot_list_2_driver_2_serial_s5p段中。
    通过查看u-boot.map得到如下:

    1..u_boot_list_2_driver_1  
    2.                0x23e36754        0x0 drivers/built-in.o  
    3..u_boot_list_2_driver_2_gpio_exynos  
    4.                0x23e36754       0x44 drivers/gpio/built-in.o  
    5.                0x23e36754                _u_boot_list_2_driver_2_gpio_exynos  
    6..u_boot_list_2_driver_2_root_driver  
    7.                0x23e36798       0x44 drivers/built-in.o  
    8.                0x23e36798                _u_boot_list_2_driver_2_root_driver  
    9..u_boot_list_2_driver_2_serial_s5p  
    10.                0x23e367dc       0x44 drivers/serial/built-in.o  
    11.                0x23e367dc                _u_boot_list_2_driver_2_serial_s5p  
    12..u_boot_list_2_driver_2_simple_bus_drv  
    13.                0x23e36820       0x44 drivers/built-in.o  
    14.                0x23e36820                _u_boot_list_2_driver_2_simple_bus_drv  
    15..u_boot_list_2_driver_3  
    16.                0x23e36864        0x0 drivers/built-in.o  
    
    

    最终,所有driver结构体以列表的形式被放在.u_boot_list_2_driver_1和.u_boot_list_2_driver_3的区间中。
    这个列表简称driver table。

  4. 如何获取、API

    想要获取driver需要先获取driver table。

    (1) 可以通过以下宏来获取driver table

    1.    struct driver *drv =  
    2.        ll_entry_start(struct driver, driver);  
    3.// 会根据.u_boot_list_2_driver_1的段地址来得到uclass_driver table的地址  
    4.  
    5.    const int n_ents = ll_entry_count(struct driver, driver);  
    6.// 获得driver table的长度  
    
    

    (2) 接着通过遍历这个driver table,得到相应的driver。

    1.struct driver *lists_driver_lookup_name(const char *name)  
    2.// 从driver table中获取名字为name的driver。
    
    

4. uboot 中设备的表达

4.1 说明

uboot中添加设备主要有两种方式:

  • 通过直接定义平台设备(这种方式基本上不使用)
  • 通过在设备树添加设备信息
    注意:这里只是设备的定义,最终还是会被uboot解析成udevice结构体的。

那么寄存器定义是否也需要放入device中呢?其实不必。

其实不必,当然放进去也可以。其实硬件设备的资源信息是否要放入device中有一个关键的原则:

  • 如果该资源信息影响driver的通用性,就把它放到device中。

    比如uart驱动在不同芯片上基地址不同,这个信息就需要放在device中,那么针对不同的硬件设备平台,就只需要修改device中的信息即可,而不需要修改driver。

  • 而模块内部某些信息,如寄存器位宽和寄存器定义都是跟硬件设计无关的,这是这一类硬件模块内部的特性,所以可以不放在device中,直接在driver中编码即可。

其实对于哪些硬件资源信息需要放入device,并没有统一的规则,主要还是看需求而定。

4.2 直接定义平台设备

这种方式除了根设备外基本上不使用。

(1)通过U_BOOT_DRVINFO宏来进行定义或者直接定义struct driver_info结构体
(2)U_BOOT_DRVINFO宏以ns16550_serial为例

1.U_BOOT_DRVINFO(devkit8000_uart) = {  
2.    "ns16550_serial",  
3.    &devkit8000_serial  
4.};  

U_BOOT_DRVINFO实现如下:和上述的U_BOOT_DRIVER类似

1.#define U_BOOT_DRVINFO(__name)                      \  
2.    ll_entry_declare(struct driver_info, __name, driver_info)  

注:在老版本的uboot中,没有U_BOOT_DRVINFO,而是用U_BOOT_DEVICE替代

(3)直接定义struct driver_info结构体,以根设备为例 uboot会创建一个根设备root,作为所有设备的根设备,root的定义如下:

1.static struct driver_info root_info = {  
2.    .name       = "root_driver",  
3.};  

4.3 在设备树添加设备信息

在对应的dts文件中添加相应的设备节点和信息,以tiny210的serial为例:
arch/arm/dts/s5pv210-tiny210.dts

/dts-v1/;
#include "skeleton.dtsi"
/{
        aliases {
                console = "/serial@e2900000";
        };  
 
        serial@e2900000 {
                compatible = "samsung,exynos4210-uart";
                reg = <0xe2900000 0x100>;
                interrupts = <0 51 0>; 
                id = <0>;
        };  
};

5. U-Boot DM的使能

要打开DM ,最后反映在几个配置信息上:

  • CONFIG_DM=y,全局DM模型打开
  • CONFIG_DM_XXX=y,某个驱动的DM模型的打开
  • 可以通过KconifgMakefile来查看对应宏的使用情况。

6. uboot DM的初始化

6.1 主要工作

  1. DM根设备的初始化

    创建根设备root的udevice,存放在gd->dm_root中。

    typedef struct global_data {
       // dts中的根节点,第一个创建的udevice
       struct udevice  *dm_root;
    
       // relocation之前的根设备
       struct udevice  *dm_root_f;
    
      // uclass的链表, 挂的是有udevice的uclass
       struct list_head uclass_root;  
    } gd_
    
    

    根设备其实是一个虚拟设备,主要是为uboot的其他设备提供一个挂载点。

    初始化uclass链表gd->uclass_root

  2. DM中udevice和uclass的解析

    • udevice的创建和uclass的创建
    • udevice和uclass的绑定
    • uclass_driver和uclass的绑定
    • driver和udevice的绑定
    • 部分driver和uclass_driver函数的调用

6.2 初始化入口函数

dm初始化的接口在dm_init_and_scan中。
可以发现在uboot relocate之前的initf_dm和之后的initr_dm都调用了这个函数。

1.static int initf_dm(void)  
2.{  
3.#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)  
4.    int ret;  
5.    bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");  
6.    ret = dm_init_and_scan(true);   // 调用dm_init_and_scan对DM进行初始化和设备的解析  
7.    bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);  
8.    if (ret)  
9.        return ret;  
10.    if (IS_ENABLED(CONFIG_TIMER_EARLY)) {  
11.        ret = dm_timer_init();  
12.        if (ret)  
13.            return ret;  
14.    }  
15.#endif  
16.   
17.    return 0;  
18.} 

1.#ifdef CONFIG_DM  
2.static int initr_dm(void)  
3.{  
4.    int ret;  
5.    /* Save the pre-reloc driver model and start a new one */  
6.    gd->dm_root_f = gd->dm_root;  // 存储relocate之前的根设备  
7.    gd->dm_root = NULL;  
8.#ifdef CONFIG_TIMER  
9.    gd->timer = NULL;  
10.#endif  
11.    bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r");  
12.    ret = dm_init_and_scan(false);  // 调用dm_init_and_scan对DM进行初始化和设备的解析  
13.    bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);  
14.    if (ret)  
15.        return ret;  
16.    return 0;  
17.}  
18.#endif 

主要区别在于参数。
首先说明一下dts节点中的“u-boot,dm-pre-reloc”属性,当设置了这个属性时,则表示这个设备在relocate之前就需要使用。

当dm_init_and_scan的参数为true时,只会对带有“u-boot,dm-pre-reloc”属性的节点进行解析。而当参数为false的时候,则会对所有节点都进行解析。

6.3 DM初始化流程

1.initr_dm  
2.    dm_init_and_scan  //对DM进行初始化和设备的解析  
3.        dm_init    //初始化DM根设备  
4.            device_bind_by_name(NULL, false, &root_info,&DM_ROOT_NON_CONST)  
5.                //DM_ROOT_NON_CONST是指根设备udevice,root_info为driver_info结构体类型,表示根设备的					设备信息  
6.                //device_bind_by_name会查找和设备信息匹配的driver,然后创建对应的udevice和uclass并进					行绑定,最后放在DM_ROOT_NON_CONST中  
7.                lists_driver_lookup_name(info->name)  //根据driver_info的name成员找到对应的driver												结构体  
8.                    ll_entry_start(struct driver, driver)   //获取.u_boot_list_2_driver_1的段地址,													作为driver table的首地址  
9.                    ll_entry_count(struct driver, driver)   //获取driver table的长度  
10.                device_bind_common      //根据driver结构体,创建根设备的uclass结构体和udevice结构体									并进行绑定  
11.                    uclass_get(drv->id, &uc)   //根据uclass_id获取uclass结构体  
12.                        uclass_find        //通过匹配uclass->uclass_driver->id获取uclass结构体  
13.                        uclass_add         //上一步未获取到uclass结构体则创建对应的uclass结构体,并与										其对应的uclass_driver进行绑定  
14.                            lists_uclass_lookup     //获取id对应的uclass_driver结构体,未获取到则报												错返回  
15.                     uc->uc_drv = uc_drv    //将uclass与uclass_driver进行绑定  
16.                             list_add       //将uclass结构体添加到global_data->uclass_root链										    表中 
17.                    dev->driver = drv     //绑定udevice与driver  
18.                    uclass_bind_device     //绑定uclass结构体和udevice结构体,主要是实现了将udevice										链接到uclass的设备链表中  
19.            device_probe    //对根设备进行probe操作  
20.        dm_scan  
21.            dm_scan_plat   //从平台设备中解析udevice和uclass并绑定  
22.                lists_bind_drivers   //根设备的udevice作为父节点,以参数传入  
23.                    bind_drivers_pass  
24.                        device_bind_by_name  //根据传入的driver_info信息,创建对应的udevice和uclass										 并进行绑定  
25.            dm_extended_scan    //从dtb中解析udevice和uclass并绑定  
26.                dm_scan_fdt  
27.                    dm_scan_fdt_node  
28.                        //传入参数,parent=gd->dm_root,表示以root设备作为父设备开始解析  
29.                        //传参parent_node为设备树根节点  
30.                        //pre_reloc_only=0,不只是解析relotion之前的设备  
31.                        ofnode_first_subnode    //获取设备树的第一个子节点  
32.                        ofnode_next_subnode     //查找下一个子节点  
33.                        ofnode_is_enabled       //判断节点状态是否是disable,如果是的话直接忽略  
34.                        lists_bind_fdt     //从dtb中解析udevice和uclass并绑定  
35.                            ll_entry_start(struct driver, driver)    // 获取driver table地址  
36.                            ll_entry_count(struct driver, driver)    // 获取driver table长度  
37.                            ofnode_get_property(node, "compatible", &compat_length)     //获取设备															树节点compatible属性  
38.                            driver_check_compatible      //判断driver中的of_match->compatibile字段													和dts节点是否匹配  
39.                            device_bind_with_driver_data  
40.                                device_bind_common       //根据driver结构体,创建对应设备的uclass结           													构体和udevice结构体并进行绑定  
41.                dm_scan_fdt_ofnode_path     //一些节点本身不是设备,但是包含一些设备,遍历其包含的设备  
42.                    ofnode_path       //找到节点下包含的设备  
43.                    dm_scan_fdt_node   
44.            dm_scan_other    

在全局数据global_data定义了DM根节点,dm初始化的接口在dm_init_and_scan(bool pre_reloc_only)中,初始化流程主要有两次,入口函数分别是static int initf_dm(void)static int initr_dm(void)。第一次是在重定位之前,调用的是initf_dm函数。第二次是在重定位之后,调用的是initr_dm函数。

typedef struct global_data {
// dts中的根节点,第一个创建的udevice
   struct udevice  *dm_root;
// relocation之前的根设备
struct udevice *dm_root_f;
// uclass的链表, 挂的是有udevice的uclass
struct list_head uclass_root;
} gd_

DM root初始化函数dm_init。

int dm_init(bool of_live)
{
	gd->uclass_root = &DM_UCLASS_ROOT_S_NON_CONST;
    //初始化uclass_root链表头
	INIT_LIST_HEAD(DM_UCLASS_ROOT_NON_CONST);
    //创建一个device dm_root并将其绑定到driver name “root_driver”。
    device_bind_by_name(NULL, false, &root_info,
					  &DM_ROOT_NON_CONST);
    //探测设备udevice dm_root并激活它
    ret = device_probe(DM_ROOT_NON_CONST);
}

1.device_bind_by_name分析

lists_driver_lookup_name通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址。

device_bind_common创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。

int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
			const struct driver_info *info, struct udevice **devp)
{
	struct driver *drv;
	uint platdata_size = 0;

	// 通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址
	drv = lists_driver_lookup_name(info->name); // info->name = "root_driver" 

#if CONFIG_IS_ENABLED(OF_PLATDATA)
	platdata_size = info->platdata_size;
#endif
	// 创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。
	return device_bind_common(parent, drv, info->name,
			(void *)info->platdata, 0, ofnode_null(), platdata_size,
			devp);
}

2. device_probe分析

DM_ROOT_NON_CONST代表gd中得dm_root:(((gd_t *)gd)->dm_root)device_probe是一个通用的函数, 其大概步骤如下:

/**
 * device_probe() - 探测一个设备并激活它
 *
 * 激活一个设备以便它可以随时使用。首先探查它的所有父节点。
 */
int device_probe(struct udevice *dev)
{
	const struct driver *drv;
	int ret;
	int seq;

	// 检测该device是否已经激活,已激活就直接返回。
	if (dev->flags & DM_FLAG_ACTIVATED)
		return 0;

	drv = dev->driver; // 获取该设备对应的driver

	/**
	 * device_ofdata_to_platdata() - 设备读取平台数据
	 * 
	 * 读取设备的平台数据(通常是从设备树中),以便提供探测设备所需的信息。
	 */
	ret = device_ofdata_to_platdata(dev);

	// 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
	if (dev->parent) {
		ret = device_probe(dev->parent);

		if (dev->flags & DM_FLAG_ACTIVATED)
			return 0;
	}

	/**
	 * uclass_resolve_seq() - 解析device的序列号
	 *
	 * 在"dev->seq = -1"时,然后"dev->req_seq = -1"代表自动分配一个序列号,"dev->req_seq > 0"代表分配
	 * 指定的序列号。如果请求的序列号正在使用中,那么该设备将被分配另一个序列号。
	 *
	 * 注意,该函数不会改变设备的seq值,dev->seq需要手动赋值修改。
	 */
	seq = uclass_resolve_seq(dev);
	dev->seq = seq;

	// 标记该设备处于激活状态。
	dev->flags |= DM_FLAG_ACTIVATED;

	 //处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
	if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	/**
	 * uclass_pre_probe_device() - 处理一个即将被probed的设备
	 *
	 * 先执行uclass需要的任何预处理,然后才可以探测它。这包括uclass的pre-probe()方法和父uclass的child_pre_probe()方法。
	 */
	ret = uclass_pre_probe_device(dev);

	if (dev->parent && dev->parent->driver->child_pre_probe) {
		ret = dev->parent->driver->child_pre_probe(dev);
	}

	// 只处理具有有效ofnode的设备
	if (dev_of_valid(dev) && !(dev->driver->flags & DM_FLAG_IGNORE_DEFAULT_CLKS)) {
		// 处理{clocks/clock-parents/clock-rates}属性配置时钟
		ret = clk_set_defaults(dev, 0);
	}

	// 执行该设备的driver的probe函数,激活该设备。
	if (drv->probe) {
		ret = drv->probe(dev);
	}

	/**
	 * uclass_post_probe_device() - 处理一个刚刚被probed过的设备
	 *
	 * 对uclass探测设备后,需要执行的操作。
	 */
	ret = uclass_post_probe_device(dev);

	if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	return 0;
}

  1. 检测该device是否已经激活,已激活就直接返回。
  2. 获取该设备对应的driver。
  3. 读取设备的平台数据。
  4. 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
  5. 标记该设备处于激活状态。
  6. 处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
  7. 如果配置了IOMMU,则需要先打开IOMMU。
  8. 预使能设备,包括uclass的pre-probe()和父uclass的child_pre_probe()方法。
  9. 处理{clocks/clock-parents/clock-rates}属性配置时钟。
  10. 执行该设备的driver的probe函数,激活该设备。
  11. uclass_post_probe_device() 处理一个刚刚被probed过的设备。
  12. 最后处理IOMUX驱动,如果是IOMUX驱动则需要设置pinctl的默认状态。

6.4 Device Tree节点的设备初始化

主要通过:dm_scan(pre_reloc_only)

dm_scan函数是在其他地方(设备树)搜索设备并进行驱动匹配,然后bind。主要分为三步:

  1. dm_scan_plat,搜索并绑定所有的驱动到根节点(((gd_t *)gd)->dm_root)上;
  2. dm_extended_scan,扫描dtb文件;
  3. dm_scan_other,留给厂商自定义覆盖的弱函数。

本节主要展开分析dm_extended_scan函数,下面是这个函数的流程:

dm_extended_scan(bool pre_reloc_only)
-->dm_scan_fdt(pre_reloc_only)
    -->dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only)
    	-->lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only)  
-->for : dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only)
    -->dm_scan_fdt_node(gd->dm_root, node, pre_reloc_only)

首先这个函数定义了需要扫描的三个父节点,以下的扫描和绑定都是基于这三个节点展开的。

const char * const nodes[] = {
		"/chosen",
		"/clocks",
		"/firmware"
};

Device Tree设备初始化的核心函数是dm_scan_fdt_node->lists_bind_fdt,扫描设备树遍历父节点,并且将驱动绑定和设备树节点绑定。

步骤:

  1. ofnode_get_property() ,根据node节点获取compatible属性字符串list;
  2. 遍历兼容字符串列表,driver_check_compatible尝试匹配每个字符串兼容字符串,以便我们按优先级顺序匹配从第一个字符串到最后一个;
  3. 找到匹配的驱动,device_bind_with_driver_data() 创建一个设备并且绑定到driver。

6.5 设备树节点和driver的绑定

device_bind_common用于绑定设备树节点和驱动,主要有三处调用,分别是:

  • device_bind_by_name。在dm模型初始化的时候来初始化根设备global_data->dm_root

  • device_bind_with_driver_data。在扫描设备树并绑定驱动的时候调用。

  • device_bind。在可以选择其余驱动的bind函数中调用,手动绑定想要的设备树节点和驱动。例如下图中apple笔记本的pinctl驱动
    在这里插入图片描述

    绑定的主要步骤:

    1. uclass_get() 根据driver->id获取一个uclass(.id = UCLASS_PINCTRL),如果它不存在就创建它。每个类都由一个ID标识,一个从0到n-1的数字,其中n是类的数量。这个函数允许根据类的ID查找类。

      uclass_get(drv->id, &uc)
      
      
    2. 创建一个新的device,申请一个struct udevice空间

      dev = calloc(1, sizeof(struct udevice))
      
      
    3. 初始化新device的相关链表头

      INIT_LIST_HEAD(&dev->sibling_node);
      INIT_LIST_HEAD(&dev->child_head); 
      INIT_LIST_HEAD(&dev->uclass_node);
      INIT_LIST_HEAD(&dev->devres_head);
      
      
    4. 初始化新udevice相关数据,将新udevice和传入的driver、uclass进行绑定。

      dev->platdata = platdata;-------->传入
      dev->driver_data = driver_data;-->传入
      dev->name = name;------>传入
      dev->node = node;
      dev->parent = parent;
      dev->driver = drv;----->传入
      dev->uclass = uc;
      
      
    5. dev->seq为该设备分配的序列号(-1 = none)。这是在设备probe时设置的,并且在设备的uclass中是唯一的。dev->req_seq为此设备请求的序列号(-1 = any)

      dev->seq = -1;
      dev->req_seq = -1;
      if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
      	if (uc->uc_drv->name && ofnode_valid(node))
      		dev_read_alias_seq(dev, &dev->req_seq);
      } else {
      	dev->req_seq = uclass_find_next_free_req_seq(drv->id);
      }
      
      
    6. sibling_node对应该设备,并将它添加到parent的child_head设备列表中

      if (parent)
      	list_add_tail(&dev->sibling_node, &parent->child_head);
      
      
    7. uclass_bind_device() 将udevice与uclass进行关联

      uclass_bind_device(dev);
      
      
    8. device绑定成功后,就会调用drv->bind

      if (drv->bind) {
      		ret = drv->bind(dev);
      }
      
      
    9. 在一个新的child被绑定后,就会调用parent的parent->driver->child_post_bind(dev)

      if (parent && parent->driver->child_post_bind) {
      		ret = parent->driver->child_post_bind(dev);
      }
      
      
    10. 在一个新设备绑定到这个uclass后被调用

      if (uc->uc_drv->post_bind) {
      		ret = uc->uc_drv->post_bind(dev);
      }
      
      

6.6. 总结

对于DM模型初始化来说,uboot会在启动序列中使用dm_init创建一个dm_root(udevice)并将其绑定到“root_driver”(driver),然后device_probe来激活这个设备。第二步使用dm_scan来绑定设备树中的设备和驱动到dm_root下面。

7.DM各部分的绑定关系

udevice与driver的绑定:

  1. 设备树形式的udevice:通过驱动的of_match和compatible属性来配对,绑定。
  2. U_BOOT_DRVINFO定义的udevice:通过name属性来绑定。

udevice与uclass的绑定: udevice内的driver下的uclass_id,来与uclass对应的uclass_driver的uclass_id进行匹配。

**uclass与uclass_driver的绑定:**已知udevice内的driver下的uclass_id,创建uclass的同时,通过`uclass_id找到对应的uclass_driver对象,然后将uclass_driver绑定到uclass上!

8. DM工作流程

经过前面的DM初始化以及设备解析之后,我们只是建立了udevice和uclass之间的绑定关系。但是此时udevice还没有被probe,其对应设备还没有被激活。
激活一个设备主要是通过device_probe函数,所以在DM的工作流程的最后一步就是调用device_probe函数。

8.1 代码支持

以mtd-uclass为例。

1.定义一个uclass_driver

mtd-uclass.c中定义一个uclass_driver

1.UCLASS_DRIVER(mtd) = {  
2.    .id     = UCLASS_MTD,   //注意这里的uclass id  
3.    .name       = "mtd",  
4.    .per_device_auto    = sizeof(struct mtd_info),  
5.};  

2. 定义设备
  • 通过设备树定义

    1.&nand_controller {  
    2.    compatible="marvell,mvebu-pxa3xx-nand";   //注意这里的compatible  
    3.    status = "okay";  
    4.    label = "pxa3xx_nand-0";  
    5.    nand-rb = <0>;  
    6.    marvell,nand-keep-config;  
    7.    nand-on-flash-bbt;  
    8.    nand-ecc-strength = <4>;  
    9.    nand-ecc-step-size = <512>;  
    10.};  
    
    
  • 直接定义

    1.U_BOOT_DRVINFO(paa3xx_nand) = {  
    2.    .name  = "pxa3xx-nand",    //注意这里的name  
    3.};  
    
    
3.定义设备驱动
1.U_BOOT_DRIVER(pxa3xx_nand) = {  
2.    .name = "pxa3xx-nand",   //注意这里的name  
3.    .id = UCLASS_MTD,    //注意这里的uclass_id  
4.    .of_match = pxa3xx_nand_dt_ids,  
5.    .probe = pxa3xx_nand_probe,  
6.    .priv_auto  = sizeof(struct pxa3xx_nand_info) +  
7.        sizeof(struct pxa3xx_nand_host) * CONFIG_SYS_MAX_NAND_DEVICE,  
8.};  
9.  
10.static const struct udevice_id pxa3xx_nand_dt_ids[] = {  
11.    {  
12.        .compatible = "marvell,mvebu-pxa3xx-nand",  //注意这里的compatible  
13.        .data = PXA3XX_NAND_VARIANT_ARMADA370,  
14.    },  
15.    {  
16.        .compatible = "marvell,armada-8k-nand-controller",  
17.        .data = PXA3XX_NAND_VARIANT_ARMADA_8K,  
18.    },  
19.    {}  
20.};  

4.定义初始化入口
1.void board_nand_init(void)  
2.{  
3.    struct udevice *dev;  
4.    int ret;  
5.  
6.    ret = uclass_get_device_by_driver(UCLASS_MTD,  
7.            DM_DRIVER_GET(pxa3xx_nand), &dev);  
8.    if (ret && ret != -ENODEV) {  
9.        pr_err("Failed to initialize %s. (error %d)\n", dev->name,  
10.               ret);  
11.    }  
12.}  

8.2 udevice和对应uclass的创建

在DM初始化的过程中uboot自己创建对应的udevice和uclass。

8.3 udevice和对应uclass的绑定

在DM初始化的过程中uboot自己实现将udevice绑定到对应的uclass中。

8.4 对应udevice的probe

由模块自己实现。

例如nand则需要在初始化过程中,选择需要的udevice进行probe。初始化流程如下initr_nand->nand_init->board_nand_init->uclass_get_device_by_driver->uclass_get_device_tail->device_probe。

uclass_get_device_by_driver函数,通过uclass_id获取对应uclass,从uclass的设备链表中获取udevice,udevice->driver与DM_DRIVER_GET(pxa3xx_nand)匹配上,则调用udevice对应的driver进行probe。

  • 3
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老衲不依

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值