device_tree分析

在内核中,经常会听到“设备树”这一概念,根据名字,大致可以理解为计算机系统中的设备按照树型结构进行了组织。

首先,了解一下设备树二进制文件DTB。DTB(Devicetree Blob)是DTS的二进制文件格式,使用DTC工具可以将DTS源文件编译成DTB,随后,引导程序(BIOS)可以将DTB文件所在的地址传递给内核,从而用于内核对设备树的创建。

DTB的文件结构

|-----------------------|
|     struct fdt_header |
|-----------------------|
|     (free space)      |
|-----------------------|
|memory reservation 	|
|-----------------------|
|     (free space)      |
|-----------------------|
|     structure		    |
|-----------------------|
|     (free space)      |
|-----------------------|
|     string 	        |
|-----------------------|
|     (free space)      | 
|-----------------------|

其中,关于structure block等每个部分的组成结构如下图所示:
在这里插入图片描述

由DTB的文件结构可知DTB主要包含了3个部分:

fdt_header	//文件头结构;
structure	//存放含Node和Property;
string	//存放Property的Name;把Property Name单独分为一个区域的原因是,有很多Property Name是重复的,单独一个区域可以使用指针引用,节约空间。

DTB中struct fdt_header数据结构:

struct fdt_header {
	uint32_t magic;
	uint32_t totalsize;	//total size of DT block
	uint32_t off_dt_struct;	//offset to structure
	uint32_t off_dt_strings;	//offset to string
	uint32_t off_mem_rsvmap;	//offset to memory reserve map
	uint32_t version;
	uint32_t last_comp_version;
	uint32_t boot_cpuid_phys;
	uint32_t size_dt_strings;	//size of the string block
	uint32_t size_dt_struct;	//size of the structure block
};

structure主要用来存放节点信息,DTB中每个节点都存在一个节点信息头,即struct fdt_node_header数据结构:

struct fdt_node_header {
	fdt32_t tag;	//当前节点的类型
	char name[0];	
};

关于节点中的属性,可以利用struct property_header属性信息头对其进行管理:

struct fdt_property {
	fdt32_t tag;	//属性标签
	fdt32_t len;
	fdt32_t nameoff;	
	char data[0];
};

而对于每种格式的起点和结尾,可以使用5种不同的token来进行表示:

FDT_BEGIN_NODE(0x00000001)
FDT_END_NODE(0x00000002)
FDT_PROP(0x00000003)
FDT_NOP(0x00000004)
FDT_END(0x00000009)

内核通过调用device_tree_init()函数解析DTB文件内容,从而创建设备树结构。其原型如下:

void __init device_tree_init(void)
{
	if (!initial_boot_params)	//initial_boot_params为DTB文件的入口地址
		return;
	unflatten_and_copy_device_tree();
}

void __init unflatten_and_copy_device_tree(void)
{
	int size;
	void *dt;
	
	if (!initial_boot_params) {
		pr_warn("No valid device tree found, continuing without\n");
		return;
	}
	
	size = fdt_totalsize(initial_boot_params);
	//获取设备树文件大小
	//return fdt32_l(&((const struct fdt_header *)(fdt))->totalsize)
	//根据该执行过程,可知,initial_boot_params实际为fdt_header结构体
	
	dt = early_init_dt_alloc_memory_arch(size, roundup_pow_of_two(FDT_V17_SIZE));
	//申请fdt_header所需的空间
	if (dt) {
		memcpy(dt, initial_boot_params, size);
		//将fdt_header信息复制到申请的空间
		initial_boot_params = dt;
		//改变全局变量initial_boot_params指针
	}
	unflatten_device_tree();
	//创建设备树
}

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
	//创建设备树
	of_alias_scan(early_init_dt_alloc_memory_arch);
	//为设备树中具有别名的设备节点创建别名
	unittest_unflatten_overlay_base();
	//创建用于单元测试的设备树
}

