1、uboot驱动模型简介
1.1、什么是Uboot驱动模型
学过Linux的朋友基本都知道Linux的设备驱动模型,Uboot根据Linux的驱动模型架构,也引入了Uboot的驱动模型(driver model :DM)。
这种驱动模型为驱动的定义和访问接口提供了统一的方法。提高了驱动之间的兼容性以及访问的标准型,uboot驱动模型和kernel中的设备驱动模型类似。
1.2、为什么要有驱动模型呢
1,提高代码的可重用性:为了能够使代码在不同硬件平台,不同体系架构下运行,必须要最大限度的提高代码的可重用性。
2,高内聚,低耦合:分层的思想也是为了达到这一目标,低耦合体现在对外提供统一的抽象访问接口,高内聚将相关度紧密的集中抽象实现。
3,便于管理:在不断发展过程中,硬件设备越来越多,驱动程序也越来越多,为了更好的管理驱动,也需要一套优秀的驱动架构!
1.3、如何使用uboot的DM模型
DM模型的使用,可以通过menuconfig来配置。make menuconfig
①:menuconfig配置全局DM模型
Device Drivers -> Generic Driver Options -> Enable Driver Model
通过上面的路径来打开Driver Model模型,最终配置在.config文件中,CONFIG_DM=y
②:指定某个驱动的DM模型
全局的DM模型打开后,我们对于不通的驱动模块,使能或者失能DM功能。如MMC驱动为例:
Device Drivers -> MMC Host controller Support -> Enable MMC controllers using Driver Model
最终反映在.config文件中的CONFIG_DM_MMC=y
在对应的驱动中,可以看到判断#if !CONFIG_IS_ENABLED(DM_MMC),来判断是否打开DM驱动模型。
在管理驱动的Makefile文件中,也能看到obj-KaTeX parse error: Expected group after '_' at position 8: (CONFIG_̲(SPL_)DM_MMC) += mmc-uclass.o,来判断是否将驱动模型加入到编译选项中。
总之,我们要打开DM模型,最后反映在几个配置信息上:
- CONFIG_DM=y,全局DM模型打开
- CONFIG_DM_XXX=y,某个驱动的DM模型的打开
- 可以通过Kconifg、Makefile来查看对应宏的编译情况
2、DM模型整体架构
2.1、DM的四个组成部分
- udevice
驱动程序实例,就是指设备对象,可以理解为kernel中的device。 - driver
udevice的驱动,和底层硬件设备通信,并且为设备提供面向上层的接口。 - uclass
管理着对应某一个类别下的所有的udevice。
例如:一个IIC驱动程序,其驱动程序框架是一致的,只有一种,但是IIC驱动的设备可以有很多,如EEPROM,MCU等;所有在这里呢,dev_head链表就是用来管理该驱动类下的所有的设备。 - uclass_driver
对应uclass的驱动程序。主要提供uclass操作时,如绑定、激活、移除udevice时的一些操作接口。
2.2、相互之间的关系
- driver会和udevice绑定。uclass_driver会和uclass绑定。udevice会和uclass绑定。
- udevice找到对应的uclass的方式主要是通过:udevice对应的driver的id和uclass对应的uclass_driver的id是否匹配。
这里先简单介绍一下:uclass和udevice都是动态生成的。在解析设备树中的设备或直接定义的平台设备的时候,会动态生成udevice。然后找到udevice对应的driver,通过driver中的uclass id得到uclass_driver id。从uclass链表中查找对应的uclass是否已经生成,没有生成的话则动态生成uclass。
3、DM四个主要组成部分详细介绍
后续以数据结构、如何定义、存放位置、如何获取四个部分进行说明。
3.0、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.1、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.2、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实现如下:
1.#define UCLASS_DRIVER(__name) \
2. ll_entry_declare(struct uclass_driver, __name, uclass)
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 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。
可以通过以下宏来获取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的长度
接着通过遍历这个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。
可以通过以下宏来获取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的长度
接着通过遍历这个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结构体的。
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、uboot DM的初始化
5.1、主要工作
- DM根设备的初始化
创建根设备root的udevice,存放在gd->dm_root中。
根设备其实是一个虚拟设备,主要是为uboot的其他设备提供一个挂载点。
初始化uclass链表gd->uclass_root - DM中udevice和uclass的解析
udevice的创建和uclass的创建
udevice和uclass的绑定
uclass_driver和uclass的绑定
driver和udevice的绑定
部分driver和uclass_driver函数的调用
5.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的时候,则会对所有节点都进行解析。
5.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
6、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上!
7、DM工作流程
经过前面的DM初始化以及设备解析之后,我们只是建立了udevice和uclass之间的绑定关系。但是此时udevice还没有被probe,其对应设备还没有被激活。
激活一个设备主要是通过device_probe函数,所以在DM的工作流程的最后一步就是调用device_probe函数。
7.1 工作流程简单说明
(1)代码支持
以mtd-uclass为例:
<1>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.}
(2)udevice和对应uclass的创建
在DM初始化的过程中uboot自己创建对应的udevice和uclass。
(3)udevice和对应uclass的绑定
在DM初始化的过程中uboot自己实现将udevice绑定到对应的uclass中。
(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。