[uboot] (番外篇)uboot 驱动模型

[uboot] (番外篇)uboot 驱动模型uboot 引入了驱动模型 drivermodel 这种驱动模型为驱动的定义和访问接口提供了统一的方法 提高了驱动之间的兼容性以及访问的标准型 uboot 驱动模型和 kernel 中的设备驱动模型类似 但是又有所区别 在后续我们将驱动模型 drivermodel 简称为 DM 其实在 uboot 里面也是这样简称的 这篇文章里介绍的 DM 的流程以及如何使用

[uboot] uboot流程系列
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)
[project X] tiny210(s5pv210)从存储设备加载代码到DDR
[uboot] (第一章)uboot流程——概述
[uboot] (第二章)uboot流程——uboot-spl编译流程
[uboot] (第三章)uboot流程——uboot-spl代码流程
[uboot] (第四章)uboot流程——uboot编译流程
[uboot] (第五章)uboot流程——uboot启动流程
[uboot] (番外篇)global_data介绍
[uboot] (番外篇)uboot relocation介绍
[uboot] (番外篇)uboot之fdt介绍




















建议先看《[uboot] (番外篇)uboot之fdt介绍》,了解一下uboot的fdt的功能,在驱动模型中会使用到。

==============================================================================================================

一、说明

1、uboot的驱动模型简单介绍

具体细节建议参考./doc/driver-model/README.txt

2、如何使能uboot的DM功能

(1)配置CONFIG_DM
在configs/tiny210_defconfig中定义了如下:

CONFIG_DM=y

(2)使能相应的uclass driver的config
DM和uclass是息息相关的,如果我们希望在某个模块引入DM,那么就需要使用相应模块的uclass driver来代替旧版的通用driver。
关于uclass我们会在后续继续说明。
以serial为例,为了在serial中引入DM,我在configs/tiny210_defconfig0中打开了CONFIG_DM_SERIAL宏,如下






CONFIG_DM_SERIAL=y

看driver/serial/Makefile

ifdef CONFIG_DM_SERIAL obj-y += serial-uclass.o  引入dm的serial core驱动 else obj-y += serial.o  通用的serial core驱动 endif  可以发现编译出来的serial core的驱动代码是不一样的。

(3)对应设备驱动也要引入dm的功能
其设备驱动主要是实现和底层交互,为uclass层提供接口。后续再具体说明。

__后续都以serial-uclass进行说明

二、uboot DM整体架构

1、DM的四个组成部分

uboot的DM主要有四个组成部分

  • udevice
    简单就是指设备对象,可以理解为kernel中的device。

  • driver
    udevice的驱动,可以理解为kernel中的device_driver。和底层硬件设备通信,并且为设备提供面向上层的接口。

  • uclass
    先看一下README.txt中关于uclass的说明:

Uclass - a group of devices which operate in the same way. A uclass provides a way of accessing individual devices within the group, but always using the same interface. For example a GPIO uclass provides operations for get/set value. An I2C uclass may have 10 I2C ports, 4 with one driver, and 6 with another.
  • uclass_driver
    对应uclass的驱动程序。主要提供uclass操作时,如绑定udevice时的一些操作。

2、调用关系框架图

DM下的接口调用流程

3、相互之间的关系

结合上图来看:

  • 上层接口都是和uclass的接口直接通讯。
  • uclass可以理解为一些具有相同属性的udevice对外操作的接口,uclass的驱动是uclass_driver,主要为上层提供接口。
  • udevice的是指具体设备的抽象,对应驱动是driver,driver主要负责和硬件通信,为uclass提供实际的操作集。
  • udevice找到对应的uclass的方式主要是通过:udevice对应的driver的id和uclass对应的uclass_driver的id是否匹配。
  • udevice会和uclass绑定。driver会和udevice绑定。uclass_driver会和uclass绑定。

