Linux设备树DTB存储格式

文章目录

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

DTB存储格式

Linux设备树DTB存储格式_第1张图片
Linux设备树DTB存储格式_第2张图片

  • 头部(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中的节点信息举例

Linux设备树DTB存储格式_第3张图片

  • dt_struct存储节点数值及名称相关信息,如:”A string“及node1
  • dt_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部分
    Linux设备树DTB存储格式_第4张图片

DTB数据结构

dtb文件为大端存储模式

struct ftd_header区域数据结构

struct fdt_header {
uint32_t magic; //dtb文件的固定开始数字 0xd00dfeed
uint32_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;// =FDT_PROP
    fdt32_t len;//后接属性值的长度(如果是字符串包括'\0')
    fdt32_t nameoff;//属性名在strings block区域的偏移值(很多节点的属性名相同,节省空间)
    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的内容。
    Linux设备树DTB存储格式_第5张图片
    案例:
led {
	compatible = "jz2440_led";
	pin = <S3C2410_GPF(5)>;
};

FDT_BEGIN_NODE+节点名称开始,然后是FDT_PROP描述节点的属性,然后是struct(len+nameoff)+val,该节点的属性描述完成后,使用FDT_END_NODE表示节点结束;最后,当所有节点描述完毕后,使用FDT_END结束struct block。
Linux设备树DTB存储格式_第6张图片

strings block

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

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

Linux设备树DTB存储格式_第7张图片

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_mach
	
	arch_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保存到compat
		compat指向一系列字符串(一个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);
		/*
			调用回调函数it
			offset: 节点起始位置在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

Linux设备树DTB存储格式_第8张图片

  • 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文件系统挂接 */
};
  1. kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表
  2. 在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保存了系统内存相关的信息
    Linux设备树DTB存储格式_第9张图片
  1. 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的属性名称,没有为 */
    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_POPULATED      3 /* 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构成的树状结构:
    Linux设备树DTB存储格式_第10张图片
    为了突出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_device
  • kernel为该节点创建platform_device后,将其注册到platform_bus_type,根据kernel的总线-设备-驱动模型,如果匹配总线上的某个platform_driver,那么该驱动的probe函数会被调用
    Linux设备树DTB存储格式_第11张图片

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

  1. 所有设备树的信息存放于/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对应的目录。
  1. /proc与设备树:/proc/device-tree作为链接文件指向/sys/firmware/devicetree/base。

你可能感兴趣的:(Linux,linux,运维,服务器)