void *__unflatten_device_tree(const void *blob, struct device_node *dad, struct device_node **mynodes, void *(*dt_alloc)(u64 size, u64 align), bool detached)
//__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
{
	int size;
	void *mem;
	...
	if (!blob) {
		pr_debug("No device tree pointer\n");
		return NULL;
	}
	...
	if (fdt_check_header(blob)) {
	//检查DTB文件格式是否正确
		pr_err("Invalid device tree blob header\n");
		return NULL;
	}
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
	//此时,该操作只是用来计算设备树所使用的空间
	...
	size = ALIGN(szie, 4);
	//4字节对齐
	...
	mem = dt_alloc(szie + 4, __alignof__(struct device_node));
	//申请设备树地址空间
	...
	memset(mem, 0, size);
	*(__be32 *)(mem+size) = cpu_to_be32(0xdeadbeef);
	//结尾标注
	...
	unflatten_dt_nodes(blob, mem, dad, mynodes);
	//创建设备树
	...
	return mem;
}
static int unflatten_dt_nodes(const void *blob, void *mem, struct device_node *dad, struct device_node *nodepp)
{
//unflatten_dt_nodes(blob, NULL, dad, NULL);
//unflatten_dt_nodes(blob, mem, dad, mynodes);
	struct device_node *root;
	int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH 64
	struct device_node *nps[FDT_MAX_DEPTH];
	void *base = mem;
	bool dryrun = !base;

	if (nodepp)
		*nodepp = NULL;
	if (dad)
		depth = inital_depth = 1;
	root = dad;
	nps[depth] = dad;

	for (offset = 0; offset >= 0 && depth >= initial_depth; offset = fdt_next_node(blob, offset, &depth)) {
	//fdt_next_node()函数,该函数计算offset在DTB文件中的偏移量。
		...
		if (!populate_node(blob, offset, &mem, nps[depth], &nps[depth+1], dryrun))
		//该函数创建设备节点,即struct device_node。
			return mem-base;
			//返回设备节点大小
		...
	}
	...
	return mem - base;
}

static bool populate_node(const void *blob, int offset, void **mem, struct device_node *dad, struct device_node **pnp, bool dryrun)
{
	struct device_node *np;
	const char *pathp;
	unsigned int l, allocl;

	pathp = fdt_get_name(blob, offset, &l);
	//return ((const char *)fdt + (fdt32_ld(&((const struct fdt_header *)(fdt))->off_dt_struct)) + offset)->name;
	//即fdt_node_header->name
	...

	allocl = ++l;
	//name的字符长度
	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));
	//设备节点申请空间
	if (!dryrun) {
		char *fn;
		of_node_init(np);
		np->full_name = fn = ((char *)np) + sizeof(*np);
	
		memcpy(fn, pathp, l);
	
		if (dad != NULL) {
			np->parent = dad;
			np->sibling = dad->child;
			dad->child = np;
		}
	}

	populate_properties(blob, offset, mem, np, pathp, dryrun);
	if (!dryrun) {
		np->name = of_get_property(np, "name", NULL);
		if (!np->name)
			np->name = "<NULL>"
	}

	*pnp = np;
	return true;
}

static void populate_properties() 
{
	struct property *pp, **pprev = NULL;
	int cur;
	bool has_name = false;

	pprev = &np->properties;
	for (cur = fdt_first_property_offset(blob, offset); cur >= 0; cur = fdt_next_property_offset(blob, cur)) {
		const __be32 *val;
		const char *pname;
		u32 sz;
	
		val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
		...

		pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property));
		...
		pp->name = (char *)pname;
		pp->length = sz;
		pp->value = (___be32 *)val;
		*pprev = pp;
		pprev = &pp->next;	
	}

 	if (!has_name) {
		const char *p = nodename, *ps = p, *pa = NULL;
		int len;

		while (*p) {
			if ((*p) == '@')
				pa = p;
			else if ((*p) == '/')
				ps = p + 1;
			p++;
		}

		if (pa < ps)
			pa = p;
		len = (pa - ps) + 1;
		pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
					__alignof__(struct property));
		if (!dryrun) {
			pp->name   = "name";
			pp->length = len;
			pp->value  = pp + 1;
			*pprev     = pp;
			pprev      = &pp->next;
			memcpy(pp->value, ps, len - 1);
			((char *)pp->value)[len - 1] = 0;
			pr_debug("fixed up name for %s -> %s\n",
				 nodename, (char *)pp->value);
		}
	}
	if (!dryrun)
		*pprev = NULL;	
}

