linux内核解析设备树节点

准备

  使用的源码包为华为官方的ascend200AI加速模块的SDK,其下载地址位于:点击跳转
  使用的固件与驱动版本为:1.0.9.alpha
  压缩包名称为:A200-3000-sdk_20.2.0.zip
  将A200-3000-sdk_20.2.0.zip解压后可以看到Ascend310-source-minirc.tar.gz压缩包,这个压缩包里有ascend200AI加速模块的linux内核源码包、设备树及驱动文件等。

DTB文件分析

DTB文件内容布局

  • struct ftd_header:用来表明各个分部的偏移地址,整个文件的大小,版本号等;
  • memory reservationblock:在设备树中使用/memreserve/ 定义的保留内存信息;
  • structure block:保存节点的信息,节点的结构;
  • strings block:保存属性的名字,单独作为字符串保存;

struct ftd_header

  表示整个设备树的块和地址信息。

struct fdt_header {
uint32_t magic;
uint32_t totalsize;
uint32_t off_dt_struct;
uint32_t off_dt_strings;
uint32_t off_mem_rsvmap;
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};

  例如:

00000000: D0 0D FE ED 00 00 25 40 00 00 00 38 00 00 21 C8    P.~m..%@...8..!H
00000010: 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00    ...(............
00000020: 00 00 03 78 00 00 21 90 00 00 00 00 00 00 00 00    ...x..!.........
00000030: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00    ................
00000040: 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00 01    ................
00000050: 00 00 00 03 00 00 00 04 00 00 00 0F 00 00 00 01    ................
00000060: 00 00 00 03 00 00 00 0F 00 00 00 1B 78 6C 6E 78    ............xlnx
00000070: 2C 7A 79 6E 71 2D 37 30 30 30 00 00 00 00 00 01    ,zynq-7000......
00000080: 63 68 6F 73 65 6E 00 00 00 00 00 03 00 00 00 16    chosen..........
00000090: 00 00 00 26 63 6F 6E 73 6F 6C 65 3D 74 74 79 50    ...&console=ttyP
000000a0: 53 30 2C 31 31 35 32 30 30 00 00 00 00 00 00 02    S0,115200.......
000000b0: 00 00 00 01 61 6C 69 61 73 65 73 00 00 00 00 03    ....aliases.....
000000c0: 00 00 00 18 00 00 00 2F 2F 61 6D 62 61 2F 65 74    .......//amba/et
  • magic:模数,必须为0xd00dfeed
  • totalsize:整个dtb文件的大小,如上为00 00 25 40,即为9536字节
  • off_dt_struct,即structure block的偏移地址,如上00 00 00 38,即56字节
  • off_dt_strings,即strings block的偏移地址;,如上00 00 21 C8,即8648字节
  • off_mem_rsvmap,即memory reservation block的偏移地址;00 00 00 28,即40字节,后面8个字节表示保留内存的address,8个字节表示保留内存的size
  • size_dt_strings:strings block的的大小
  • size_dt_struct:structure block的大小

memory reservationblock

  设备树的memreserve信息,只有16个字节,8个字节表示保留内存的address,8个字节表示保留内存的size,他在内核解析设备树时告诉内核要保留哪里的内存在存放设备树。

structure block

  存放所有属性的值,每个节点以0x00000001开始,后面接节点名字,以0x00000002结束,0x00000003表示后面要跟属性值了,后面依次是4字节的val的长度,4字节val的名字在string block的偏移,然后跟val的字符。整个 structure block以0x00000009结束。在dts文件中会有节点的别名,编译为dtb文件是没有的,外部别名添加在信息会全部汇总在节点里。
例如:
dts:

#size-cells = <0x1>;
	compatible = "xlnx,zynq-7000";

	chosen {
		bootargs = "console=ttyPS0,115200";
	};

	aliases {
		ethernet0 = "/amba/ethernet@e000b000";
		serial0 = "/amba/serial@e0001000";
		serial1 = "/amba/serial@e0000000";
		spi0 = "/amba/spi@e000d000";
		spi1 = "/amba/spi@e0006000";
		spi2 = "/amba/spi@e0007000";
	};

	memory {
		device_type = "memory";
		reg = <0x0 0x10000000>;
	};

	cpus {

dtb:

00000070: 2C 7A 79 6E 71 2D 37 30 30 30 00 00 00 00 00 01    ,zynq-7000......
00000080: 63 68 6F 73 65 6E 00 00 00 00 00 03 00 00 00 16    chosen..........
00000090: 00 00 00 26 63 6F 6E 73 6F 6C 65 3D 74 74 79 50    ...&console=ttyP
000000a0: 53 30 2C 31 31 35 32 30 30 00 00 00 00 00 00 02    S0,115200.......
000000b0: 00 00 00 01 61 6C 69 61 73 65 73 00 00 00 00 03    ....aliases.....
000000c0: 00 00 00 18 00 00 00 2F 2F 61 6D 62 61 2F 65 74    .......//amba/et
000000d0: 68 65 72 6E 65 74 40 65 30 30 30 62 30 30 30 00    hernet@e000b000.
000000e0: 00 00 00 03 00 00 00 16 00 00 00 39 2F 61 6D 62    ...........9/amb
000000f0: 61 2F 73 65 72 69 61 6C 40 65 30 30 30 31 30 30    a/serial@e000100
00000100: 30 00 00 00 00 00 00 03 00 00 00 16 00 00 00 41    0..............A
00000110: 2F 61 6D 62 61 2F 73 65 72 69 61 6C 40 65 30 30    /amba/serial@e00
00000120: 30 30 30 30 30 00 00 00 00 00 00 03 00 00 00 13    00000...........
00000130: 00 00 00 49 2F 61 6D 62 61 2F 73 70 69 40 65 30    ...I/amba/spi@e0
00000140: 30 30 64 30 30 30 00 00 00 00 00 03 00 00 00 13    00d000..........
00000150: 00 00 00 4E 2F 61 6D 62 61 2F 73 70 69 40 65 30    ...N/amba/spi@e0
00000160: 30 30 36 30 30 30 00 00 00 00 00 03 00 00 00 13    006000..........
00000170: 00 00 00 53 2F 61 6D 62 61 2F 73 70 69 40 65 30    ...S/amba/spi@e0
00000180: 30 30 37 30 30 30 00 00 00 00 00 02 00 00 00 01    007000..........
00000190: 6D 65 6D 6F 72 79 00 00 00 00 00 03 00 00 00 07    memory..........
000001a0: 00 00 00 58 6D 65 6D 6F 72 79 00 00 00 00 00 03    ...Xmemory......
000001b0: 00 00 00 08 00 00 00 64 00 00 00 00 10 00 00 00    .......d........

  首先0x00000001+63 68 6F 73 65 6E 00(chosen),表示chosen节点开始,00 00 00 03表示后面要跟一个属性,00 00 00 16表示val有22个字节,即console=ttyPS0,115200,00 00 00 26表示在string block的偏移地址,然后跟val,灾后00 00 00 00 02结束该节点。

strings block

  存放设备树所有的属性名字,例如model、compatible等。

000021a0: 00 00 00 00 00 00 00 1F 00 00 00 02 00 00 00 00    ................
000021b0: 00 00 00 20 00 00 00 02 00 00 00 02 00 00 00 02    ................
000021c0: 00 00 00 02 00 00 00 09 23 61 64 64 72 65 73 73    ........#address
000021d0: 2D 63 65 6C 6C 73 00 23 73 69 7A 65 2D 63 65 6C    -cells.#size-cel
000021e0: 6C 73 00 63 6F 6D 70 61 74 69 62 6C 65 00 62 6F    ls.compatible.bo
000021f0: 6F 74 61 72 67 73 00 65 74 68 65 72 6E 65 74 30    otargs.ethernet0
00002200: 00 73 65 72 69 61 6C 30 00 73 65 72 69 61 6C 31    .serial0.serial1
00002210: 00 73 70 69 30 00 73 70 69 31 00 73 70 69 32 00    .spi0.spi1.spi2.
00002220: 64 65 76 69 63 65 5F 74 79 70 65 00 72 65 67 00    device_type.reg.
00002230: 63 6C 6F 63 6B 73 00 63 6C 6F 63 6B 2D 6C 61 74    clocks.clock-lat
00002240: 65 6E 63 79 00 63 70 75 30 2D 73 75 70 70 6C 79    ency.cpu0-supply
00002250: 00 6F 70 65 72 61 74 69 6E 67 2D 70 6F 69 6E 74    .operating-point
00002260: 73 00 69 6E 74 65 72 72 75 70 74 73 00 69 6E 74    s.interrupts.int
00002270: 65 72 72 75 70 74 2D 70 61 72 65 6E 74 00 72 65    errupt-parent.re
00002280: 67 75 6C 61 74 6F 72 2D 6E 61 6D 65 00 72 65 67    gulator-name.reg
00002290: 75 6C 61 74 6F 72 2D 6D 69 6E 2D 6D 69 63 72 6F    ulator-min-micro

  按照顺序存放字符,以0x00(字符‘.’)来区分单词

machine_desc

  这个machine_desc主要是匹配根节点的compatible属性。这个结构体会描述和硬件相关的一些信息。
  mdesc = setup_machine_fdt(__atags_pointer),__atags_pointer是uboot传入的参数存放的位置。

  1. 一般内核处理根节点compatible属性的过程:

  内核会根据根节点compatible中string的顺序去找合适的machine_desc,每个machine_desc是表明这个内核能支持哪些设备。例如arch/arm/mach_zynq/common.c

static const char * const zynq_dt_match[] = {
	"xlnx,zynq-7000",
	NULL
};

DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
	/* 64KB way size, 8-way associativity, parity disabled */
	.l2c_aux_val    = 0x00400000,
	.l2c_aux_mask	= 0xffbfffff,
	.smp		= smp_ops(zynq_smp_ops),
	.map_io		= zynq_map_io,
	.init_irq	= zynq_irq_init,
	.init_machine	= zynq_init_machine,
	.init_late	= zynq_init_late,
	.init_time	= zynq_timer_init,
	.dt_compat	= zynq_dt_match,
	.reserve	= zynq_memory_init,
MACHINE_END

  这个dt_compat表示能匹配到"xlnx,zynq-7000",表示本内核支持设备树根节点为"xlnx,zynq-7000"的设备。DT_MACHINE_START是一个__attribute__的宏,他将名字为__mach_desc_XILINX_EP107的结构体放到名为.arch.info.init段中。linux内核将所有用MACHINE_START定义的结构体都被组织到.arch.info.init段中,和内核模块注册的机制一样,同时定义了__arch_info_begin和__arch_info_end 两个空的machine_desc结构体对象,在链接脚本中会将.arch.info.init段链接在这两个变量中间,这样就能通过__arch_info_begin变量地址遍历到整个.arch.info.init段了
函数调用过程:

start_kernel
	-->setup_arch
		//如果设备树正确则使用设备树来设置machine
		-->mdesc = setup_machine_fdt(__atags_pointer);
			//找到最匹配的machine_desc并且返回
			-->mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
				-->get_next_compat
					//按顺序遍历.arch.info.init段的内容
					-->arch_get_next_mach
					{
						static const struct machine_desc *mdesc = __arch_info_begin;
						const struct machine_desc *m = mdesc;
						if (m >= __arch_info_end)
								return NULL;
						mdesc++;
						*match = m->dt_compat;
						return m;
					}
						
		//如果设备树解析出错,则使用uboot传入的参数
		-->mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
		
  1. 华为昇腾310内核

   设备树节点:

/memreserve/	0x0000000000000000 0x0000000000010000;
/ {
	compatible = "hisilicon,hi1910-asic", "hisilicon,mini";
	hisi,boardid = <1 0 0 4>;

   去内核源码中grep"hisilicon,hi1910-asic", “hisilicon,mini”;是找不到的,然后就跳进去看了下

start_kernel
	-->setup_arch // arch/arm64/kernel/setup.c
		//注意这里是没有返回值的
		-->setup_machine_fdt(__fdt_pointer);
			-->of_flat_dt_get_machine_name();//Clinux-4.19/drivers/of/fdt.c
			{
				const char *name;
				//返回0
				unsigned long dt_root = of_get_flat_dt_root();
			
				name = of_get_flat_dt_prop(dt_root, "model", NULL);
				if (!name)
					name = of_get_flat_dt_prop(dt_root, "compatible", NULL);
				return name;
			}

   这里直接找的是根节点下的model属性,找不到的话找compatible,然后pr_info(“Machine model: %s\n”, name);打印了一下信息,然后就没了,也没有machine_desc的返回值。
  后面的一些内核和硬件架构相关的初始化代码好像也没有用到这个machine_desc,所以有没有这个machine_desc因该没有关系。

dtb转化为device_node

setup_machine_fdt

start_kernel
	-->setup_arch // arch/arm64/kernel/setup.c
		-->setup_machine_fdt(__fdt_pointer);
			-->early_init_dt_scan
				//检查设备树的魔数是否为D0 0D FE ED 00 00 25 40
				//版本version是否正确,这样好像是早期的版本dtb文件分布不同
				//将设备树的首地址传给initial_boot_params,后面经常用到
				-->early_init_dt_verify
				//解析设备树的部分节点
				//1.chosen节点下的bootargs节点
				//2.{size,address}-cells节点
				//3.memory,reg,linux,usable-memory等和内存相关的节点信息
				-->early_init_dt_scan_nodes
				{
					of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
					of_scan_flat_dt(early_init_dt_scan_root, NULL);
					of_scan_flat_dt(early_init_dt_scan_memory, NULL);
				}

arm_memblock_init

  和内存初始化相关的一些操作,没有整明白,网上查了下大概是使用设备树中的memreserve节点的信息,将这个地方保留存放设备树,不要让内核使用。

unflatten_device_tree

start_kernel
	-->setup_arch // arch/arm64/kernel/setup.c
		-->unflatten_device_tree
			//	initial_boot_params存放的是dtb文件的起始地址 of_root是device_node结构体,根节点会保存在里面
			-->__unflatten_device_tree(initial_boot_params, NULL, &of_root,
							early_init_dt_alloc_memory_arch, false);
				//unflatten_dt_nodes第一次调用,计算出整个设备树需要多少个device_node结构体
				//然后返回需要的大小size
				-->size = unflatten_dt_nodes(blob, NULL, dad, NULL);
				//根据上面的size申请内存
				-->mem = dt_alloc(size + 4, __alignof__(struct device_node));
				//unflatten_dt_nodes第二次调用,传入mem,真正的开始将dtb文件构造为device_node
				-->unflatten_dt_nodes(blob, mem, dad, mynodes);
					//遍历能用的节点,如果没有status则返回true
					//如果status属性为ok或者okay则返回true
					//否则返回false
					//按顺序遍历能用的节点
						...fdt_next_node()...
					-->of_fdt_device_is_available
					{
						const char *status = fdt_getprop(blob, node, "status", NULL);
						if (!status)
							return true;
						if (!strcmp(status, "ok") || !strcmp(status, "okay"))
							return true;
						return false;
					}
					-->populate_node
					{
						//获得节点名字,长度存在l
						pathp = fdt_get_name(blob, offset, &l);
						//申请内存,此时申请的内存为一个device_node结构体加上节点名字的长度
						allocl = ++l;
						np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
						__alignof__(struct device_node));
						//这个full的指针指向的是sizeof(struct device_node)后面的节点名字
						np->full_name = fn = ((char *)np) + sizeof(*np);
						//设置parent、sibling、child指针
						if (dad != NULL) {
							np->parent = dad;
							np->sibling = dad->child;
							dad->child = np;
						}
						//构造本届点device_node中的properties
						//properties结构体存放属性名、值,通过next指针串成链表
						populate_properties(blob, offset, mem, np, pathp, dryrun);
						//如果有name和device_type则设置device_node的name和device_type,没有则为null
						np->name = of_get_property(np, "name", NULL);
						np->type = of_get_property(np, "device_type", NULL);
						if (!np->name)
							np->name = "<NULL>";
						if (!np->type)
							np->type = "<NULL>";
					}

总结

  device_node结构体内容如下所示:

struct device_node {
	const char *name;
	const char *type;
	phandle phandle;
	const char *full_name;
	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
};

  跟节点下每一个大括号都会被转化为1个device_node结构体:

  1. name不是设备树下子节点的名字,而是它的name属性,如果没有name属性,则他为空
  2. type来自于节点中device_type属性,没有则为空
  3. full_name才是设备树节点的名字,比如led@0x01
  4. properties节点的属性
  5. parent指向父节点的device_node结构体
  6. child指向子节点的device_node结构体
  7. sibling指向同级节点

  property 结构体:

struct property {
	char	*name;
	int	length;
	void	*value;
	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
};
  1. name:属性名
  2. value:属性的值
  3. length:按字节计算value的长度
  4. next:指向下一个property属性,串成一个链表

device_node转化为platform_device

哪些节点会被转化为platform_device

  1. 节点必须有compatible属性
  2. 必须是根节点的子节点
  3. 如果根节点的子节点有simple-bus、simple-mfd或者isa等属性,它的子节点也能被转化为platform_device
  4. 如果是子节点下的子节点,则如父节点的probe函数来处理,例如spi节点下的子节点,会在spi_register_master函数内被处理,被转化为一个spi_device结构体,i2c则是被转化为一个i2c_client结构体

函数调用过程

start_kernel
-->rest_init
	-->pid = kernel_thread(kernel_init, NULL, CLONE_FS);
		-->kernel_init
			-->kernel_init_freeable
				-->do_basic_setup
					-->do_initcalls
					{
						//内核initcall机
						for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
							do_initcall_level(level);
					}
//drivers/of/platform.c,内核initcall机制,会放到指定的段中按顺序指向段内的函数
arch_initcall_sync(of_platform_default_populate_init);
#define arch_initcall_sync(fn)		__define_initcall(fn, 3s)
of_platform_default_populate_init
	//处理特殊的节点,没用过
	-->for_each_matching_node(node, reserved_mem_matches)
	-->of_platform_device_create(node, NULL, NULL);
	-->node = of_find_node_by_path("/firmware");
	//处理除上以外所有的节点
	-->of_platform_default_populate
		//of_default_bus_match_table表中的子节点也能被转化为platform_device
		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 /* CONFIG_ARM_AMBA */
			{} /* Empty terminated list */
		};
		-->of_platform_populate(root, of_default_bus_match_table, lookup,
				    parent);
		{
			//获取根节点
			root = root ? of_node_get(root) : of_find_node_by_path("/");
			{
				//获取根节点的子节点
				for_each_child_of_node(root, child) {
					//对每个子结点执行这个函数
					//默认根节点下的子节点都是总线
					rc = of_platform_bus_create(child, matches, lookup, parent, true);
						//开始创建platform_device
						-->of_platform_device_create_pdata
							//将io、中断和reg属性转化为resources资源。
							//里面还会处理dev结构体的其他内容,都是根据device_node来的
							-->of_device_alloc
					//如果该节点的compatible不含有of_default_bus_match_table里的内容,则返回
					//否则处理它的子节点
					if (!dev || !of_match_node(matches, bus))
						return 0;
				}
			}
		}

总结

  platform_device 结构体如下:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};
  1. num_resources表示有多少个resources,一般是设备树节点下的reg属性和interrupts属性和io资源
  2. resource是一个指针,指向设备树节点的资源,会从device_node得到
  3. dev结构体里的内容都是根据device_node来的
  4. dev->of_node指向的是它的device_node
  5. dev->bus默认为platform_bus_type

platform总线和设备匹配过程

//drivers/base/platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform总线和设备匹配优先级:

  1. platform_dev.driver_override 和 platform_driver.drv->name
  2. platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
  3. platform_dev.name 和 platform_driver.id_table
  4. platform_dev.name 和 platform_driver.drv->name
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值