【u-boot】u-boot源码分析笔记(08)| u-boot驱动模型分析_02

一、开篇

在《u-boot驱动模型分析01》一文中,描述了u-boot驱动模型相关的数据结构,也描述了在实际的u-boot驱动程序中如何声明驱动程序,并剖析了U_BOOT_DRIVER宏定义的背后实现机制。

本文将从u-boot启动主线的角度出发,分析u-boot的驱动模型是如何建立的。

在开始之前,先描述一下u-boot中用于描述一个具体设备的数据结构:struct udevice。该数据结构保存着一个设备的具体信息,这个设备是一个绑定到一个特定端口或外设(本质上是一个驱动实例)的驱动。

struct udevice定义如下(/include/dm/device.h):

struct udevice {
	const struct driver *driver;
	const char *name;
	void *platdata;
	void *parent_platdata;
	void *uclass_platdata;
	int of_offset;
	ulong driver_data;
	struct udevice *parent;
	void *priv;
	struct 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
};

结构中各元素含义如下:

  • driver:表示此设备使用的驱动程序。

  • name :设备名称,通常为FDT节点的名称。

  • platdata:为这个设备的配置数据。

  • parent_platdata:父总线(bus)对此设备的配置数据

  • uclass_platdata:该类(uclass)设备的配置数据。

  • of_offset:此设备的设备树节点偏移量。

  • driver_data:驱动数据字,用于匹配该设备的驱动。

  • parent:此设备的父设备。当该值为NULL时表示此设备为顶级设备。

  • priv:此设备的私有数据。

  • uclass:指向该设备所属类的指针。

  • uclass_priv:uclass为此设备提供的私有数据。

  • parent_priv:父设备为此设备提供的私有数据。

  • uclass_node:用于uclass链接到此设备。

  • child_head:设备的子节点列表。

  • sibling_node:设备列表中的下一个设备。

  • flags:设备的标志。

  • req_seq:标识请求此设备的序列号(如果值为-1代表任何设备 )

  • seq:为该设备分配的序列号(-1 = none)。该值是在设备被探测时设置,并且在设备的类(uclass)中是唯一的)

一个设备通过调用bind产生,分为两种方式:(1)由U_BOOT_DEVICE()宏(在这种情况下,platdata是非null)(2)设备树中的节点(在这种情况下,偏移量为>= 0)。如果是通过设备树中节点产生,则会将设备树信息转化为平台数据,通过驱动程序将数据转化为平台的方法实现(如果设备有设备树节点,则会在probe方法之前调用)。

在数据结构创建过程中,platdataprivuclass_priv可以由驱动分配,也可以使用struct driverstruct uclass_driverauto_alloc_size成员来让驱动模型自动完成分配操作。

二、驱动模型分析

我们从《u-boot启动主线分析【01】—_main》这篇文章中已经知道,u-boot的启动主线由board_init_fboard_init_r代表的执行过程完成。然而在两个函数中都会对设备模型进行初始化,其中board_init_f对应initf_dm()函数;board_init_r对应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);
	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;
	ret = dm_init_and_scan(false);
	if (ret)
		return ret;
#ifdef CONFIG_TIMER_EARLY
	gd->timer = NULL;
	ret = dm_timer_init();
	if (ret)
		return ret;
#endif

	return 0;
}

上述两个函数实现大致是一样的,都调用到dm_init_and_scan()dm_timer_init()这两个函数,但是在参数处略有不同。

对于dm_init_and_scan()函数,该函数用于初始化驱动模型结构并扫描设备,它有一个布尔类型的参数,如果该参数为真,则只绑定带有DM_FLAG_PRE_RELOC标志的驱动;如果该参数为假,则绑定所有的驱动。

然后会调用dm_timer_init()函数初始化计时器以保持时间能正常走时。

在u-boot启动主线中,initf_dm()函数会首先被调用,所以在调用dm_init_and_scan函数时传递的参数为true,去绑定一些经过DM_FLAG_PRE_RELOC标志的驱动。随着u-boot启动,到达启动后期时会在initr_dm()函数中调用dm_init_and_scan(false)绑定所有的驱动。

好啦,从上文可见,u-boot驱动模型与dm_init_and_scan()函数相关,下文将来分析该函数。

三、dm_init_and_scan函数分析

dm_init_and_scan()函数定义如下(/driver/core/root.c):

int dm_init_and_scan(bool pre_reloc_only)
{
	int ret;

	ret = dm_init();
	if (ret) {
		debug("dm_init() failed: %d\n", ret);
		return ret;
	}
	ret = dm_scan_platdata(pre_reloc_only);
	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);
		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;
}

