Linux裝置樹的傳遞及Kernel中對裝置樹的分析

大雄45發表於2021-08-15
導讀 當 U-Boot 將裝置樹載入到記憶體指定位置後,ARM 核心的 SoC 以通用暫存器 r2 來傳遞 dtb 在記憶體中的地址。kernel 獲取到該地址後對 dtb 檔案做進一步的處理。
裝置樹的傳遞

當使用 bootm 載入 kernel 映象時(bootz 是對 bootm 的一種封裝以及功能擴充套件,實質一樣)。U-Boot 跳轉到 kernel 的入口函式是 boot_jump_

這個函式的 C 檔案在 arch/arm/lib 下,說明裝置樹的傳遞的方式是與 SoC 架構相關的。不同的 SoC 在 bring-up 時,這個函式格外重要,這是 U-Boot 與 kernel 之間銜接、互動資訊的一個關鍵 API。U-Boot 的這個函式執行結束後,將 CPU 的控制權完整的交給 kernel。

/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
...
  debug("## Transferring control to Linux (at address %08lx)" \
    "...\n", (ulong) kernel_entry);
  bootstage_mark(BOOTSTAGE_ID_RUN_OS);
  announce_and_cleanup(fake);
  if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
    r2 = (unsigned long)images->ft_addr;
  else
    r2 = gd->bd->bi_boot_params;
...
}

r2 作為存放裝置樹地址的暫存器,其取值有兩種方式,分別是例化 bootm_header_t 這個資料結構的 ft_addr,以及利用 U-Boot 的板級啟動引數作為裝置樹的地址。

bootm_header_t 方式

資料結構 bootm_header_t 的定義如下,供各種核心的 SoC 使用,每家廠商根據自己 CPU 的特點對各個成員進行不同的例化。

/*
 * Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
 * routines.
 */
typedef struct bootm_headers {
  ...
  char    *ft_addr;  /* flat dev tree address */
  ulong    ft_len;    /* length of flat device tree */
  ...
} bootm_headers_t;

用 bootm_header_t 的方式,U-Boot 需支援裝置樹以及檔案非空。

Linux裝置樹的傳遞及Kernel中對裝置樹的分析Linux裝置樹的傳遞及Kernel中對裝置樹的分析

ft_len 以及 ft_addr 屬於 bootm_header_t,在 U-Boot 解析映象檔案時,例項化這兩個成員。函式呼叫棧如下:

do_bootz(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
-bootz_start()
--bootm_find_images(int flag, int argc, char *const argv[], ulong start,ulong size)
---boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,&images.ft_addr, &images.ft_len);
   u-boot-v2021.04/common/image-fdt.c
gd->bd->bi_boot_params 方式

這種屬於比較古老的一種方式了,目前基本不會採用。bi_boot_params 是一個存放核心啟動引數的地址,通常是在板級初始化中進行指定。

程式碼執行到此處,r2 是否為預期的值,一是可以透過列印的方式、再有使用除錯工具連上去確認。

kernel 對裝置樹的解析

解析分兩個階段,第一階段進行校驗以及啟動引數的再調整;第二階段完成裝置樹的解壓,也就是將裝置樹由 FDT 變成 EDT,建立 device_node。

第一階段

kernel 啟動日誌中與裝置樹相關的第一條列印如下,也就是列印出當前硬體裝置的模型名,"OF: fdt: Machine model: V2P-CA9" 。

Booting Linux on physical CPU 0x0
Linux version 5.4.124 (qemu@qemu) (gcc version 6.5.0 (Linaro GCC 6.5-2018.12)) #3 SMP Fri Jun 25 15:26:02 CST 2021
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
OF: fdt: Machine model: V2P-CA9

這個模型名是在裝置樹檔案的頭部定義的,定義當前裝置的總體名稱。

// SPDX-License-Identifier: GPL-2.0
/*
 * ARM Ltd. Versatile Express
 *
 * CoreTile Express A9x4
 * Cortex-A9 MPCore (V2P-CA9)
 *
 * HBI-0191B
 */
/dts-v1/;
#include "vexpress-v2m.dtsi"
/ {
  model = "V2P-CA9";
  ...
  }

但這並不是 kernel 對裝置樹第一次進行處理的地方。在此之前已有其他的操作。函式呼叫棧如下:

setup_arch(char **cmdline_p) arch/arm/kernel/setup.c
    atags_vaddr = FDT_VIRT_BASE(__atags_pointer);
    setup_machine_fdt(void *dt_virt) arch/arm/kernel/devtree.c
        early_init_dt_verify()
        of_flat_dt_match_machine()  drivers/of/fdt.c
        early_init_dt_scan_nodes();
        __machine_arch_type = mdesc->nr;

第 2 行、__atags_pointer 是 dtb 在記憶體中的地址,這個地址在彙編階段(若映象為 zImage,那麼在解壓縮階段就完成了)便獲取到了。由於執行到 setup_arch 時 mmu 已經使能並且 4K 的段頁表也已經完成了對映,而 U-Boot 傳遞給 kernel 的裝置樹 fdt 地址屬於實體地址,因此需要將實體地址轉換成虛擬地址。

head-common.S
  .align  2
  .type  __mmap_switched_data, %object
__mmap_switched_data:
#ifdef CONFIG_XIP_KERNEL
#ifndef CONFIG_XIP_DEFLATED_DATA
  .long  _sdata        @ r0
  .long  __data_loc      @ r1
  .long  _edata_loc      @ r2
#endif
  .long  __bss_stop      @ sp (temporary stack in .bss)
#endif
  .long  __bss_start      @ r0
  .long  __bss_stop      @ r1
  .long  init_thread_union + THREAD_START_SP @ sp
  .long  processor_id      @ r0
  .long  __machine_arch_type    @ r1
  .long  __atags_pointer      @ r2

第一階段對裝置樹的配置主要包括:

A 對 dtb 檔案進行 crc32 校驗,檢測裝置樹檔案是否合法 early_init_dt_verify()

B early_init_dt_scan_nodes()
        /* Retrieve various information from the /chosen node */
        of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);
        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
C 更新__machine_arch_type
D 更新 chosen

上面這個 chosen 資訊可以在 kernel 起來後再次檢視做了哪些修改。

第二階段

第二階段單純的是將裝置樹 ABI 檔案進行解壓縮,由 FDT 變成 EDT,生成相應的 device_node 結點。這個階段的函式呼叫棧如下:

unflatten_device_tree();
    *__unflatten_device_tree()
        /* First pass, scan for size */
        size = unflatten_dt_nodes(blob, NULL, dad, NULL);
         /* Second pass, do actual unflattening */
        unflatten_dt_nodes(blob, mem, dad, mynodes);
            unflatten_dt_nodes()
                populate_node()

device_nodes 結點如下:

Linux裝置樹的傳遞及Kernel中對裝置樹的分析Linux裝置樹的傳遞及Kernel中對裝置樹的分析

device_node 建立完成後,kernel 建立 platform_device 時依據這個階段完成的工作情況進行對應的裝置註冊,供驅動程式碼使用。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2787004/,如需轉載,請註明出處,否則將追究法律責任。

相關文章