Rickey 裘 一无所知

uboot 设备驱动模型


最近在移植uboot-2015.04的时候发现,uboot的设备驱动也带驱动模型了,第一次见到的时候还真是愣了一下,特别是调试的时候没有以前那么方便直接了。而且设备模型和设备树捆绑在一起,又得花费一番功夫来了解了。不禁深深的感慨,搞技术的真真切切就是活到老学到老,而且这种一直学习的状态其实是外界不断强加给你的,有时候真的觉得挺累。人家改改模型,你就得重新去学习适应。其实这种学习都是高投入低产出的,真的希望国内搞技术的同志们能够加油加油加油,争取走到产业的上游,让人家去用我们的东西,让人家去跟随我们的步伐。希望有人看到这些东西,加快他们前进的步伐,能把更多的精力花在创造更多价值的事情上!!!

先讲几大要点:

要点1: 设备模型始于initf_dm接口 要点2: 整个过程穿插着设备树的东西,所以最好理解设备树

1. 设备驱动入口

以前驱动入口就是一个类似init函数的东东,现在不一样了,都统一为一个宏$U_BOOT_DRIVER$

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,
};

这里先不管这个东西,知道就行,一会儿用到的时候再解释。

2. 驱动模型

如果说设备驱动对象是一个成员,那么模型就是用来管理这些成员的规则。整个模型的入口是initf_dm, 其实就是调用了dm_init_and_scan

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

	return 0;
}

dm_init_and_scan函数中调用了以下几个函数:

  1. dm_init
  2. dm_scan_platdata
  3. dm_scan_fdt (在配置了of_control的情况下)
  4. dm_scan_other

2.1 dm_init函数。

/* drivers/core/root.c */
int dm_init(void)
{
	int ret;

	if (gd->dm_root) {
		dm_warn("Virtual root driver already exists!\n");
		return -EINVAL;
	}
	// 初始化uclass_list
	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;
#ifdef CONFIG_OF_CONTROL
	DM_ROOT_NON_CONST->of_offset = 0;
#endif
	ret = device_probe(DM_ROOT_NON_CONST);
	if (ret)
		return ret;

	return 0;
}

其实我很讨厌分析代码,首先肯定免不了贴代码,容易照本宣科;其次语言难以发挥,很不爽。其实每个函数无非做了两件事,第一件事:焚香沐浴更衣,第二件事:真刀真枪开干。说多了就显得无聊,这里也是这样的,无非就是结构初始化,然后就调用一些接口做些事情。至于初始化为了做什么,其实都在代码中,tag跳一下,稍微看看就能懂了。这里做实际工作的就是两个地方

device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);

device_probe(DM_ROOT_NON_CONST);

2.1.1 device_bind_by_name

原型是这样的

/* drivers/core/device.c */
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, const struct driver_info *info, struct udevice **devp);

int device_probe(struct udevice *dev);

既然叫device_bind_by_name,那么肯定有这么几个点是要猜到的:

  1. 这个接口是要进行搜索的
  2. 搜索是根据名字来的

一开始可能会觉得自己只能马后炮分析一下,但是我觉得要做到条件反射,因为计算机编程语言本质上也是语言,对语言不就应该条件反射式的反应吗?不然怎么能够理解并且构建丰富多彩的世界呢。 再贴代码

int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
			const struct driver_info *info, struct udevice **devp)
{
	struct driver *drv;

	// 搜索driver,这里重点关注root driver
	drv = lists_driver_lookup_name(info->name);
	if (!drv)
		return -ENOENT;
	if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC))
		return -EPERM;

	// 绑定driver和device,重点关注root driver和root device
	return device_bind(parent, drv, info->name, (void *)info->platdata, -1, devp);
}

关于list_driver_lookup_name就没必要贴了,只要知道她会根据名字返回对应的通过U_BOOT_DRIVER 声明的驱动就行。这里返回的是root_driver,这个要贴一下,还有个uclass也贴一下,因为马上用到,注意两者id是一样的。

/* This is the root driver - all drivers are children of this */
U_BOOT_DRIVER(root_driver) = {
	// 这个名字和info->name是不是同一个东西?哈哈
	.name	= "root_driver",
	.id	= UCLASS_ROOT,
};

/* This is the root uclass */
UCLASS_DRIVER(root) = {
	.name	= "root",
	.id	= UCLASS_ROOT,
};

好,我们得到了root_driver,其实看这个名字也要有条件反射,既然是root,那么其他driver就是node了,而且是可以通过root搜索到的。所以我们就要把这些node和root绑定起来。这是后面要做的事情,root_driver我们还没处理,先来看device_bind。再贴代码,靠,真他妈恶心。

int device_bind(struct udevice *parent, struct driver *drv, const char *name, void *platdata, int of_offset, struct udevice **devp)
{
	struct udevice *dev;
	struct uclass *uc;
	int ret = 0;

	*devp = NULL;
	if (!name)
		return -EINVAL;
	// 和前面那个搜索driver的接口一样,这里是根据id搜索对应的uclass
	ret = uclass_get(drv->id, &uc);
	if (ret)
		return ret;

	// 创建设备udevice对象
	dev = calloc(1, sizeof(struct udevice));
	if (!dev)
		return -ENOMEM;

	// 构建树形结构需要的对象,放兄弟和儿子的地方
	INIT_LIST_HEAD(&dev->sibling_node);
	INIT_LIST_HEAD(&dev->child_head);
	INIT_LIST_HEAD(&dev->uclass_node);
	// 配置设备对象
	dev->platdata = platdata;
	dev->name = name;
	dev->of_offset = of_offset;
	dev->parent = parent;
	dev->driver = drv;
	dev->uclass = uc;

	dev->seq = -1;
	dev->req_seq = -1;
#ifdef CONFIG_OF_CONTROL
	/*
	 * 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.
	 */
	 /* 这里uc和uc_drv已经勾搭上了,其实她们在uclass_get的时候就好上了
	  * 回顾一下文章贴的第一段代码,那个sdmmc驱动里有个flags,在这里就能用上
	  */
	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);
		}
	}
