2014年4月28日 星期一

Soc的board file中多組的DT_MACHINE_START與device tree之間的對應關係

[本文章重點] 您將會學到 kernel 在開機時如何藉由 device tree 在多組 board file 裡的DT_MACHINE_START 中找到正確的 machine description.

Ps. 本文章以 Marvell PXA1L88 的 source code 為例子

例如: (我以 Marvell PXA1L88 的 board file - mmpx-dt.c 作為例子)
1:  DT_MACHINE_START(PXA1088_DT, "PXA1088")  
2:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
3:       .map_io          = mmp_map_io,  
4:       .init_irq     = pxa988_dt_irq_init,  
5:       .init_time     = mmpx_timer_init,  
6:       .init_machine     = pxa988_dt_init_machine_1,  
7:       .dt_compat     = pxa1088_dt_board_compat,  
8:       .reserve     = pxa988_reserve,  
9:       .restart     = mmp_arch_restart,  
10:  MACHINE_END  
11:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
12:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
13:       .map_io          = mmp_map_io,  
14:       .init_irq     = pxa988_dt_irq_init,  
15:       .init_time     = mmpx_timer_init,  
16:       .init_machine     = pxa988_dt_init_machine_2,  
17:       .dt_compat     = pxa1L88_dt_board_compat,  
18:       .reserve     = pxa988_reserve,  
19:       .restart     = mmp_arch_restart,  
20:  MACHINE_END  
21:  DT_MACHINE_START(PXA1U88_DT, "PXA1U88")  
22:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
23:       .map_io          = mmp_map_io,  
24:       .init_irq     = pxa988_dt_irq_init,  
25:       .init_time     = mmpx_timer_init,  
26:       .init_machine     = pxa988_dt_init_machine_3,  
27:       .dt_compat     = pxa1U88_dt_board_compat,  
28:       .reserve     = pxa988_reserve,  
29:       .restart     = mmp_arch_restart,  
30:  MACHINE_END  

在這麼多組的 machine description 中 , 它是如何正確地選中DT_MACHINE_START(PXA1L88_DT, "PXA1L88") ?
此例一共有三組
DT_MACHINE_START(PXA1088_DT, "PXA1088")
DT_MACHINE_START(PXA1L88_DT, "PXA1L88")
DT_MACHINE_START(PXA1U88_DT, "PXA1U88")

[文章目錄]
  1. 什麼是 DT_MACHINE_START , MACHINE_END?
  2. kernel 如何在多組 machine description 中找到正確的匹配
[什麼是DT_MACHINE_START , MACHINE_END?]
   Device tree 的引進主要就是要解決 ARM Linux 中, arch/arm/plat-xxx和arch/arm/mach-xxx 中有太多不重要的 board level 的描述, 我們希望盡量在單一的 board file 中能夠支援更多 board level 的描述,在透過一些機制去找到正確的 machine description.
   那支援 device tree 的 machine description 是如何宣告?