4、GD中和DM相关的部分

 typedef struct global_data { #ifdef CONFIG_DM struct udevice *dm_root; /* Root instance for Driver Model */ // DM中的根设备,也是uboot中第一个创建的udevice,也就对应了dts里的根节点。 struct udevice *dm_root_f; /* Pre-relocation root instance */ // 在relocation之前DM中的根设备 struct list_head uclass_root; /* Head of core tree */ // uclass链表,所有被udevice匹配的uclass都会被挂载到这个链表上 #endif } gd_t;

三、DM四个主要组成部分详细介绍

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

0、uclass id

每一种uclass都有自己对应的ID号。定义于其uclass_driver中。其附属的udevice的driver中的uclass id必须与其一致。
所有uclass id定义于include/dm/uclass-id.h中
列出部分id如下




enum uclass_id { /* These are used internally by driver model */ UCLASS_ROOT = 0, UCLASS_DEMO, UCLASS_CLK, /* Clock source, e.g. used by peripherals */ UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */ UCLASS_SERIAL, /* Serial UART */ }

1、uclass

  • (1)数据结构
struct uclass { void *priv; // uclass的私有数据指针 struct uclass_driver *uc_drv; // 对应的uclass driver struct list_head dev_head; // 链表头,连接所属的所有udevice struct list_head sibling_node; // 链表节点,用于把uclass连接到uclass_root链表上 };
  • (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实现了这个功能。
    有如下API:






int uclass_get(enum uclass_id key, struct uclass ucp); // 从gd->uclass_root链表获取对应的uclass

2、uclass_driver

  • (1)数据结构
    include/dm/uclass.h

struct uclass_driver { const char *name; // 该uclass_driver的命令 enum uclass_id id; // 对应的uclass id /* 以下函数指针主要是调用时机的区别 */ int (*post_bind)(struct udevice *dev); // 在udevice被绑定到该uclass之后调用 int (*pre_unbind)(struct udevice *dev); // 在udevice被解绑出该uclass之前调用 int (*pre_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之前调用 int (*post_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之后调用 int (*pre_remove)(struct udevice *dev);// 在该uclass的一个udevice进行remove之前调用 int (*child_post_bind)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备被绑定到该udevice之后调用 int (*child_pre_probe)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备进行probe之前调用 int (*init)(struct uclass *class); // 安装该uclass的时候调用 int (*destroy)(struct uclass *class); // 销毁该uclass的时候调用 int priv_auto_alloc_size; // 需要为对应的uclass分配多少私有数据 int per_device_auto_alloc_size; // int per_device_platdata_auto_alloc_size; // int per_child_auto_alloc_size; // int per_child_platdata_auto_alloc_size; // const void *ops; //操作集合 uint32_t flags; // 标识为 };
  • (2)如何定义
    通过UCLASS_DRIVER来定义uclass_driver.
    以serial-uclass为例




UCLASS_DRIVER(serial) = { .id = UCLASS_SERIAL, .name = "serial", .flags = DM_UC_FLAG_SEQ_ALIAS, .post_probe = serial_post_probe, .pre_remove = serial_pre_remove, .per_device_auto_alloc_size = sizeof(struct serial_dev_priv), };

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))) 关于ll_entry_declare我们在《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》已经介绍过了

最终得到一个如下结构体

struct uclass_driver _u_boot_list_2_uclass_2_serial = { .id = UCLASS_SERIAL, // 设置对应的uclass id .name = "serial", .flags = DM_UC_FLAG_SEQ_ALIAS, .post_probe = serial_post_probe, .pre_remove = serial_pre_remove, .per_device_auto_alloc_size = sizeof(struct serial_dev_priv), }

并且存放在.u_boot_list_2_uclass_2_serial段中。

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




.u_boot_list_2_uclass_1 0x23e368e0 0x0 drivers/built-in.o .u_boot_list_2_uclass_2_gpio 0x23e368e0 0x48 drivers/gpio/built-in.o 0x23e368e0 _u_boot_list_2_uclass_2_gpio // gpio uclass driver的符号 .u_boot_list_2_uclass_2_root 0x23e36928 0x48 drivers/built-in.o 0x23e36928 _u_boot_list_2_uclass_2_root // root uclass drvier的符号 .u_boot_list_2_uclass_2_serial 0x23e36970 0x48 drivers/serial/built-in.o 0x23e36970 _u_boot_list_2_uclass_2_serial // serial uclass driver的符号 .u_boot_list_2_uclass_2_simple_bus 0x23e369b8 0x48 drivers/built-in.o 0x23e369b8 _u_boot_list_2_uclass_2_simple_bus .u_boot_list_2_uclass_3 0x23e36a00 0x0 drivers/built-in.o 0x23e36a00 . = ALIGN (0x4)
  • (4)如何获取、API
    想要获取uclass_driver需要先获取uclass_driver table。
    可以通过以下宏来获取uclass_driver table




 struct uclass_driver *uclass = ll_entry_start(struct uclass_driver, uclass); // 会根据.u_boot_list_2_uclass_1的段地址来得到uclass_driver table的地址 const int n_ents = ll_entry_count(struct uclass_driver, uclass); // 获得uclass_driver table的长度
struct uclass_driver *lists_uclass_lookup(enum uclass_id id) // 从uclass_driver table中获取uclass id为id的uclass_driver。

3、udevice

  • (1)数据结构
    include/dm/device.h

struct udevice { const struct driver *driver; // 该udevice对应的driver const char *name; // 设备名 void *platdata; // 该udevice的平台数据 void *parent_platdata; // 提供给父设备使用的平台数据 void *uclass_platdata; // 提供给所属uclass使用的平台数据 int of_offset; // 该udevice的dtb节点偏移,代表了dtb里面的这个节点node ulong driver_data; // 驱动数据 struct udevice *parent; // 父设备 void *priv; // 私有数据的指针 struct uclass *uclass; // 所属uclass void *uclass_priv; // 提供给所属uclass使用的私有数据指针 void *parent_priv; // 提供给其父设备使用的私有数据指针 struct list_head uclass_node; // 用于连接到其所属uclass的链表上 struct list_head child_head; // 链表头,连接其子设备 struct list_head sibling_node; // 用于连接到其父设备的链表上 uint32_t flags; // 标识 int req_seq; int seq; #ifdef CONFIG_DEVRES struct list_head devres_head; #endif };
  • (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

#define uclass_foreach_dev(pos, uc) \ list_for_each_entry(pos, &uc->dev_head, uclass_node) #define uclass_foreach_dev_safe(pos, next, uc) \ list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node) int uclass_get_device(enum uclass_id id, int index, struct udevice devp); // 通过索引从uclass中获取udevice int uclass_get_device_by_name(enum uclass_id id, const char *name, // 通过设备名从uclass中获取udevice struct udevice devp); int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice devp); int uclass_get_device_by_of_offset(enum uclass_id id, int node, struct udevice devp); int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent, const char *name, struct udevice devp); int uclass_first_device(enum uclass_id id, struct udevice devp); int uclass_first_device_err(enum uclass_id id, struct udevice devp); int uclass_next_device(struct udevice devp); int uclass_resolve_seq(struct udevice *dev);

4、driver

和uclass_driver方式是相似的。

  • (1)数据结构
    include/dm/device.h

struct driver { char *name; // 驱动名 enum uclass_id id; // 对应的uclass id const struct udevice_id *of_match; // compatible字符串的匹配表,用于和device tree里面的设备节点匹配 int (*bind)(struct udevice *dev); // 用于绑定目标设备到该driver中 int (*probe)(struct udevice *dev); // 用于probe目标设备,激活 int (*remove)(struct udevice *dev); // 用于remove目标设备。禁用 int (*unbind)(struct udevice *dev); // 用于解绑目标设备到该driver中 int (*ofdata_to_platdata)(struct udevice *dev); // 在probe之前,解析对应udevice的dts节点,转化成udevice的平台数据 int (*child_post_bind)(struct udevice *dev); // 如果目标设备的一个子设备被绑定之后,调用 int (*child_pre_probe)(struct udevice *dev); // 在目标设备的一个子设备被probe之前,调用 int (*child_post_remove)(struct udevice *dev); // 在目标设备的一个子设备被remove之后,调用 int priv_auto_alloc_size; //需要分配多少空间作为其udevice的私有数据 int platdata_auto_alloc_size; //需要分配多少空间作为其udevice的平台数据 int per_child_auto_alloc_size; // 对于目标设备的每个子设备需要分配多少空间作为父设备的私有数据 int per_child_platdata_auto_alloc_size; // 对于目标设备的每个子设备需要分配多少空间作为父设备的平台数据 const void *ops; /* driver-specific operations */ // 操作集合的指针,提供给uclass使用,没有规定操作集的格式,由具体uclass决定 uint32_t flags; // 一些标志位 };
  • (2)如何定义
    通过U_BOOT_DRIVER来定义一个driver
    以s5pv210为例:
    driver/serial/serial_s5p.c






U_BOOT_DRIVER(serial_s5p) = { .name = "serial_s5p", .id = UCLASS_SERIAL, .of_match = s5p_serial_ids, .ofdata_to_platdata = s5p_serial_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata), .probe = s5p_serial_probe, .ops = &s5p_serial_ops, .flags = DM_FLAG_PRE_RELOC, };

U_BOOT_DRIVER实现如下:

#define U_BOOT_DRIVER(__name) \ ll_entry_declare(struct driver, __name, driver) #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))) 关于ll_entry_declare我们在《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》已经介绍过了

最终得到如下一个结构体

struct driver _u_boot_list_2_driver_2_serial_s5p= { .name = "serial_s5p", .id = UCLASS_SERIAL, .of_match = s5p_serial_ids, .ofdata_to_platdata = s5p_serial_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata), .probe = s5p_serial_probe, .ops = &s5p_serial_ops, .flags = DM_FLAG_PRE_RELOC, };

并且存放在.u_boot_list_2_driver_2_serial_s5p段中

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




 .u_boot_list_2_driver_1 0x23e36754 0x0 drivers/built-in.o .u_boot_list_2_driver_2_gpio_exynos 0x23e36754 0x44 drivers/gpio/built-in.o 0x23e36754 _u_boot_list_2_driver_2_gpio_exynos .u_boot_list_2_driver_2_root_driver 0x23e36798 0x44 drivers/built-in.o 0x23e36798 _u_boot_list_2_driver_2_root_driver .u_boot_list_2_driver_2_serial_s5p 0x23e367dc 0x44 drivers/serial/built-in.o 0x23e367dc _u_boot_list_2_driver_2_serial_s5p .u_boot_list_2_driver_2_simple_bus_drv 0x23e36820 0x44 drivers/built-in.o 0x23e36820 _u_boot_list_2_driver_2_simple_bus_drv .u_boot_list_2_driver_3 0x23e36864 0x0 drivers/built-in.o
  • (4)如何获取、API
    想要获取driver需要先获取driver table。
    可以通过以下宏来获取driver table




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

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

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

四、DM的一些API整理

先看一下前面一节理解一下。

1、uclass相关API

int uclass_get(enum uclass_id key, struct uclass ucp); // 从gd->uclass_root链表获取对应的uclass 

2、uclass_driver相关API

struct uclass_driver *lists_uclass_lookup(enum uclass_id id) // 从uclass_driver table中获取uclass id为id的uclass_driver。

3、udevice相关API

#define uclass_foreach_dev(pos, uc) \ list_for_each_entry(pos, &uc->dev_head, uclass_node) #define uclass_foreach_dev_safe(pos, next, uc) \ list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node) int device_bind(struct udevice *parent, const struct driver *drv, const char *name, void *platdata, int of_offset, struct udevice devp) // 初始化一个udevice,并将其与其uclass、driver绑定。 int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, const struct driver_info *info, struct udevice devp) // 通过name获取driver并且调用device_bind对udevice初始化,并将其与其uclass、driver绑定。 int uclass_bind_device(struct udevice *dev) // 绑定udevice到其对应的uclass的设备链表中 { uc = dev->uclass; list_add_tail(&dev->uclass_node, &uc->dev_head); } int uclass_get_device(enum uclass_id id, int index, struct udevice devp); // 通过索引从uclass中获取udevice,注意,在获取的过程中就会对设备进行probe int uclass_get_device_by_name(enum uclass_id id, const char *name, // 通过设备名从uclass中获取udevice struct udevice devp); int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice devp); int uclass_get_device_by_of_offset(enum uclass_id id, int node, struct udevice devp); int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent, const char *name, struct udevice devp); int uclass_first_device(enum uclass_id id, struct udevice devp); int uclass_first_device_err(enum uclass_id id, struct udevice devp); int uclass_next_device(struct udevice devp); int uclass_resolve_seq(struct udevice *dev);

4、driver相关API

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

五、uboot 设备的表达

1、说明

uboot中可以通过两种方法来添加设备

  • 通过直接定义平台设备(这种方式基本上不使用)
  • 通过在设备树添加设备信息

注意:这里只是设备的定义,最终还是会被uboot解析成udevice结构体的。

2、直接定义平台设备(这种方式除了根设备外基本上不使用)

U_BOOT_DEVICE(overo_uart) = { "ns16550_serial", &overo_serial };
#define U_BOOT_DEVICE(__name) \ ll_entry_declare(struct driver_info, __name, driver_info) /* Declare a list of devices. The argument is a driver_info[] array */ #define U_BOOT_DEVICES(__name) \ ll_entry_declare_list(struct driver_info, __name, driver_info)
static const struct driver_info root_info = { .name = "root_driver", };

3、在设备树添加设备信息

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

dts的内容这里不多说了。

六、uboot DM的初始化

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函数的调用

2、入口说明

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

static int initf_dm(void) { #if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN) int ret; ret = dm_init_and_scan(true); // 调用dm_init_and_scan对DM进行初始化和设备的解析 if (ret) return ret; #endif return 0; } #ifdef CONFIG_DM static int initr_dm(void) { int ret; /* Save the pre-reloc driver model and start a new one */ gd->dm_root_f = gd->dm_root; // 存储relocate之前的根设备 gd->dm_root = NULL; ret = dm_init_and_scan(false); // 调用dm_init_and_scan对DM进行初始化和设备的解析 if (ret) return ret; return 0; } #endif

2、dm_init_and_scan说明

driver/core/root.c

int dm_init_and_scan(bool pre_reloc_only) { int ret; ret = dm_init(); // DM的初始化 if (ret) { debug("dm_init() failed: %d\n", ret); return ret; } ret = dm_scan_platdata(pre_reloc_only); // 从平台设备中解析udevice和uclass if (ret) { debug("dm_scan_platdata() failed: %d\n", ret); return ret; } if (CONFIG_IS_ENABLED(OF_CONTROL)) { ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); // 从dtb中解析udevice和uclass if (ret) { debug("dm_scan_fdt() failed: %d\n", ret); return ret; } } ret = dm_scan_other(pre_reloc_only); if (ret) return ret; return 0; }

3、DM的初始化——dm_init

#define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root) // 宏定义根设备指针gd->dm_root #define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root) // 宏定义gd->uclass_root,uclass的链表 int dm_init(void) { int ret; if (gd->dm_root) { // 根设备已经存在,说明DM已经初始化过了 dm_warn("Virtual root driver already exists!\n"); return -EINVAL; } INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST); // 初始化uclass链表 ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST); // DM_ROOT_NON_CONST是指根设备udevice,root_info是表示根设备的设备信息 // device_bind_by_name会查找和设备信息匹配的driver,然后创建对应的udevice和uclass并进行绑定,最后放在DM_ROOT_NON_CONST中。 // device_bind_by_name后续我们会进行说明,这里我们暂时只需要了解root根设备的udevice以及对应的uclass都已经创建完成。 if (ret) return ret; #if CONFIG_IS_ENABLED(OF_CONTROL) DM_ROOT_NON_CONST->of_offset = 0; #endif ret = device_probe(DM_ROOT_NON_CONST); // 对根设备执行probe操作, // device_probe后续再进行说明 if (ret) return ret; return 0; }

4、从平台设备中解析udevice和uclass——dm_scan_platdata

跳过。

5、从dtb中解析udevice和uclass——dm_scan_fdt

 int dm_scan_fdt(const void *blob, bool pre_reloc_only) // 此时传进来的参数blob=gd->fdt_blob, pre_reloc_only=0 { return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only); // 直接调用dm_scan_fdt_node } int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset, bool pre_reloc_only) // 此时传进来的参数 // parent=gd->dm_root,表示以root设备作为父设备开始解析 // blob=gd->fdt_blob,指定了对应的dtb // offset=0,从偏移0的节点开始扫描 // pre_reloc_only=0,不只是解析relotion之前的设备 { int ret = 0, err; /* 以下步骤相当于是遍历每一个dts节点并且调用lists_bind_fdt对其进行解析 */ for (offset = fdt_first_subnode(blob, offset); // 获得blob设备树的offset偏移下的节点的第一个子节点 offset > 0; offset = fdt_next_subnode(blob, offset)) { // 循环查找下一个子节点 if (!fdtdec_get_is_enabled(blob, offset)) { // 判断节点状态是否是disable,如果是的话直接忽略 dm_dbg(" - ignoring disabled device\n"); continue; } err = lists_bind_fdt(parent, blob, offset, NULL); // 解析绑定这个节点,dm_scan_fdt的核心,下面具体分析 if (err && !ret) { ret = err; debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL), ret); } } return ret; }

lists_bind_fdt是从dtb中解析udevice和uclass的核心
其具体实现如下:
driver/core/lists.c




int lists_bind_fdt(struct udevice *parent, const void *blob, int offset, struct udevice devp) // parent指定了父设备,通过blob和offset可以获得对应的设备的dts节点,对应udevice结构通过devp返回 { struct driver *driver = ll_entry_start(struct driver, driver); // 获取driver table地址 const int n_ents = ll_entry_count(struct driver, driver); // 获取driver table长度 const struct udevice_id *id; struct driver *entry; struct udevice *dev; bool found = false; const char *name; int result = 0; int ret = 0; dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL)); // 打印当前解析的节点的名称 if (devp) *devp = NULL; for (entry = driver; entry != driver + n_ents; entry++) { // 遍历driver table中的所有driver,具体参考三、4一节 ret = driver_check_compatible(blob, offset, entry->of_match, &id); // 判断driver中的compatibile字段和dts节点是否匹配 name = fdt_get_name(blob, offset, NULL); // 获取节点名称 if (ret == -ENOENT) { continue; } else if (ret == -ENODEV) { dm_dbg("Device '%s' has no compatible string\n", name); break; } else if (ret) { dm_warn("Device tree error at offset %d\n", offset); result = ret; break; } dm_dbg(" - found match at '%s'\n", entry->name); ret = device_bind(parent, entry, name, NULL, offset, &dev); // 找到对应的driver,调用device_bind进行绑定,会在这个函数中创建对应udevice和uclass并切进行绑定,后面继续说明 if (ret) { dm_warn("Error binding driver '%s': %d\n", entry->name, ret); return ret; } else { dev->driver_data = id->data; found = true; if (devp) *devp = dev; // 将udevice设置到devp指向的地方中,进行返回 } break; } if (!found && !result && ret != -ENODEV) { dm_dbg("No match for node '%s'\n", fdt_get_name(blob, offset, NULL)); } return result; }
int device_bind(struct udevice *parent, const struct driver *drv, const char *name, void *platdata, int of_offset, struct udevice devp) // parent:父设备 // drv:设备对应的driver // name:设备名称 // platdata:设备的平台数据指针 // of_offset:在dtb中的偏移,即代表了其dts节点 // devp:所创建的udevice的指针,用于返回 { struct udevice *dev; struct uclass *uc; int size, ret = 0; ret = uclass_get(drv->id, &uc); // 获取driver id对应的uclass,如果uclass原先并不存在,那么会在这里创建uclass并其uclass_driver进行绑定 dev = calloc(1, sizeof(struct udevice)); // 分配一个udevice dev->platdata = platdata; // 设置udevice的平台数据指针 dev->name = name; // 设置udevice的name dev->of_offset = of_offset; // 设置udevice的dts节点偏移 dev->parent = parent; // 设置udevice的父设备 dev->driver = drv; // 设置udevice的对应的driver,相当于driver和udevice的绑定 dev->uclass = uc; // 设置udevice的所属uclass dev->seq = -1; dev->req_seq = -1; if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) { /* * Some devices, such as a SPI bus, I2C bus and serial ports * are numbered using aliases. * * This is just a 'requested' sequence, and will be * resolved (and ->seq updated) when the device is probed. */ if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) { if (uc->uc_drv->name && of_offset != -1) { fdtdec_get_alias_seq(gd->fdt_blob, uc->uc_drv->name, of_offset, &dev->req_seq); } // 设置udevice的alias请求序号 } } if (!dev->platdata && drv->platdata_auto_alloc_size) { dev->flags |= DM_FLAG_ALLOC_PDATA; dev->platdata = calloc(1, drv->platdata_auto_alloc_size); // 为udevice分配平台数据的空间,由driver中的platdata_auto_alloc_size决定 } size = uc->uc_drv->per_device_platdata_auto_alloc_size; if (size) { dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA; dev->uclass_platdata = calloc(1, size); // 为udevice分配给其所属uclass使用的平台数据的空间,由所属uclass的driver中的per_device_platdata_auto_alloc_size决定 } /* put dev into parent's successor list */ if (parent) list_add_tail(&dev->sibling_node, &parent->child_head); // 添加到父设备的子设备链表中 ret = uclass_bind_device(dev); // uclass和udevice进行绑定,主要是实现了将udevice链接到uclass的设备链表中 /* if we fail to bind we remove device from successors and free it */ if (drv->bind) { ret = drv->bind(dev); // 执行udevice对应driver的bind函数 } if (parent && parent->driver->child_post_bind) { ret = parent->driver->child_post_bind(dev); // 执行父设备的driver的child_post_bind函数 } if (uc->uc_drv->post_bind) { ret = uc->uc_drv->post_bind(dev); if (ret) goto fail_uclass_post_bind; // 执行所属uclass的post_bind函数 } if (devp) *devp = dev; // 将udevice进行返回 dev->flags |= DM_FLAG_BOUND; // 设置已经绑定的标志 // 后续可以通过dev->flags & DM_FLAG_ACTIVATED或者device_active宏来判断设备是否已经被激活 return 0;

七、DM工作流程

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

1、device_probe

driver/core/device.c

int device_probe(struct udevice *dev) { const struct driver *drv; int size = 0; int ret; int seq; if (dev->flags & DM_FLAG_ACTIVATED) return 0; // 表示这个设备已经被激活了 drv = dev->driver; assert(drv); // 获取这个设备对应的driver /* Allocate private data if requested and not reentered */ if (drv->priv_auto_alloc_size && !dev->priv) { dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags); // 为设备分配私有数据 } /* Allocate private data if requested and not reentered */ size = dev->uclass->uc_drv->per_device_auto_alloc_size; if (size && !dev->uclass_priv) { dev->uclass_priv = calloc(1, size); // 为设备所属uclass分配私有数据 } // 这里过滤父设备的probe seq = uclass_resolve_seq(dev); if (seq < 0) { ret = seq; goto fail; } dev->seq = seq; dev->flags |= DM_FLAG_ACTIVATED; // 设置udevice的激活标志 ret = uclass_pre_probe_device(dev); // uclass在probe device之前的一些函数的调用 if (drv->ofdata_to_platdata && dev->of_offset >= 0) { ret = drv->ofdata_to_platdata(dev); // 调用driver中的ofdata_to_platdata将dts信息转化为设备的平台数据 } if (drv->probe) { ret = drv->probe(dev); // 调用driver的probe函数,到这里设备才真正激活了 } ret = uclass_post_probe_device(dev); return ret; } 

主要工作归纳如下:

  • 分配设备的私有数据
  • 对父设备进行probe
  • 执行probe device之前uclass需要调用的一些函数
  • 调用driver的ofdata_to_platdata,将dts信息转化为设备的平台数据
  • 调用driver的probe函数
  • 执行probe device之后uclass需要调用的一些函数

2、通过uclass来获取一个udevice并且进行probe

int uclass_get_device(enum uclass_id id, int index, struct udevice devp) //通过索引从uclass的设备链表中获取udevice,并且进行probe int uclass_get_device_by_name(enum uclass_id id, const char *name, struct udevice devp) //通过设备名从uclass的设备链表中获取udevice,并且进行probe int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice devp) //通过序号从uclass的设备链表中获取udevice,并且进行probe int uclass_get_device_by_of_offset(enum uclass_id id, int node, struct udevice devp) //通过dts节点的偏移从uclass的设备链表中获取udevice,并且进行probe int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent, const char *name, struct udevice devp) //通过设备的“phandle”属性从uclass的设备链表中获取udevice,并且进行probe int uclass_first_device(enum uclass_id id, struct udevice devp) //从uclass的设备链表中获取第一个udevice,并且进行probe int uclass_next_device(struct udevice devp) //从uclass的设备链表中获取下一个udevice,并且进行probe
int uclass_get_device(enum uclass_id id, int index, struct udevice devp) { struct udevice *dev; int ret; *devp = NULL; ret = uclass_find_device(id, index, &dev); //通过索引从uclass的设备链表中获取对应的udevice return uclass_get_device_tail(dev, ret, devp); // 调用uclass_get_device_tail进行设备的get,最终会调用device_probe来对设备进行probe } int uclass_get_device_tail(struct udevice *dev, int ret, struct udevice devp) { ret = device_probe(dev); // 调用device_probe对设备进行probe,这个函数在前面说明过了 if (ret) return ret; *devp = dev; return 0; }

3、工作流程简单说明

serial-uclass较为简单,我们以serial-uclass为例

  • (0)代码支持
    < 1 > serial-uclass.c中定义一个uclass_driver

UCLASS_DRIVER(serial) = { .id = UCLASS_SERIAL, //注意这里的uclass id .name = "serial", .flags = DM_UC_FLAG_SEQ_ALIAS, .post_probe = serial_post_probe, .pre_remove = serial_pre_remove, .per_device_auto_alloc_size = sizeof(struct serial_dev_priv), };

< 2 > 定义s5pv210的serial的dts节点

 serial@e { compatible = "samsung,exynos4210-uart"; //注意这里的compatible  reg = <0xe 0x100>; interrupts = <0 51 0>; id = <0>; }; 

< 3 > 定义设备驱动

U_BOOT_DRIVER(serial_s5p) = { .name = "serial_s5p", .id = UCLASS_SERIAL, //注意这里的uclass id .of_match = s5p_serial_ids, .ofdata_to_platdata = s5p_serial_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata), .probe = s5p_serial_probe, .ops = &s5p_serial_ops, .flags = DM_FLAG_PRE_RELOC, }; static const struct udevice_id s5p_serial_ids[] = { { .compatible = "samsung,exynos4210-uart" }, //注意这里的compatible  { } };
  • (1)udevice和对应uclass的创建
    在DM初始化的过程中uboot自己创建对应的udevice和uclass。
    具体参考“六、uboot DM的初始化”




  • (2)udevice和对应uclass的绑定
    在DM初始化的过程中uboot自己实现将udevice绑定到对应的uclass中。
    具体参考“六、uboot DM的初始化”




  • (3)对应udevice的probe
    由模块自己实现。例如serial则需要在serial的初始化过程中,选择需要的udevice进行probe。
    serial-uclass只是操作作为console的serial,并不具有通用性,这里简单的了解下。
    代码如下,过滤掉无关代码
    driver/serial/serial-uclass.c








int serial_init(void) { serial_find_console_or_panic(); // 调用serial_find_console_or_panic进行作为console的serial的初始化 gd->flags |= GD_FLG_SERIAL_READY; return 0; } static void serial_find_console_or_panic(void) { const void *blob = gd->fdt_blob; struct udevice *dev; int node; if (CONFIG_IS_ENABLED(OF_CONTROL) && blob) { /* Check for a chosen console */ // 这里过滤掉获取指定的serial的dts节点的代码 if (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node, &dev)) { // 这里调用uclass_get_device_by_of_offset,通过dts节点的偏移从uclass的设备链表中获取udevice,并且进行probe。 // 注意,是在这里完成设备的probe的!!! gd->cur_serial_dev = dev; // 将udevice存储在gd->cur_serial_dev,后续uclass中可以直接通过gd->cur_serial_dev获取到对应的设备并且进行操作 // 但是注意,这种并不是通用做法!!! return; } } }
  • (4)uclass的接口调用
    • 可以通过先从root_uclass链表中提取对应的uclass,然后通过uclass->uclass_driver->ops来进行接口调用,这种方法比较具有通用性。
    • 可以通过调用uclass直接expert的接口,不推荐,但是serial-uclass使用的是这种方式。
      这部分应该属于serial core,但是也放在了serial-uclass.c中实现。
      以serial_putc调用为例,serial-uclass使用如下:





void serial_putc(char ch) { if (gd->cur_serial_dev) _serial_putc(gd->cur_serial_dev, ch);// 将console对应的serial的udevice作为参数传入 } static void _serial_putc(struct udevice *dev, char ch) { struct dm_serial_ops *ops = serial_get_ops(dev);// 获取设备对应的driver函数的ops操作集 int err; do { err = ops->putc(dev, ch); // 以udevice为参数,调用ops中对应的操作函数 } while (err == -EAGAIN); }

到此整个流程简单介绍到这。

这里几乎都是纸上谈兵,后续会来一篇gpio-uclass的使用实战。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/206205.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午4:13
下一篇 2026年3月19日 下午4:13


相关推荐

  • 国产数据库乱象_四代户户通怎么开户

    国产数据库乱象_四代户户通怎么开户其实这篇文章是我周末开始写的,写这篇文章的这个周末,我的很多时候都是在思考一个数据库国产化替代的建设方案,翻阅了大量的资料。今年正好是我参加工作后的第31个年头,工作的最初十年,我写了十年代码,从汇编、COBOL到C语言,写了几十万行代码;随后的十几年,我一直在帮助用户用好数据库,也在帮助Oracle推广RAC技术;2015年开始,我一边继续从事数据库优化的工作,一边在帮助客户如何从Oracle迁移到成本更低的数据库系统上。所以对国产数据库我一直有一种十分特殊的情感,这是一种爱恨交织的情感。所以今天最后用“

    2026年1月27日
    5
  • 突发!马化腾发朋友圈,腾讯发出一个绝密大信号!

    突发!马化腾发朋友圈,腾讯发出一个绝密大信号!

    2026年3月12日
    3
  • python2 nonlocal_python unboundlocalerror

    python2 nonlocal_python unboundlocalerrornonlocal关键字用来在函数或其他作用域中使用并修改外层(非全局)变量。意义:nonlocal使用能够弥补global和闭包的两个问题。对于global,只能使用全局变量,对于嵌套函数中的内层函数而言,无法通过global使用外层函数,通过nonlocal就可以,当然直接读取也可以(闭包)。对于闭包,内层函数可以读取外层函数的变量,但是如果在内部函数中尝试进行修改外部变量,且外部变量为不可变类型,则需要在变量前加nonlocal,如果变量为可变类型,则不需要添加nonlocal。”’nonl

    2025年9月21日
    9
  • KindEditor是一套很方便的html编译器插件

    KindEditor是一套很方便的html编译器插件

    2021年11月5日
    60
  • formData原生实现图片上传

    formData原生实现图片上传

    2022年2月11日
    53
  • Python 打开文件对话框「建议收藏」

    Python 打开文件对话框「建议收藏」以下内容来自http://interactivepython.org/runestone/static/thinkcspy/GUIandEventDrivenProgramming/02_standard_dialog_boxes.html#file-chooserimporttkinterastkfromtkinterimportfiledialogimportosa…

    2022年8月30日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号