Linux Device tree(二) - DT分析

  • 分析DT。

1.DTB format

  The Devicetree Blob (DTB) format is a flat binary encoding of devicetree data. It used to exchange devicetree data between software programs. For example, when booting an operating system, firmware will pass a DTB to the OS kernel.

  The DTB format encodes the devicetree data within a single, linear, pointerless data structure. It consists of a small header, followed by three variable sized sections: the memory reservation block , the structure
block, and the strings block. These should be present in the flattened devicetree in that order.
在这里插入图片描述

Note:
  The (free space) sections may not be present, though in some cases they might be required to satisfy the alignment constraints of the individual blocks.

1.1.Header

  The layout of the header for the devicetree is defined by the following C structure. All the header fields are 32-bit integers,stored in big-endian format.

  Flattened Devicetree Header Fields:

 57 struct fdt_header {
 58     fdt32_t magic;           /* magic word FDT_MAGIC */
 59     fdt32_t totalsize;       /* total size of DT block */
 60     fdt32_t off_dt_struct;       /* offset to structure */
 61     fdt32_t off_dt_strings;      /* offset to strings */
 62     fdt32_t off_mem_rsvmap;      /* offset to memory reserve map */
 63     fdt32_t version;         /* format version */
 64     fdt32_t last_comp_version;   /* last compatible version */
 65 
 66     /* version 2 fields below */
 67     fdt32_t boot_cpuid_phys;     /* Which physical CPU id we're
 68                         booting on */
 69     /* version 3 fields below */
 70     fdt32_t size_dt_strings;     /* size of the strings block */
 71 
 72     /* version 17 fields below */
 73     fdt32_t size_dt_struct;      /* size of the structure block */
 74 };
  • magic: This field shall contain the value 0xd00dfeed (big-endian).
  • totalsize This field shall contain the total size in bytes of the devicetree data structure. This size shall encompass all sections of the structure: the header, the memory reservation block, structure block and strings block, as well as any free space gaps between the blocks or after the final block.
  • off_dt_struct This field shall contain the offset in bytes of the structure block from the beginning of the header.
  • off_dt_strings This field shall contain the offset in bytes of the strings block from the beginning of the header.
  • off_mem_rsvmap This field shall contain the offset in bytes of the memory reservation block from the beginning of the header.
  • boot_cpuid_phys This field shall contain the physical ID of the system’s boot CPU. It shall be identical to the physical ID given in the reg property of that CPU node within the devicetree.
  • size_dt_strings This field shall contain the length in bytes of the strings block section of the devicetree blob.
  • size_dt_struct This field shall contain the length in bytes of the structure block section of the devicetree blob.

  执行fdtdump –sd xxx.dtb > xxx.txt,打开xxx.txt文件,部分输出信息如下所示:

// magic:  0xd00dfeed
// totalsize:  0xce4 (3300)
// off_dt_struct: 0x38
// off_dt_strings: 0xc34
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0xb0
// size_dt_struct: 0xbfc

2.内核验证dtb合法性

  dtb里面存放了各种硬件信息,如果dtb有问题,会导致后续开机过程中读取的设备信息有问题而导致无法开机。

  在生成dtb的时候会在头部上添加一个幻数magic,而验证dtb是否合法主要也就是看这个dtb的magic是否和预期的值一致。 其中,magic是一个固定的值,0xd00dfeed(大端)或者0xedfe0dd0(小端)。

  DTB的幻数,也就是OF_DT_MAGIC定义如下:

   arch/arm/kernel/head-common.S:
   15 #ifdef CONFIG_CPU_BIG_ENDIAN                                                                           
   16 #define OF_DT_MAGIC 0xd00dfeed                  
   17 #else                                           
   18 #define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */
   19 #endif  

  以vexpress-v2p-ca9.dtb 为例:

~/Documents/work/code/linux/linux-stable$ hexdump -C arch/arm/boot/dts/vexpress-v2p-ca9.dtb | more
00000000  d0 0d fe ed 00 00 37 07  00 00 00 38 00 00 33 70  |......7....8..3p|
00000010  00 00 00 28 00 00 00 11  00 00 00 10 00 00 00 00  |...(............|
00000020  00 00 03 97 00 00 33 38  00 00 00 00 00 00 00 00  |......38........|
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 08  00 00 00 00 56 32 50 2d  |............V2P-|
00000050  43 41 39 00 00 00 00 03  00 00 00 04 00 00 00 06  |CA9.............|
00000060  00 00 01 91 00 00 00 03  00 00 00 04 00 00 00 0e  |................|
00000070  00 00 00 0f 00 00 00 03  00 00 00 22 00 00 00 20  |..........."... |
00000080  61 72 6d 2c 76 65 78 70  72 65 73 73 2c 76 32 70  |arm,vexpress,v2p|
00000090  2d 63 61 39 00 61 72 6d  2c 76 65 78 70 72 65 73  |-ca9.arm,vexpres|
000000a0  73 00 00 00 00 00 00 03  00 00 00 04 00 00 00 2b  |s..............+|

  dtb前4个字节就是0xd00dfeed,即magic(大端)。 只要提取待验证dtb的地址上的数据的前四个字节,与0xd00dfeed(大端)或者0xedfe0dd0(小端)进行比较,如果匹配的话,就说明对应待验证dtb就是一个合法的dtb。

3.Boot loader 传参

  Essentially, the boot loader should provide (as a minimum) the following:

  • 1.Setup and initialise the RAM.
  • 2.Initialise one serial port.
  • 3.Detect the machine type.
  • 4.Setup the kernel tagged list.
  • 5.Load initramfs.
  • 6.Call the kernel image.

Note:Documentation/arm/Booting

3.1.Detect the machine type

  The boot loader should detect the machine type its running on by some method. Whether this is a hard coded value or some algorithm that
looks at the connected hardware is beyond the scope of this document.
The boot loader must ultimately be able to provide a MACH_TYPE_xxx
value to the kernel. (see linux/arch/arm/tools/mach-types). This
should be passed to the kernel in register r1.

  For DT-only platforms, the machine type will be determined by device tree. set the machine type to all ones (~0). This is not strictly necessary, but assures that it will not match any existing types.

3.2.Setup boot data

  The boot loader must provide either a tagged list or a dtb image for passing configuration data to the kernel. The physical address of the boot data is passed to the kernel in register r2.

3.2.1.ATAGS interface

  Minimal information is passed from firmware to the kernel with a tagged list of predefined parameters.

  • r0 : 0
  • r1 : Machine type number
  • r2 : Physical address of tagged list in system RAM

3.2.2.Entry with a flattened device-tree block

  Firmware loads the physical address of the flattened device tree block (dtb) into r2, r1 is not used, but it is considered good practice to use a valid
machine number as described in Documentation/arm/Booting.

  • r0 : 0
  • r1 : Valid machine type number. When using a device tree, a single machine type number will often be assigned to represent a class or family of SoCs.
  • r2 : physical pointer to the device-tree block (defined in chapter II) in RAM. Device tree can be located anywhere in system RAM, but it should be aligned on a 64 bit boundary.

  The kernel will differentiate between ATAGS and device tree booting by reading the memory pointed to by r2 and looking for either the flattened device tree block magic value (0xd00dfeed) or the ATAG_CORE value at offset 0x4 from r2 (0x54410001).

3.2.1. Setup the device tree

  The boot loader must load a device tree image (dtb) into system ram
at a 64bit aligned address and initialize it with the boot data. The dtb format is documented in Documentation/devicetree/booting-without-of.txt. The kernel will look for the dtb magic value of 0xd00dfeed at the dtb physical address to determine if a dtb has been passed instead of a tagged list.

  The boot loader must pass at a minimum the size and location of the
system memory, and the root filesystem location. The dtb must be placed in a region of memory where the kernel decompressor will not overwrite it, while remaining within the region which will be covered by the kernel’s low-memory mapping.A safe location is just above the 128MiB boundary from start of RAM.

实现如下:

cmd/bootz.c:
do_bootz
  ->do_bootm_states
    -> bootm_os_get_boot_func
      ->do_bootm_linux
        ->boot_jump_linux

static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number;//获取mechine_id
    void (*kernel_entry)(int zero, int arch, uint params);
    kernel_entry = (void (*)(int, int, uint))images->ep;
       r2 = (unsigned long)images->ft_addr;//获取dtb地址。
       kernel_entry(0, machid, r2);
}

  最后跳转到kernel_entry(0, machid, r2); 通过参数,将0传入到r0,machid传入到r1,dtb的地址传入到r2。最终就调用到kernel的入口。

3.2.2.kernel获取dtb地址

  arch/arm/kernel/head.S定义uboot和kernel的参数传递要求:

