Linux device tree 簡要筆記


第一、DTS簡介
     在嵌入式設備上,可能有不同的主板---它們之間差異表現在主板資源不盡相同,比如I2C、SPI、GPIO等接口定義有差別,或者是Timer不同,等等。於是這就產生了BSP的一個說法。所謂BSP,即是是板級支持包,英文全名為:Board Support Package。是介於主板硬件和操縱系統之間的一層。每一個主板,都有自己對應的BSP文件。在kernel/arch/arm/mach-* 目錄下,放置着不同主板的BSP文件,比如展訊的某一個項目的BSP文件為:

1 kernel/arch/arm/mach-sc/board-sp7731gea.c 

     根據linux設備驅動最抽象的模型(即設備、驅動、總線模型),設備和驅動都會向系統進行注冊的。那么,系統正式運行之前,需要登記自己的板載資源,以便后面進行使用。以展訊sc7731-5.1上某個項目為例,這些資源分別包括了:CPU、Memory、UART、TIMER、CLOCK、GPIO、keypad、I2C、FB、SPI等等。這些資源信息,大部分都需要在 board-sp7731gea.c 文件中進行注冊。以登記I2C為例:

 1 static struct ft5x0x_ts_platform_data ft5x0x_ts_info = { 
 2     .irq_gpio_number    = GPIO_TOUCH_IRQ,
 3     .reset_gpio_number  = GPIO_TOUCH_RESET,
 4     .vdd_name           = "vdd28",
 5 };
 6 
 7 static struct i2c_board_info i2c0_boardinfo[] = {
 8     {
 9         I2C_BOARD_INFO(FOCALTECH_TS_NAME, FOCALTECH_TS_ADDR),
10         .platform_data = &ft5x0x_ts_info,
11     },
12 };
13 
14 static struct i2c_board_info i2c1_boardinfo[] = {
15     {I2C_BOARD_INFO("sensor_main",0x3C),},
16     {I2C_BOARD_INFO("sensor_sub",0x21),},
17 };
18 
19 static int sc8810_add_i2c_devices(void)
20 {
21     i2c_register_board_info(0, i2c1_boardinfo, ARRAY_SIZE(i2c1_boardinfo));
22     i2c_register_board_info(1, i2c0_boardinfo, ARRAY_SIZE(i2c0_boardinfo));
23     return 0;    
24 }
25 
26 static void __init sc8830_init_machine(void)
27 {
28     //...
29     sc8810_add_i2c_devices();
30     //...
31 }

      這上面的I2C設備有這幾個:Camera 前后攝、觸摸屏(TP)。其中,需要調用 i2c_register_board_info() 這個kernel提供的API對I2C設備進行登記注冊。那除開I2C設備以外,比如SPI設備、其他音頻設備等等,都可以采用相應的API向系統進行登記。但是這樣有兩個問題:1.每改動一次板載資源,就得去修改一次BSP文件;2.大量的描述硬件細節的代碼,沖進了kernel(Linus似乎對此不能忍受)。
     那么可以把不變的東西和變化的東西分開來做。不變的邏輯,以少量精確的代碼搞定;變化的資源,可以形成一個資源配置文件。基於這種思想,Linux device tree(DTS)便應運而生。所謂DTS,它是一個以 ".dts"結尾的文件,該文件會被編譯成dtb文件,uboot會把該文件放置到某特定的內存區域,並把相關參數傳給kernel;kernel起來之初,便會去解析該文件,以便拿到板載資源配置。DTS文件中內容框架是一棵樹的結構,其由一系列的結點(node)和屬性(property)鍵值對組成,此處不進行具體分析。DTS文件一般放在 "kernel/arch/arm/boot/dts/ " 目錄下。

二、支持DTS
      linux 3.x kernel已經默認支持了DTS , 可以 make menuconfig -> Boot options -> Flattened Device Tree support 選項;同時,uboot配置文件中也需要定義支持該功能,以展訊某項目為例,在 u-boot64/include/configs/sp7731gea.h 文件中,定義 CONFIG_OF_LIBFDT 宏控:

1 #define CONFIG_OF_LIBFDT

      怎么讓特定的DTS參加編譯呢?在 kernel/arch/arm/boot/dts/Makefile 文件中,比如我們要選擇 sprd-scx35_sp7731gea.dts 文件進行編譯,則進行如下定義:

1 dtb-$(CONFIG_MACH_SP7731GEA) += sprd-scx35_sp7731gea.dtb

     在 kernel/arch/arm/configs/sp7731gea-dt_defconfig 文件中定義 CONFIG_MACH_SP7731GEA 即可。

三、解析DTS簡要流程

 1 //在文件 ./kernel/init/main.c 中:
 2 asmlinkage void __init start_kernel(void)
 3 {
 4     //..
 5     setup_arch(&command_line); //選擇了什么架構,就去執行該架構下的該函數。比如這里是ARM,則選擇進入了 ./kernel/arch/arm/kernel/setup.c 文件
 6     //...
 7 }
 8 
 9 //在文件    ./kernel/arch/arm/kernel/setup.c 中:
10 void __init setup_arch(char **cmdline_p)
11 {
12     //...
13     mdesc = setup_machine_fdt(__atags_pointer);  //獲取對應機器的dts , __atags_pointer __atags_pointer是uboot傳給kernel的一個物理地址。
14     //....
15     unflatten_device_tree(); //全部解析dts樹
16     //....
17 }

