Linux 3.10 ARM Device Tree 的初始化

本文代碼均來自標準 linux kernel 3.10,可以到這裏下載 https://www.kernel.org/
    以 arch/arm/mach-msm/board-dt-8960.c 爲例,在該文件中的 msm_dt_init 函數的作用就是利用 dt(device tree)結構初始化 platform device。

點擊(此處)摺疊或打開

  1. static void __init msm_dt_init(void)
  2. {
  3.     of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
  4. }
    of_platform_populate 實現在 drivers/of/platform.c,是 OF 的標準函數。

點擊(此處)摺疊或打開

  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.     root = root ? of_node_get(root) : of_find_node_by_path("/");
  9.     if (!root)
  10.         return -EINVAL;

  11.     for_each_child_of_node(root, child) {
  12.         rc = of_platform_bus_create(child, matches, lookup, parent, true);
  13.         if (rc)
  14.             break;
  15.     }

  16.     of_node_put(root);
  17.     return rc;
  18. }
    其中of_platform_populate 函數的註釋寫得很明白:“Populate platform_devices from device tree data”。但是這個“device tree data”又是從那裏來的呢?
    在 of_platform_populate 中如果 root 爲 NULL,則將 root 賦值爲根節點,這個根節點是用 of_find_node_by_path 取到的。

點擊(此處)摺疊或打開

  1. struct device_node *of_find_node_by_path(const char *path)
  2. {
  3.     struct device_node *np = of_allnodes;
  4.     unsigned long flags;

  5.     raw_spin_lock_irqsave(&devtree_lock, flags);
  6.     for (; np; np = np->allnext) {
  7.         if (np->full_name && (of_node_cmp(np->full_name, path) == 0)
  8.             && of_node_get(np))
  9.             break;
  10.     }
  11.     raw_spin_unlock_irqrestore(&devtree_lock, flags);
  12.     return np;
  13. }
    在這個函數中有一個很關鍵的全局變量:of_allnodes,它的定義是在 drivers/of/base.c 裏面。

點擊(此處)摺疊或打開

  1. struct device_node *of_allnodes;
    這應該所就是那個所謂的device tree data”了。它應該指向了 device tree 的根節點。問題又來了,這個 of_allnodes 又是咋來的呢?我們知道 device tree 是由 DTC(Device Tree Compiler)編譯成二進制文件 DTB(Ddevice Tree Blob),然後在系統上電之後由 bootloader 加載到內存中去,這個時候還沒有 device tree,而在內存中只有一個所謂的 DTB,這只是一個以某個內存地址開始的一堆原始的 dt 數據,沒有樹結構。kernel 的任務需要把這些數據轉換成一個樹結構然後再把這棵樹的根節點的地址賦值給 of_allnodes 就行了。這個過程一定是非常重要,因爲沒有這個 device tree 那所有的設備就沒辦法初始化,所以這個 dt 樹的形成一定在 kernel 剛剛啓動的時候就完成了。
   既然如此,我們來看看 kernel 初始化的代碼(init/main.c),大部分代碼與本主題無關用 ... 代替。

點擊(此處)摺疊或打開

  1. asmlinkage void __init start_kernel(void)
  2. {
  3.     ...
  4.     setup_arch(&command_line);
  5.     ...
  6. }
    這個 setup_arch 就是各個架構自己的設置函數(arch 是 architecture 的縮寫),哪個參與了編譯就調用哪個,在本文中應當是 arch/arm/kernel/setup.c 中的 setup_arch。
    同樣,無關的代碼以 ... 代替。

點擊(此處)摺疊或打開

  1. void __init setup_arch(char **cmdline_p)
  2. {
  3.     ...
  4.     mdesc = setup_machine_fdt(__atags_pointer);
  5.     ...
  6.     unflatten_device_tree();
  7.     ...
  8. }
    看到了吧,setup_machine_fdt,其中 fdt 的 f 就是扁平flat)的意思。這個時候 DTB 只是加載到內存中的 .dtb 文件而已,這個文件中不僅包含數據結構,還包含了一些文件頭等信息,kernel 需要從這些信息中獲取到數據結構相關的信息,然後再生成設備樹。這個函數的調用還有個參數 __atags_pointer,看名字似乎這是一個指針,幹嘛的呢?以後再說,先進入函數看看。

點擊(此處)摺疊或打開

  1. /* kernel/arch/arm/kernel/devtree.c */
  2. struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
  3. {
  4.     ...
  5.     devtree = phys_to_virt(dt_phys);
  6.     ...
  7.     initial_boot_params = devtree;
  8.     ...
  9. }
    現在有點意思了,phys_to_virt 字面上的意思是物理地址轉換成虛擬地址,那就是說 __atags_pointer 是一個物理地址,這就印證了我們的猜測,__atags_pointer 的確是一個指針,再看變量 devtree 它指向了一個 struct boot_param_header 結構體。隨後 kernel 把這個指針賦給了全局變量 initial_boot_params。也就是說以後 kernel 會是用這個指針指向的數據去初始化 device tree。

點擊(此處)摺疊或打開

  1. struct boot_param_header {
  2.     __be32 magic; /* magic word OF_DT_HEADER */
  3.     __be32 totalsize; /* total size of DT block */
  4.     __be32 off_dt_struct; /* offset to structure */
  5.     __be32 off_dt_strings; /* offset to strings */
  6.     __be32 off_mem_rsvmap; /* offset to memory reserve map */
  7.     __be32 version; /* format version */
  8.     __be32 last_comp_version; /* last compatible version */
  9.     /* version 2 fields below */
  10.     __be32 boot_cpuid_phys; /* Physical CPU id we're booting on */
  11.     /* version 3 fields below */
  12.     __be32 dt_strings_size; /* size of the DT strings block */
  13.     /* version 17 fields below */
  14.     __be32 dt_struct_size; /* size of the DT structure block */
  15. };
    看這個結構體,很像之前所說的文件頭,有魔數、大小、數據結構偏移量、版本等等,kernel 就應該通過這個結構獲取數據,並最終生成設備樹。現在回到 setup_arch,果然在隨後的代碼中有這麼一個函數。

點擊(此處)摺疊或打開

  1. /* kernel/drivers/of/fdt.c */
  2. void __init unflatten_device_tree(void)
  3. {
  4.     __unflatten_device_tree(initial_boot_params, &of_allnodes,
  5.                 early_init_dt_alloc_memory_arch);
  6.     ...
  7. }
    看見了吧,of_allnodes 就是在這裏賦值的,device tree 也是在這裏建立完成的。__unflatten_device_tree 函數我們就不去深究了,推測其功能應該就是解析數據、申請內存、填充結構等等。
    到此爲止,device tree 的初始化就算完成了,在以後的啓動過程中,kernel 就會依據這個 dt 來初始化各個設備。但是還有一個小問題,那就是在 setup_arch 函數中 __atags_pointer 從何而來。全局搜索這個變量,結構在 arch/arm/kernel/head-common.S 中發現了它的蹤跡。

點擊(此處)摺疊或打開

  1. #define ATAG_CORE 0x54410001
  2. ...
  3. __mmap_switched:
  4.     adr r3, __mmap_switched_data

  5.     ldmia {r4, r5, r6, r7}
  6.     ...
  7.     THUMB( ldr sp, [r3, #16] )
  8.     ...
  9.     str r2, [r6] @ Save atags pointer
  10.     ...
  11. __mmap_switched_data:
  12.     ...
  13.     .long __atags_pointer @ r6
  14.     ...
    arm 彙編不懂,大概能看出來給 __atags_pointer 賦值的過程(‘@’之後是註釋)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章