/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * This is normally called from the decompressor code.  The requirements
 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags or dtb pointer.
  • r0 = 0
    硬性规定r0寄存器为0,没有什么意义。
  • r1 = machine nr
    r1寄存器里面存放machine ID。
  • r2 = atags or dtb pointer
    r2寄存器里面存放atags或者dtb的地址指针。

  目前kernel支持旧的tag list的方式,同时也支持DT的方式。r2可能是dtb的地址指针(bootloader要传递给内核之前要copy到memory中),也可以能是tag list的指针。本文重点分析DT方式。

  ARM汇编部分的启动代码(head.S和head-common.S),负责传参工作:

  • 把uboot传来的r1值, 赋给变量: __machine_arch_type
  • 把uboot传来的r2值, 赋给变量: __atags_pointer // dtb首地址

  代码分析:
  _vet_atags 检查bootloader传入的参数列表atags的合法性。

 arch/arm/kernel/head.S:
  114     /*
  115      * r1 = machine no, r2 = atags or dtb,
  116      * r8 = phys_offset, r9 = cpuid, r10 = procinfo
  117      */
  118     bl  __vet_atags
  
arch/arm/kernel/head-common.S:
__vet_atags:
    tst    r2, #0x3            @ aligned?保证dtb的地址是四字节对齐的
    bne    1f

    ldr    r5, [r2, #0]    @获取dtb的前四个字节,存放在r5寄存器中
#ifdef CONFIG_OF_FLATTREE
    ldr    r6, =OF_DT_MAGIC    @ is it a DTB?,获取dtb的幻数,0xd00dfeed(大端)或者0xedfe0dd0(小端)
    cmp    r5, r6    @前四个字节和幻数进行对比
    beq    2f    @匹配,则说明是一个合法的dtb文件,跳到2
#endif
    bne    1f @不匹配,跳到1

2:    ret    lr                @ atag/dtb pointer is ok,直接返回,此时r2存放了dtb的地址

1:    mov    r2, #0@错误返回,此时,r2上是0
    ret    lr
ENDPROC(__vet_atags)

4.Kernel DT

  Linux uses DT data for three major purposes:

  • platform identification;
  • runtime configuration;
  • device population.

在这里插入图片描述
4.1.匹配platform(machine描述符)

  On ARM, setup_arch() in arch/arm/kernel/setup.c will call setup_machine_fdt() in arch/arm/kernel/devtree.c which searches through the machine_desc table and selects the machine_desc which best matches the device tree data. It determines the best match by looking at the ‘compatible’
property in the root device tree node, and comparing it with the dt_compat list in struct machine_desc (which is defined in arch/arm/include/asm/mach/arch.h if you’re curious). 如上图绿色部分所示:

  • setup_machine_fdt函数的功能就是根据Device Tree的信息,找到最适合的machine描述符,如果返回值为NULL,则调用setup_machine_tags,即使用ATAGS方式找到最适合的machine描述符。
  • of_flat_dt_match_machine是在machine描述符的列表中scan,找到最合适的那个machine描述符。machine描述符列表是静态定义的,DT_MACHINE_START和MACHINE_END用来定义一个machine描述符。编译的时候,compiler会把这些machine descriptor放到一个特殊的段中(.arch.info.init),形成machine描述符的列表。目前匹配machine描述符使用compatible strings,也就是dt_compat成员,这是一个string list,定义了这个machine所支持的列表。在扫描machine描述符列表的时候需要不断的获取下一个machine描述符的compatible字符串的信息。machine描述符:
struct machine_desc {  
    ...
    const char *const     *dt_compat;    /* array of device tree 'compatible' strings    *
    …
   };

4.2.runtime configuration

  In most cases, a DT will be the sole method of communicating data from firmware to the kernel, so also gets used to pass in runtime and configuration data like the kernel parameters string and the location of an initrd image.
  Most of this data is contained in the /chosen node, and when booting
Linux it will look something like this:

    chosen {
        bootargs = "console=ttyS0,115200 loglevel=8";
        initrd-start = <0xc8000000>;
        initrd-end = <0xc8200000>;                                                                           
    };

  如上图粉色部分所示,主要对三种类型的信息进行处理,分别是:

  • /chosen节点中 bootargs属性

    • /chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来。
  • 根节点的 #address-cells #size-cells属性

    • 根节点的#address-cells和#size-cells属性指定属性参数的位数,比如指定前面memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示。
  • /memory中的 reg属性。

    • /memory中的reg属性指定了不同板子内存的大小和起始地址。

总结:

  • /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
  • 确定根节点的这2个属性的值: #address-cells, #size-cells
    存入全局变量: dt_root_addr_cells, dt_root_size_cells
  • 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);

4.3.Device population

  uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,为什么内核运行中,不会去覆盖DTB所占用的那块内存呢?

  在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当内核启动时,也会把设备树所占用的区域保留下来。如下就是函数调用过程:

start_kernel 
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        arm_memblock_init(mdesc);   // arch/arm/kernel/setup.c
            early_init_fdt_reserve_self();
                    /* Reserve the dtb region */
                    // 把DTB所占区域保留下来, 即调用: memblock_reserve
                    early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                      fdt_totalsize(initial_boot_params),
                                      0);           
            early_init_fdt_scan_reserved_mem();  // 根据dtb中的memreserve信息, 调用memblock_reserve

4.3.1.device_node

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

struct device_node {
	const char *name;              /* node的名称,取最后一次“/”和“@”之间子串 */
	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_POPULATED      3 /* device already created for the node */
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */

  Device Tree的解析首先从unflatten_device_tree()开始,代码列出如下:

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, &of_root,
				early_init_dt_alloc_memory_arch);

	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
	of_alias_scan(early_init_dt_alloc_memory_arch);
}

