300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Linux设备树DTB存储格式

Linux设备树DTB存储格式

时间:2020-07-30 09:12:31

相关推荐

Linux设备树DTB存储格式

文章目录

DTB存储格式编译和查看工具Device Tree中的节点信息举例Device Tree文件结构DTB数据结构struct ftd_header区域数据结构memory reservation block区域数据结构struct block区域strings block内核对设备树中平台信息的处理machine_desc内核源码处理分析setup_archsetup_machine_fdtof_flat_dt_match_machine内核对设备树运行时配置信息处理解析/chosen节点解析根节点的{size,address}-cells属性解析/memory节点kernel解析Device Tree从DTB到struct device_node示例将device_node转换成platform_device设备树在文件系统中的表示

DTB存储格式

头部(struct ftd_header):用来表明各个部分的偏移地址,整个文件的大小,版本号等等;内存的保留信息块(memory reservation block):存放dts文件中申明的需要预留的内存的信息;节点块(structure block):各个节点的信息将放在structure block中;字符串块(strings block):存放字符串信息;

编译和查看工具

dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件对应的dtb文件

Linux终端执行ftddump –h

Device Tree中的节点信息举例

dt_struct存储节点数值及名称相关信息,如:”A string“及node1dt_string存储属性名,如:“a-string-property”可以给一个设备节点添加lable,之后可以通过&lable的形式访问这个lable,这种引用是通过phandle(pointer handle)进行的。例如,图中的node1就是一个lable,node@0的子节点child-node@0通过&node1引用node@1节点,在经过DTC工具编译之后,&node1会变成一个特殊的整型数字n,假设n值为1,那么在node@1节点下自动生成两个属性,属性如下:

linux,phandle = <0x00000001>;

phandle = <0x00000001>;

node@0的子节点child-node@0中的a-reference-to-something = <&node1>会变成a-reference-to-something = < 0x00000001>。此处0x00000001就是一个phandle值(独一无二的整型值),在后续kernel中通过这个特殊的数字间接找到引用的节点

Device Tree文件结构

dtb的头部首先存放的是fdt_header的结构体信息,接着是填充区域,填充大小为off_dt_struct – sizeof(struct fdt_header),填充的值为0。接着就是struct fdt_property结构体的相关信息。最后是dt_string部分

DTB数据结构

dtb文件为大端存储模式

struct ftd_header区域数据结构

struct fdt_header {uint32_t magic; //dtb文件的固定开始数字 0xd00dfeeduint32_t totalsize; //dtb文件的大小uint32_t off_dt_struct; //structure block区域的地址偏移值,从文件开头计算uint32_t off_dt_strings; //strings block区域的地址偏移值,从文件开头计算uint32_t off_mem_rsvmap; //memory reservation block区域的地址偏移值,从文件开头计算uint32_t version; //设备树数据结构的版本uint32_t last_comp_version; //所用版本向后兼容的最低版本的设备树数据结构uint32_t boot_cpuid_phys; //系统引导CPU的物理ID,它的值应该与设备树文件中CPU节点下的reg属性值相等uint32_t size_dt_strings; //structure block区域的字节数uint32_t size_dt_struct; //strings block区域的字节数};

memory reservation block区域数据结构

该区域信息描述了一块受保护的内存区域,该内存区域不用作一般内存使用memory reservation block区域的结尾固定是一个address和size全0的结构体

struct fdt_reserve_entry {uint64_t address; //开始地址uint64_t size; //大小};

假设要将64M内存的最高1M留下自己使用,则在dts文件中添加下面这句:/memreserve/ 0x33f00000 0x100000

/memreserve/ 0x33f00000 0x100000;/ {name = "sample"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";};};

struct block区域

Struct Block是由一系列连续的片组成的,每个片的以一个32位token开始,token按照大字节序存储,某些token后面还会接着额外的数据(extra data)。所有的token都是32bit对齐的,不满32bit的会在前面补0。

struct fdt_node_header {fdt32_t tag;//对应的token,如FDT_BEGIN_NODE、char name[0];};//描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROP;len为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址struct fdt_property {fdt32_t tag;fdt32_t len;fdt32_t nameoff;char data[0];};

五种类型的token:

0x00000001 //FDT_BEGIN_NODE,表示一个note节点开始,说明后面的内容都是节点信息。FDT_BEGIN_NODE后接节点名字,节点名字是以"\0"结尾的字符串,如果名字中包含地址,则该字符串中也应该包含地址(32位对齐,不满足补0)。0x00000002 //FDT_END_NODE,表示一个note节点结束。FDT_END_NODE 没有额外的信息,所以它后面马上接着下一个除FDT_PROP之外的token。0x00000003 //FDT_PROP,表示一个属性开始。后接描述该属性信息的extra data,它的extra data是按以下C结构体存储的,该结构体后接属性名字,以"\0"结尾的字符串。0x00000004 //FDT_NOP,特殊数据,解析设备树时将被忽略,这个token没有extra data,所以后面紧接下一个token。如果要从dtb去除莫个note或者属性,可以用他覆盖该note或者属性,这样就不用移动dtb文件中其他数据了0x00000009 //FDT_END,表示struct block区域结束。一个dtb文件中应该只包含一个FDT_END token,并且FDT_END token应该是最后一个token。没有extra data,后面直接接string block的内容。

案例:

led {compatible = "jz2440_led";pin = <S3C2410_GPF(5)>;};

FDT_BEGIN_NODE+节点名称开始,然后是FDT_PROP描述节点的属性,然后是struct(len+nameoff)+val,该节点的属性描述完成后,使用FDT_END_NODE表示节点结束;最后,当所有节点描述完毕后,使用FDT_END结束struct block。

strings block

存放struct block中用到的属性名,可能这些属性名重复率很高,这样节省空间。该区域的字符串简单地拼接在一起,没有字节对其。

内核对设备树中平台信息的处理

machine_desc

一个kernel镜像通常会支持很多板子,针对每种板子,kernel都会为其定义一个struct machine_desc的结构,其中就记录各个板子的硬件信息,比如板子的ID号、名字、支持的中断数量、初始化函数等在kernel启动时,可以根据u-boot传递的参数/DTB文件选则合适的machine_desc,从而正确的初始化当前硬件kernel会将一系列machine_desc集中存放在.init.arch.info节中,形成如同数组一样的内存分布,并以符号__arch_info_begin和__arch_info_end记录该节的起始和结尾,如此一来,就可以向访问数组元素那样访问每个machine_desc

.init.arch.info : {__arch_info_begin = .;*(.arch.info.init)__arch_info_end = .;}

选则machine_desc时,kernel首先会获取DTB的根节点的compatible属性,将其中的一个或多个字符串与machine_desc的dt_compat成员记录的一个或多个字符串进行比较,当匹配时,返回相应的machine_desc,compatible属性值中,位置靠前的字符串会优先比较

内核源码处理分析

setup_arch

void __init setup_arch(char **cmdline_p){const struct machine_desc *mdesc;/* 初始化一些处理器相关的全局变量 */setup_processor();/* 优先按照设备树获取machine_desc */mdesc = setup_machine_fdt(__atags_pointer);/* 如果u-boot传递的不是DTB,则按照ATAGS获取machine_desc */if (!mdesc)mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);....../* 记录获取到的machine_desc及其名字 */machine_desc = mdesc;machine_name = mdesc->name;dump_stack_set_arch_desc("%s", mdesc->name);....../* 将boot_command_line的内容拷贝到cmd_line */strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);/* 输出指向启动参数的指针 */*cmdline_p = cmd_line;....../* 根据DTB创建device_node树 */unflatten_device_tree();......#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER/* 设置handle_arch_irq */handle_arch_irq = mdesc->handle_irq;#endif....../* 调用machine_desc中注册的初始化函数 */if (mdesc->init_early)mdesc->init_early();}

setup_machine_fdt

setup_arch函数调用setup_machine_fdt解析设备树中的相关信息,并返回合适的machine_desc

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys){const struct machine_desc *mdesc, *mdesc_best = NULL;/* 验证DTB文件是否存在:地址不为NULL && 文件头部magic正确 *//* initial_boot_params = dt_phys */if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))return NULL;/* 获取compatible属性并匹配合适的machine_desc */mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);if (!mdesc) {/* 打印一些信息 */....../* 把当前kernel支持的单板的名字和单板ID打印出来 *//* 该函数不会返回(内部有死循环) */dump_machine_table();}/* 当DTB文件提供的数据有问题,这里会做一些修补工作 */if (mdesc->dt_fixup)mdesc->dt_fixup();/* 获取运行时配置信息,再第3节中细说 */early_init_dt_scan_nodes();/* 记录machine ID */__machine_arch_type = mdesc->nr;return mdesc;}

of_flat_dt_match_machine

of_flat_dt_match_machine函数是匹配合适的machine_desc的关键

/*传入的第一个参数为NULL传入的第二个参数为arch_get_next_macharch_get_next_mach的原理非常简单:初始化一个静态局部变量为__arch_info_begin,每次被调用时该变量(指针)+1并返回,如果超出了__arch_info_end,则返回NULL*/const void * __init of_flat_dt_match_machine(const void *default_match,const void * (*get_next_compat)(const char * const**)){const void *data = NULL;const void *best_data = default_match;const char *const *compat;unsigned long dt_root;unsigned int best_score = ~1, score = 0;/* dt_root = 0 */dt_root = of_get_flat_dt_root();/* 遍历所有machine_desc,将machine_desc的dt_compat保存到compatcompat指向一系列字符串(一个machine_desc也可能支持多个单板)*/while ((data = get_next_compat(&compat))) {/*DTB根节点的compatible属性值是一系列字符串,假设为"aaa", "bbb", "ccc"machine_desc的dt_compat(指针的指针)也指向一系列字符串,假设为"xxx", "ccc"第一轮比较(score = 0):1、score++, compatible的"aaa"<==>dt_compat的"xxx"2、score++, compatible的"bbb"<==>dt_compat的"xxx"3、score++, compatible的"ccc"<==>dt_compat的"xxx"第二轮比较(score = 0):1、score++, compatible的"aaa"<==>dt_compat的"ccc"2、score++, compatible的"bbb"<==>dt_compat的"ccc"3、score++, compatible的"ccc"<==>dt_compat的"ccc",此时匹配上,返回score(值为3)*/score = of_flat_dt_match(dt_root, compat);/* 记录得分最低(最匹配)的machine_desc */if (score > 0 && score < best_score) {best_data = data;best_score = score;}}/* 没有匹配到合适的machine_desc就返回NULL */if (!best_data) {/* 打印根节点的compatible属性值 */......return NULL;}/* 打印根节点的model属性值,若不存在则打印compatible属性值 */pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());return best_data;}

内核对设备树运行时配置信息处理

kernel使用setup_arch ==> setup_machine_fdt ==> early_init_dt_scan_nodes来处理DTB中的运行时配置信息

void __init early_init_dt_scan_nodes(void){int rc = 0;/* 获取/chosen节点的信息 */rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);if (!rc)pr_warn("No chosen node found, continuing without\n");/* 获取根节点的{size,address}-cells属性值,之后才方便解析根节点的子节点的reg属性 */of_scan_flat_dt(early_init_dt_scan_root, NULL);/* 解析/memory节点,设置内存信息 */of_scan_flat_dt(early_init_dt_scan_memory, NULL);}

/*** 遍历DTB的节点,直到参数传入的回调函数it返回非0值*/int __init of_scan_flat_dt(int (*it)(unsigned long node,const char *uname, int depth,void *data),void *data){/* blob指向DTB在内存中的起始地址 */const void *blob = initial_boot_params;const char *pathp;int offset, rc = 0, depth = -1;/* 若设备树不存在则返回 */if (!blob)return 0;/* 从根节点开始遍历 */for (offset = fdt_next_node(blob, -1, &depth);/* 如果找到了有效的节点并且回调函数it返回0,则执行循环体 */offset >= 0 && depth >= 0 && !rc;/* 继续遍历下一个节点 */offset = fdt_next_node(blob, offset, &depth)) {/* 获取节点名 */pathp = fdt_get_name(blob, offset, NULL);/* 对于老版本的设备树,得到的是节点的路径名,因此要去掉多余的前缀 *//* 不过fdt_get_name已经考虑过这个问题了,这里有点多余 */if (*pathp == '/')pathp = kbasename(pathp);/*调用回调函数itoffset: 节点起始位置在DTB的structure block中的偏移pathp : 指向节点名depth : 节点的深度(层次)data : 参数data,取决于调用者*/rc = it(offset, pathp, depth, data);}return rc;}

解析/chosen节点

传入的回调函数early_init_dt_scan_chosen用于解析/chosen节点

/*offset: 节点起始位置在DTB的structure block中的偏移pathp : 指向节点名depth : 节点的深度(层次)data : boot_command_line,一个字符数组*/int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,int depth, void *data){int l;const char *p;const void *rng_seed;pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);/* 如果遍历到的不是作为根节点的子节点的chosen节点,则指示of_scan_flat_dt继续遍历下一个节点 */if (depth != 1 || !data ||(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))return 0;/* 当前节点是/chosen节点 *//* 解析/chosen节点的initrd属性,设置全局变量phys_initrd_start和phys_initrd_size */early_init_dt_check_for_initrd(node);/* 获取/chosen节点的bootargs属性的属性值 */p = of_get_flat_dt_prop(node, "bootargs", &l);/* 如果属性存在,则p指向bootargs属性值——一个字符串,l记录了字符串的长度(含'\0') */if (p != NULL && l > 0)/* 将启动参数拷贝到boot_command_line */strlcpy(data, p, min(l, COMMAND_LINE_SIZE));/** CONFIG_CMDLINE配置项意味着如果u-boot传递的参数不含启动参数,那么* CONFIG_CMDLINE就是默认的启动参数。如果含有启动参数,那么,是追加* 还是覆盖已有的启动参数,取决于另外两个配置项CONFIG_CMDLINE_EXTEND* 和CONFIG_CMDLINE_FORCE。*/#ifdef CONFIG_CMDLINE#if defined(CONFIG_CMDLINE_EXTEND)strlcat(data, " ", COMMAND_LINE_SIZE);strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);#elif defined(CONFIG_CMDLINE_FORCE)strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);#else/* 如果DTB不带有启动参数,就使用kernel的启动参数——CONFIG_CMDLINE */if (!((char *)data)[0])strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);#endif#endif /* CONFIG_CMDLINE */pr_debug("Command line is: %s\n", (char*)data);/* 对rng-seed节点的解析,暂时不清楚这个东西 */rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);if (rng_seed && l > 0) {......}/* 返回非0值,指示of_scan_flat_dt停止遍历 */return 1;}

解析根节点的{size,address}-cells属性

在解析/memory节点之前,应该先得到根节点的{size,address}-cells属性值,因为/memory节点使用reg属性来存放内存的起始地址和长度,而解析reg属性少不了{size,address}-cells

int __init early_init_dt_scan_root(unsigned long node, const char *uname,int depth, void *data){const __be32 *prop;/* 验证当前节点是否是根节点 */if (depth != 0)return 0;/* 设置默认值 */dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;/* 如果有#size-cells属性则获取其值,并重新设置dt_root_size_cells */prop = of_get_flat_dt_prop(node, "#size-cells", NULL);if (prop)/* 注意大小端的转换 */dt_root_size_cells = be32_to_cpup(prop);pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);/* 如果存在#address-cells属性,则重新设置dt_root_addr_cells */prop = of_get_flat_dt_prop(node, "#address-cells", NULL);if (prop)dt_root_addr_cells = be32_to_cpup(prop);pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);/* 停止遍历 */return 1;}

解析/memory节点

