6. 內(nèi)核對(duì)設(shè)備樹的處理

一、設(shè)備樹的描述

對(duì)于設(shè)備樹,其描述的信息可以分成三部分;在內(nèi)核中,對(duì)設(shè)備樹的處理也會(huì)分成三部分:

Linux uses DT data for three major purposes:
1) platform identification,   (平臺(tái)識(shí)別信息)
2) runtime configuration, and (運(yùn)行時(shí)配置信息)
3) device population.         (設(shè)備信息)

二、內(nèi)核head.S對(duì)dtb的簡單處理

u-boot把一些參數(shù),把設(shè)備樹文件傳給內(nèi)核,那么內(nèi)核如何處理設(shè)備樹文件呢?從內(nèi)核的第一個(gè)啟動(dòng)文件head.S著手,分析其對(duì)dtb的處理。(詳細(xì)的head.s的分析可參見:嵌入式Linux完全開發(fā)手冊(cè) - 移植Linux內(nèi)核 - Linux內(nèi)核啟動(dòng)概述)
bootloader啟動(dòng)內(nèi)核時(shí),會(huì)設(shè)置r0,r1,r2三個(gè)寄存器,由這三個(gè)寄存器將參數(shù)傳給內(nèi)核:

  • r0一般設(shè)置為0;
  • r1一般設(shè)置為machine id (在使用設(shè)備樹時(shí)該參數(shù)沒有被使用);
  • r2一般設(shè)置ATAGS或DTB的開始地址;
    這里額外介紹下machine id的作用。假設(shè)一個(gè)內(nèi)核鏡像uImage可以支持多種單板:

head.S分析如下:

  • a. [ __lookup_processor_type ] 使用匯編指令讀取CPU ID, 根據(jù)該ID找到對(duì)應(yīng)的proc_info_list結(jié)構(gòu)體(里面含有這類CPU的初始化函數(shù)、信息)
  • b. [ __vet_atags ] 判斷是否存在可用的ATAGS或DTB;
  • c. [ __create_page_tables ] 創(chuàng)建頁表, 即創(chuàng)建虛擬地址和物理地址的映射關(guān)系;
  • d. [ __enable_mmu ] 使能MMU, 以后就要使用虛擬地址了
  • e. [ __mmap_switched ] 上述函數(shù)里將會(huì)調(diào)用__mmap_switched
  • f. 把bootloader傳入的r2參數(shù), 保存到變量__atags_pointer中
  • g. 調(diào)用C函數(shù)start_kernel
head.S/head-common.S  : 
把bootloader傳來的r1值, 賦給了C變量: __machine_arch_type
把bootloader傳來的r2值, 賦給了C變量: __atags_pointer     // dtb首地址

由此可見,head.S對(duì)dtb的處理還是比較簡單的,只是將u-boot傳來的R2寄存器里的值,也就是DTB的首地址賦給了內(nèi)核中的C變量:__atags_pointer 。

三、內(nèi)核對(duì)設(shè)備樹中平臺(tái)信息的處理(選擇machine_desc)

  1. platform identification, (平臺(tái)識(shí)別信息)

上面的介紹中,已經(jīng)知道在以前u-boot的ATAG傳參中,會(huì)傳入machine id 由此來匹配machine desc;當(dāng)使用dtb傳參時(shí),u-boot并不傳遞machine id了,內(nèi)核machine desc 的依賴于dtb 根節(jié)點(diǎn)下的compatible屬性,內(nèi)核根據(jù)該屬性來找到合適的machine desc。

作為內(nèi)核的使用者,我們已經(jīng)列出清單,期望在內(nèi)核中找到支持某個(gè)單板的machine desc 。
對(duì)于內(nèi)核,每個(gè)machine desc需表明能支持哪些單板;

compatible屬性對(duì)machine desc的查找
  • a. 設(shè)備樹根節(jié)點(diǎn)的compatible屬性列出了一系列的字符串,表示它兼容的單板名,從"最兼容"到次之。

  • b. 內(nèi)核中有多個(gè)machine_desc, 其中有dt_compat成員, 它指向一個(gè)字符串?dāng)?shù)組, 里面表示該machine_desc支持哪些單板

  • c. 使用compatile屬性的值, 跟每一個(gè)machine_desc.dt_compat 比較,成績?yōu)?吻合的compatile屬性值的位置",成績?cè)降驮狡ヅ? 對(duì)應(yīng)的machine_desc即被選中