3.1 setup_machine_fdt

 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     /* check device tree validity */
 8     if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)
 9         return NULL;
10 
11     /* Search the mdescs for the 'best' compatible value match */
12     initial_boot_params = devtree;
13     dt_root = of_get_flat_dt_root(); //獲取dts的根
14     
15     for_each_machine_desc(mdesc) {
16         score = of_flat_dt_match(dt_root, mdesc->dt_compat);
17         if (score > 0 && score < mdesc_score) {
18             mdesc_best = mdesc;
19             mdesc_score = score;
20         }
21     }
22     
23     if (!mdesc_best) {
24       //....
25         dump_machine_table(); /* does not return */  //進入了這里是否很麻煩?
26     }
27 
28     model = of_get_flat_dt_prop(dt_root, "model", NULL);
29     if (!model)
30         model = of_get_flat_dt_prop(dt_root, "compatible", NULL);
31     if (!model)
32         model = "<unknown>";
33     pr_info("Machine: %s, model: %s\n", mdesc_best->name, model);
34 
35     /* Retrieve various information from the /chosen node */
36     of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
37     /* Initialize {size,address}-cells info */
38     of_scan_flat_dt(early_init_dt_scan_root, NULL);
39     /* Setup memory, calling early_init_dt_add_memory_arch */
40     of_scan_flat_dt(early_init_dt_scan_memory, NULL);
41 
42     /* Change machine number to match the mdesc we're using */
43     __machine_arch_type = mdesc_best->nr;
44 
45     return mdesc_best; //返回目標機器的指針
46 }

     上面這個函數綜合來看的話,就是根據dts來尋找對應的機器了。以展訊某個項目為例,其 sprd-scx35_sp7731gea.dts 文件中定義了字符串 "sprd,sp8835eb" 來標識機器。則它必須要在BSP文件中找到相關的定義,才會繼續初始化下去,否則,就認為找不到對應的機器,將跳入 dump_machine_table() 里面,在該函數里面進行死循環。那么,這個BSP文件中是如何提供一個合適的標志來匹配該字符串呢?在對應的BSP文件 board-sp7731gea.c 中這樣定義:

1 static const char *sprd_boards_compat[] __initdata = {
2   "sprd,sp8835eb",
3   NULL,
4 };
5 
6 MACHINE_START(SCPHONE, "sc8830")
7 //...
8   .dt_compat    = sprd_boards_compat,
9 MACHINE_END 

       因為 MACHINE_START ...  MACHINE_END 這一對宏的關系,該文件(board-sp7731gea.c)在編譯的時候,會被編譯器編譯鏈接到 “.arch.info.init” 段落中去:

1 #define MACHINE_START(_type, _name)            \
2 static const struct machine_desc __mach_desc_##_type    \
3 __used                            \
4 __attribute__((__section__(".arch.info.init"))) = {    \
5     .name        = _name,
6 
7 #define MACHINE_END                \
8 };

       於是,在 setup_machine_fdt() 函數里面,為了匹配特定DTS的機器標志,程序便會去 ".arch.info.init" 段落中將該文件內容讀取出來。setup_machine_fdt()中的 for_each_machine_desc(mdesc) 就干了這事:

1 extern struct machine_desc __arch_info_begin[], __arch_info_end[];
2 #define for_each_machine_desc(p)            \
3     for (p = __arch_info_begin; p < __arch_info_end; p++)

      那么,__arch_info_begin 和 __arch_info_end 在哪里定義呢?在文件 kernel/arch/arm/kernel/vmlinux.lds.S (編譯器的鏈接腳本)中:

1 //....
2     .init.arch.info : {
3         __arch_info_begin = .;
4         *(.arch.info.init)
5         __arch_info_end = .;
6     }        
7 //....

     說簡單了:

1 for_each_machine_desc(mdesc) {
2     score = of_flat_dt_match(dt_root, mdesc->dt_compat); //將獲取的機器 dt_compat 與DTS中的 機器標識符進行比較
3     if (score > 0 && score < mdesc_score) {
4         mdesc_best = mdesc;
5         mdesc_score = score;
6     }
7 }

     這段代碼,便是循環去 ".arch.info.init" 區域讀取目標機器,然后將該機器定義的 dt_compat 和 DTS根目錄下的機器標志進行匹配。一旦正確獲取到目標機器后,便返回該機器的指針。

3.2 unflatten_device_tree

      這個函數的目的便是通過DTS的內容,創建一個設備節點樹。(create tree of device_nodes from flat blob)。在文件 kernel/drivers/of/fdt.c 中:

1 void __init unflatten_device_tree(void)
2 {
3     __unflatten_device_tree(initial_boot_params, &of_allnodes,
4             early_init_dt_alloc_memory_arch);
5 
6 /* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
7     of_alias_scan(early_init_dt_alloc_memory_arch);
8 }

     其中, of_allnodes 是一個全局變量。解析出來的設備節點將形成一個鏈表,而 of_allnodes 則是該鏈表的頭節點。

五、機器初始化簡要流程

1 start_kernel --> setup_arch --> do_initcalls --> customize_machine

參考資料:
ARM Linux 3.x的設備樹(Device Tree) http://blog.csdn.net/21cnbao/article/details/8457546
linux device tree源代碼解析 http://www.blog.chinaunix.net/uid-27717694-id-4274992.html
Linux 3.10 ARM Device Tree 的初始化 http://blog.chinaunix.net/uid-20522771-id-3785808.html


免責聲明!

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



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