#endif
	if (!dev->platdata && drv->platdata_auto_alloc_size) {
		dev->flags |= DM_FLAG_ALLOC_PDATA;
		dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
		if (!dev->platdata) {
			ret = -ENOMEM;
			goto fail_alloc1;
		}
	}
	if (parent) {
		int size = parent->driver->per_child_platdata_auto_alloc_size;

		if (!size) {
			size = parent->uclass->uc_drv->
					per_child_platdata_auto_alloc_size;
		}
		if (size) {
			dev->flags |= DM_FLAG_ALLOC_PARENT_PDATA;
			dev->parent_platdata = calloc(1, size);
			if (!dev->parent_platdata) {
				ret = -ENOMEM;
				goto fail_alloc2;
			}
		}
	}

	/* put dev into parent's successor list */
	if (parent)
		list_add_tail(&dev->sibling_node, &parent->child_head);

	ret = uclass_bind_device(dev);
	if (ret)
		goto fail_uclass_bind;

	/* if we fail to bind we remove device from successors and free it */
	if (drv->bind) {
		ret = drv->bind(dev);
		if (ret)
			goto fail_bind;
	}
	if (parent && parent->driver->child_post_bind) {
		ret = parent->driver->child_post_bind(dev);
		if (ret)
			goto fail_child_post_bind;
	}

	if (parent)
		dm_dbg("Bound device %s to %s\n", dev->name, parent->name);
	*devp = dev;

	return 0;

fail_child_post_bind:
	if (drv->unbind && drv->unbind(dev)) {
		dm_warn("unbind() method failed on dev '%s' on error path\n",
			dev->name);
	}

fail_bind:
	if (uclass_unbind_device(dev)) {
		dm_warn("Failed to unbind dev '%s' on error path\n",
			dev->name);
	}
fail_uclass_bind:
	list_del(&dev->sibling_node);
	if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
		free(dev->parent_platdata);
		dev->parent_platdata = NULL;
	}
fail_alloc2:
	if (dev->flags & DM_FLAG_ALLOC_PDATA) {
		free(dev->platdata);
		dev->platdata = NULL;
	}
fail_alloc1:
	free(dev);

	return ret;
}

root_driver由于是混沌初开,没有什么parent,init这些东西,所以直接返回0了。

2.1.2 device_probe

对于root driver来讲,没有什么动作,进入下一个重要部分。

2.2 dm_scan_platdata

这里先记录两个宏,其实前面已经用到过了,放这里提示一下

/* Cast away any volatile pointer */
#define DM_ROOT_NON_CONST		(((gd_t *)gd)->dm_root)
#define DM_UCLASS_ROOT_NON_CONST	(((gd_t *)gd)->uclass_root)

这个函数会通过ll_entry_*宏去搜索driver_info字段,但是链接时并没有,所以应该直接返回0。我有点困惑,难道目前还没用?

2.3 dm_scan_fdt

移植的这个版本中,驱动信息都是通过设备树定义的,她里面调用了

/* drivers/core/root.c */
dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);

传进去四个参数:根设备dm_root,设备树blob,offset=0,pre_reloc_only=True。 继续贴代码吧,因为这里是重点了。

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

	// 遍历node,至于fdt_first_subnode怎么做的,这里可以不用关心
	for (offset = fdt_first_subnode(blob, offset);
	     offset > 0;
	     offset = fdt_next_subnode(blob, offset)) {
	     // pre_reloc_only = True
		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;
	}

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

	return ret;
}

也不知道是不是眼睛有缺陷,有时候我会把for和if看混淆。在公司里的时候也遇到过这样的问题,还叫同事帮忙看,看了好久都没发现。这次看的时候又把第一个for看成if了,然后觉得表达式里的语法不对,不过还好很快就发现了。 这个函数看名字就知道是干什么的,就是用来扫描设备树,提取各个有效node的。这里记录一点,对函数的理解我觉得可以分为三个层次:

  1. 架构层次:放在架构里,知道她是干嘛的,可以顺利阅读框架代码。
  2. 模块层次:知道这个函数输入和输出,并且输出和输入的对应关系,可以调试框架。
  3. 实现层次:熟悉具体是怎么实现的,就是源代码怎么写的,可以调试模块。

心里知道当前工作所处的层次,比如现在只要知道这个函数是干嘛的,那就没必要去追究实现层次的东西,人的精力终归是有限的,什么阶段做什么事情,对不对? 那么这三个层次各需要怎么做呢?首先是架构层次,因为虽然我们是要知道函数是干嘛的,但是知道怎么实现的,不就知道是干嘛的了吗?其实不是这样的,首先我们肯定知道我们当前的框架是用来干嘛的,然后进一步细分就知道模块是用来干嘛的,在此基础上,结合函数位置和名字就很容易知道这个函数是用来干嘛的,这是一种自顶向下的分析方式,但是从实现开始则是自底向上的。这里又要多嘴一句了,其实相对于细节,把握顶层设计更容易把握方向,代码如此,社会也是如此。明确政策和社会导向能让你更加容易切入一个领域,少走好多弯路,归根到底人的生命是有限的啊,人这一生就是在用有限的生命最大化地最好自己想做的事情。 在设备树这一块上,这篇文章还处于架构层次,至于模块层次,还要另外写一篇文章分析,尽请期待。

2.4 dm_scan_other

是个__weak,直接返回0。


评论