int __init early_init_dt_scan_memory(unsigned long node, const char *uname,int depth, void *data){/* 获取/memory节点的device_type属性 */const char *type = of_get_flat_dt_prop(node, "device_type", NULL);const __be32 *reg, *endp;int l;bool hotpluggable;/* /memory节点的device_type属性值必须是memory */if (type == NULL || strcmp(type, "memory") != 0)return 0;/* 获取/memory节点的linux,usable-memory或reg属性值(存放了内存的起始地址和长度信息) */reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);if (reg == NULL)reg = of_get_flat_dt_prop(node, "reg", &l);if (reg == NULL)return 0;endp = reg + (l / sizeof(__be32));/* 获取hotpluggable属性值(指示是否可以热插拔) */hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);pr_debug("memory scan node %s, reg size %d,\n", uname, l);/* 遍历reg属性记录的一块或多块内存 */while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {u64 base, size;/* 获取当前内存块的起始地址 */base = dt_mem_next_cell(dt_root_addr_cells, &reg);size = dt_mem_next_cell(dt_root_size_cells, &reg);if (size == 0)continue;pr_debug(" - %llx , %llx\n", (unsigned long long)base,(unsigned long long)size);/* 对base和size进行一系列校验后,调用memblock_add添加内存块(struct memblock) */early_init_dt_add_memory_arch(base, size);if (!hotpluggable)continue;/* 若当前内存块可以热插拔,那么标记之 */if (early_init_dt_mark_hotplug_memory_arch(base, size))pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",base, base + size);}return 0;}

kernel解析Device Tree

Device Tree文件结构描述就以上struct fdt_header、struct fdt_node_header及struct fdt_property三个结构体描述kernel会根据Device Tree的结构解析出kernel能够使用的struct property结构体,并根据Device Tree中的属性填充结构体struct property结构体描述如下:

struct property {char *name; /* 指向属性名字符串,位于DTB的strings block*/int length; /* 属性的长度 */void *value; /* void *类型,指向属性值,位于DTB的structure block */struct property *next; /* 一个节点的所有属性构成一个链表 */unsigned long _flags;unsigned int unique_id;struct bin_attribute attr; /* 属性文件,与sysfs文件系统挂接 */};

kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表在early_init_dt_scan_nodes()中会做以下三件事 扫描/chosen或者/chose@0节点下面的bootargs属性值到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中扫描根节点下面,获取{size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中扫描具有device_type =“memory”属性的/memory或者/memory@0节点下面的reg属性值,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息

Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:

struct device_node {const char *name; /* node的名称,取最后一次“/”和“@”之间子串,位于DTB的structure block*/const char *type; /* device_type的属性名称,没有为<NULL> */phandle phandle;/* phandle属性值 */const char *full_name; /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */struct fwnode_handle fwnode;struct property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */struct property *deadprops; /* removed properties */struct device_node *parent; /* 父节点 */struct device_node *child; /* 子节点 */struct device_node *sibling; /* 姊妹节点,与自己同等级的node */struct kobject kobj;/* sysfs文件系统目录体现 */unsigned long _flags;/* 当前node状态标志位,见/include/linux/of.h line124-127 */void *data;};/* flag descriptions (need to be visible even when !CONFIG_OF) */#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */#define OF_DETACHED 2 /* node has been detached from the device tree*/#define OF_POPULATED3 /* device already created for the node */#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */

从DTB到struct device_node示例

kernel调用unflatten_device_tree函数,将DTB文件中的设备节点转换为一个个的struct device_node

设备树示例:

/ {model = "SMDK2416";compatible = "samsung,s3c2416";#address-cells = <1>;#size-cells = <1>;memory@30000000 {device_type = "memory";reg = <0x30000000 0x4000000>;};pinctrl@56000000 {name = "example_name";compatible = "samsung,s3c2416-pinctrl";};};

设备树文件被DTC编译为DTB之后,被u-boot传递给kernel,然后内核读取其节点信息,建立如下的由device_node构成的树状结构:

为了突出device_node的name成员和full_name成员的差别,在示例设备树中没有对节点memory@30000000设置name属性,则对应的device_node的name成员置为,源于函数populate_node的处理。

将device_node转换成platform_device

能转换platform_device的先决条件:含有compatible属性的根节点的子节点,或者compatibe属性值为"simple-bus"、“simple-mfd”、“isa”、"arm,amba-bus"的节点的含有compatible属性的子节点drivers/of/platform.c的of_platform_default_populate_init函数负责为合适的设备节点构建platform_devicekernel为该节点创建platform_device后,将其注册到platform_bus_type,根据kernel的总线-设备-驱动模型,如果匹配总线上的某个platform_driver,那么该驱动的probe函数会被调用

设备树在文件系统中的表示

所有设备树的信息存放于/sys/firmware目录下: /sys/firmware/fdt 该文件表示原始DTB文件,可用hexdump -C /sys/firmware/fdt查看/sys/firmware/devicetree 以目录结构呈现设备树,每个device_node对应一个目录,每个属性对应节点目录下的一个文件,比如根节点对应base目录,该目录下有compatible等文件所有的platform_device会在/sys/devices/platform下对应一个目录,这些platform_device有来自设备树的,也有来自.c文件中手工注册的。由kernel根据设备树创建的platform_device对应的目录下存在一个名为of_node的软链接,链接向该platform_device对应的device_node对应的目录。 /proc与设备树:/proc/device-tree作为链接文件指向/sys/firmware/devicetree/base。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。