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>;
};
```
