内核小碎碎-第四集 解析dtb

DTB文件格式如图【图片摘自https://blog.csdn.net/cc289123557/article/details/51782449】
在这里插入图片描述
Device Tree源文件的结构分为header、fill_area、dt_struct及dt_string四个区域。
Device Tree文件结构以struct fdt_header、struct fdt_node_header及struct fdt_property三个结构体描述。
节点(node)信息使用struct fdt_node_header结构体描述。

struct fdt_node_header {
	fdt32_t tag;
	char name[0];
};

tag是标识node的起始结束等信息的标志位,name指向node名称的首地址
tag的取值:

#define FDT_BEGIN_NODE	0x1		/* Start node: full name */
#define FDT_END_NODE	0x2		/* End node */
#define FDT_PROP	      0x3		/* Property: name off, size, content */
#define FDT_NOP		0x4		/* nop */
#define FDT_END		0x9

FDT_BEGIN_NODE和FDT_END_NODE标识node节点的起始和结束,FDT_PROP标识node节点下面的属性起始符,FDT_END标识Device Tree的结束标识符。
描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROP;len为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址。

struct fdt_property {
	fdt32_t tag;
	fdt32_t len;
	fdt32_t nameoff;
	char data[0];
};

可参考内核的Booting-without-of.txt描述,主要内容如下:

 a) ATAGS interface.  Minimal information is passed from firmware to the kernel with a tagged list of predefined parameters.
                r0 : 0
                r1 : 机器码
                r2 : tag参数列表在系统ram中的物理地址
b) 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).

II - The DT block format

1) Header
---------
   The kernel is passed the physical address pointing to an area of memory that is roughly described in include/linux/of_fdt.h by the structure boot_param_header:
struct boot_param_header { u32     magic;                  /* magic word OF_DT_HEADER */
        u32     totalsize;              /* total size of DT block */
        u32     off_dt_struct;          /* offset to structure */
        u32     off_dt_strings;         /* offset to strings */
        u32     off_mem_rsvmap;         /* offset to memory reserve map
                                           */ u32     version;                /* format version */
        u32     last_comp_version;      /* last compatible version */
        /* version 2 fields below */
        u32     boot_cpuid_phys;        /* Which physical CPU id we're booting on */ /* version 3 fields below */
        u32     size_dt_strings;        /* size of the strings block */
        /* version 17 fields below */
        u32	size_dt_struct;		/* size of the DT structure block */ }; 
So the typical layout of a DT block (though the various parts don't need to be in that order) looks like this (addresses go from top to bottom):
     base -> |  struct boot_param_header  |
             ------------------------------
             |      (alignment gap) (*)   |
             ------------------------------
             |      memory reserve map    |
             ------------------------------
             |      (alignment gap)       |
             ------------------------------
             |                            |
             |    device-tree structure   |
             |                            |
             ------------------------------
             |      (alignment gap)       |
             ------------------------------
             |                            |
             |     device-tree strings    |
             |                            |
      -----> ------------------------------
      |
      |
      --- (base + totalsize)

This device-tree itself is separated in two different blocks, a structure block and a strings block. Both need to be aligned to a 4 byte boundary.
It’s basically a tree of nodes, each node having two or more named properties. A property can have a value or not.
a “unit name” that is used to differentiate nodes with the same name at the same level, it is usually made of the node names, the “@” sign, and a “unit address”, which definition is specific to the bus type the node sits on.
Every node which actually represents an actual device (that is, a node which isn’t only a virtual “container” for more nodes, like “/cpus” is) is also required to have a “compatible” property indicating the specific hardware and an optional list of devices it is fully backwards compatible with.
“OF_DT_BEGIN_NODE” token starts a new node,
“OF_DT_END_NODE” ends that node definition.
The tree has to be “finished” with a OF_DT_END token

Here's the basic structure of a single node:
     * token OF_DT_BEGIN_NODE (that is 0x00000001)
     * for version 1 to 3, this is the node full path as a zero terminated string, starting with "/". For version 16 and later,
       this is the node unit name only (or an empty string for the
       root node)
     * [align gap to next 4 bytes boundary]
     * for each property:
        * token OF_DT_PROP (that is 0x00000003)
        * 32-bit value of property value size in bytes (or 0 if no value)
        * 32-bit value of offset in string block of property name
        * property value data if any
        * [align gap to next 4 bytes boundary]
     * [child nodes if any]
     * token OF_DT_END_NODE (that is 0x00000002)
So the node content can be summarized as a start token, a full path, a list of properties, a list of child nodes, and an end token. Every child node is a full node structure itself as defined above.
  1. Device tree “strings” block
    In order to save space, property names, which are generally redundant, are stored separately in the “strings” block. This block is simply the whole bunch of zero terminated strings for all property names concatenated together. The device-tree property definitions in the structure block will contain offset values from the beginning of the strings block.
1.  unflatten_device_tree解析
void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);	//@1

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

	unittest_unflatten_overlay_base();
}

@1: 解析设备树dtb,保存在of_root,指向保存device_nod结构体的指针数组。

//在unflatten_dt_nodes函数中
struct device_node *nps[FDT_MAX_DEPTH];

if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];

@2:
获取指向别名节点的指针of_aliases
获取指向chosen节点的指针

在void __init setup_arch(char **cmdline_p)的最后,
if (mdesc->init_early)
mdesc->init_early();
而 mdesc来自于
mdesc = setup_machine_fdt(__atags_pointer);

  1. machine_desc解析
/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt:		virtual address pointer to dt blob
 *
 * If a dtb was passed to the kernel, then use it to choose the correct
 * machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(void *dt)
{
	const struct machine_desc *mdesc;
	unsigned long dt_root;

	if (!early_init_dt_scan(dt))
		return NULL;

	mdesc = of_flat_dt_match_machine(NULL, arch_get_next_mach);		//@1
	if (!mdesc)
		machine_halt();

	dt_root = of_get_flat_dt_root();
	arc_set_early_base_baud(dt_root);

	return mdesc;
}

@1:

	dt_root = of_get_flat_dt_root();
	while ((data = get_next_compat(&compat))) {
		score = of_flat_dt_match(dt_root, compat);
		if (score > 0 && score < best_score) {
			best_data = data;
			best_score = score;
		}
	}
static const void * __init arch_get_next_mach(const char *const **match)
{
	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;
}

在vmlinux.lds.S中
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
系统编译的machine_desc在这个段中.

.init.arch.info段中依次读取内核编译进去的machine_desc的描述,可能多个。见DT_MACHINE_START定义处,dt_compat定义在上面紧邻处。与uboot命令行传递来的设备树dtb的root根节点的compatible,若匹配上,则接着运行,否则停止运行,machine_halt();

machine_desc来自于:
设备树根节点的compatible属性列出了一系列的字符串,
表示它兼容的单板名,从"最兼容"到次之

内核中有多个machine_desc,其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板
S5pv210.c
static char const *const s5pv210_dt_compat[] __initconst = {
“samsung,s5pc110”,
“samsung,s5pv210”,
NULL 必须以null结尾,他们程序依次判断结束
};

DT_MACHINE_START(S5PV210_DT, “Samsung S5PC110/S5PV210-based board”)
.dt_compat = s5pv210_dt_compat,
.map_io = s5pv210_dt_map_io,
.restart = s5pv210_dt_restart,
.init_late = s5pv210_dt_init_late,
MACHINE_END

#define DT_MACHINE_START(_name, _namestr)
static const struct machine_desc _mach_desc##_name
__used
attribute((section(".arch.info.init"))) = {
.nr = ~0,
.name = _namestr,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值