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")
[文章目錄]
- 什麼是 DT_MACHINE_START , MACHINE_END?
- 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( ) ...)