uboot將一些參數,設備樹文件傳給內核,那么內核如何處理這些設備樹文件呢?本章就kernel解析設備樹的過程和原理,本章的主要內容以Device Tree相關的數據流分析為索引,對ARM linux kernel的代碼進行解析。主要的數據流包括:
(1)設備樹對於內核的意義
(2)從u-boot傳遞dtb開始,kernel初始化流程,如何將dtb並將其轉換成Device Tree Structure
(3)傳遞運行時參數傳遞以及platform的識別流程分析
(4)如何將Device Tree Structure並入linux kernel的設備驅動模型。
1 設備樹的作用
設備樹對於內核的作用:
平台標識:告訴內核dtb支持哪些平台 ; 用DT 來標識特定的machine ; root 節點的compatible 字段,匹配machine_desc的dt_compat
運行時配置:chosen節點的屬性
設備信息集合:傳遞各種設備信息
以下code是基於linux-4.9.73。
2 初始化流程
從上一章我們已經知道fdt的地址是作為參數傳遞到kernel。下面看一下kernel階段怎么獲取這個地址值的。bootloader啟動內核時,會設置r0,r1,r2三個寄存器,
r0一般設置為0;
r1一般設置為machine id (在使用設備樹時該參數沒有被使用);
r2一般設置ATAGS或DTB的開始地址;
對於啟動的流程代碼如下:
1 ENTRY(stext) 2 ARM_BE8(setend be ) @ ensure we are in BE8 mode 3 4 THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM. 5 THUMB( bx r9 ) @ If this is a Thumb-2 kernel, 6 THUMB( .thumb ) @ switch to Thumb now. 7 THUMB(1: ) 8 9 #ifdef CONFIG_ARM_VIRT_EXT 10 bl __hyp_stub_install 11 #endif 12 @ ensure svc mode and all interrupts masked 13 safe_svcmode_maskall r9 14 15 mrc p15, 0, r9, c0, c0 @ get processor id 16 bl __lookup_processor_type @ r5=procinfo r9=cpuid 17 movs r10, r5 @ invalid processor (r5=0)? 18 THUMB( it eq ) @ force fixup-able long branch encoding 19 beq __error_p @ yes, error 'p' 20 21 #ifdef CONFIG_ARM_LPAE 22 mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0 23 and r3, r3, #0xf @ extract VMSA support 24 cmp r3, #5 @ long-descriptor translation table format? 25 THUMB( it lo ) @ force fixup-able long branch encoding 26 blo __error_lpae @ only classic page table format 27 #endif 28 29 #ifndef CONFIG_XIP_KERNEL 30 adr r3, 2f 31 ldmia r3, {r4, r8} 32 sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET) 33 add r8, r8, r4 @ PHYS_OFFSET 34 #else 35 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case 36 #endif 37 38 /* 39 * r1 = machine no, r2 = atags or dtb, 40 * r8 = phys_offset, r9 = cpuid, r10 = procinfo 41 */ 42 bl __vet_atags 43 #ifdef CONFIG_SMP_ON_UP 44 bl __fixup_smp 45 #endif 46 #ifdef CONFIG_ARM_PATCH_PHYS_VIRT 47 bl __fixup_pv_table 48 #endif 49 bl __create_page_tables 50 51 /* 52 * The following calls CPU specific code in a position independent 53 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of 54 * xxx_proc_info structure selected by __lookup_processor_type 55 * above. On return, the CPU will be ready for the MMU to be 56 * turned on, and r0 will hold the CPU control register value. 57 */ 58 ldr r13, =__mmap_switched @ address to jump to after 59 @ mmu has been enabled 60 adr lr, BSYM(1f) @ return (PIC) address 61 mov r8, r4 @ set TTBR1 to swapper_pg_dir 62 ARM( add pc, r10, #PROCINFO_INITFUNC ) 63 THUMB( add r12, r10, #PROCINFO_INITFUNC ) 64 THUMB( ret r12 ) 65 1: b __enable_mmu 66 ENDPROC(stext) 67 .ltorg 68 #ifndef CONFIG_XIP_KERNEL 69 2: .long . 70 .long PAGE_OFFSET 71 #endif
(1)__lookup_processor_type : 使用匯編指令讀取CPU ID, 根據該ID找到對應的proc_info_list結構體(里面含有這類CPU的初始化函數、信息)
(2)__vet_atags : 判斷是否存在可用的ATAGS或DTB
在匯編的階段,大概可以看出來用變量__atags_pointer指向FDT的首地址,執行完匯編的階段就會調到C代碼的流程里面了
3 平台信息處理 machine_desc
3.1 start_kernel
進入到start_kernel的處理流程中,code位於:linux-4.9.73\init\main.c
1 asmlinkage __visible void __init start_kernel(void) 2 { 3 char *command_line; 4 char *after_dashes; 5 6 set_task_stack_end_magic(&init_task); 7 smp_setup_processor_id(); 8 debug_objects_early_init(); 9 10 /* 11 * Set up the the initial canary ASAP: 12 */ 13 boot_init_stack_canary(); 14 15 cgroup_init_early(); 16 17 local_irq_disable(); 18 early_boot_irqs_disabled = true; 19 20 /* 21 * Interrupts are still disabled. Do necessary setups, then 22 * enable them 23 */ 24 boot_cpu_init(); 25 page_address_init(); 26 pr_notice("%s", linux_banner); 27 setup_arch(&command_line);//設置架構相關的內容 28 29 ... 30 31 }
3.2 函數setup_arch
此處只是重點的關注fdt的處理,直接進到setup_ arch()函數。定義位於:arch\arm\kernel\setup.c
setup_arch 就是各個架構自己的設置函數,調用編譯的架構。
1 void __init setup_arch(char **cmdline_p) 2 { 3 const struct machine_desc *mdesc; 4 5 setup_processor(); 6 mdesc = setup_machine_fdt(__atags_pointer);__atags_pointer是一個物理地址,即__atags_pointer 是一個指針 7 if (!mdesc) 8 mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);//從dts中找到對應的設備描述結構體 9 machine_desc = mdesc; 10 machine_name = mdesc->name; 11 ... 12 13 unflatten_device_tree();//根據設備描述結構體生成對應設備節點結構體 14 ... 15 }
首先通過set_machine_fdt來設置set_machine描述符,如果返回值是NULL,那么就采用傳統的方式,如果u-boot傳遞了,就采用設備樹方式
(1)傳統方式:對於如何確定mdesc,舊的方法是靜態定義若干的machine描述符(struct machine_desc),在系統啟動的時候,通過machine type ID作為索引,在這些靜態定義的machine描述符中,找到對應哪個ID匹配的描述符。
(2)設備樹:通過__atags_pointer來找到對應的machine_desc設備描述符
首先我們來看看struct machine_desc的定義方式,code位於:arch\arm\include\asm\mach\arch.h
1 struct machine_desc { 2 ... 3 unsigned int nr; /* architecture number */ 4 const char *name; /* architecture name */ 5 unsigned long atag_offset; /* tagged list (relative) */ 6 const char *const *dt_compat; /* array of device tree 7 ... 8 }
nr成員就是過去使用的machine type ID。內核machine描述符的table有若干個entry,每個都有自己的ID。bootloader傳遞了machine type ID,指明使用哪一個machine描述符。而dtb方式中目前匹配machine描述符使用compatible strings,也就是dt_compat成員,這是一個string list,定義了這個machine所支持的列表。
3.2 函數setup_machine_fdt
函數的功能就是根據Device Tree的信息,找到最適合的machine描述符。其主要做了下面幾件事情:
(1)傳進來的fdt地址是物理地址,所以用phys_to_virt()函數轉換為虛擬地址,同時進行合法檢測
(2)在machine描述符的列表中scan,找到最合適的那個machine描述符。和傳統的方法類似,也是靜態定義的。DT_MACHINE_START和MACHINE_END用來定義一個machine描述符。編譯的時候,compiler會把這些machine descriptor放到一個特殊的段中(.arch.info.init),形成machine描述符的列表。
(3)of_get_flat_dt_prop(dt_root, “compatible”, &size)使用compatile屬性的值, 跟’’‘每一個machine_desc.dt_compat’’'比較,成績為"吻合的compatile屬性值的位置",成績越低越匹配, 對應的machine_desc即被選中。
code位於:arch\arm\kernel\devtree.c
1 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) 2 { 3 const struct machine_desc *mdesc, *mdesc_best = NULL; 4 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))//phys_to_virt 字面上的意思是物理地址轉換成虛擬地址 5 return NULL; 6 7 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); 8 9 if (!mdesc) { 10 const char *prop; 11 int size; 12 unsigned long dt_root; 13 14 early_print("\nError: unrecognized/unsupported " 15 "device tree compatible list:\n[ "); 16 17 dt_root = of_get_flat_dt_root(); 18 prop = of_get_flat_dt_prop(dt_root, "compatible", &size); 19 while (size > 0) { 20 early_print("'%s' ", prop); 21 size -= strlen(prop) + 1; 22 prop += strlen(prop) + 1; 23 } 24 early_print("]\n\n"); 25 26 dump_machine_table(); /* does not return */ 27 } 28 29 /* We really don't want to do this, but sometimes firmware provides buggy data */ 30 if (mdesc->dt_fixup) 31 mdesc->dt_fixup(); 32 33 early_init_dt_scan_nodes(); 34 35 /* Change machine number to match the mdesc we're using */ 36 __machine_arch_type = mdesc->nr; 37 38 return mdesc; 39 }
3.3 函數arch_get_next_mach
_arch_info_begin指向machine描述符列表第一個entry。通過mdesc++不斷的移動machine描述符指針(Note:mdesc是static的)。match返回了該machine描述符的compatible string list。具體匹配的算法倒是很簡單,就是比較字符串而已,最終找到對應的machine type。 從該流程可以知道,內核是可以支持很多種不同類型的設備,只要在bootloader傳遞的時候,傳遞對應不同的dtb表即可。
1 static const void * __init arch_get_next_mach(const char *const **match) 2 { 3 static const struct machine_desc *mdesc = __arch_info_begin; 4 const struct machine_desc *m = mdesc; 5 6 if (m >= __arch_info_end) 7 return NULL; 8 9 mdesc++; 10 *match = m->dt_compat; 11 return m; 12 }
3.4 運行時參數傳遞
設備樹只是起一個信息傳遞的作用,對這些信息配置的處理,也比較簡單,即從設備樹的DTB文件中,把這些設備信息提取出來賦給內核中的某個變量即可。那么在系統初始化的過程中,我們需要將DTB轉換成節點是device_node的樹狀結構,以便后續方便操作。緊接上述,3.2 函數setup_machine_fdt最后調用函數early_init_dt_scan_nodes,定義位於drivers\of\fdt.c,如下:
1 void __init early_init_dt_scan_nodes(void) 2 { 3 /* Retrieve various information from the /chosen node */ 4 of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); 5 6 /* Initialize {size,address}-cells info */ 7 of_scan_flat_dt(early_init_dt_scan_root, NULL); 8 9 /* Setup memory, calling early_init_dt_add_memory_arch */ 10 of_scan_flat_dt(early_init_dt_scan_memory, NULL); 11 }
該函數主要完成3個工作:
(1)掃描 /chosen node,保存運行時參數(bootargs)到boot_command_line,此外,還通過early_init_dt_check_for_initrd處理initrd相關的property,並保存在initrd_start和initrd_end這兩個全局變量中 。其中主要是解析dts的配置為:
chosen { bootargs = "earlycon=sprd_serial,0x70100000,115200n8 loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw androidboot.hardware=sc9830"; linux,initrd-start = <0x85500000>; linux,initrd-end = <0x855a3212>; };
bootargs屬性就是內核啟動的命令行參數,它里面可以指定根文件系統在哪里,第一個運行的應用程序是哪一個,指定內核的打印信息從哪個設備里打印出來
(2)掃描根節點,獲取 {size,address}-cells信息,並保存在dt_root_size_cells和dt_root_addr_cells全局變量中 ,memory中的reg屬性的地址是32位還是64位,大小是用一個32位表示,還是兩個32位表示
(3)掃描DTB中的memory node,並把相關信息保存在meminfo中,全局變量meminfo通過memblock_add保存了系統內存相關的信息。
4 dtb解析成device node
uboot把設備樹DTB文件隨便放到內存的某一個地方就可以使用,為什么內核運行中,他不會去覆蓋DTB所占用的那塊內存呢?在設備樹文件中,可以使用/memreserve/指定一塊內存,這塊內存就是保留的內存,內核不會占用它。即使你沒有指定這塊內存,當我們內核啟動時,他也會把設備樹所占用的區域保留下來。內核在arm_memblock_init中會使用early_init_fdt_scan_reserved_mem來配置fdt的內存,通知也回對memreserve指定內存進行保留操作。
4.1 函數early_init_fdt_scan_reserved_mem
(1)initial_boot_params實際上是dtb的虛擬地址,在early_init_dt_verify初始化的時候設定,首先進來就判斷dtb是否存在,如果存在就將dtb的空間進行保留
(2)對fdt中的每一個節點調用__fdt_scan_reserved_mem函數,進行reserved-memory節點的掃描,之后調用fdt_init_reserved_mem函數進行內存預留的動作
1 void __init early_init_fdt_scan_reserved_mem(void) 2 { 3 int n; 4 u64 base, size; 5 6 if (!initial_boot_params) 7 return; 8 9 /* Reserve the dtb region */ 10 early_init_dt_reserve_memory_arch(__pa(initial_boot_params), 11 fdt_totalsize(initial_boot_params), 12 0); 13 14 /* Process header /memreserve/ fields */ 15 for (n = 0; ; n++) { 16 fdt_get_mem_rsv(initial_boot_params, n, &base, &size); 17 if (!size) 18 break; 19 early_init_dt_reserve_memory_arch(base, size, 0); 20 } 21 22 of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); 23 fdt_init_reserved_mem(); 24 }
4.2 函數unflatten_device_tree
說完了dtb對於內存的流程,那么來到這節的重點,dtb解析成device node。
setup_arch 函數緊接着調用函數unflatten_device_tree生成相應的設備節點結構體。首先來看看下面的代碼,定義位於:linux-4.9.73\drivers\of\fdt.c
主要功能:
掃描DTB,將device node被組織成global list。全局變量struct device_node *allnodes就是指向設備樹的global list tree。
1 void __init unflatten_device_tree(void) 2 { 3 __unflatten_device_tree(initial_boot_params, NULL, &of_root, 4 early_init_dt_alloc_memory_arch, false); 5 6 /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ 7 of_alias_scan(early_init_dt_alloc_memory_arch); 8 }
分析以上代碼,在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)。
另外上述函數中初始化了of_root,在drivers\of\base.c中定義的全局設備節點變量,后面要用到。
struct device_node *of_root;
設備節點結構體定義如下:
1 struct device_node { 2 const char *name;//device node name 3 const char *type;//對應device_type的屬性 4 phandle phandle;//對應該節點的phandle屬性 5 const char *full_name;//從“/”開始的,表示該node的full path 6 struct fwnode_handle fwnode; 7 8 struct property *properties;//該節點的屬性列表 9 struct property *deadprops; /* removed properties如果需要,刪除某些屬性,並掛入到deadprops的列表 */ 10 struct device_node *parent;//parent、child以及sibling將所有的device node連接起來 11 struct device_node *child; 12 struct device_node *sibling; 13 struct kobject kobj; 14 unsigned long _flags; 15 void *data; 16 #if defined(CONFIG_SPARC) 17 const char *path_component_name; 18 unsigned int unique_id; 19 struct of_irq_controller *irq_trans; 20 #endif 21 };
struct device_node最終一般會被掛接到具體的struct device結構體。struct device_node結構體描述如下:
1 struct device {
2 ... 3 4 struct device_node *of_node; /* associated device tree node */ 5 ... 6 }
4.3 函數__unflatten_device_tree
在__unflatten_device_tree()函數中,兩次調用unflatten_dt_nodes()函數。第一次:是為了得到Device Tree轉換成struct device_node和struct property結構體需要分配的內存大小。
第二次調用才是具體填充每一個struct device_node和struct property結構體。
那么Device Tree中的每一個node節點經過kernel處理都會生成一個struct device_node的結構體。
1 static void *__unflatten_device_tree(const void *blob, 2 struct device_node *dad, 3 struct device_node **mynodes, 4 void *(*dt_alloc)(u64 size, u64 align), 5 bool detached) 6 { 7 int size; 8 void *mem; 9 10 pr_debug(" -> unflatten_device_tree()\n"); 11 12 if (!blob) { 13 pr_debug("No device tree pointer\n"); 14 return NULL; 15 } 16 17 pr_debug("Unflattening device tree:\n"); 18 pr_debug("magic: %08x\n", fdt_magic(blob)); 19 pr_debug("size: %08x\n", fdt_totalsize(blob)); 20 pr_debug("version: %08x\n", fdt_version(blob)); 21 /*health check代碼,例如檢查DTB header的magic,確認blob的確指向一個DTB*/ 22 if (fdt_check_header(blob)) { 23 pr_err("Invalid device tree blob header\n"); 24 return NULL; 25 } 26 /*scan過程分成兩輪,第一輪主要是確定device-tree structure的長度,保存在size變量中*/ 27 /* First pass, scan for size */ 28 size = unflatten_dt_nodes(blob, NULL, dad, NULL);//第一次調用此函數,為了得到Device Tree轉換成struct device_node和struct property結構體需要分配的內存大小 29 if (size < 0) 30 return NULL; 31 32 size = ALIGN(size, 4); 33 pr_debug(" size is %d, allocating...\n", size); 34 /*初始化的時候,並不是掃描到一個node或者property就分配相應的內存,實際上內核是一次性的分配了一大片內存,這些內存包括了所有的struct device_node、node name、struct property所需要的內存*/ 35 /* Allocate memory for the expanded device tree */ 36 mem = dt_alloc(size + 4, __alignof__(struct device_node)); 37 if (!mem) 38 return NULL; 39 40 memset(mem, 0, size); 41 42 *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); 43 44 pr_debug(" unflattening %p...\n", mem); 45 46 /* Second pass, do actual unflattening */ 47 unflatten_dt_nodes(blob, mem, dad, mynodes);//第二次調用才是具體填充每一個struct device_node和struct property結構體 48 if (be32_to_cpup(mem + size) != 0xdeadbeef) 49 pr_warning("End of tree marker overwritten: %08x\n", 50 be32_to_cpup(mem + size)); 51 52 if (detached && mynodes) { 53 of_node_set_flag(*mynodes, OF_DETACHED); 54 pr_debug("unflattened tree is detached\n"); 55 } 56 57 pr_debug(" <- unflatten_device_tree()\n"); 58 return mem; 59 }
device tree 的初始化就算完成了,在以后的啟動過程中,kernel 就會依據這個 dt 來初始化各個設備。
5 platform_device的創建
在linux kernel引入統一設備模型之后,bus、driver和device形成了設備模型中的鐵三角。在驅動初始化的時候會將代表該driver的一個數據結構掛入bus上的driver鏈表,device的數據結構掛入bus上的devie鏈表,那么如何讓device遇到“對”的那個driver呢?那么就要靠緣分了,也就是bus的match函數來完成。在傳統的方式中,代碼中會定義一個static struct platform_device *xxx_devices的靜態數組,在初始化的時候調用platform_add_devices。這些靜態定義的platform_device往往又需要靜態定義各種resource,那么對於設備樹,也就是需要根據device_node的樹狀結構(root是of_allnodes)將一個個的device node掛入到相應的總線device鏈表中即可。代碼定義位於arch\arm\kernel\setup.c。
1 static int __init customize_machine(void) 2 { 3 /* 4 * customizes platform devices, or adds new ones 5 * On DT based machines, we fall back to populating the 6 * machine from the device tree, if no callback is provided, 7 * otherwise we would always need an init_machine callback. 8 */ 9 if (machine_desc->init_machine) 10 machine_desc->init_machine(); 11 12 return 0; 13 } 14 arch_initcall(customize_machine);
那么Linux系統是怎么知道哪些device node要注冊為platform_device,哪些是用於i2c_client,哪些是用於spi_device?不知道你有沒有注意到調用of_platform_populate的時候給它傳遞了一個參數of_default_bus_match_table
1 const struct of_device_id of_default_bus_match_table[] = { 2 { .compatible = "simple-bus", }, 3 #ifdef CONFIG_ARM_AMBA 4 { .compatible = "arm,amba-bus", }, 5 #endif /* CONFIG_ARM_AMBA */ 6 {} /* Empty terminated list */ 7 };
那么在dts文件會也會對對應的驅動進行配置
1 ap-apb { 2 compatible = "simple-bus"; 3 #address-cells = <1>; 4 #size-cells = <1>; 5 ranges; 6 7 uart0: serial@70000000 { 8 compatible = "sprd,sc9836-uart"; 9 reg = <0x70000000 0x100>; 10 interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>; 11 clock-names = "uart", "source","enable"; 12 clocks = <&clk_uart0>, <&ext_26m>, 13 <&clk_ap_apb_gates 13>; 14 status = "disabled"; 15 }; 16 }
如果某個device node的compatible屬性的值與數組of_default_bus_match_table中的任意一個元素的compatible的值match,那么這個device node的child device node(device_node的child成員變量指向的是這個device node的子節點,也是一個鏈表)仍舊會被注冊為platform_device。
5.1 函數of_platform_populate
init_machine函數中最終會調用of_platform_populate函數。下面來看看重點的解析過程,定位於:drivers\of\platform.c。
1 int of_platform_populate(struct device_node *root, 2 const struct of_device_id *matches, 3 const struct of_dev_auxdata *lookup, 4 struct device *parent) 5 { 6 struct device_node *child; 7 int rc = 0; 8 9 root = root ? of_node_get(root) : of_find_node_by_path("/"); 10 if (!root) 11 return -EINVAL; 12 13 for_each_child_of_node(root, child) { 14 rc = of_platform_bus_create(child, matches, lookup, parent, true); 15 if (rc) 16 break; 17 } 18 19 of_node_put(root); 20 return rc; 21 }
該函數主要完成:
(1)獲取根節點,如果傳遞進來的參數root為NULL,那么需要通過of_find_node_by_path函數找到device tree中的根節點。
(2)得到根節點之后,就可以通過這個根節點來遍歷device tree中的節點了。得到一個子節點之后,調用of_platform_bus_create函數為每一個節點創建platform_device結構體。
5.2 函數of_find_node_opts_by_path
在分析函數of_platform_bus_create之前先分析下函數of_find_node_opts_by_path來獲取根節點。
在這個函數中有一個很關鍵的全局變量:of_root,它的定義是在 drivers/of/base.c 里面:struct device_node *of_root;
它指向了 device tree 的根節點。那么,這個of_root又是咋來的呢?我們知道 device tree 是由 DTC(Device Tree Compiler)編譯成二進制文件DTB(Ddevice Tree Blob)的,然后在系統上電之后由 bootloader 加載到內存中去,這個時候還沒有device tree,而在內存中只有一個所謂的 DTB,這只是一個以某個內存地址開始的一堆原始的 dt 數據,沒有樹結構。kernel 的任務需要把這些數據轉換成一個樹結構然后再把這棵樹的根節點的地址賦值給allnodes 就行了。這個過程一定是非常重要,因為沒有這個 device tree 那所有的設備就沒辦法初始化,所以這個 dt 樹的形成一定在 kernel 剛剛啟動的時候就完成了。of_root的初始化如上述4.2分析。
1 struct device_node *of_find_node_opts_by_path(const char *path, const char **opts) 2 { 3 struct device_node *np = NULL; 4 struct property *pp; 5 unsigned long flags; 6 const char *separator = strchr(path, ':'); 7 8 if (opts) 9 *opts = separator ? separator + 1 : NULL; 10 11 if (strcmp(path, "/") == 0) 12 return of_node_get(of_root); 13 14 /* The path could begin with an alias */ 15 if (*path != '/') { 16 int len; 17 const char *p = separator; 18 19 if (!p) 20 p = strchrnul(path, '/'); 21 len = p - path; 22 23 /* of_aliases must not be NULL */ 24 if (!of_aliases) 25 return NULL; 26 27 for_each_property_of_node(of_aliases, pp) { 28 if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) { 29 np = of_find_node_by_path(pp->value); 30 break; 31 } 32 } 33 if (!np) 34 return NULL; 35 path = p; 36 } 37 38 /* Step down the tree matching path components */ 39 raw_spin_lock_irqsave(&devtree_lock, flags); 40 if (!np) 41 np = of_node_get(of_root); 42 while (np && *path == '/') { 43 path++; /* Increment past '/' delimiter */ 44 np = __of_find_node_by_path(np, path); 45 path = strchrnul(path, '/'); 46 if (separator && separator < path) 47 break; 48 } 49 raw_spin_unlock_irqrestore(&devtree_lock, flags); 50 return np; 51 }
5.3 函數of_platform_bus_create
為每一個節點創建platform_device結構體。
1 static int of_platform_bus_create(struct device_node *bus,//要創建的device node 2 const struct of_device_id *matches,//要匹配的list 3 const struct of_dev_auxdata *lookup,//附屬數據 4 struct device *parent, bool strict)//parent指向父節點,strict是否要求完全匹配 5 { 6 const struct of_dev_auxdata *auxdata; 7 struct device_node *child; 8 struct platform_device *dev; 9 const char *bus_id = NULL; 10 void *platform_data = NULL; 11 int rc = 0; 12 13 /* Make sure it has a compatible property */ 14 if (strict && (!of_get_property(bus, "compatible", NULL))) { 15 pr_debug("%s() - skipping %s, no compatible prop\n", 16 __func__, bus->full_name); 17 return 0; 18 } 19 20 auxdata = of_dev_lookup(lookup, bus);//在傳入lookup table尋找和該device node匹配的附加數據 21 if (auxdata) {//如果找到,那么就用附加數據中的靜態定義的內容 22 bus_id = auxdata->name; 23 platform_data = auxdata->platform_data; 24 } 25 /*ARM公司提供了CPU core,除此之外,它設計了AMBA的總線來連接SOC內的各個block。符合這個總線標准的SOC上的外設叫做ARM Primecell Peripherals。如果一個device node的compatible屬性值是arm,primecell的話,可以調用of_amba_device_create來向amba總線上增加一個amba device*/ 26 if (of_device_is_compatible(bus, "arm,primecell")) { 27 /* 28 * Don't return an error here to keep compatibility with older 29 * device tree files. 30 */ 31 of_amba_device_create(bus, bus_id, platform_data, parent); 32 return 0; 33 } 34 /*如果不是ARM Primecell Peripherals,那么我們就需要向platform bus上增加一個platform device了*/ 35 dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); 36 if (!dev || !of_match_node(matches, bus)) 37 return 0; 38 39 for_each_child_of_node(bus, child) { 40 pr_debug(" create child: %s\n", child->full_name); 41 rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); 42 if (rc) { 43 of_node_put(child); 44 break; 45 } 46 } 47 of_node_set_flag(bus, OF_POPULATED_BUS); 48 return rc; 49 }
(1)需要確定節點是否有"compatible"屬性,如果沒有"compatible"屬性,則直接返回,即不會創建platform設備的。
(2)如果"compatible"屬性值有"arm,primecell",則會調用of_amba_device_create函數去創建amba_device,它設計了AMBA的總線來連接SOC內的各個block。符合這個總線標准的SOC上的外設叫做ARM Primecell Peripherals
(3)如果不是ARM Primecell Peripherals,那么我們就需要向platform bus上增加一個platform device了,of_platform_device_create_pdata才是真正的platform_device
(4)一個device node可能是一個橋設備,因此要重復調用of_platform_bus_create來把所有的device node處理掉。
5.4 函數of_platform_device_create_pdata
1 static struct platform_device *of_platform_device_create_pdata( 2 struct device_node *np, 3 const char *bus_id, 4 void *platform_data, 5 struct device *parent) 6 { 7 struct platform_device *dev; 8 9 if (!of_device_is_available(np) || //檢查status屬性,確保是enable或者OK的 10 of_node_test_and_set_flag(np, OF_POPULATED)) 11 return NULL; 12 13 dev = of_device_alloc(np, bus_id, parent); 14 if (!dev) 15 goto err_clear_flag; 16 17 of_dma_configure(&dev->dev); 18 dev->dev.bus = &platform_bus_type; 19 dev->dev.platform_data = platform_data; 20 21 /* We do not fill the DMA ops for platform devices by default. 22 * This is currently the responsibility of the platform code 23 * to do such, possibly using a device notifier 24 */ 25 26 if (of_device_add(dev) != 0) { 27 platform_device_put(dev); 28 goto err_clear_flag; 29 } 30 31 return dev; 32 33 err_clear_flag: 34 of_node_clear_flag(np, OF_POPULATED); 35 return NULL; 36 }
(1)of_device_is_available函數,這個函數主要是用於檢測"status"屬性的,如果沒有"status"屬性,那還好說直接返回true。如果有"status"屬性,而它的值又不是"okay"或"ok",那么不好意思,返回false,否則還是返回true。所以"status"屬性就是用來檢測是否可用,是否需要創建platform_node
(2)of_device_alloc除了分配struct platform_device的內存,還分配了該platform device需要的resource的內存。當然,這就需要解析該device node的interrupt資源以及memory address資源。
(3)回到of_platform_device_create_pdata函數中,平台設備已經申請好了,然后對平台設備繼續進行賦值操作,例如平台設備的總線賦值為平台總線,平台設備的私有數據賦值為platform_data,最終會調用of_device_add函數將平台設備注冊到內核中。
也就是說當of_platform_populate()函數執行完畢,kernel就為DTB中所有包含compatible屬性名的第一級node創建platform_device結構體,並向平台設備總線注冊設備信息。如果第一級node的compatible屬性值等於“simple-bus”、“simple-mfd”或者"arm,amba-bus"的話,kernel會繼續為當前node的第二級包含compatible屬性的node創建platform_device結構體,並注冊設備。Linux系統下的設備大多都是掛載在平台總線下的,因此在平台總線被注冊后,會根據of_root節點的樹結構,去尋找該總線的子節點,所有的子節點將被作為設備注冊到該總線上。
參考博文:
http://www.wowotech.net/device_model/dt-code-analysis.html
https://blog.csdn.net/thisway_diy/article/details/84336817