一、設(shè)備樹的描述
對(duì)于設(shè)備樹,其描述的信息可以分成三部分;在內(nèi)核中,對(duì)設(shè)備樹的處理也會(huì)分成三部分:
Linux uses DT data for three major purposes:
1) platform identification, (平臺(tái)識(shí)別信息)
2) runtime configuration, and (運(yùn)行時(shí)配置信息)
3) device population. (設(shè)備信息)
二、內(nèi)核head.S對(duì)dtb的簡單處理
u-boot把一些參數(shù),把設(shè)備樹文件傳給內(nèi)核,那么內(nèi)核如何處理設(shè)備樹文件呢?從內(nèi)核的第一個(gè)啟動(dòng)文件head.S著手,分析其對(duì)dtb的處理。(詳細(xì)的head.s的分析可參見:嵌入式Linux完全開發(fā)手冊(cè) - 移植Linux內(nèi)核 - Linux內(nèi)核啟動(dòng)概述)
bootloader啟動(dòng)內(nèi)核時(shí),會(huì)設(shè)置r0,r1,r2三個(gè)寄存器,由這三個(gè)寄存器將參數(shù)傳給內(nèi)核:
- r0一般設(shè)置為0;
- r1一般設(shè)置為machine id (在使用設(shè)備樹時(shí)該參數(shù)沒有被使用);
- r2一般設(shè)置ATAGS或DTB的開始地址;
這里額外介紹下machine id的作用。假設(shè)一個(gè)內(nèi)核鏡像uImage可以支持多種單板:

head.S分析如下:
- a. [ __lookup_processor_type ] 使用匯編指令讀取CPU ID, 根據(jù)該ID找到對(duì)應(yīng)的proc_info_list結(jié)構(gòu)體(里面含有這類CPU的初始化函數(shù)、信息)
- b. [ __vet_atags ] 判斷是否存在可用的ATAGS或DTB;
- c. [ __create_page_tables ] 創(chuàng)建頁表, 即創(chuàng)建虛擬地址和物理地址的映射關(guān)系;
- d. [ __enable_mmu ] 使能MMU, 以后就要使用虛擬地址了
- e. [ __mmap_switched ] 上述函數(shù)里將會(huì)調(diào)用__mmap_switched
- f. 把bootloader傳入的r2參數(shù), 保存到變量__atags_pointer中
- g. 調(diào)用C函數(shù)start_kernel
head.S/head-common.S :
把bootloader傳來的r1值, 賦給了C變量: __machine_arch_type
把bootloader傳來的r2值, 賦給了C變量: __atags_pointer // dtb首地址
由此可見,head.S對(duì)dtb的處理還是比較簡單的,只是將u-boot傳來的R2寄存器里的值,也就是DTB的首地址賦給了內(nèi)核中的C變量:__atags_pointer 。
三、內(nèi)核對(duì)設(shè)備樹中平臺(tái)信息的處理(選擇machine_desc)
- platform identification, (平臺(tái)識(shí)別信息)
上面的介紹中,已經(jīng)知道在以前u-boot的ATAG傳參中,會(huì)傳入machine id 由此來匹配machine desc;當(dāng)使用dtb傳參時(shí),u-boot并不傳遞machine id了,內(nèi)核machine desc 的依賴于dtb 根節(jié)點(diǎn)下的compatible屬性,內(nèi)核根據(jù)該屬性來找到合適的machine desc。

作為內(nèi)核的使用者,我們已經(jīng)列出清單,期望在內(nèi)核中找到支持某個(gè)單板的machine desc 。
對(duì)于內(nèi)核,每個(gè)machine desc需表明能支持哪些單板;

compatible屬性對(duì)machine desc的查找
a. 設(shè)備樹根節(jié)點(diǎn)的compatible屬性列出了一系列的字符串,表示它兼容的單板名,從"最兼容"到次之。
b. 內(nèi)核中有多個(gè)machine_desc, 其中有dt_compat成員, 它指向一個(gè)字符串?dāng)?shù)組, 里面表示該machine_desc支持哪些單板
c. 使用compatile屬性的值, 跟每一個(gè)machine_desc.dt_compat 比較,成績?yōu)?吻合的compatile屬性值的位置",成績?cè)降驮狡ヅ? 對(duì)應(yīng)的machine_desc即被選中