(kernel/arch/arm/include/asm/mach/arch.h)
1:  #define DT_MACHINE_START(_name, _namestr)          \  
2:  static const struct machine_desc __mach_desc_##_name     \  
3:   __used                                   \  
4:   __attribute__((__section__(".arch.info.init"))) = {     \  
5:       .nr          = ~0,                    \  
6:       .name          = _namestr,  
7:  #endif  
從這巨集得知, 它將 struct machine_desc 的物件放在 .arch.info.init 這個 section 中, DT_MACHINE_START(PXA1L88_DT, "PXA1L88") 展開就會是

1:  [.arch.info.init]  
2:    struct machine_desc __mach_desc_PXA1L88_DT = {  
3:          .nr       = ~0,  
4:          .name   = "PXA1L88",  
5:          ...  
6:    }  

我們可以透過 System.map 去驗證
1:  c0a21598 T __arch_info_begin  
2:  c0a21598 t __mach_desc_PXA1U88_DT  
3:  c0a215e8 t __mach_desc_PXA1L88_DT  
4:  c0a21638 t __mach_desc_PXA1088_DT  
5:  c0a21688 T __arch_info_end  
所以從 System.map 得知, board file 裡的所有 DT_MACHINE_START( ) 都被 build 進去並放在arch.info.init 這 section 內
(補充: kernel/arch/arm/kernel/vmlinux.lds.S)
1:  .init.arch.info : {  
2:            __arch_info_begin = .;  
3:            *(.arch.info.init)  
4:            __arch_info_end = .;  
5:       }d  

[kernel 如何在多組 machine description 中找到正確的匹配]
   從上一段文章得知所有的 DT_MACHINE_START( )宣告的 machine description 都被 build 進去了, 那 kernel 如何做出正確的選擇呢?  答案就透過 "compatible" 這關鍵字.
 
    我們先談一下 device tree , 當我們在 build Android images 時就會選擇 TARGET_PRODUCT , 接下來就會知道
1:  KERNEL_DEFCONFIG := pxa988_defconfig  
2:  ###!!!! the order of the dtb file here need to align with the code in u-boot###  
3:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Linux kernel 會利用 dtc (device tree compiler) build 出 .dtb 檔 (device tree blob)
(kernel/arch/arm/boot/dts/Makefile)
1:  dtb-$(CONFIG_ARCH_MMP) += edenconcord.dtb edenfpga.dtb pxa988-dkb.dtb pxa1088dkb.dtb\  
2:                  pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-v10.dtb\  
3:                  pxa1U88-dkb.dtb pxa1986sdk.dtb pxa1L88-tablet-l7-v10.dtb  
雖然 kernel 會 build 出很多 .dtb 檔, 但是只有以下特定的 .dtb 檔才會被放進 images 裡
1:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Marvell的作法是將這些 .dtb 檔直接放在 u-boot.bin 的尾端, u-boot 會告訴 kernel 關於 Flattened Device Tree blob 的 address, kernel 再從這 address 將這些 device tree blob載到memory 特定的位址上.

    在 .dts 檔中, 我們會看到 compatible 這些關鍵字, 例如:
在 pxa1L88-dkb-v10.dts 中, compatible = "mrvl,pxa1L88-dkb-v10", "mrvl,pxa1L88";
在 pxa1L88-dkb-v20.dts 中, compatible = "mrvl,pxa1L88-dkb-v20", "mrvl,pxa1L88";
在 pxa1L88-tablet-l7-v10.dts 中, compatible = "mrvl,pxa1L88-tablet-l7-v10", "mrvl,pxa1L88";

   我們再回到 board file - mmpx-dt.c ,
1:  static const char *pxa1L88_dt_board_compat[] __initdata = {  
2:       "mrvl,pxa1L88-dkb-v10",  
3:       "mrvl,pxa1L88-dkb-v20",  
4:       "mrvl,pxa1L88-tablet-v10",  
5:       "mrvl,pxa1L88-tablet-l7-v10",  
6:       NULL,  
7:  };  
8:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
9:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
10:       .map_io          = mmp_map_io,  
11:       .init_irq     = pxa988_dt_irq_init,  
12:       .init_time     = mmpx_timer_init,  
13:       .init_machine     = pxa988_dt_init_machine_2,  
14:       .dt_compat     = pxa1L88_dt_board_compat,  
15:       .reserve     = pxa988_reserve,  
16:       .restart     = mmp_arch_restart,  
17:  MACHINE_END  

      我們可以看到 .dt_compat = pxa1L88_dt_board_compat, 而 pxa1L88_dt_board_compat[] 裡面的字串是不是跟剛才的那些 .dts 檔案的 compatible 裡的 value 值一樣 ( .dts 檔案裡面就是紀錄一堆 key = value 這樣的資料並排成樹狀結構). Linux kernel 在開機做初始化時, 會將 u-boot 告訴它的 device tree 與 kernel image 本身 build 進去的 machine description ( 就是 DT_MACHINE_START( ) 所宣告並放在 .arch.info.init section中) 去做字串比較, 進而找到相符的 machine description. 之後在Linux kernel 在做後續的初始化時,就會執行該 machine description 的 .init_machine 所指的function去展開 device tree. 所以, 此例就會去展開 DT_MACHINE_START(PXA1L88_DT, "PXA1L88").
1:  start_kernel( ) [kernel/init/main.c]  
2:    -> setup_arch( ) [kernel/arch/arm/kernel/setup.c]  
3:      -> setup_machine_fdt( ) [kernel/arch/arm/kernel/devtree.c]  
4:           devtree = phys_to_virt(dt_phys); /*這時候MMU已經起來了,所以要轉成 virtual mem addr 才可以存取的到*/  
5:           /* check device tree validity */  
6:           if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)  
7:              return NULL;  
8:            /* Search the mdescs for the 'best' compatible value match */  
9:            initial_boot_params = devtree;  
10:           dt_root = of_get_flat_dt_root(); /*指到 u-boot 傳給 kernel 的 device three*/  
11:           for_each_machine_desc(mdesc) {  /*歷遍 .arch.info.init 中的 struct machine_desc*/  
12:              score = of_flat_dt_match(dt_root, mdesc->dt_compat);  
13:              if (score > 0 && score < mdesc_score) {  
14:                  mdesc_best = mdesc; /*如果找到了, 就將 mdesc_best 指到該結構物件*/  
15:                  mdesc_score = score;  
16:              }  
17:            }  
18:            -> of_flat_dt_match( ) [kernel/drivers/of/fdt.c]  
19:              -> of_fdt_match( )  
20:                -> of_fdt_is_compatible( )  
21:                    cp = of_fdt_get_property(blob, node, "compatible", &cplen); /*取出 compatible 的 value 值*/  
22:                    ...  
23:                    while (cplen > 0) {  
24:                          score++;  
25:                          if (of_compat_cmp(cp, compat, strlen(compat)) == 0) /*透過 strncmp() 做字串的比較*/  
26:                               return score;  
27:                          ...  
28:                     }  
29:  
補充: 關於device tree 會用到的functions 大都可以在 kernel/drivers/of 目錄下找到, 而且這些functions 都是以 of_XXX( ) 命名. ( 例如: of_fdt_match( ), of_fdt_is_compatible( ) ...)

3 則留言:

  1. 请问第三方人士如何获得PXA1L88的内核源代码,采用此方案的手机厂商并未释出。

    回覆刪除
    回覆
    1. hi, 因為此文章算是工作上的心得記錄. 不過, 我想重點應該放在 device tree 上的應用與概念.

      刪除
  2. machine_desc好像已經remove了?

    回覆刪除