int fdt_next_node(const void *fdt, int offset, int *depth)
//假设该函数为启动阶段第一次被调用,在按照上边的函数,fdt为initial_boot_params,offset为0,*depth为0
{
	int nextoffset = 0;
	uint32_t tag;

	if (offset >= 0)
		if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
		//该函数中进行相关的检查之后,返回offset
			return nextoffset;
	do {
		offset = nextoffset;
		//假设当前offset为0
		tag = fdt_next_tag(fdt, offset, &nextoffset);
		//根据offset,查找DTB文件中的标签
		switch (tag) {
			case FDT_PROP:
			case FDT_NOP:
				break;
			case FDT_BEGIN_NODE:
			//如果找到一个设备节点,则depth加1
				if (depth)
					(*depth)++;
				break;
			case FDT_END_NODE:
			//如果结束了一个设备节点信息的获取,则depth减一
				if (depth && ((--(*depth)) < 0))
					return nextoffset;
				break;
			case FDT_END:
				if ((nextoffset >= 0) || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
					return -FDT_ERR_NOTFOUND;
				else
					return nextoffset;
		}
	} while (tag != FDT_BEGIN_NODE);

	return offset;
}

uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
//查询下一个标签,假设当前为初始阶段,一切都从头开始,则startoffset与nextoffset全部为0
{
	const fdt32_t *tagp, *lenp;
	uint32_t tag;
	int offset = startoffset;
	const char *p;

	*nextoffset = -FDT_ERR_TRUNCATED;
	tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
	//return (const char *)fdt + (fdt32_ld(&((const struct fdt_header *)(fdt))->off_dt_struct)) + offset;
	//可以看出这里从DTB文件中,获取存放设备节点的地址
	if (!can_assume(VALID_DTB) && !tagp)
		return FDT_END;
	tag = fdt32_to_cpu(*tagp);
	//读取tagp中的几位,并进行移位组合,构成新的数据传递给tag
	offset += FDT_TAGSIZE;
	//offset偏移4个字节长度,可见标签存放了四个字节位
	*nextoffset = -FDT_ERR_BADSTRUCTURE;
	switch (tag) {
		case FDT_BEGIN_NODE:
		//设备节点的命名
			do {
				p = fdt_offset_ptr(fdt, offset++, 1);
			} while (p && (*p != '\0'));
			if (!can_assume(VALID_DTB) && !p)
				return FDT_END;
			break;
		case FDT_PROP:
		//设备节点的属性
			lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
			if (!can_assume(VALID_DTB) && !lenp)
				return FDT_END;
			offset += sizeof(struct fdt_property) - FDT_TAGSIZE + fdt32_to_cpu(*lenp);
			if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 && ((offset - fdt32_to_cpu(*lenp)) % 8) != 0)
				offset += 4;
			break;
		case FDT_END:
		case FDT_END_NODE:
		case FDT_NOP:
			break;
		default:
			return FDT_END;
	}
	if (!fdt_offset_ptr(fdt, startoffste, offset - startoffset))
		return FDT_END;
	*nextoffset = FDT_TAGALIGN(offset);
	return tag;
}

综上,便是设备树的构建过程,关键的地方在与对二进制文件DTB的解析。关于,设备节点之间关系的创建,在源代码中并没有很好的理解,只能看到struct device_node nps数组的应用,具体还需要根据DTB文件安格式以及内容标识来理解。

另外,在简单的了解一下结构体struct device_node。

struct device_node {
	const char *name;
	phandle phandle;
	//typedef u32 phandle
	//可见,phandle是用来存放一个地址的。
	...
	struct property *properties;
	//用来存放设备节点的属性
	...
	struct device_node *parent;
	...
	void *data;
	...
}

通过上边的代码可以知道,设备树的构建过程,即根据dts文件所创间的设备节点,且各个设备节点之间可能存在一定的关联,比如:父子节点,兄弟节点等。当设备节点创建完成后,内核还会根据创建的设备节点来创建platform_device设备。

内核中,关于设备树的执行文件都位于drivers/of目录下,在该目录中有不同的执行文件,其中在platform.c文件中,存在一个特殊的函数,该函数利用了编译器特性进行了声明,并在内核初始化的后期,通过do_initcalls函数来调用执行。

static int __init of_platform_default_populate_init(void)
{
	struct device_node *node;

	device_links_supplier_sync_state_pause();

	if (!of_have_populated_dt())
		return -ENODEV;

	for_each_matching_node(node, reserved_mem_matches)
		of_platform_device_create(node, NULL, NULL);
		//根据device_node,即设备节点,来创建platform_device。

	node = of_find_node_by_path("/firmware");
	//根据“/firmware”路径名来获取设备节点
	if (node) {
		of_platform_populate(node, NULL, NULL, NULL);
		//根据device_node,创建platform_device。
		of_node_put(node);
	}

	fw_devlink_pause();
	of_platform_default_populate(NULL, NULL, NULL);
	//创建platform_device设备
	fw_devlink_resume();

	return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

关于arch_initcall_sync函数,其定义如下:

#define arch_initcall_sync(fn) __define_initcall(fn, 3s)

可见,该函数通过do_initcalls函数来调用,从而得到执行。
在上述的of_platform_default_populate_init()函数中,可以看见有三处地方都在创建platform_device设备,其中第二处与第三处只是参数上存在差异。这里先来分析第三处,因为前两处需要进行条件判断,而第三处则不需要。

int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup, struct device *parent)
{
	return of_platform_populate(root, of_default_bus_match_table, lookup, parent);
}
//关于of_default_bus_match_table,其原型如下:
//const struct of_device_id of_default_bus_match_table[] = {
//	{.compatible = "simple-bus"},
//	{.compatible = "simple-mfd"},
//	{.compatible = "isa"},
//#ifdef CONFIG_ARM_AMBA
//	{.compatible = "arm,amba-bus"},
//#endif
//	{}
//};

