博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
device_tree分析
阅读量:4095 次
发布时间:2019-05-25

本文共 16033 字,大约阅读时间需要 53 分钟。

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

首先,了解一下设备树二进制文件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 = "
" } *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);}

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

你可能感兴趣的文章
维吉尼亚之加解密及破解
查看>>
DES加解密
查看>>
TCP/IP协议三次握手与四次握手流程解析
查看>>
PHP 扩展开发 : 编写一个hello world !
查看>>
inet_ntoa、 inet_aton、inet_addr
查看>>
用模板写单链表
查看>>
用模板写单链表
查看>>
链表各类操作详解
查看>>
C++实现 简单 单链表
查看>>
数据结构之单链表——C++模板类实现
查看>>
Linux的SOCKET编程 简单演示
查看>>
正则匹配函数
查看>>
Linux并发服务器编程之多线程并发服务器
查看>>
聊聊gcc参数中的-I, -L和-l
查看>>
[C++基础]034_C++模板编程里的主版本模板类、全特化、偏特化(C++ Type Traits)
查看>>
C语言内存检测
查看>>
Linux epoll模型
查看>>
Linux select TCP并发服务器与客户端编程
查看>>
Linux系统编程——线程池
查看>>
基于Visual C++2013拆解世界五百强面试题--题5-自己实现strstr
查看>>