static void __unflatten_device_tree(const void *blob,
			     struct device_node **mynodes,
			     void * (*dt_alloc)(u64 size, u64 align))
{
	unsigned long size;
	int start;
	void *mem;

    ...
	/* First pass, scan for size */
	start = 0;
	size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
	size = ALIGN(size, 4);

	/* Allocate memory for the expanded device tree */
	mem = dt_alloc(size + 4, __alignof__(struct device_node));
	memset(mem, 0, size);

	/* Second pass, do actual unflattening */
	start = 0;
	unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
}

  在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。

  在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。unflatten_dt_node()代码列出如下:


static void * unflatten_dt_node(const void *blob,
				void *mem,
				int *poffset,
				struct device_node *dad,
				struct device_node **nodepp,
				unsigned long fpsize,
				bool dryrun)
{
	const __be32 *p;
	struct device_node *np;
	struct property *pp, **prev_pp = NULL;
	const char *pathp;
	unsigned int l, allocl;
	static int depth;
	int old_depth;
	int offset;
	int has_name = 0;
	int new_format = 0;

	/* 获取node节点的name指针到pathp中 */
	pathp = fdt_get_name(blob, *poffset, &l);
	if (!pathp)
		return mem;

	allocl = ++l;

	/* version 0x10 has a more compact unit name here instead of the full
	 * path. we accumulate the full path size using "fpsize", we'll rebuild
	 * it later. We detect this because the first character of the name is
	 * not '/'.
	 */
	if ((*pathp) != '/') {
		new_format = 1;
		if (fpsize == 0) {
			/* root node: special case. fpsize accounts for path
			 * plus terminating zero. root node only has '/', so
			 * fpsize should be 2, but we want to avoid the first
			 * level nodes to have two '/' so we use fpsize 1 here
			 */
			fpsize = 1;
			allocl = 2;
			l = 1;
			pathp = "";
		} else {
			/* account for '/' and path size minus terminal 0
			 * already in 'l'
			 */
			fpsize += l;
			allocl = fpsize;
		}
	}

	/* 分配struct device_node内存,包括路径全称大小 */
	np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
				__alignof__(struct device_node));
	if (!dryrun) {
		char *fn;
		of_node_init(np);

		/* 填充full_name,full_name指向该node节点的全路径名称字符串 */
		np->full_name = fn = ((char *)np) + sizeof(*np);
		if (new_format) {
			/* rebuild full path for new format */
			if (dad && dad->parent) {
				strcpy(fn, dad->full_name);
				fn += strlen(fn);
			}
			*(fn++) = '/';
		}
		memcpy(fn, pathp, l);

		/* 节点挂接到相应的父节点、子节点和姊妹节点 */
		prev_pp = &np->properties;
		if (dad != NULL) {
			np->parent = dad;
			np->sibling = dad->child;
			dad->child = np;
		}
	}
	/* 处理该node节点下面所有的property */
	for (offset = fdt_first_property_offset(blob, *poffset);
	     (offset >= 0);
	     (offset = fdt_next_property_offset(blob, offset))) {
		const char *pname;
		u32 sz;

		if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
			offset = -FDT_ERR_INTERNAL;
			break;
		}

		if (pname == NULL) {
			pr_info("Can't find property name in list !\n");
			break;
		}
		if (strcmp(pname, "name") == 0)
			has_name = 1;
		pp = unflatten_dt_alloc(&mem, sizeof(struct property),
					__alignof__(struct property));
		if (!dryrun) {
			/* 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. */
			
			/* 处理phandle,得到phandle值 */
			if ((strcmp(pname, "phandle") == 0) ||
			    (strcmp(pname, "linux,phandle") == 0)) {
				if (np->phandle == 0)
					np->phandle = be32_to_cpup(p);
			}
			/* And we process the "ibm,phandle" property
			 * used in pSeries dynamic device tree
			 * stuff */
			if (strcmp(pname, "ibm,phandle") == 0)
				np->phandle = be32_to_cpup(p);
			pp->name = (char *)pname;
			pp->length = sz;
			pp->value = (__be32 *)p;
			*prev_pp = pp;
			prev_pp = &pp->next;
		}
	}
	/* with version 0x10 we may not have the name property, recreate
	 * it here from the unit name if absent
	 */
	/* 为每个node节点添加一个name的属性 */
	if (!has_name) {
		const char *p1 = pathp, *ps = pathp, *pa = NULL;
		int sz;

		/* 属性name的value值为node节点的名称,取“/”和“@”之间的子串 */
		while (*p1) {
			if ((*p1) == '@')
				pa = p1;
			if ((*p1) == '/')
				ps = p1 + 1;
			p1++;
		}
		if (pa < ps)
			pa = p1;
		sz = (pa - ps) + 1;
		pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
					__alignof__(struct property));
		if (!dryrun) {
			pp->name = "name";
			pp->length = sz;
			pp->value = pp + 1;
			*prev_pp = pp;
			prev_pp = &pp->next;
			memcpy(pp->value, ps, sz - 1);
			((char *)pp->value)[sz - 1] = 0;
		}
	}
	/* 填充device_node结构体中的name和type成员 */
	if (!dryrun) {
		*prev_pp = 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>";
	}

	old_depth = depth;
	*poffset = fdt_next_node(blob, *poffset, &depth);
	if (depth < 0)
		depth = 0;
	/* 递归调用node节点下面的子节点 */
	while (*poffset > 0 && depth > old_depth)
		mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
					fpsize, dryrun);

	if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
		pr_err("unflatten: error %d processing FDT\n", *poffset);

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun && np->child) {
		struct device_node *child = np->child;
		np->child = NULL;
		while (child) {
			struct device_node *next = child->sibling;
			child->sibling = np->child;
			np->child = child;
			child = next;
		}
	}

	if (nodepp)
		*nodepp = np;

	return mem;
}

  通过以上函数处理就得到了所有的struct device_node结构体,为每一个node都会自动添加一个名称为“name”的property,property.length的值为当前node的名称取最后一个“/”和“@”之间的子串(包括‘\0’)。例如:/serial@e2900800,则length = 7,property.value = device_node.name = “serial”。