分析下代碼的調(diào)用流程:
從上面的分析已經(jīng)知道,內(nèi)核在啟動(dòng)后,將dtb文件的首地址保存到變量__atags_pointer中,然后調(diào)用start_kernel,接下來從start_kernel開始分析:
函數(shù)調(diào)用過程:

start_kernel // init/main.c
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        mdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.c
                   // 判斷是否有效的dtb, drivers/of/ftd.c
                    early_init_dt_verify(phys_to_virt(dt_phys) 
                                    initial_boot_params = params; //將dtb的地址保存在全局變量里
                    // 找到最匹配的machine_desc, drivers/of/ftd.c
                    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);  
                                    while ((data = get_next_compat(&compat))) {
                                        score = of_flat_dt_match(dt_root, compat);
                                        if (score > 0 && score < best_score) {
                                            best_data = data;
                                            best_score = score;
                                        }
                                    }
                    
        machine_desc = mdesc;

四、內(nèi)核對(duì)設(shè)備樹中運(yùn)行時(shí)配置信息的處理

  1. runtime configuration, (運(yùn)行時(shí)配置信息)

運(yùn)行時(shí)的三種配置信息總結(jié)如下:

  • a. /chosen節(jié)點(diǎn)中bootargs屬性的值, 存入全局變量: boot_command_line
  • b. 確定根節(jié)點(diǎn)的這2個(gè)屬性的值: #address-cells, #size-cells
    存入全局變量: dt_root_addr_cells, dt_root_size_cells
  • c. 解析/memory中的reg屬性, 提取出"base, size", 最終調(diào)用memblock_add(base, size);

函數(shù)調(diào)用過程:

start_kernel // init/main.c
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        mdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.c
                    early_init_dt_scan_nodes();      // drivers/of/ftd.c
                        /* 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);
5

五、內(nèi)核對(duì)設(shè)備樹中設(shè)備信息的處理

  1. device population. (設(shè)備信息)

內(nèi)核啟動(dòng)后,會(huì)將dtb中的所有節(jié)點(diǎn)轉(zhuǎn)換為device_node,了解了device_node結(jié)構(gòu)體和properties結(jié)構(gòu)體,就可知道大致情況。

?每一個(gè)節(jié)點(diǎn)都轉(zhuǎn)換為一個(gè)device_node結(jié)構(gòu)體:

struct device_node {
    const char *name;  // 來自節(jié)點(diǎn)中的name屬性, 如果沒有該屬性, 則設(shè)為"NULL"
    const char *type;  // 來自節(jié)點(diǎn)中的device_type屬性, 如果沒有該屬性, 則設(shè)為"NULL"
    phandle phandle;
    const char *full_name;  // 節(jié)點(diǎn)的名字, node-name[@unit-address]
    struct fwnode_handle fwnode;

    struct  property *properties;  // 節(jié)點(diǎn)的屬性
    struct  property *deadprops;    /* removed properties */
    struct  device_node *parent;   // 節(jié)點(diǎn)的父親
    struct  device_node *child;    // 節(jié)點(diǎn)的孩子(子節(jié)點(diǎn))
    struct  device_node *sibling;  // 節(jié)點(diǎn)的兄弟(同級(jí)節(jié)點(diǎn))
#if defined(CONFIG_OF_KOBJ)
    struct  kobject kobj;
#endif
    unsigned long _flags;
    void    *data;
#if defined(CONFIG_SPARC)
    const char *path_component_name;
    unsigned int unique_id;
    struct of_irq_controller *irq_trans;
#endif
};

?device_node結(jié)構(gòu)體中有properties, 用來表示該節(jié)點(diǎn)的屬性,每一個(gè)屬性對(duì)應(yīng)一個(gè)property結(jié)構(gòu)體:

struct property {
    char    *name;    // 屬性名字, 指向dtb文件中的字符串
    int length;       // 屬性值的長度
    void    *value;   // 屬性值, 指向dtb文件中value所在位置, 數(shù)據(jù)仍以big endian存儲(chǔ)
    struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
    unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
    unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
    struct bin_attribute attr;
#endif
};
  • a. 在DTB文件中,
    每一個(gè)節(jié)點(diǎn)都以TAG(FDT_BEGIN_NODE, 0x00000001)開始, 節(jié)點(diǎn)內(nèi)部可以嵌套其他節(jié)點(diǎn),
    每一個(gè)屬性都以TAG(FDT_PROP, 0x00000003)開始
  • b. 這些device_node構(gòu)成一棵樹, 根節(jié)點(diǎn)為: of_root
    跟蹤下代碼:
