---
# System prepended metadata

title: Parsing dtb
tags: [dtb, device tree, Linux Kernel Study, dts, init]

---

Parsing dtb
===
###### tags: `Linux Kernel Study` `dts` `dtb` `device tree` `init`

u-boot會透過通用暫存器傳遞dtb的起始位址
在kernel啟動初始階段將dtb的起始位址保存到__atags_pointer, 因此__atags_pointer指向dtb在記憶體中的起始位址。
然後跳轉到 start_kernel()
(arch/arm/kernel/head-common.S)
(arch/arm/kernel/head.S)

## struct machine_desc

用來描述platform架構的硬體資訊, 以vexpress-a9來說, 該結構體定義在:
arch/arm/include/asm/mach/arch.h

kernel初始化階段根據dtb選擇machine_desc來基於對不同的platform進行初始化

## Flow

**start_kernel**() (init/main.c)
　　＋--**setup_arch**() (arch/arm/kernel/setup.c)
　　　　＋-- mdesc = **setup_machine_fdt**(__atags_pointer) (arch/arm/kernel/devtree.c) 
　　　　｜　　＋-- **of_flat_dt_match_machine**() (/drivers/of/fdt.c)
　　　　｜　　｜　　＋-- **arch_get_next_mach**()
　　　　｜　　｜　　｜　　called by callback get_next_compat()
　　　　｜　　｜　　｜　
　　　　｜　　｜　　＋-- **of_flat_dt_match**()
　　　　｜　　＋-- **early_init_dt_scan_nodes**() (/drivers/of/fdt.c)
　　　　｜　　　　　＋-- **of_scan_flat_dt**(early_init_dt_scan_chosen, boot_command_line)
　　　　｜　　　　　｜　　＋-- **early_init_dt_scan_chosen**()
　　　　｜　　　　　＋-- **of_scan_flat_dt**(early_init_dt_scan_root, NULL);
　　　　｜　　　　　｜　　＋--**early_init_dt_scan_root**()
　　　　｜　　　　　＋-- **of_scan_flat_dt**(early_init_dt_scan_memory, NULL);
　　　　｜　　　　　｜　　＋-- **early_init_dt_scan_memory**()
　　　　＋-- **unflatten_device_tree**()

## setup_arch

```c=
void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	......
	/* 傳入dtb的起始位址__atags_pointer獲取platform的machine_desc結構體*/
	mdesc = setup_machine_fdt(__atags_pointer);
	/* 不是dtb格式的話用atags獲得machine_desc*/
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	/* 以上兩者都沒有的話, 報err*/
	if (!mdesc) {
		early_print("\nError: invalid dtb and unrecognized/unsupported machine ID\n");
		early_print("  r1=0x%08x, r2=0x%08x\n", __machine_arch_type,
			    __atags_pointer);
		if (__atags_pointer)
			early_print("  r2[]=%*ph\n", 16,
				    phys_to_virt(__atags_pointer));
		dump_machine_table();
	}
	/* 保存machine_desc與其name*/
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

	......
	/* 創建device node tree*/
	unflatten_device_tree();

	......
}
```

## setup_machine_fdt

```c=
/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt_phys: physical address of dt blob
 *
 * If a dtb was passed to the kernel in r2, then use it to choose the
 * correct machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

	......

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
		return NULL;

	/* 
	 * iterate all machine_desc尋找匹配的machine_desc, 
	 * callback arch_get_next_mach用來獲取下一個mdesc
	 */
	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

	if (!mdesc) {
		......
		dump_machine_table(); /* does not return */
	}

	/* We really don't want to do this, but sometimes firmware provides buggy data */
	if (mdesc->dt_fixup)
		mdesc->dt_fixup();

	/** 獲取dtb的一些資訊*/
	early_init_dt_scan_nodes();

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

```

## of_flat_dt_match_machine