int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent)
{
//这里分析的为第三次创建,因此参数中除matches外,其余参数均为NULL
	struct device_node *child;
	int rc = 0;

	root = root ? of_node_get(root) : of_find_node_by_path("/");
	//当root为空时,此时通过of_find_node_by_path来获取根设备节点,该函数调用of_find_node_opts_by_path函数来完成
	if (root)
		return -EINVAL;

	pr_debug("%s()\n", __func__);
	pr_debug(" starting at: %pOF\n", root);

	device_links_supplier_sync_state_pause();
	for_each_child_of_node(root, child) {
	//该循环遍历所有的设备节点
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		//在该函数中,创建设备节点
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	device_links_supplier_sync_state_resume();

	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;	
}

struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
	struct device_node *np = NULL;
	struct property *pp;
	unsigned long flags;
	const char *separator = strchar(path, ":");

	if (opts)
		*opts = separator ? separator + 1 : NULL;

	if (strcmp(path, "/") == 0)
		return of_node_get(of_root);
	//如果此时路径为“/”,则直接返回of_root节点,of_root为全局变量,为设备树的根节点

	if (*path != '/') {
		int len;
		const char *p = separator;
	
		if (!p)
			p = strchrnul(path, '/');
		len = p - path;

		if (!of_aliases)
			return NULL;

		for_each_property_of_node(of_aliases, pp) {
		//如果路径不为“/”,则通过别名来查找所需的节点
			if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) {
				np = of_find_node_by_path(pp->value);
				break;
			}
		}
		if (!np)
			return NULL;
		path = p;
	}

	raw_spin_lock_irqsave(&devtree_lock, flags);
	if (!np)
		np = of_node_get(of_root);
	np = __of_find_node_by_full_path(np, path);
	//将设当路径为“/:xxx”时,上述条件不成立,此时,则先获取到根设备节点,随后利用该函数俩遍历所有的子节点找到所需的节点
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}

static int of_platform_bus_create(struct device_node *bus, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent, bool strict)
{
	const struct of_dev_auxdata *auxdata;
	struct device_nodev *child;
	struct platform_device *dev;
	const char *bus_id = NULL;
	void *platform_data = NULL;
	int rc = 0;

	...
	auxdata = of_dev_lookup(lookup, bus);
	//以第三处为例分析,则lookup此时为NULL,因此该函数返回NULL
	if (auxdata) {
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}

	if (of_device_is_compatible(bus, "arm, primecell")) {
			of_amba_device_create(bus, bus_id, platform_data, parent);
			return 0;
	}

	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	//根据假设的情况,此时除bus为of_root之外,其余参数均为NULL
	//该函数定义struct platform_device结构体对象,并对该对象中的struct device属性以及其他属性进行赋值
	//最后,调用of_device_add()函数添加该platform_device设备
	if (!dev || !of_match_node(matches, bus))
		return 0;

	for_each_child_of_node(bus, child) {
	//当当前节点被创建为platform_device设备之后,开始遍历该节点下的子节点来创建platform device
		pr_debug("	create child: %pOF\n", child);
		rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(bus, OF_POPULATED_BUS);
	return rc;
}

关于of_platform_device_create_pdata(),其定义如下:

static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id, void *platform_data, struct device *parent)
{
	struct platform_device *dev;

	if (!of_device_is_avaliable(np) || of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

	dev = of_device_alloc(np, bus_id, parent);
	//该函数中主要对platform_device设备中的属性进行初始化
	if (!dev)
		goto err_clear_flag;

	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	if (!dev->dev.dma_mask)
		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;

	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_msi_configure(&dev->dev, dev->dev.of_node);
	//上述过程主要对platform device中的strct device属性进行初始化

	if (of_device_add(dev) != 0) {
	//添加plarform_device设备
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;
	...
}

struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent)
{
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
	if (!dev)
		return NULL;

	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	num_irq = of_irq_count(np);

	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		for (i = 0; i < num_reg; i++, res++) {
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all leagcy IRQ resources mapped for %p0Fn\n", np);
	}

	dev->dev.of_node = of_node_get(np);
	dev->dev.fwnode = &np->fwnode;
	dev->dev.parent = parent ? : &platform_bus;

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);
	
	return dev;
}

int of_device_add(struct platform_device *ofdev)
{
	BUG_ON(ofdev->dev.of_node);
	
	ofdev->name = dev_name(&ofdev->dev);
	ofdev->id = PLATFORM_DEVID_NONE;
	
	set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));

	return device_add(&ofdev->dev);
}

综上,便是关于设备树的一些简单分析。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值