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.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.};
-
如何定义
uclass是uboot自动生成。并且不是所有uclass都会生成,有对应uclass driver并且有被udevice匹配到的uclass才会生成。
具体参考后面的uboot DM初始化一节。或者参考uclass_add实现。
-
存放位置
所有生成的uclass都会被挂载gd->uclass_root链表上。
-
如何获取、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
-
数据结构
定义在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.};
-
如何定义
通过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.}
-
存放位置
通过上述,我们知道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。 -
如何获取、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
-
数据结构
定义在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.};
-
如何定义
(1) 通过宏U_BOOT_DRVINFO静态定义
(2) 在dtb存在的情况下,由uboot解析dtb后动态生成,后续在“uboot DM的初始化”一节中具体说明。
-
存放位置
-
连接到对应uclass中
也就是会连接到uclass->dev_head中。
-
连接到父设备的子设备链表中
也就是会连接到udevice->child_head中,并且最终的根设备是gd->dm_root这个根设备。
-
-
如何获取、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方式是相似的。
-
数据结构
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.};
-
如何定义
通过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.};
-
存放位置
通过上述,我们知道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。 -
如何获取、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模型的打开- 可以通过
Kconifg
、Makefile
来查看对应宏的使用情况。
6. uboot DM的初始化
6.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
-
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;
}
- 检测该device是否已经激活,已激活就直接返回。
- 获取该设备对应的driver。
- 读取设备的平台数据。
- 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
- 标记该设备处于激活状态。
- 处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
- 如果配置了IOMMU,则需要先打开IOMMU。
- 预使能设备,包括uclass的
pre-probe()
和父uclass的child_pre_probe()
方法。 - 处理
{clocks/clock-parents/clock-rates}
属性配置时钟。 - 执行该设备的driver的probe函数,激活该设备。
uclass_post_probe_device()
处理一个刚刚被probed过的设备。- 最后处理IOMUX驱动,如果是IOMUX驱动则需要设置pinctl的默认状态。
6.4 Device Tree节点的设备初始化
主要通过:dm_scan(pre_reloc_only)
dm_scan
函数是在其他地方(设备树)搜索设备并进行驱动匹配,然后bind。主要分为三步:
dm_scan_plat
,搜索并绑定所有的驱动到根节点(((gd_t *)gd)->dm_root)
上;dm_extended_scan
,扫描dtb文件;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
,扫描设备树遍历父节点,并且将驱动绑定和设备树节点绑定。
步骤:
ofnode_get_property()
,根据node节点获取compatible
属性字符串list;- 遍历兼容字符串列表,
driver_check_compatible
尝试匹配每个字符串兼容字符串,以便我们按优先级顺序匹配从第一个字符串到最后一个; - 找到匹配的驱动,
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驱动
绑定的主要步骤:
-
uclass_get()
根据driver->id
获取一个uclass(.id = UCLASS_PINCTRL),如果它不存在就创建它。每个类都由一个ID标识,一个从0到n-1的数字,其中n是类的数量。这个函数允许根据类的ID查找类。uclass_get(drv->id, &uc)
-
创建一个新的device,申请一个struct udevice空间
dev = calloc(1, sizeof(struct udevice))
-
初始化新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);
-
初始化新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;
-
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); }
-
sibling_node对应该设备,并将它添加到parent的child_head设备列表中
if (parent) list_add_tail(&dev->sibling_node, &parent->child_head);
-
uclass_bind_device()
将udevice与uclass进行关联uclass_bind_device(dev);
-
device绑定成功后,就会调用
drv->bind
if (drv->bind) { ret = drv->bind(dev); }
-
在一个新的child被绑定后,就会调用parent的
parent->driver->child_post_bind(dev)
if (parent && parent->driver->child_post_bind) { ret = parent->driver->child_post_bind(dev); }
-
在一个新设备绑定到这个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的绑定:
- 设备树形式的udevice:通过驱动的of_match和compatible属性来配对,绑定。
- 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。