函數(shù)調(diào)用過程:
start_kernel // init/main.c
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        arm_memblock_init(mdesc);   // arch/arm/kernel/setup.c
            early_init_fdt_reserve_self();
                    /* Reserve the dtb region */
                    // 把DTB所占區(qū)域保留下來, 即調(diào)用: memblock_reserve
                    early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                      fdt_totalsize(initial_boot_params),
                                      0);           
            early_init_fdt_scan_reserved_mem();  // 根據(jù)dtb中的memreserve信息, 調(diào)用memblock_reserve
            
        unflatten_device_tree();    // arch/arm/kernel/setup.c
            __unflatten_device_tree(initial_boot_params, NULL, &of_root,
                        early_init_dt_alloc_memory_arch, false);            // drivers/of/fdt.c
                
                /* First pass, scan for size */
                size = unflatten_dt_nodes(blob, NULL, dad, NULL);
                
                /* Allocate memory for the expanded device tree */
                mem = dt_alloc(size + 4, __alignof__(struct device_node));
                
                /* Second pass, do actual unflattening */
                unflatten_dt_nodes(blob, mem, dad, mynodes);
                    populate_node
                        np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
                                    __alignof__(struct device_node));
                        
                        np->full_name = fn = ((char *)np) + sizeof(*np);
                        
                        populate_properties
                                pp = unflatten_dt_alloc(mem, sizeof(struct property),
                                            __alignof__(struct property));
                            
                                pp->name   = (char *)pname;
                                pp->length = sz;
                                pp->value  = (__be32 *)val;

對(duì)于驅(qū)動(dòng)開發(fā)者,設(shè)備樹中還有一個(gè)備受關(guān)注的信息:platform_device

dts -> dtb -> device_node -> platform_device

在上述,我們已經(jīng)知道dts中的節(jié)點(diǎn)信息終將被轉(zhuǎn)換為 device_node ,但并不是所有device_node都會(huì)被轉(zhuǎn)換成platform_device,需滿足:

  • 根節(jié)點(diǎn)下含有compatile屬性的子節(jié)點(diǎn)
  • 如果一個(gè)結(jié)點(diǎn)的compatile屬性含有這些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子結(jié)點(diǎn)(需含compatile屬性)也可以轉(zhuǎn)換為platform_device
    i2c, spi等總線節(jié)點(diǎn)下的子節(jié)點(diǎn), 應(yīng)該交給對(duì)應(yīng)的總線驅(qū)動(dòng)程序來處理, 它們不應(yīng)該被轉(zhuǎn)換為platform_device
轉(zhuǎn)換規(guī)則:
  • platform_device中含有resource數(shù)組, 它來自device_node的reg, interrupts屬性;
  • platform_device.dev.of_node指向device_node, 可以通過它獲得其他屬性;

總結(jié)下:

  • a. 內(nèi)核函數(shù)of_platform_default_populate_init, 遍歷device_node樹, 生成platform_device
  • b. 并非所有的device_node都會(huì)轉(zhuǎn)換為platform_device
    只有以下的device_node會(huì)轉(zhuǎn)換:
    • b.1 該節(jié)點(diǎn)必須含有compatible屬性
    • b.2 根節(jié)點(diǎn)的子節(jié)點(diǎn)(節(jié)點(diǎn)必須含有compatible屬性)
    • b.3 含有特殊compatible屬性的節(jié)點(diǎn)的子節(jié)點(diǎn)(子節(jié)點(diǎn)必須含有compatible屬性):
      這些特殊的compatilbe屬性為: "simple-bus","simple-mfd","isa","arm,amba-bus"

示例:

比如以下的節(jié)點(diǎn), 
/mytest會(huì)被轉(zhuǎn)換為platform_device, 
因?yàn)樗嫒?simple-bus", 它的子節(jié)點(diǎn)/mytest/mytest@0 也會(huì)被轉(zhuǎn)換為platform_device

/i2c節(jié)點(diǎn)一般表示i2c控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
/i2c/at24c02節(jié)點(diǎn)不會(huì)被轉(zhuǎn)換為platform_device, 它被如何處理完全由父節(jié)點(diǎn)的platform_driver決定, 一般是被創(chuàng)建為一個(gè)i2c_client。

類似的也有/spi節(jié)點(diǎn), 它一般也是用來表示SPI控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
/spi/flash@0節(jié)點(diǎn)不會(huì)被轉(zhuǎn)換為platform_device, 它被如何處理完全由父節(jié)點(diǎn)的platform_driver決定, 一般是被創(chuàng)建為一個(gè)spi_device。