从上述代码片段可见,dm_init_and_scan()函数中会调用4个函数执行具体操作,它们是:
(1)dm_init(),(2)dm_scan_platdata(),(3)dm_scan_fdt(),(4)dm_scan_other()

在u-boot的驱动模型中,驱动和类是以树形来构建和管理的,树根定义如下(/driver/core/root.c):

【u-boot】u-boot源码分析笔记(08)| u-boot驱动模型分析_02_第1张图片

所以在
dm_init_and_scan()函数中会初始化驱动树和类树的根,然后扫描并绑定来自平台数据和FDT的可用设备。调用dm_init()来建立驱动模型结构。

(3-1)dm_init函数分析

dm_init()函数定义如下:

int dm_init(void)
{
	int ret;
 
 //判断dm_root是否存在,如果存在则会返回。
	if (gd->dm_root) {
		dm_warn("Virtual root driver already exists!\n");
		return -EINVAL;
	}
  
  //初始化((gd_t *)gd)->uclass_root链表。
	INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);

#if defined(CONFIG_NEEDS_MANUAL_RELOC)
	fix_drivers();
	fix_uclass();
#endif

//创建设备并绑定驱动程序。
	ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
	if (ret)
		return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
	DM_ROOT_NON_CONST->of_offset = 0;
#endif
//探测(probe)一个设备,并激活它
	ret = device_probe(DM_ROOT_NON_CONST);
	if (ret)
		return ret;

	return 0;
}

上述函数将执行以下四个步骤操作:

(1)判断dm_root是否存在,如果存在则会返回。

(2)初始化((gd_t *)gd)->uclass_root链表。

(3)创建设备并绑定驱动程序。

(4)探测(probe)一个设备,并激活它。

dm_init()函数将初始化驱动模型的数据结构。即初始化驱动(driver)树和类(uclass)树的根。

(3-2)dm_scan_platdata函数分析

我们知道u-boot有两种方式描述设备:(1)平台数据,(2)设备树方式。这一点与linux内核也一致。

dm_scan_platdata()函数则会扫描所有的平台数据并绑定驱动程序。将扫描所有可用的平台数据并为每个数据创建驱动程序。

dm_scan_platdata定义如下:

int dm_scan_platdata(bool pre_reloc_only)
{
	int ret;
 //搜索并将所有驱动程序绑定到父驱动程序
	ret = lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);
	if (ret == -ENOENT) {
		dm_warn("Some drivers were not found\n");
		ret = 0;
	}

	return ret;
}

(3-3)dm_scan_fdt函数分析

这个函数是如果u-boot配置了支持设备树绑定设备驱动,那么将会执行该函数。dm_scan_fdt函数用于扫描设备树,绑定驱动程序。这将扫描设备树并为每个节点创建一个驱动程序(只检查顶级子节点)。

dm_scan_fdt()函数定义如下(/driver/core/root.c):

int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
		     bool pre_reloc_only)
{
	int ret = 0, err;

	for (offset = fdt_first_subnode(blob, offset);
	     offset > 0;
	     offset = fdt_next_subnode(blob, offset)) {
		if (pre_reloc_only &&
		    !fdt_getprop(blob, offset, "u-boot,dm-pre-reloc", NULL))
			continue;
		if (!fdtdec_get_is_enabled(blob, offset)) {
			dm_dbg("   - ignoring disabled device\n");
			continue;
		}
		err = lists_bind_fdt(parent, blob, offset, NULL);
		if (err && !ret) {
			ret = err;
			debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL),
			      ret);
		}
	}

	if (ret)
		dm_warn("Some drivers failed to bind\n");

	return ret;
}

int dm_scan_fdt(const void *blob, bool pre_reloc_only)
{
	return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
}

从以上代码片段可知,dm_scan_fdt()函数的本质操作则是一个for循环,在循环结构中会依次遍历设备节点,并调用lists_bind_fdt()函数绑定设备树节点,即将这个设备节点绑定到父节点上。

(3-4)dm_scan_other函数分析

dm_scan_other函数用于扫描其他设备。

有些时候,在实际开发中,驱动模型可能检测不到一些设备。在u-boot源码中,dm_scan_other()是一个弱函数定义,没有具体的操作。那么这个弱函数则可以由开发人员来实现,用于创建自己硬件板卡提供的设备。我们可通过在每个设备上调用device _bind()来实现这一点。

四、总结

本文从u-boot的启动主线开始一直描述到驱动模型的初始化,对于u-boot的驱动模型是通过调用dm_init_and_scan()函数来建立的。在该函数中,创建并初始化了与驱动模型相关的数据结构,还调用dm_scan_platdatadm_scan_fdt扫描平台数据或者设备树节点,根据扫描的结果绑定对应的设备驱动。

你可能感兴趣的:(小生聊【u-boot】,u-boot,驱动模型,C语言)