```c=
/**
 * of_flat_dt_match_machine - Iterate match tables to find matching machine.
 *
 * @default_match: A machine specific ptr to return in case of no match.
 * @get_next_compat: callback function to return next compatible match table.
 *
 * Iterate through machine match tables to find the best match for the machine
 * compatible string in the FDT.
 */
const void * __init of_flat_dt_match_machine(const void *default_match,
		const void * (*get_next_compat)(const char * const**))
{
	const void *data = NULL;
	const void *best_data = default_match;
	const char *const *compat;
	unsigned long dt_root;
	unsigned int best_score = ~1, score = 0;

	dt_root = of_get_flat_dt_root();
	/* iterate all machine_desc, 找到最合適的 struct machine_desc*/
	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;
		}
	}
	if (!best_data) {
		......
	}

	pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

	return best_data;
}
```

## early_init_dt_scan_nodes
分別獲得dts中 /chosen (u-boot傳送的boot args), {size,address}-cells info,
以及 memory node的資訊

```c=
void __init early_init_dt_scan_nodes(void)
{
	int rc = 0;

	/* Retrieve various information from the /chosen node */
	rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
	if (!rc)
		pr_warn("No chosen node found, continuing without\n");

	/* Initialize {size,address}-cells info */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* Setup memory, calling early_init_dt_add_memory_arch */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
```
#### {size,address}-cells
在 dts中 address-cells代表用多少個cell表示base address,
size-cells表示該空間有多大, 如 /arch/arm/boot/dts/vexpress-v2p-ca9.dts
address-cells與size-cells皆為1, 因此可以看到在memory node reg的部分表示addr與size各使用一個cell,
0x60000000 為 memery的base address
0x40000000 為 memory的大小, 為 1 GB


```c=
/dts-v1/;
#include "vexpress-v2m.dtsi"

/ {
	model = "V2P-CA9";
	......
	compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
	......
	#address-cells = <1>;
	#size-cells = <1>;

	chosen { };
	......
		
	memory@60000000 {
		device_type = "memory";
		reg = <0x60000000 0x40000000>;
	};
```

## of_scan_flat_dt

在early_init_dt_scan_nodes呼叫三次of_scan_flat_dt,分別傳入callback
early_init_dt_scan_chosen,
early_init_dt_scan_root,
early_init_dt_scan_memory
以分別獲取dtb的/chosen, {size,address}-cells, memory資訊

of_scan_flat_dt會iterate dtb的節點, 並呼叫callback it, 
callback return 1則停止iterate,
回傳0,代表不是這個節點, 繼續iterate

```c=
/**
 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
 * @it: callback function
 * @data: context data pointer
 *
 * This function is used to scan the flattened device-tree, it is
 * used to extract the memory information at boot before we can
 * unflatten the tree
 */
int __init of_scan_flat_dt(int (*it)(unsigned long node,
				     const char *uname, int depth,
				     void *data),
			   void *data)
{
	const void *blob = initial_boot_params; /*blob指向dtb起始?*/
	const char *pathp;
	int offset, rc = 0, depth = -1;

	if (!blob)
		return 0;
	
	for (offset = fdt_next_node(blob, -1, &depth); /* 從根node開始iterate, 呼叫callback*/
	     offset >= 0 && depth >= 0 && !rc; /** rc=0繼續for迴圈*/
	     offset = fdt_next_node(blob, offset, &depth)) { /*繼續下一個節點*/

		pathp = fdt_get_name(blob, offset, NULL); /*獲取節點名稱*/
		
		rc = it(offset, pathp, depth, data); /* 呼叫 callback, return 1則停止for迴圈*/
	}
	return rc;
}
```

## unflatten_device_tree
將 dtb 轉換為樹狀結構,每個節點保存在struct device_node中

#### struct device_node

該結構用來保存 device_tree節點

```c=
struct device_node {
	const char *name;
	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)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};
```

device_node內嵌的property結構描述節點的屬性
```c=
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
};
```

```c=
/dts-v1/;
#include "vexpress-v2m.dtsi"

/ { /* root節點 (struct device_node)*/
	/* 這些是struct property*/
	model = "V2P-CA9";
	......
	compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
	......
	#address-cells = <1>;
	#size-cells = <1>;

	chosen { };
	......
		
	memory@60000000 { /* 節點node :struct device_info*/
		/* 這些是struct property*/
		device_type = "memory";
		reg = <0x60000000 0x40000000>;
	};
```

![](https://i.imgur.com/HWSDelR.png)