分析下代碼的調(diào)用流程:
從上面的分析已經(jīng)知道,內(nèi)核在啟動(dòng)后,將dtb文件的首地址保存到變量__atags_pointer中,然后調(diào)用start_kernel,接下來從start_kernel開始分析:
函數(shù)調(diào)用過程:
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
// 判斷是否有效的dtb, drivers/of/ftd.c
early_init_dt_verify(phys_to_virt(dt_phys)
initial_boot_params = params; //將dtb的地址保存在全局變量里
// 找到最匹配的machine_desc, drivers/of/ftd.c
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
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;
}
}
machine_desc = mdesc;
四、內(nèi)核對(duì)設(shè)備樹中運(yùn)行時(shí)配置信息的處理
- runtime configuration, (運(yùn)行時(shí)配置信息)
運(yùn)行時(shí)的三種配置信息總結(jié)如下:
- a. /chosen節(jié)點(diǎn)中bootargs屬性的值, 存入全局變量: boot_command_line
- b. 確定根節(jié)點(diǎn)的這2個(gè)屬性的值: #address-cells, #size-cells
存入全局變量: dt_root_addr_cells, dt_root_size_cells - c. 解析/memory中的reg屬性, 提取出"base, size", 最終調(diào)用memblock_add(base, size);
函數(shù)調(diào)用過程:
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
early_init_dt_scan_nodes(); // drivers/of/ftd.c
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* 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);
5
五、內(nèi)核對(duì)設(shè)備樹中設(shè)備信息的處理
- device population. (設(shè)備信息)
內(nèi)核啟動(dòng)后,會(huì)將dtb中的所有節(jié)點(diǎn)轉(zhuǎn)換為device_node,了解了device_node結(jié)構(gòu)體和properties結(jié)構(gòu)體,就可知道大致情況。
?每一個(gè)節(jié)點(diǎn)都轉(zhuǎn)換為一個(gè)device_node結(jié)構(gòu)體:
struct device_node {
const char *name; // 來自節(jié)點(diǎn)中的name屬性, 如果沒有該屬性, 則設(shè)為"NULL"
const char *type; // 來自節(jié)點(diǎn)中的device_type屬性, 如果沒有該屬性, 則設(shè)為"NULL"
phandle phandle;
const char *full_name; // 節(jié)點(diǎn)的名字, node-name[@unit-address]
struct fwnode_handle fwnode;
struct property *properties; // 節(jié)點(diǎn)的屬性
struct property *deadprops; /* removed properties */
struct device_node *parent; // 節(jié)點(diǎn)的父親
struct device_node *child; // 節(jié)點(diǎn)的孩子(子節(jié)點(diǎn))
struct device_node *sibling; // 節(jié)點(diǎn)的兄弟(同級(jí)節(jié)點(diǎn))
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
?device_node結(jié)構(gòu)體中有properties, 用來表示該節(jié)點(diǎn)的屬性,每一個(gè)屬性對(duì)應(yīng)一個(gè)property結(jié)構(gòu)體:
struct property {
char *name; // 屬性名字, 指向dtb文件中的字符串
int length; // 屬性值的長度
void *value; // 屬性值, 指向dtb文件中value所在位置, 數(shù)據(jù)仍以big endian存儲(chǔ)
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
};
- a. 在DTB文件中,
每一個(gè)節(jié)點(diǎn)都以TAG(FDT_BEGIN_NODE, 0x00000001)開始, 節(jié)點(diǎn)內(nèi)部可以嵌套其他節(jié)點(diǎn),
每一個(gè)屬性都以TAG(FDT_PROP, 0x00000003)開始 - b. 這些device_node構(gòu)成一棵樹, 根節(jié)點(diǎn)為: of_root
跟蹤下代碼:
函數(shù)調(diào)用過程:
start_kernel // init/main.c
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所占區(qū)域保留下來, 即調(diào)用: memblock_reserve
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params),
0);
early_init_fdt_scan_reserved_mem(); // 根據(jù)dtb中的memreserve信息, 調(diào)用memblock_reserve
unflatten_device_tree(); // arch/arm/kernel/setup.c
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
populate_node
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
np->full_name = fn = ((char *)np) + sizeof(*np);
populate_properties
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
對(duì)于驅(qū)動(dòng)開發(fā)者,設(shè)備樹中還有一個(gè)備受關(guān)注的信息:platform_device。
dts -> dtb -> device_node -> platform_device
在上述,我們已經(jīng)知道dts中的節(jié)點(diǎn)信息終將被轉(zhuǎn)換為 device_node ,但并不是所有device_node都會(huì)被轉(zhuǎn)換成platform_device,需滿足:
- 根節(jié)點(diǎn)下含有compatile屬性的子節(jié)點(diǎn)
- 如果一個(gè)結(jié)點(diǎn)的compatile屬性含有這些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子結(jié)點(diǎn)(需含compatile屬性)也可以轉(zhuǎn)換為platform_device
i2c, spi等總線節(jié)點(diǎn)下的子節(jié)點(diǎn), 應(yīng)該交給對(duì)應(yīng)的總線驅(qū)動(dòng)程序來處理, 它們不應(yīng)該被轉(zhuǎn)換為platform_device
轉(zhuǎn)換規(guī)則:
- platform_device中含有resource數(shù)組, 它來自device_node的reg, interrupts屬性;
- platform_device.dev.of_node指向device_node, 可以通過它獲得其他屬性;
總結(jié)下:
- a. 內(nèi)核函數(shù)of_platform_default_populate_init, 遍歷device_node樹, 生成platform_device
- b. 并非所有的device_node都會(huì)轉(zhuǎn)換為platform_device
只有以下的device_node會(huì)轉(zhuǎn)換:- b.1 該節(jié)點(diǎn)必須含有compatible屬性
- b.2 根節(jié)點(diǎn)的子節(jié)點(diǎn)(節(jié)點(diǎn)必須含有compatible屬性)
- b.3 含有特殊compatible屬性的節(jié)點(diǎn)的子節(jié)點(diǎn)(子節(jié)點(diǎn)必須含有compatible屬性):
這些特殊的compatilbe屬性為: "simple-bus","simple-mfd","isa","arm,amba-bus"
示例:
比如以下的節(jié)點(diǎn),
/mytest會(huì)被轉(zhuǎn)換為platform_device,
因?yàn)樗嫒?simple-bus", 它的子節(jié)點(diǎn)/mytest/mytest@0 也會(huì)被轉(zhuǎn)換為platform_device
/i2c節(jié)點(diǎn)一般表示i2c控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
/i2c/at24c02節(jié)點(diǎn)不會(huì)被轉(zhuǎn)換為platform_device, 它被如何處理完全由父節(jié)點(diǎn)的platform_driver決定, 一般是被創(chuàng)建為一個(gè)i2c_client。
類似的也有/spi節(jié)點(diǎn), 它一般也是用來表示SPI控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
/spi/flash@0節(jié)點(diǎn)不會(huì)被轉(zhuǎn)換為platform_device, 它被如何處理完全由父節(jié)點(diǎn)的platform_driver決定, 一般是被創(chuàng)建為一個(gè)spi_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>;
};
};
};
函數(shù)調(diào)用過程:
a. of_platform_default_populate_init (drivers/of/platform.c) 被調(diào)用到過程:
start_kernel // init/main.c
rest_init();
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn)); // 就是調(diào)用"arch_initcall_sync(fn)"中定義的fn函數(shù)
b. of_platform_default_populate_init (drivers/of/platform.c) 生成platform_device的過程:
of_platform_default_populate_init
of_platform_default_populate(NULL, NULL, NULL);
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true); // 調(diào)用過程看下面
dev = of_device_alloc(np, bus_id, parent); // 根據(jù)device_node節(jié)點(diǎn)的屬性設(shè)置platform_device的resource
if (rc) {
of_node_put(child);
break;
}
}
c. of_platform_bus_create(bus, matches, ...)的調(diào)用過程(處理bus節(jié)點(diǎn)生成platform_devie, 并決定是否處理它的子節(jié)點(diǎn)):
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus節(jié)點(diǎn)的platform_device結(jié)構(gòu)體
if (!dev || !of_match_node(matches, bus)) // 如果bus節(jié)點(diǎn)的compatile屬性不吻合matches成表, 就不處理它的子節(jié)點(diǎn)
return 0;
for_each_child_of_node(bus, child) { // 取出每一個(gè)子節(jié)點(diǎn)
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 處理它的子節(jié)點(diǎn), of_platform_bus_create是一個(gè)遞歸調(diào)用
if (rc) {
of_node_put(child);
break;
}
}
d. I2C總線節(jié)點(diǎn)的處理過程:
/i2c節(jié)點(diǎn)一般表示i2c控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
platform_driver的probe函數(shù)中會(huì)調(diào)用i2c_add_numbered_adapter:
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); // 設(shè)備樹中的i2c子節(jié)點(diǎn)被轉(zhuǎn)換為i2c_client
}
e. SPI總線節(jié)點(diǎn)的處理過程:
/spi節(jié)點(diǎn)一般表示spi控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
platform_driver的probe函數(shù)中會(huì)調(diào)用spi_register_master, 即spi_register_controller:
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); // 設(shè)備樹中的spi子節(jié)點(diǎn)被轉(zhuǎn)換為spi_device
spi = spi_alloc_device(ctlr);
rc = of_spi_parse_dt(ctlr, spi, nc);
rc = spi_add_device(spi);
}
最后,便是platform_device跟platform_driver的匹配過程了,具體的過程分析可移步:
1. 驅(qū)動(dòng)程序分層分離概念-總線設(shè)備驅(qū)動(dòng)模型。
2. 字符設(shè)備驅(qū)動(dòng)-總線設(shè)備驅(qū)動(dòng)模型寫法。