4.5.device_node转换为platform_device

  经过以上解析,Device Tree的数据已经全部解析出具体的struct device_node和struct property结构体,device_node需要和具体的device进行绑定。

4.5.1.platform_device和device_node的绑定过程

  调用流程start_kernel->rest_init->kernel_init->kernel_init_freeable->
do_basic_setup->do_initcalls,do_initcalls函数中,在do_initcalls函数中,kernel会依次执行各个initcall函数,然后会执行customize_machine,代码如下:

static int __init customize_machine(void)
{
    if (machine_desc->init_machine)  
        machine_desc->init_machine();
    return 0;
}
arch_initcall(customize_machine); 

DT_MACHINE_START(IMX6SL, "Freescale i.MX6 SoloLite (Device Tree)")
      .init_machine   = imx6sl_init_machine,
MACHINE_END   

 static void __init imx6sl_init_machine(void)
  {
      of_platform_default_populate(NULL, NULL, parent);
  }
  
  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);
  }

1.重点分析of_platform_populate():

int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;
	
	 //之前传入的NULL,就是从/节点下开始,即根节点
	root = root ? of_node_get(root) : of_find_node_by_path("/"); 
	if (!root)
		return -EINVAL;

	pr_debug("%s()\n", __func__);
	pr_debug(" starting at: %s\n", root->full_name);

	for_each_child_of_node(root, child) {  //这里面是一个for循环,如果root节点下面有child,就执行一遍
		rc = of_platform_bus_create(child, matches, lookup, parent, true);  //重要函数
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}

