linux設備驅動(20)設備樹詳解4-kernel解析dts


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

https://blog.csdn.net/u012489236/article/details/97271797

https://www.cnblogs.com/biglucky/p/4057495.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM