天天看點

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.

Linux Device tree(二) - DT分析

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.
Linux Device tree(二) - DT分析

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’)。例如:/[email protected],則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/[email protected] 也會被轉換為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/[email protected]節點不會被轉換為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的父子關系。

[[email protected] root]# cd /proc/device-tree/
[[email protected] base]# ls
#address-cells                   [email protected]
#size-cells                      [email protected]
[email protected]                     [email protected]
aliases                          pmu
amba                             [email protected]
backlight                        [email protected]
[email protected]        [email protected]
camera                           [email protected]
...
           
/proc/device-tree 是連結檔案, 指向 /sys/firmware/devicetree/base

6.2./sys/firmware

  在/sys/firmware下也可以看到devicetree的導出資訊:

[[email protected] root]# cd /sys/firmware/
[[email protected] 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檢視:

[[email protected] 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

繼續閱讀