of_platform_bus_create:


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_node *child;
	struct platform_device *dev;
	const char *bus_id = NULL;
	void *platform_data = NULL;
	int rc = 0;

	//确保device node有compatible属性的代码
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %s, no compatible prop\n",
			 __func__, bus->full_name);
		return 0;
	}

	if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
		pr_debug("%s() - skipping %s, already populated\n",
			__func__, bus->full_name);
		return 0;
	}
	
	//在传入的lookup table寻找和该device node匹配的附加数据
	auxdata = of_dev_lookup(lookup, bus);
	if (auxdata) {
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}
	
	/*ARM公司提供了CPU core,除此之外,它设计了AMBA的总线来连接SOC内的各个block。
	符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals。
	如果一个device node的compatible属性值是arm,primecell的话,
	可以调用of_amba_device_create来向amba总线上增加一个amba device。*/
	if (of_device_is_compatible(bus, "arm,primecell")) {
		/*
		 * Don't return an error here to keep compatibility with older
		 * device tree files.
		 */
		of_amba_device_create(bus, bus_id, platform_data, parent);
		return 0;
	}

	//如果不是ARM Primecell Peripherals,那么我们就需要向platform bus上增加一个platform device了 
	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;
	
	// 一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉。
	for_each_child_of_node(bus, child) {
		pr_debug("   create child: %s\n", child->full_name);
		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_available(np) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))  //check status属性,和是否已经注册过
		return NULL;

	// of_device_alloc除了分配struct platform_device的内存,还分配了该platform device需要的resource的内存
	//这里就根据struct device_node创建了struct platform_device
	dev = of_device_alloc(np, bus_id, parent);  
	if (!dev)
		goto err_clear_flag;

	//设定platform_device 中的其他成员 
	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_dma_configure(&dev->dev, dev->dev.of_node);
	of_msi_configure(&dev->dev, dev->dev.of_node);
	of_reserved_mem_device_init_by_idx(&dev->dev, dev->dev.of_node, 0);

	if (of_device_add(dev) != 0) {  //把这个platform device加入统一设备模型系统中 
		of_dma_deconfigure(&dev->dev);
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;
}

  到这里就很清楚device_node是如何最终成为platform_device了。

2.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 /* CONFIG_ARM_AMBA */
      {} /* Empty terminated list */
  };   

重要问题:

  • 哪些device_node可以转换为platform_device?

    • 根节点下含有compatile属性的子节点;
    • 如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device;
    • i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device。
  • device_node怎么转换为platform_device?

    • platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
    • platform_device.dev.of_node指向device_node, 可以通过它获得其他属性。

本节总结:

  • 内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device;
  • 并非所有的device_node都会转换为platform_device,只有以下的device_node会转换:
    • 该节点必须含有compatible属性;
    • 根节点的子节点(节点必须含有compatible属性);
    • 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):
      这些特殊的compatilbe属性为: “simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”
    • 示例: 比如以下的节点, /mytest会被转换为platform_device, 因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device
    / {
          mytest {
              compatile = "mytest", "simple-bus";
              mytest@0 {
                    compatile = "mytest_0";
              };
          };
          
          i2c {
              compatile = "samsung,i2c";
              at24c02 {
                    compatile = "at24c02";                      
              };
          };

          spi {
              compatile = "samsung,spi";              
              flash@0 {
                    compatible = "winbond,w25q32dw";
                    spi-max-frequency = <25000000>;
                    reg = <0>;
                  };
          };
      };

注意:

  • /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

  • /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。

  • /spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

  • /spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。

I2C总线节点的处理过程:

   i2c_add_numbered_adapter   // drivers/i2c/i2c-core-base.c
        __i2c_add_numbered_adapter
            i2c_register_adapter
                of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c
                    for_each_available_child_of_node(bus, node) {
                        client = of_i2c_register_device(adap, node);
                                        client = i2c_new_device(adap, &info);   // 设备树中的i2c子节点被转换为i2c_client
                    }

SPI总线节点的处理过程:

   spi_register_controller        // drivers/spi/spi.c
        of_register_spi_devices   // drivers/spi/spi.c
            for_each_available_child_of_node(ctlr->dev.of_node, nc) {
                spi = of_register_spi_device(ctlr, nc);  // 设备树中的spi子节点被转换为spi_device
                                spi = spi_alloc_device(ctlr);
                                rc = of_spi_parse_dt(ctlr, spi, nc);
                                rc = spi_add_device(spi);
            }       

5.设备树操作函数

include/linux/ 目录头文件按如下分类:

  • dtb -> device_node -> platform_device

