范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

超详细Uboot驱动开发(三)Uboot驱动模型

  #头条创作挑战赛#
  全文耗时一周,精心汇总20000余字,希望对大家有所帮助,感觉可以的点赞,关注,不迷路,后续还有更多干货!
  看文章前,答应我,静下心来,慢慢品!  文章目录3.1、什么是Uboot驱动模型3.2、为什么要有驱动模型呢3.3、如何使用uboot的DM模型①:menuconfig配置全局DM模型②:指定某个驱动的DM模型3.4、DM模型数据结构① global_data② uclass③ uclass_driver④ uclass_id⑤ udevice⑥ driver3.5、DM驱动模型之上帝视角3.6、DM模型——Udevice与driver绑定① dm_init② lists_bind_fdt3.7、DM模型——probe探测函数的执行3.8、DM模型——uclass与uclass_driver绑定3.9参考文档
  3.1、什么是Uboot驱动模型
  学过Linux的朋友基本都知道Linux的设备驱动模型,Uboot根据Linux的驱动模型架构,也引入了Uboot的驱动模型( driver model :DM )。
  这种驱动模型为驱动的定义和访问接口提供了统一的方法 。提高了驱动之间的兼容性以及访问的标准型,uboot驱动模型和kernel中的设备驱动模型类似。 3.2、为什么要有驱动模型呢
  无论是Linux还是Uboot,一个新对象的产生必定有其要解决的问题,驱动模型也不例外!  提高代码的可重用性 :为了能够使代码在不同硬件平台,不同体系架构下运行,必须要最大限度的提高代码的可重用性。 高内聚,低耦合 :分层的思想也是为了达到这一目标,低耦合体现在对外提供统一的抽象访问接口,高内聚将相关度紧密的集中抽象实现。 便于管理 :在不断发展过程中,硬件设备越来越多,驱动程序也越来越多,为了更好的管理驱动,也需要一套优秀的驱动架构! 3.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-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o  ,来判断是否将驱动模型加入到编译选项中。
  总之,我们要打开DM模型,最后反映在几个配置信息上: CONFIG_DM=y  ,全局DM模型打开CONFIG_DM_XXX=y  ,某个驱动的DM模型的打开可以通过 Kconifg  、Makefile  来查看对应宏的编译情况
  3.4、DM模型数据结构
  要想了解DM模型整套驱动框架,我们必须先了解它的一砖一瓦!也就是组成驱动框架的各个数据结构。 ① global_datatypedef struct global_data { ... #ifdef CONFIG_DM 	struct udevice	*dm_root;	/* Root instance for Driver Model */ 	struct udevice	*dm_root_f;	/* Pre-relocation root instance */ 	struct list_head uclass_root;	/* Head of core tree */ #endif ... }
  global_data  ,管理着整个Uboot的全局变量,其中dm_root  ,dm_root_f  ,uclass_root  用来管理整个DM模型。这几个变量代表什么意思呢?dm_root  :DM模型的根设备dm_root_f  :重定向前的根设备uclass_root  :uclass  链表的头
  这几个变量,最终要的作用就是:管理整个模型中的 udevice  设备信息和uclass  驱动类。② uclass
  我们首先看一下 uclass  这个结构体/**  * struct uclass - a U-Boot drive class, collecting together similar drivers  *  * A uclass provides an interface to a particular function, which is  * implemented by one or more drivers. Every driver belongs to a uclass even  * if it is the only driver in that uclass. An example uclass is GPIO, which  * provides the ability to change read inputs, set and clear outputs, etc.  * There may be drivers for on-chip SoC GPIO banks, I2C GPIO expanders and  * PMIC IO lines, all made available in a unified way through the uclass.  *  * @priv: Private data for this uclass  * @uc_drv: The driver for the uclass itself, not to be confused with a  * "struct driver"  * @dev_head: List of devices in this uclass (devices are attached to their  * uclass when their bind method is called)  * @sibling_node: Next uclass in the linked list of uclasses  */ struct uclass { 	void *priv;								//uclass的私有数据 	struct uclass_driver *uc_drv;			//uclass类的操作函数集合 	struct list_head dev_head;				//该uclass的所有设备 	struct list_head sibling_node;			//下一个uclass的节点 };
  根据注释,我们就可以了解到, uclass  相当于老师,管理着对应某一个类别下  的所有的udevice  。例如:一个IIC驱动程序,其驱动程序框架是一致的,只有一种,但是IIC驱动的设备可以有很多,如EEPROM,MCU6050等;
  所有在这里呢,  dev_head  链表就是用来管理该驱动类下的所有的设备。
  总结: uclass  ,来管理该类型下的所有设备,并且有对应的uclass_driver  驱动。定义
  uclass  是uboot  自动生成的,并且不是所有uclass  都会生成,有对应uclass_driver  并且有被udevice  匹配到的uclass  才会生成。存放
  所有生成的uclass都会被挂载 gd->uclass_root  链表上。相关API
  直接遍历链表  gd->uclass_root  链表并且根据uclass_id  来获取到相应的uclass  。int uclass_get(enum uclass_id key, struct uclass **ucp); // 从gd->uclass_root链表获取对应的ucla ss ③ uclass_driver
  正如上面,我们看到了 uclass  类所包含uclass_driver  结构体,uclass_driver  正如其名,它就是uclass  的驱动程序。其主要作用是:为uclass  提供统一管理的接口,结构体如下:/**  * struct uclass_driver - Driver for the uclass  *  * A uclass_driver provides a consistent interface to a set of related  * drivers.  */ 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;   // 标识为 }; 定义
  uclass_driver  主要通过UCLASS_DRIVER  来定义,这里就简单说明一下底层代码,耐心看哦!
  下面以pinctrl为例  UCLASS_DRIVER(pinctrl) = { 	.id = UCLASS_PINCTRL, 	.post_bind = pinctrl_post_bind, 	.flags = DM_UC_FLAG_SEQ_ALIAS, 	.name = "pinctrl", }; /* Declare a new 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)))
  上面基本上就是我们的底层代码了,稍微有点绕,但是也不难!我们只需要将宏进行替换就行了!
  通过上面的定义,我们替换掉宏之后,最终得到的定义如下: struct uclass_driver _u_boot_list_2_uclass_2_pinctrl = { 	.id = UCLASS_PINCTRL, 	.post_bind = pinctrl_post_bind, 	.flags = DM_UC_FLAG_SEQ_ALIAS, 	.name = "pinctrl", } //同时存放在段._u_boot_list_2_uclass_2_pinctrl中,也就是section段的内容 存放
  由上面结构体可得,其定义之后都被存放在了段 ._u_boot_list_2_uclass_2_pinctrl  中,那么去哪里可以看到呢?
  在 u-boot.map  文件中搜索,._u_boot_list_2_uclass_2_pinctrl  ,就可以查到程序中定义的所有驱动程序。
  这里相信大家会有疑问,为什么是 uclass_2  呢?我们大概看一下,也会看到uclass_1  和uclass_3  ,这两个代表什么呢?往下看!
  相关API
  想要获取uclass_driver需要先获取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。
  正如注释描述,上文中提到的 uclass_1  和uclass_3  起到定位作用,用于计算uclass_2  的长度!
  上述的API,主要用于根据 uclass_id  来查找到对应的uclass_driver  ,进而操作对应的uclass  下的udevice  。④ uclass_id
  我们在 uclass_driver  中,看到一个uclass_id  类型,这种类型与uclass  有什么关系呢?
  我们知道, uclass  代表驱动的一个类别,uclass_driver  是uclass  的驱动程序,为uclass  提供统一操作接口。而对于不同类型的驱动,就需要uclass_id  来区分了!
  事实上,每一种类型的设备``uclass 都有唯一对应的  uclass_id,贯穿设备模型,也是  udevice与  uclass`相关联的关键之处。enum uclass_id { 	/* These are used internally by driver model */ 	UCLASS_ROOT = 0, 	UCLASS_DEMO, 	UCLASS_TEST, 	UCLASS_TEST_FDT, 	UCLASS_TEST_BUS, 	UCLASS_TEST_PROBE, ...... 	/* U-Boot uclasses start here - in alphabetical order */ 	UCLASS_ACPI_PMC,	/* (x86) Power-management controller (PMC) */ 	UCLASS_ADC,		/* Analog-to-digital converter */ 	UCLASS_AHCI,		/* SATA disk controller */ 	UCLASS_AUDIO_CODEC,	/* Audio codec with control and data path */ 	UCLASS_AXI,		/* AXI bus */ 	UCLASS_BLK,		/* Block device */ 	UCLASS_BOARD,		/* Device information from hardware */ ...... };
  在这里,我们就把他当作一个设备识别的标志即可!
  最后,压轴的两个结构体出来了,也是DM模型最终操作的对象。  ⑤ udevice/**  * struct udevice - An instance of a driver  *  * This holds information about a device, which is a driver bound to a  * particular port or peripheral (essentially a driver instance).  *  */ struct udevice { 	const struct driver *driver;		//device 对应的driver 	const char *name;					//device 的名称 	void *platdata; 	void *parent_platdata; 	void *uclass_platdata; 	ofnode node;						//设备树节点 	ulong driver_data; 	struct udevice *parent;				//父设备 	void *priv;							// 私有数据的指针 	struct uclass *uclass;				//驱动所属的uclass 	void *uclass_priv; 	void *parent_priv; 	struct list_head uclass_node; 	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 }; 定义 **硬编码:**代码中调用U_BOOT_DEVICE  宏来定义设备资源,实际为一个设备实例。**设备树:**将设备描述信息写在对应的DTS文件中,然后编译成DTB,最终由uboot解析设备树后动态生成的。传参方式:通过命令行或者接口将设备资源信息传递进来,非常灵活。存放
  udevice  是最基础的一个设备单元,我们把它作为一个独立的个体,上层所有的操作,最终都与该结构体有关。
  我们创建一个设备后,为了服从统一的管理,该结构体会被连接到DM模型下,并入到机制中。那么 udevice  会被连接到哪里呢?将 udevice  连接到对应的uclass  中,uclass  主要用来管理着同一类的驱动除此之外,有父子关系的 udevice  ,还会连接到udevice->child_head  链表下,方便调用
  大概可以理解为下面这样:
  相关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);
  这些相关的API,主要作用就是根据 uclass_id  ,查找对应的uclass  ,然后根据索引值或者名称,来查找到对应的udevice  ⑥ driverstruct driver { 	char *name;							//驱动名称 	enum uclass_id id;					//驱动所对应的uclass_id	 	const struct udevice_id *of_match;	//匹配函数 	int (*bind)(struct udevice *dev);	//绑定函数 	int (*probe)(struct udevice *dev);	//注册函数 	int (*remove)(struct udevice *dev); 	int (*unbind)(struct udevice *dev); 	int (*ofdata_to_platdata)(struct udevice *dev); 	int (*child_post_bind)(struct udevice *dev); 	int (*child_pre_probe)(struct udevice *dev); 	int (*child_post_remove)(struct udevice *dev); 	int priv_auto_alloc_size; 	int platdata_auto_alloc_size; 	int per_child_auto_alloc_size; 	int per_child_platdata_auto_alloc_size; 	const void *ops;	/* driver-specific operations */ 	uint32_t flags; #if CONFIG_IS_ENABLED(ACPIGEN) 	struct acpi_ops *acpi_ops; #endif }; 定义
  driver  对象,主要通过U_BOOT_DRIVER  来定义
  以pinctrl来举例  U_BOOT_DRIVER(xxx_pinctrl) = { 	.name		= "xxx_pinctrl", 	.id		= UCLASS_PINCTRL, 	.of_match	= xxx_pinctrl_match, 	.priv_auto_alloc_size = sizeof(struct xxx_pinctrl), 	.ops		= &xxx_pinctrl_ops, 	.probe		= xxx_v2s_pinctrl_probe, 	.remove 	= xxx_v2s_pinctrl_remove, }; /* Declare a new 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)))
  通过上面的定义,最终我们定义的结构体如下: struct driver _u_boot_list_2_driver_2_xxx_pinctrl = { 	.name		= "xxx_pinctrl", 	.id		= UCLASS_PINCTRL, 	.of_match	= xxxx_pinctrl_match, 	.priv_auto_alloc_size = sizeof(struct xxx_pinctrl), 	.ops		= &xxxx_pinctrl_ops, 	.probe		= xxxx_pinctrl_probe, 	.remove 	= xxxx_pinctrl_remove, } //同时存放在段._u_boot_list_2_driver_2_xxx_pinctrl中 存放
  由上面结构体可得,其定义之后都被存放在了段 ._u_boot_list_2_driver_2_xxx  中,那么去哪里可以看到呢?
  在 u-boot.map  文件中搜索,._u_boot_list_2_driver  ,就可以查到程序中定义的所有驱动程序。
  最终,所有driver结构体以列表的形式被放在 .u_boot_list_2_driver_1  和.u_boot_list_2_driver_3  的区间中。相关API   /*先获取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);		// 通过.u_boot_list_2_driver_3的段地址 减去 .u_boot_list_2_driver_1的段地址 获得driver table的长度  /*遍历所有的driver*/ struct driver *lists_driver_lookup_name(const char *name)	// 从driver table中获取名字为name的driver。
  正如注释描述,上文中提到的 driver_1  和driver_3  起到定位作用,用于计算driver_2  的长度!
  上述的API,主要用于根据 name  来查找到对应的driver  驱动程序。
  综上,DM模型相关的数据结构介绍完毕,整体设计的架构如下:
  正如红线部分,如何实现 driver  和udevice  的绑定、uclass  、uclass_driver  的绑定呢?
  要想真正搞懂这些,我们不得不去深入到DM的初始化流程。
  3.5、DM驱动模型之上帝视角
  对于DM模型,我们站在上帝视角来观察整套模型框架是如何的!
  从 对象设计 的角度来看,Uboot的驱动模型可以分为 静态形式和动态形式 。 静态模式 :对象是离散的,和其他对象分隔开,减小对象复杂度,利于模块化设计。 动态模式:运行态表达形式的对象是把所有的静态对象组合成层次视图,有清晰的数据关联视图
  在静态模式下 ,驱动模型主要将对象分为 udevice  和driver  ,即设备和驱动程序,两个就像火车的两条轨道,永远也不会产生交集,驱动和设备可以想注册多少就注册多少。
  我们看一下 udevice  的描述:/**  * struct udevice - An instance of a driver  *  * This holds information about a device, which is a driver bound to a  * particular port or peripheral (essentially a driver instance).  *  */
  udevice  是driver  的一个实例,两个不相交的铁轨,终归也是想要发生爱情的。那么如何让其产生交集呢?这就是动态模式需要做的工作了!
  **在动态模式下,**引入了 uclass  和uclass_driver  两个数据结构,实现了对udevice  和driver  的管理。
  看一下 uclass  和uclass_driver  两个结构体的说明:/**  * struct uclass - a U-Boot drive class, collecting together similar drivers  *  */   /**  * struct uclass_driver - Driver for the uclass  *  * A uclass_driver provides a consistent interface to a set of related  * drivers.  *  */ uclass :设备组公共属性对象,作为 udevice  的一个属性,主要用来管理某个驱动类的所有的设备。uclass_driver :设备组公共行为对象, uclass  的驱动程序,主要将uclass  管理的设备和驱动实现绑定、注册,移除等操作。
  通过这两个结构体的引入,可以将毫不相关的 udevice  是driver  关联起来!
  udevice  与driver  的绑定:通过驱动的of_match  和compatible  属性来配对,绑定。
  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`上!
  整体结构如下:
  3.6、DM模型——Udevice与driver绑定
  相信站在上帝视角看完DM的整体架构,大家都对DM框架有一定了解,下面我们来看看具体的实现细节!
  DM的初始化分为两个部分,一个是在 relocate  重定向之前的初始化:initf_dm  ,一个是在relocate  重定向之后的初始化:initr_dm  。
  我们对比这两个函数: static int initf_dm(void) { #if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN) 	int ret;  	bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f"); 	ret = dm_init_and_scan(true);					//这里为true 	bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F); 	if (ret) 		return ret; #endif #ifdef CONFIG_TIMER_EARLY 	ret = dm_timer_init(); 	if (ret) 		return ret; #endif  	return 0; }  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; 	gd->dm_root = NULL; #ifdef CONFIG_TIMER 	gd->timer = NULL; #endif 	bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r"); 	ret = dm_init_and_scan(false);						//这里为false 	bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R); 	if (ret) 		return ret;  	return 0; }
  两个均调用了 dm_init_and_scan  这个接口,这两个的关键区别在于参数的不同。首先说明一下dts节点中的" u-boot,dm-pre-reloc  "属性,当设置了这个属性时,则表示这个设备在relocate  之前就需要使用。当dm_init_and_scan的参数为 true  时,只会对带有"u-boot,dm-pre-reloc  "属性的节点进行解析。而当参数为false  的时候,则会对所有节点都进行解析。
  DM初始化的大体步骤如下:
  如上程序执行流程图,下面我们详细讲解几个函数。  ① dm_initint dm_init(bool of_live) { 	int ret;  	if (gd->dm_root) { 		dm_warn("Virtual root driver already exists! "); 		return -EINVAL; 	} 	INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);  #if defined(CONFIG_NEEDS_MANUAL_RELOC) 	fix_drivers(); 	fix_uclass(); 	fix_devices(); #endif  	ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);		//查找root_driver驱动,并绑定 	if (ret) 		return ret; #if CONFIG_IS_ENABLED(OF_CONTROL) # if CONFIG_IS_ENABLED(OF_LIVE) 	if (of_live) 		DM_ROOT_NON_CONST->node = np_to_ofnode(gd->of_root); 	else #endif 		DM_ROOT_NON_CONST->node = offset_to_ofnode(0); #endif 	ret = device_probe(DM_ROOT_NON_CONST);										//probe激活root_driver驱动 	if (ret) 		return ret;  	return 0; }
  dm_init  这个函数,名字起的容易让人误导,这个函数主要做的就是初始化了根设备root_driver  ,根据这个跟设备,初始化了global_data  中的dm_root  、uclass_root  。② lists_bind_fdt
  我们通常会使用设备树来定义各种设备,所以这个函数才是主角。
  这个函数主要用来查找子设备,并且根据查找到的子设备,进而查找对应驱动进行绑定!即:实现了 driver  和device  的绑定。int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp, 		   bool pre_reloc_only) { 	struct driver *driver = ll_entry_start(struct driver, driver);				//获得驱动列表的起始地址 	const int n_ents = ll_entry_count(struct driver, driver);					//获得驱动列表的总数量 	const struct udevice_id *id; 	struct driver *entry; 	struct udevice *dev; 	bool found = false; 	const char *name, *compat_list, *compat; 	int compat_length, i; 	int result = 0; 	int ret = 0;  	if (devp) 		*devp = NULL; 	name = ofnode_get_name(node); 	log_debug("bind node %s ", name);  	compat_list = ofnode_get_property(node, "compatible", &compat_length);		//得到compatible属性,用于匹配driver驱动 	if (!compat_list) { 		if (compat_length == -FDT_ERR_NOTFOUND) { 			log_debug("Device "%s" has no compatible string ", 				  name); 			return 0; 		}  		dm_warn("Device tree error at node "%s" ", name); 		return compat_length; 	}  	/* 	 * Walk through the compatible string list, attempting to match each 	 * compatible string in order such that we match in order of priority 	 * from the first string to the last. 	 */ 	for (i = 0; i < compat_length; i += strlen(compat) + 1) { 		compat = compat_list + i; 		log_debug("   - attempt to match compatible string "%s" ", 			  compat);  		for (entry = driver; entry != driver + n_ents; entry++) {				//循环判断所有驱动是否匹配	 			ret = driver_check_compatible(entry->of_match, &id, 						      compat); 			if (!ret) 				break; 		} 		if (entry == driver + n_ents) 			continue;  		if (pre_reloc_only) { 			if (!ofnode_pre_reloc(node) && 			    !(entry->flags & DM_FLAG_PRE_RELOC)) { 				log_debug("Skipping device pre-relocation "); 				return 0; 			} 		}  		log_debug("   - found match at "%s": "%s" matches "%s" ", 			  entry->name, entry->of_match->compatible, 			  id->compatible); 		ret = device_bind_with_driver_data(parent, entry, name, 						   id->data, node, &dev);								//该函数,用于创建udevice对象,并与查找到的driver绑定 		if (ret == -ENODEV) { 			log_debug("Driver "%s" refuses to bind ", entry->name); 			continue; 		} 		if (ret) { 			dm_warn("Error binding driver "%s": %d ", entry->name, 				ret); 			return ret; 		} else { 			found = true; 			if (devp) 				*devp = dev; 		} 		break; 	}  	if (!found && !result && ret != -ENODEV) 		log_debug("No match for node "%s" ", name);  	return result; }
  lists_bind_fdt  这个函数,主要用来扫描设备树中的各个节点;
  根据扫描到的 udevice  设备信息,通过compatible  来匹配compatible  相同的driver  ,匹配成功后,就会创建对应的struct udevice  结构体,它会同时指向设备资源和driver,这样设备资源和driver就绑定在一起了。
  3.7、DM模型——probe探测函数的执行
  上述,完成了DM模型的初始化,但是我们只是建立了  driver  和udevice  的绑定关系,那么何时调用到我们驱动中的probe  探测函数呢?uclass  与driver  又何时匹配的呢?
  上文呢, dm_init  只是负责初始化并绑定了udevice  和driver  ,那么probe  探测函数的执行,当然是在该驱动初始化的时候喽!
  下文以mmc驱动为例!其初始化流程如下:
  详细代码在这里就不展开来叙述了!
  在MMC驱动初始化后,有没有注意到 mmc_probe  这个函数,该函数就是间接调用了我们驱动编写的probe  函数。
  执行流程在上面已经很清楚了:根据 uclass_id  ,调用``uclass_get_device_by_seq来得到  udevice,进而调用  device_probe来找到对应驱动的  probe`。int device_probe(struct udevice *dev) { 	const struct driver *drv; 	int ret; 	int seq;  	if (!dev) 		return -EINVAL;  	if (dev->flags & DM_FLAG_ACTIVATED) 		return 0;  	drv = dev->driver;													//获取driver 	assert(drv);  	ret = device_ofdata_to_platdata(dev); 	if (ret) 		goto fail;  	/* Ensure all parents are probed */ 	if (dev->parent) {													//父设备probe 		ret = device_probe(dev->parent); 		if (ret) 			goto fail;  		/* 		 * The device might have already been probed during 		 * the call to device_probe() on its parent device 		 * (e.g. PCI bridge devices). Test the flags again 		 * so that we don"t mess up the device. 		 */ 		if (dev->flags & DM_FLAG_ACTIVATED) 			return 0; 	}  	seq = uclass_resolve_seq(dev); 	if (seq < 0) { 		ret = seq; 		goto fail; 	} 	dev->seq = seq;  	dev->flags |= DM_FLAG_ACTIVATED;  	/* 	 * Process pinctrl for everything except the root device, and 	 * continue regardless of the result of pinctrl. Don"t process pinctrl 	 * settings for pinctrl devices since the device may not yet be 	 * probed. 	 */ 	if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL) 		pinctrl_select_state(dev, "default");  	if (CONFIG_IS_ENABLED(POWER_DOMAIN) && dev->parent && 	    (device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) && 	    !(drv->flags & DM_FLAG_DEFAULT_PD_CTRL_OFF)) { 		ret = dev_power_domain_on(dev); 		if (ret) 			goto fail; 	}  	ret = uclass_pre_probe_device(dev); 	if (ret) 		goto fail;  	if (dev->parent && dev->parent->driver->child_pre_probe) { 		ret = dev->parent->driver->child_pre_probe(dev); 		if (ret) 			goto fail; 	}  	/* Only handle devices that have a valid ofnode */ 	if (dev_of_valid(dev)) { 		/* 		 * Process "assigned-{clocks/clock-parents/clock-rates}" 		 * properties 		 */ 		ret = clk_set_defaults(dev, 0); 		if (ret) 			goto fail; 	}  	if (drv->probe) {												 		ret = drv->probe(dev);										//调用驱动的probe 		if (ret) 			goto fail; 	}  	ret = uclass_post_probe_device(dev); 	if (ret) 		goto fail_uclass;  	if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL) 		pinctrl_select_state(dev, "default");  	return 0; fail_uclass: 	if (device_remove(dev, DM_REMOVE_NORMAL)) { 		dm_warn("%s: Device "%s" failed to remove on error path ", 			__func__, dev->name); 	} fail: 	dev->flags &= ~DM_FLAG_ACTIVATED;  	dev->seq = -1; 	device_free(dev);  	return ret; }
  主要工作归纳如下: 根据 udevice  获取driver  然后判断是否父设备被 probe  对父设备进行probe 调用driver的probe函数
  3.8、DM模型——uclass与uclass_driver绑定
  上述完成了  driver  的probe  函数调用,基本底层都已经准备好了,uclass  何时与uclass_driver  绑定,给上层提供统一的API呢?
  uclass  与uclass_driver  绑定,也是在驱动probe  之后,确保该驱动存在,设备存在,最后为该驱动绑定uclass  与uclass_driver  ,为上层提供统一接口。
  以根据MMC驱动为例
  回到上文的驱动流程图,看到 mmc_do_preinit  这个函数了嘛?里面调用了ret = uclass_get(UCLASS_MMC, &uc);  ,该函数才是真正的将uclass  与uclass_driver  绑定。int uclass_get(enum uclass_id id, struct uclass **ucp) { 	struct uclass *uc;  	*ucp = NULL; 	uc = uclass_find(id); 	if (!uc) 		return uclass_add(id, ucp); 	*ucp = uc;  	return 0; }
  uclass_get  主要实现了:根据uclass_id  查找对应的uclass  是否被添加到global_data->uclass_root  链表中,如果没有添加到,就调用uclass_add  函数,实现uclass  与uclass_driver  的绑定,并将其添加到global_data->uclass_root  链表中。static int uclass_add(enum uclass_id id, struct uclass **ucp) { 	struct uclass_driver *uc_drv; 	struct uclass *uc; 	int ret;  	*ucp = NULL; 	uc_drv = lists_uclass_lookup(id);					//根据uclass_id查找到对应的driver 	if (!uc_drv) { 		debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id ", 		      id); 		/* 		 * Use a strange error to make this case easier to find. When 		 * a uclass is not available it can prevent driver model from 		 * starting up and this failure is otherwise hard to debug. 		 */ 		return -EPFNOSUPPORT; 	} 	uc = calloc(1, sizeof(*uc)); 	if (!uc) 		return -ENOMEM; 	if (uc_drv->priv_auto_alloc_size) { 		uc->priv = calloc(1, uc_drv->priv_auto_alloc_size); 		if (!uc->priv) { 			ret = -ENOMEM; 			goto fail_mem; 		} 	} 	uc->uc_drv = uc_drv;												//uclass与uclass_driver绑定 	INIT_LIST_HEAD(&uc->sibling_node); 	INIT_LIST_HEAD(&uc->dev_head); 	list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST);				//添加到global_data->uclass_root链表中  	if (uc_drv->init) { 		ret = uc_drv->init(uc); 		if (ret) 			goto fail; 	}  	*ucp = uc;  	return 0; fail: 	if (uc_drv->priv_auto_alloc_size) { 		free(uc->priv); 		uc->priv = NULL; 	} 	list_del(&uc->sibling_node); fail_mem: 	free(uc);  	return ret; }
  好啦,到这里基本就把Uboot的DM模型全部理清楚啦,耗时一个周,总感觉想要自己去讲明白,真的不是一件容易的事情呢!
  如果对你们有帮助,记得点个赞哦!
  更多文章,可以关注我的公~号:【嵌入式艺术】哦,一起讨论嵌入式技术!

中医养生今冬湿冷,老寒腿小心中医五运六气理论,今年冬天的天气会呈现湿冷特点。什么是五运六气五运六气,是中医通过探讨自然变化的周期性规律及其对人体的影响,从而进行养生防病的理论。五运,指木运火运土运金运水运。六92岁老人的感言降低欲望,才是长寿和快乐的秘诀长寿历来都是人们所热衷追求的,普通民众如此,高官权贵也如此。可是现实又是残酷的,寿命的长短并不会受人的愿望所支配。我们也采访了很多长寿老人,询问关于长寿的秘诀。92岁的谢大爷说减少詹姆斯骑士时队友香波特闯舞蹈节目决赛,高超技术评审叫好前NBA骑士球员香波特目前并没有在联盟打球,但他仍然活跃在大荧幕前,据报道近年他参加了美国ABC与星共舞(DancingwiththeStars)舞蹈节目,而他在节目上大秀舞艺,目暗物质的争论结束于黑洞撞击月球宇宙中比原子还小的黑洞它们可能已经在月球上留下了指纹。大约140亿年前,当宇宙的时钟开始滴答作响时,空间仍然是一个紧凑炽热疯狂的宇宙物质包。星星还没有发光,行星也没有诞生,各种形状保罗末节19分太阳9996森林狼获9连胜,唐斯35分太阳客场9996击败森林狼获得9连胜,保罗最后一节砍下19分。布克上篮先声夺人,不过拉塞尔抢断艾顿,助攻范德比尔特扣篮,唐斯命中三分,拉塞尔干拔跳投,森林狼72反超。艾顿连得5分,兰德尔15中5,尼克斯打铁大战胜步行者NBA常规赛,尼克斯主场9284胜步行者。开局沃克和勒韦尔对飚得分,之后萨博尼斯和兰德尔也开始连续得分。不过首节双方还是手紧,最终步行者以2016领先。第二节罗斯上篮奎克利连中三分勇士倒下,快船翻船,只有太阳仍在连胜!湖人还有望冲西部前3吗NBA常规赛的较量正在如火如荼地进行之中,东部战区,前三甲的争夺战进入白热化阶段,华盛顿奇才,布鲁克林篮网,芝加哥公牛都已经在新赛季取得了10场胜利。不过相比之下,奇才要少一个负场库里9记三分砍37分勇士大胜篮网,杜兰特19中6焦点之战勇士对阵篮网,库里射下9记三分得到37分,勇士11799大胜篮网,12胜2负稳坐联盟第一。乔哈里斯因脚踝伤势缺阵,米尔斯顶替首发上来命中三分,杜兰特干拔得手。哈登两次连线布布克成为NBA历史上,单场得分70的球员之一,未来可期1996年10月30日,出生于美国密西西比州格兰维尔市的布克,从小就非常热爱篮球。他的父亲梅尔文布克是一个职业篮球运动员,一直在欧洲联赛效力。由于父亲常年在海外打球,小布克人生的大希罗2676邓肯罗宾逊21分,热火轻取雷霆NBA常规赛,热火客场10390胜雷霆。阿德巴约和巴特勒都缺阵,希罗主导进攻,开场就中三分。邓罗和塔克也中三分后多尔特希罗和厄尔都中三分,希罗下场后雷霆突然连攻篮下打出一波110反武磊我们没有退路,01落后的情况下完全放开踢,全队发挥不错北京时间11月17日凌晨,国足在12强赛第6轮的较量中11战平澳大利亚,武磊点射为国足扳平武比分。赛后,武磊接受了采访,武磊表示球队在01落后的情况下完全放开了,全队的发挥不错。对
32岁张含韵新剧备受吐槽,黑眼圈太重,网友不适合演少女了早些年,张含韵等人主演的兰陵王妃一上线就受到了无数网友的追捧,剧中的张含韵造型好看,颜值在线,因此,张含韵也被大家称为最美的兰陵王妃。后来,张含韵在娱乐圈里的消息少了很多,一直到最当年的流量巨星事业受损,竟是因为她?鹿晗和关晓彤这一对情侣想必大家都知道吧当年在官宣的时候,也是引起了不小的风波微博将近瘫痪,鹿晗的粉丝知道后,出现了大规模的脱粉现象。两人都在那段时间受着网络暴力恋爱的公开让两人都造大S配合儿子录制视频,为奶奶张兰送生日祝福4月7日,恰逢汪小菲母亲张兰生日。当天开启直播的张兰,晒孙子录制的生日祝福视频。直播过程中,有网友要求看汪希箖照片。作为奶奶的张兰,拿起手机后晒出视频祝福。视频中的汪希箖口吻亲切,赵本山女儿官宣解除婚约,男友一年内花她2千万,如今含泪后悔恋爱时有多高调,分手时就有多凄凉。近日,赵本山女儿球球透露已经和男友解除婚约,还含泪表示很后悔没听爸妈的话,早日断了这段情。赵本山的女儿球球是个非常高调的人,自从当了网红后就更高调五部高质量的谍战片,每一部都是好评如潮,悬崖之上不入三甲5风声由高群书陈国富联合执导,周迅李冰冰张涵予黄晓明主演的谍战片。该片改编自同名小说,讲述了抗战时期一名汪伪政府的高官被暗杀后,日军要彻底搜查抗日地下组织,从而引发的一系列惊心动魄高并发常识TPSQPS等一概述分布式微服务ServiceMesh目前都是大家耳熟能详的词语了,现在随便一个互联网公司说出来大家都是在搞微服务。但我们搞来搞去,怎么样来衡量一个应用当前的状态到底是怎么样的?C优雅编程之事件系统(三)进阶级上一节我们解决了界面死锁的问题,参考C优雅编程之事件系统(二)入门级但是如果有100个类似的功能,该如何设计这个类呢?首先我们需要做的就是新建一个FileSearch类,把什么代码毕业即失业今日京东刘强东迷之操作,决定裁员多人美其名曰毕业。网友调侃这可真是毕业即失业啊。不过这也不必大惊小怪,其实这都是市场经济的推动,虽然对于单个家庭来说暂时遇到危机,但是对于整个社会是autojsopencv工具箱牙叔教程简单易懂功能rgb通道分离hsv通道分离灰度化边缘检测二值化霍夫直线简介本项目集合了一些opencv的常用操作,rgb通道分离hsv通道分离灰度化边缘检测。二值化。霍夫直线套娃or融会贯通?一加10ROPPOReno8等多款新机曝光vivo在4月的机海计划可以是让大家期待满满,多款新机也在近日相继亮相,感觉这4月的绝对C位,无疑是vivo了。而另一边的欧加系似乎也要跟上脚步,此前曾官宣过的一加新机OPPOK1花一百块在拼多多上买了个盲盒,看到快递我人都傻了这个世界上从来不缺少想占便宜的人。托尼前两天以身试法,终于让我意识到被人当猴耍是什么感受。事情是这样的。这段时间显卡价格暴降,托尼上班的时候,总有些小伙伴问我有没有买显卡看来有人坐