【跟着韦东山学习linux设备树】dtb转换为device

    技术2022-07-11  95

    跟着韦东山学习linux设备树—dtb转换为device_node树状结构

    引子函数调用过程1. start_kernel() 函数定义在init/main.c2. setup_arch()函数定义在arch/arm/kernel/setup.c3. arm_memblock_init()函数定义在arch/arm/mm/init.c3.1. 函数early_init_fdt_reserve_self();定义在drivers/of/fdt.c3.2. 函数early_init_dt_reserve_memory_arch();定义在drivers/of/fdt.c 4.unflatten_device_tree()函数定义在drivers/of/fdt.c4.1. 结构体struct device_node //定义在include/linux/of.h4.2. 结构体struct property 定义在include/linux/of.h dtb转换为device_node树状示意图5. __unflatten_device_tree()函数定义在drivers/of/fdt.c5.1. __unflatten_device_tree()函数,定义在drivers/of/fdt.c5.1.1 populate_node()函数,定义在drivers/of/fdt.c5.1.1.1populate_properties()函数,//定义在drivers/of/fdt.c

    引子

    设备节点只是某一个结构体而已,首先思考一个问题,uboot把dtb文件放到内存的某个地方,内核就可以使用,为何内核在运行的过程中,不会覆盖,使用dtb文件所占据的内存呢? 我们之前学习过的dtb文件中有保留内存的格式为: /memreserve/

    ; 复习如下 即使在dtb文件中不写保留某些内存的格式,内核也会把dtb文件所占据的内存保留出来,以防止在内核运行的过程中,被覆盖,那么内核是如何实现这一机制的呢?我们来看看内核中函数的调用流程

    函数调用过程

    1. start_kernel() 函数定义在init/main.c

    asmlinkage __visible void __init start_kernel(void)................ setup_arch(&command_line); .................

    2. setup_arch()函数定义在arch/arm/kernel/setup.c

    void __init setup_arch(char **cmdline_p){ .......................................... arm_memblock_init(mdesc); ................................... unflatten_device_tree(); ....................................... }

    3. arm_memblock_init()函数定义在arch/arm/mm/init.c

    void __init arm_memblock_init(const struct machine_desc *mdesc){ /* Register the kernel text, kernel data and initrd with memblock. */ memblock_reserve(__pa(KERNEL_START), KERNEL_END - KERNEL_START); arm_initrd_init(); arm_mm_memblock_reserve(); /* reserve any platform specific memblock areas */ if (mdesc->reserve) mdesc->reserve(); /*早期初始化时把fdt本身保留下来,最终会调用memblock_reserve() 将DTB文件所占的内存区域保留下来,以后内核运行都不会占用这块内存,这块内存会一直保留着,这样就可以随时使用dtb里面的数据详细代码见3.1*/ early_init_fdt_reserve_self(); /*再逐个扫描dtb文件中的节点,*/ early_init_fdt_scan_reserved_mem(); /* reserve memory for DMA contiguous allocations */ dma_contiguous_reserve(arm_dma_limit); arm_memblock_steal_permitted = false; memblock_dump_all(); }

    3.1. 函数early_init_fdt_reserve_self();定义在drivers/of/fdt.c

    /** * early_init_fdt_reserve_self() - reserve the memory used by the FDT blob */ void __init early_init_fdt_reserve_self(void) { if (!initial_boot_params) return; /* Reserve the dtb region */ early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0); }

    3.2. 函数early_init_dt_reserve_memory_arch();定义在drivers/of/fdt.c

    int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, phys_addr_t size, bool nomap) { if (nomap) return memblock_remove(base, size); return memblock_reserve(base, size); }

    4.unflatten_device_tree()函数定义在drivers/of/fdt.c

    dtb文件是扁平的(FDT:Flattened Device Tree即扁平设备树),里面含有各个设备节点,我们需要将其提取出来, 构造成一棵树。这个函数是分析重点。首先我们来学习两个重要结构体device_node和property。 initial_boot_params:存放dtb文件的起始地址

    /** * unflatten_device_tree - create tree of device_nodes from flat blob * * unflattens the device-tree passed by the firmware, creating the * tree of struct device_node. It also fills the "name" and "type" * pointers of the nodes so the normal device-tree walking functions * can be used. */ void __init unflatten_device_tree(void) { /*initial_boot_params:存放dtb文件的起始地址; __unflatten_device_tree()函数会遍历DTB文件中的每个节点,然后构造出对应的device_node结构体,并且构造出对应的树状关系 of_root:该device_node结构体变量保存上述函数构造的树的结构的根节点 可以使用of_root来遍历整个树的数据 */ __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false); /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ of_alias_scan(early_init_dt_alloc_memory_arch); unittest_unflatten_overlay_base(); }

    4.1. 结构体struct device_node //定义在include/linux/of.h

    在dts文件中每一个{}都对应着一个节点,每一个节点都对应着一个结构体device_node,每个节点有各种属性,节点里面可能还有子节点。而结构体struct device_node中的成员对应着都有描述(见下面device_node的定义),device_node中的成员中的parent,child,sibling构造出设备节点的树状结构。 节点的名字(const char *full_name)放在device_node结构体的后面(在结构体外面),然后由full_name指针,指向这里。见下示意图,代码如下 np->full_name = fn = ((char *)np) + sizeof(*np);

    struct device_node { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL" const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL" phandle phandle; const char *full_name; // 节点的名字, node-name[@unit-address] struct fwnode_handle fwnode; struct property *properties;//节点的属性(实际指向一个链表) struct property *deadprops; /* removed properties */ struct device_node *parent; // 节点的父亲 struct device_node *child; // 节点的孩子(子节点) struct device_node *sibling; // 节点的兄弟(同级节点) #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif };

    4.2. 结构体struct property 定义在include/linux/of.h

    struct property { char *name; // 属性名字, 指向dtb文件中的字符串 int length; // 按字节计算属性值的长度 void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储 struct property *next; //组成链表, #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif };

    以根节点为例,这些dts文件是如何构成成设备节点树状结构的,见示意图

    ............ / { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; memory { /* /memory */ device_type = "memory"; reg = <0x30000000 0x4000000 0 4096>; }; /* cpus { cpu { compatible = "arm,arm926ej-s"; }; }; */ chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; }; led { compatible = "jz2440_led"; pin = <S3C2410_GPF(5)>; }; };

    dtb转换为device_node树状示意图

    5. __unflatten_device_tree()函数定义在drivers/of/fdt.c

    /** * __unflatten_device_tree - create tree of device_nodes from flat blob * * unflattens a device-tree, creating the * tree of struct device_node. It also fills the "name" and "type" * pointers of the nodes so the normal device-tree walking functions * can be used. * @blob: The blob to expand * @dad: Parent device node * @mynodes: The device_node tree created by the call * @dt_alloc: An allocator that provides a virtual address to memory * for the resulting tree * @detached: if true set OF_DETACHED on @mynodes * * Returns NULL on failure or the memory chunk containing the unflattened * device tree on success. */ void *__unflatten_device_tree(const void *blob, struct device_node *dad, struct device_node **mynodes, void *(*dt_alloc)(u64 size, u64 align), bool detached) { int size; void *mem; pr_debug(" -> unflatten_device_tree()\n"); if (!blob) { pr_debug("No device tree pointer\n"); return NULL; } pr_debug("Unflattening device tree:\n"); pr_debug("magic: x\n", fdt_magic(blob)); pr_debug("size: x\n", fdt_totalsize(blob)); pr_debug("version: x\n", fdt_version(blob)); if (fdt_check_header(blob)) { pr_err("Invalid device tree blob header\n"); return NULL; } /* First pass, scan for size */ /*size就是设备树中所有节点对应的device_node结构体和property结构体的大小*/ size = unflatten_dt_nodes(blob, NULL, dad, NULL); if (size < 0) return NULL; size = ALIGN(size, 4); pr_debug(" size is %d, allocating...\n", size); /* Allocate memory for the expanded device tree */ mem = dt_alloc(size + 4, __alignof__(struct device_node)); if (!mem) return NULL; memset(mem, 0, size); *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); pr_debug(" unflattening %p...\n", mem); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); if (be32_to_cpup(mem + size) != 0xdeadbeef) pr_warning("End of tree marker overwritten: x\n", be32_to_cpup(mem + size)); if (detached && mynodes) { of_node_set_flag(*mynodes, OF_DETACHED); pr_debug("unflattened tree is detached\n"); } pr_debug(" <- unflatten_device_tree()\n"); return mem; }

    5.1. __unflatten_device_tree()函数,定义在drivers/of/fdt.c

    在__unflatten_device_tree()函数中,unflatten_dt_nodes()函数被调用了两次,第一次调用只是为了计算出设备树中所有节点对应的device_node结构体和property结构体的大小,第二次调用才是用这些结构体展开为树的结构。

    /** * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree * @blob: The parent device tree blob * @mem: Memory chunk to use for allocating device nodes and properties * @dad: Parent struct device_node * @nodepp: The device_node tree created by the call * * It returns the size of unflattened device tree or error code */ static int unflatten_dt_nodes(const void *blob, void *mem, struct device_node *dad, struct device_node **nodepp) { 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; /* * We're unflattening device sub-tree if @dad is valid. There are * possibly multiple nodes in the first level of depth. We need * set @depth to 1 to make fdt_next_node() happy as it bails * immediately when negative @depth is found. Otherwise, the device * nodes except the first one won't be unflattened successfully. */ if (dad) depth = initial_depth = 1; root = dad; nps[depth] = dad; /*for循环,将里面的dtb节点一个个地取出来,调用populate_node()函数构造device_node节点*/ for (offset = 0; offset >= 0 && depth >= initial_depth; offset = fdt_next_node(blob, offset, &depth)) { if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH)) continue; if (!IS_ENABLED(CONFIG_OF_KOBJ) && !of_fdt_device_is_available(blob, offset)) continue; if (!populate_node(blob, offset, &mem, nps[depth], &nps[depth+1], dryrun)) return mem - base; if (!dryrun && nodepp && !*nodepp) *nodepp = nps[depth+1]; if (!dryrun && !root) root = nps[depth+1]; } if (offset < 0 && offset != -FDT_ERR_NOTFOUND) { pr_err("Error %d processing FDT\n", offset); return -EINVAL; } /* * Reverse the child list. Some drivers assumes node order matches .dts * node order */ if (!dryrun) reverse_nodes(root); return mem - base; }
    5.1.1 populate_node()函数,定义在drivers/of/fdt.c

    /首先获得节点的名字/ /然后分配内存空间,在这个空间里面既含有device_node结构体的大小,也有名字的大小/ /然后写入名字/ /然后处理节点的属性,即构造property结构体,并且设置这个结构体/

    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); if (!pathp) { *pnp = NULL; return false; } /*计算这个节点名字的长度*/ allocl = ++l; /*然后分配内存空间,在这个空间里面既含有device_node结构体的大小,也有名字的大小*/ np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node)); if (!dryrun) { char *fn; of_node_init(np); /*节点的名字(const char *full_name)放在device_node结构体的后面(**在结构体外面)**,然后由full_name指针,指向这里。*/ 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; } } /*然后处理节点的属性,即构造property结构体,并且设置这个结构体*/ 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; }
    5.1.1.1populate_properties()函数,//定义在drivers/of/fdt.c

    /也是for循环,先取出第一个属性处理,然后下一个属性处理/ /得到属性的名字/ /为property结构体分配空间/ /设置property结构体的成员/

    static void populate_properties(const void *blob, int offset, void **mem, struct device_node *np, const char *nodename, bool dryrun) { struct property *pp, **pprev = NULL; int cur; bool has_name = false; pprev = &np->properties; /*也是for循环,先取出第一个属性处理,然后下一个属性处理*/ 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); if (!val) { pr_warn("Cannot locate property at 0x%x\n", cur); continue; } if (!pname) { pr_warn("Cannot find property name at 0x%x\n", cur); continue; } if (!strcmp(pname, "name")) has_name = true; /*为property结构体分配空间*/ pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property)); if (dryrun) continue; /* We accept flattened tree phandles either in * ePAPR-style "phandle" properties, or the * legacy "linux,phandle" properties. If both * appear and have different values, things * will get weird. Don't do that. */ if (!strcmp(pname, "phandle") || !strcmp(pname, "linux,phandle")) { if (!np->phandle) np->phandle = be32_to_cpup(val); } /* And we process the "ibm,phandle" property * used in pSeries dynamic device tree * stuff */ if (!strcmp(pname, "ibm,phandle")) np->phandle = be32_to_cpup(val); /*设置property结构体的成员*/ pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)val; *pprev = pp; pprev = &pp->next; } /* With version 0x10 we may not have the name property, * recreate it here from the unit name if absent */ 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; }
    Processed: 0.010, SQL: 9