5.1.处理DTB

  • of_fdt.h // dtb文件的相关操作函数,一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)

5.2.处理device_node( (include/linux/)

  • of.h // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数)
  • of_address.h // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值)
  • of_match_device(从matches数组中取出与当前设备最匹配的一项)
  • of_dma.h // 设备树中DMA相关属性的函数
  • of_gpio.h // GPIO相关的函数
  • of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息
  • of_iommu.h // 很少用到
  • of_irq.h // 中断相关的函数
  • of_mdio.h // MDIO (Ethernet PHY) API
  • of_net.h // OF helpers for network devices.
  • of_pci.h // PCI相关函数
  • of_pdt.h // 很少用到
  • of_reserved_mem.h // reserved_mem的相关函数

5.3.处理 platform_device

  • of_platform.h // 把device_node转换为platform_device时用到的函数,
    // 比如of_device_alloc(根据device_node分配设置platform_device),
    // of_find_device_by_node (根据device_node查找到platform_device),
    // of_platform_bus_probe (处理device_node及它的子节点)
  • of_device.h // 设备相关的函数, 比如 of_match_device

6.调试设备树

  在Linux系统起来后,会将解析完成的设备树导出到用户空间。

6.1./proc/device-tree

  这个目录下的目录和文件是根据device node的结构组织的,顶层目录是root device node,其他的子目录是root device node 的 child device node,同时子目录又可以再嵌套子目录,以此表示这些device node的父子关系。

[root@xxxx root]# cd /proc/device-tree/
[root@xxx base]# ls
#address-cells                   pinctrl@106E0000
#size-cells                      pinctrl@11000000
adc@126C0000                     pinctrl@11400000
aliases                          pmu
amba                             ppmu_acp@10ae0000
backlight                        ppmu_camif@11ac0000
cam-power-domain@10023C00        ppmu_cpu@106c0000
camera                           ppmu_dmc0@106a0000
...

/proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base
dtc -I fs -O dts -o devtree_running.dts /proc/device-tree

6.2./sys/firmware

  在/sys/firmware下也可以看到devicetree的导出信息:

[root@xxx root]# cd /sys/firmware/
[root@xxx firmware]# ls -F
devicetree/ fdt

  其中fdt是一个二进制文件,其中是完整的设备树镜像,也就是bootloader最终传给kernel的设备树镜像文件,如果是在Andriod系统上,可以用adb pull将该文件导出到开发机上,然后使用dtc对导出的文件进行反编译:

adb pull /sys/firmware/fdt ./fdt
dtc -I dtb -O dts -o fdt.dts ./fdt

   这样就可以用编辑器查看fdt.dts文件了。此外,这个文件可以用hexdump查看:

[root@xxx root]# hexdump -C  /sys/firmware/fdt | head -n 100
00000000  d0 0d fe ed 00 00 dc 2d  00 00 00 48 00 00 a3 ec  |.......-...H....|
00000010  00 00 00 28 00 00 00 11  00 00 00 10 00 00 00 00  |...(............|
00000020  00 00 08 ad 00 00 a3 a4  00 00 00 00 43 a7 f0 00  |............C...|
00000030  00 00 00 00 00 27 bb 09  00 00 00 00 00 00 00 00  |.....'..........|
00000040  00 00 00 00 00 00 00 00  00 00 00 01 00 00 00 00  |................|
00000050  00 00 00 03 00 00 00 04  00 00 00 00 00 00 00 01  |................|
00000060  00 00 00 03 00 00 00 04  00 00 00 0f 00 00 00 01  |................|
00000070  00 00 00 03 00 00 00 04  00 00 00 1b 00 00 00 01  |................|
00000080  00 00 00 03 00 00 00 38  00 00 00 2c 66 72 69 65  |.......8...,frie|
00000090  6e 64 6c 79 61 72 6d 2c  74 69 6e 79 34 34 31 32  |ndlyarm,tiny4412|
000000a0  00 73 61 6d 73 75 6e 67  2c 65 78 79 6e 6f 73 34  |.samsung,exynos4|
000000b0  34 31 32 00 73 61 6d 73  75 6e 67 2c 65 78 79 6e  |412.samsung,exyn|
000000c0  6f 73 34 00 00 00 00 03  00 00 00 2f 00 00 00 37  |os4......../...7|
000000d0  46 72 69 65 6e 64 6c 79  41 52 4d 20 54 49 4e 59  |FriendlyARM TINY|
000000e0  34 34 31 32 20 62 6f 61  72 64 20 62 61 73 65 64  |4412 board based|
000000f0  20 6f 6e 20 45 78 79 6e  6f 73 34 34 31 32 00 00  | on Exynos4412..|
00000100  00 00 00 01 63 68 6f 73  65 6e 00 00 00 00 00 03  |....chosen......|
00000110  00 00 00 04 00 00 08 9c  43 cf ab 08 00 00 00 03  |........C.......|
00000120  00 00 00 04 00 00 08 89  43 a7 f0 00 00 00 00 03  |........C.......|
00000130  00 00 00 11 00 00 00 3d  2f 73 65 72 69 61 6c 40  |.......=/serial@|

   在/sys/firmware/devicetree/base/下也是以device node的父子关系创建的文件和目录,/proc/device-tree是一个软连接,指向的就是/sys/firmware/devicetree/base/.

6.2.1.创建/sys/firmware/fdt以及/sys/firmware/devicetree

/sys/firmware/devicetree创建:

start_kernel
    ---> rest_init
            ---> kernel_init
                    ---> kernel_init_freeable
                            ---> do_basic_setup
                                    ---> driver_init
                                            ---> of_core_init

of_core_init函数(drivers/of/base.c):

void __init of_core_init(void)
{
    struct device_node *np;

    /* Create the kset, and register existing nodes */
    mutex_lock(&of_mutex);
    of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
    if (!of_kset) {
        mutex_unlock(&of_mutex);
        pr_err("devicetree: failed to register existing nodes\n");
        return;
    }
    for_each_of_allnodes(np)
        __of_attach_node_sysfs(np);
    mutex_unlock(&of_mutex);

    /* Symlink in /proc as required by userspace ABI */
    if (of_root)
        proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
}

/sys/firmware/fdt的创建(drivers/of/fdt.c):

#ifdef CONFIG_SYSFS
static ssize_t of_fdt_raw_read(struct file *filp, struct kobject *kobj,
                   struct bin_attribute *bin_attr,
                   char *buf, loff_t off, size_t count)
{
    memcpy(buf, initial_boot_params + off, count);
    return count;
}

static int __init of_fdt_raw_init(void)
{
    static struct bin_attribute of_fdt_raw_attr =
        __BIN_ATTR(fdt, S_IRUSR, of_fdt_raw_read, NULL, 0);

    if (!initial_boot_params)
        return 0;

    if (of_fdt_crc32 != crc32_be(~0, initial_boot_params,
                     fdt_totalsize(initial_boot_params))) {
        pr_warn("fdt: not creating '/sys/firmware/fdt': CRC check failed\n");
        return 0;
    }
    of_fdt_raw_attr.size = fdt_totalsize(initial_boot_params);
    return sysfs_create_bin_file(firmware_kobj, &of_fdt_raw_attr);
}
late_initcall(of_fdt_raw_init);

6.2.2./sys/firmware/devicetree

  • 以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件
cd base
hexdump -C "#address-cells"
hexdump -C compatible

6.3. /sys/devices/platform

  • 系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的。
    对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性,如果存在该属性,则说明probe函数匹配成功,可以用来调试。

6.4.删除设备树属性的方法

  需要删除设备树的某些属性, 但是又不想修改设备树源文件,可以利用delete-property方法。

 1 / {
 2     ... ...
 3     demo1: demo1 {
 4         compatible = "demo1";
 5         property1 = <1>;
 6         property2;
 7         property3 = <2>;
 8         property4;
 9     };
10 };
11 
12 &demo1 {
13     /delete-property/property2;
14     /delete-property/property3;
15 };

  编译完成后,执行反编译可以看到property2和property3已经消失了:

1     demo1 {
2         compatible = "demo1";
3         property1 = <0x1>;
4         property4;
5     };

参考:

内核文档:

  • Documentation/devicetree/usage-model.txt
  • Documentation/arm/Booting
  • Documentation/devicetree/booting-without-of.txt

网址:

  • https://www.cnblogs.com/pengdonglin137/p/4495056.html

  • https://www.cnblogs.com/pengdonglin137/p/5248114.html

  • http://www.wowotech.net/device_model/dt-code-file-struct-parse.html

  • http://kernel.meizu.com/device-tree.html

  • http://wiki.100ask.org/index.php?title=%E7%AC%AC%E4%B8%89%E8%AF%BE:%E5%86%85%E6%A0%B8%E5%AF%B9%E8%AE%BE%E5%A4%87%E6%A0%91%E7%9A%84%E5%A4%84%E7%90%86&variant=zh

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值