/ {
      mytest {
          compatile = "mytest", "simple-bus";
          mytest@0 {
                compatile = "mytest_0";
          };
      };
      
      i2c {
          compatile = "samsung,i2c";
          at24c02 {
                compatile = "at24c02";                      
          };
      };

      spi {
          compatile = "samsung,spi";              
          flash@0 {
                compatible = "winbond,w25q32dw";
                spi-max-frequency = <25000000>;
                reg = <0>;
              };
      };
};

函數(shù)調(diào)用過程:

a. of_platform_default_populate_init (drivers/of/platform.c) 被調(diào)用到過程:
start_kernel     // init/main.c
    rest_init();
        pid = kernel_thread(kernel_init, NULL, CLONE_FS);
                    kernel_init
                        kernel_init_freeable();
                            do_basic_setup();
                                do_initcalls();
                                    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
                                        do_initcall_level(level);  // 比如 do_initcall_level(3)
                                                                               for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
                                                                                    do_one_initcall(initcall_from_entry(fn));  // 就是調(diào)用"arch_initcall_sync(fn)"中定義的fn函數(shù)

b. of_platform_default_populate_init  (drivers/of/platform.c) 生成platform_device的過程:
of_platform_default_populate_init
    of_platform_default_populate(NULL, NULL, NULL);
        of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
            for_each_child_of_node(root, child) {
                rc = of_platform_bus_create(child, matches, lookup, parent, true);  // 調(diào)用過程看下面
                            dev = of_device_alloc(np, bus_id, parent);   // 根據(jù)device_node節(jié)點(diǎn)的屬性設(shè)置platform_device的resource
                if (rc) {
                    of_node_put(child);
                    break;
                }
            }
            
c. of_platform_bus_create(bus, matches, ...)的調(diào)用過程(處理bus節(jié)點(diǎn)生成platform_devie, 并決定是否處理它的子節(jié)點(diǎn)):
        dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  // 生成bus節(jié)點(diǎn)的platform_device結(jié)構(gòu)體
        if (!dev || !of_match_node(matches, bus))  // 如果bus節(jié)點(diǎn)的compatile屬性不吻合matches成表, 就不處理它的子節(jié)點(diǎn)
            return 0;

        for_each_child_of_node(bus, child) {    // 取出每一個(gè)子節(jié)點(diǎn)
            pr_debug("   create child: %pOF\n", child);
            rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);   // 處理它的子節(jié)點(diǎn), of_platform_bus_create是一個(gè)遞歸調(diào)用
            if (rc) {
                of_node_put(child);
                break;
            }
        }
        
d. I2C總線節(jié)點(diǎn)的處理過程:
   /i2c節(jié)點(diǎn)一般表示i2c控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
   platform_driver的probe函數(shù)中會(huì)調(diào)用i2c_add_numbered_adapter:
   
   i2c_add_numbered_adapter   // drivers/i2c/i2c-core-base.c
        __i2c_add_numbered_adapter
            i2c_register_adapter
                of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c
                    for_each_available_child_of_node(bus, node) {
                        client = of_i2c_register_device(adap, node);
                                        client = i2c_new_device(adap, &info);   // 設(shè)備樹中的i2c子節(jié)點(diǎn)被轉(zhuǎn)換為i2c_client
                    }
                    
e. SPI總線節(jié)點(diǎn)的處理過程:
   /spi節(jié)點(diǎn)一般表示spi控制器, 它會(huì)被轉(zhuǎn)換為platform_device, 在內(nèi)核中有對(duì)應(yīng)的platform_driver;
   platform_driver的probe函數(shù)中會(huì)調(diào)用spi_register_master, 即spi_register_controller:
   
   spi_register_controller        // drivers/spi/spi.c
        of_register_spi_devices   // drivers/spi/spi.c
            for_each_available_child_of_node(ctlr->dev.of_node, nc) {
                spi = of_register_spi_device(ctlr, nc);  // 設(shè)備樹中的spi子節(jié)點(diǎn)被轉(zhuǎn)換為spi_device
                                spi = spi_alloc_device(ctlr);
                                rc = of_spi_parse_dt(ctlr, spi, nc);
                                rc = spi_add_device(spi);
            }

最后,便是platform_device跟platform_driver的匹配過程了,具體的過程分析可移步:
1. 驅(qū)動(dòng)程序分層分離概念-總線設(shè)備驅(qū)動(dòng)模型。
2. 字符設(shè)備驅(qū)動(dòng)-總線設(shè)備驅(qū)動(dòng)模型寫法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容