1. 程式人生 > >Device Tree(三):程式碼分析

Device Tree(三):程式碼分析

一、前言

Device Tree總共有三篇,分別是:

1、為何要引入Device Tree,這個機制是用來解決什麼問題的?(請參考引入Device Tree的原因

2、Device Tree的基礎概念(請參考DT基礎概念

3、ARM linux中和Device Tree相關的程式碼分析(這是本文的主題)

本文主要內容是:以Device Tree相關的資料流分析為索引,對ARM linux kernel的程式碼進行解析。主要的資料流包括:

1、初始化流程。也就是掃描dtb並將其轉換成Device Tree Structure。

2、傳遞執行時引數傳遞以及platform的識別流程分析

3、如何將Device Tree Structure併入linux kernel的裝置驅動模型。

注:本文中的linux kernel使用的是3.14版本。

二、如何通過Device Tree完成執行時引數傳遞以及platform的識別功能?

1、彙編部分的程式碼分析

linux/arch/arm/kernel/head.S檔案定義了bootloader和kernel的引數傳遞要求:

MMU = off, D-cache = off, I-cache = dont care, r0 = 0, r1 = machine nr, r2 = atags or dtb pointer.

目前的kernel支援舊的tag list的方式,同時也支援device tree的方式。r2可能是device tree binary file的指標(bootloader要傳遞給核心之前要copy到memory中),也可以能是tag list的指標。在ARM的彙編部分的啟動程式碼中(主要是head.S和head-common.S),machine type ID和指向DTB或者atags的指標被儲存在變數__machine_arch_type和__atags_pointer中,這麼做是為了後續c程式碼進行處理。

2、和device tree相關的setup_arch程式碼分析

具體的c程式碼都是在setup_arch中處理,這個函式是一個總的入口點。具體程式碼如下(刪除了部分無關程式碼):

void __init setup_arch(char **cmdline_p) 

    const struct machine_desc *mdesc;

……

    mdesc = setup_machine_fdt(__atags_pointer); 
    if (!mdesc) 
        mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); 
    machine_desc = mdesc; 
    machine_name = mdesc->name;

…… 
}

對於如何確定HW platform這個問題,舊的方法是靜態定義若干的machine描述符(struct machine_desc ),在啟動過程中,通過machine type ID作為索引,在這些靜態定義的machine描述符中掃描,找到那個ID匹配的描述符。在新的核心中,首先使用setup_machine_fdt來setup machine描述符,如果返回NULL,才使用傳統的方法setup_machine_tags來setup machine描述符。傳統的方法需要給出__machine_arch_type(bootloader通過r1暫存器傳遞給kernel的)和tag list的地址(用來進行tag parse)。__machine_arch_type用來尋找machine描述符;tag list用於執行時引數的傳遞。隨著核心的不斷髮展,相信有一天linux kernel會完全拋棄tag list的機制。

3、匹配platform(machine描述符)

setup_machine_fdt函式的功能就是根據Device Tree的資訊,找到最適合的machine描述符。具體程式碼如下:

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) 

    const struct machine_desc *mdesc, *mdesc_best = NULL;

    if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) 
        return NULL;

    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

    if (!mdesc) {  
        出錯處理 
    }

    /* Change machine number to match the mdesc we're using */ 
    __machine_arch_type = mdesc->nr;

    return mdesc; 
}

early_init_dt_scan函式有兩個功能,一個是為後續的DTB scan進行準備工作,另外一個是執行時引數傳遞。具體請參考下面一個section的描述。

of_flat_dt_match_machine是在machine描述符的列表中scan,找到最合適的那個machine描述符。我們首先看如何組成machine描述符的列表。和傳統的方法類似,也是靜態定義的。DT_MACHINE_START和MACHINE_END用來定義一個machine描述符。編譯的時候,compiler會把這些machine descriptor放到一個特殊的段中(.arch.info.init),形成machine描述符的列表。machine描述符用下面的資料結構來標識(刪除了不相關的member):

struct machine_desc { 
    unsigned int        nr;        /* architecture number    */ 
    const char *const     *dt_compat;    /* array of device tree 'compatible' strings    */

……

   };

nr成員就是過去使用的machine type ID。核心machine描述符的table有若干個entry,每個都有自己的ID。bootloader傳遞了machine type ID,指明使用哪一個machine描述符。目前匹配machine描述符使用compatible strings,也就是dt_compat成員,這是一個string list,定義了這個machine所支援的列表。在掃描machine描述符列表的時候需要不斷的獲取下一個machine描述符的compatible字串的資訊,具體的程式碼如下:

static const void * __init arch_get_next_mach(const char *const **match) 

    static const struct machine_desc *mdesc = __arch_info_begin; 
    const struct machine_desc *m = mdesc;

    if (m >= __arch_info_end) 
        return NULL;

    mdesc++; 
    *match = m->dt_compat; 
    return m; 
}

__arch_info_begin指向machine描述符列表第一個entry。通過mdesc++不斷的移動machine描述符指標(Note:mdesc是static的)。match返回了該machine描述符的compatible string list。具體匹配的演算法倒是很簡單,就是比較字串而已,一個是root node的compatible字串列表,一個是machine描述符的compatible字串列表,得分最低的(最匹配的)就是我們最終選定的machine type。

4、執行時引數傳遞

執行時引數是在掃描DTB的chosen node時候完成的,具體的動作就是獲取chosen node的bootargs、initrd等屬性的value,並將其儲存在全域性變數(boot_command_line,initrd_start、initrd_end)中。使用tag list方法是類似的,通過分析tag list,獲取相關資訊,儲存在同樣的全域性變數中。具體程式碼位於early_init_dt_scan函式中:

bool __init early_init_dt_scan(void *params) 

    if (!params) 
        return false;

    /* 全域性變數initial_boot_params指向了DTB的header*/ 
    initial_boot_params = params;

    /* 檢查DTB的magic,確認是一個有效的DTB */ 
    if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) { 
        initial_boot_params = NULL; 
        return false; 
    }

    /* 掃描 /chosen node,儲存執行時引數(bootargs)到boot_command_line,此外,還處理initrd相關的property,並儲存在initrd_start和initrd_end這兩個全域性變數中 */ 
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

    /* 掃描根節點,獲取 {size,address}-cells資訊,並儲存在dt_root_size_cells和dt_root_addr_cells全域性變數中 */ 
    of_scan_flat_dt(early_init_dt_scan_root, NULL);

    /* 掃描DTB中的memory node,並把相關資訊儲存在meminfo中,全域性變數meminfo儲存了系統記憶體相關的資訊。*/ 
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);

    return true; 
}

設定meminfo(該全域性變數確定了實體記憶體的佈局)有若干種途徑:

1、通過tag list(tag是ATAG_MEM)傳遞memory bank的資訊。

2、通過command line(可以用tag list,也可以通過DTB)傳遞memory bank的資訊。

3、通過DTB的memory node傳遞memory bank的資訊。

目前當然是推薦使用Device Tree的方式來傳遞實體記憶體佈局資訊。

三、初始化流程

在系統初始化的過程中,我們需要將DTB轉換成節點是device_node的樹狀結構,以便後續方便操作。具體的程式碼位於setup_arch->unflatten_device_tree中。

void __init unflatten_device_tree(void) 

    __unflatten_device_tree(initial_boot_params, &of_allnodes, 
                early_init_dt_alloc_memory_arch);

    /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ 
    of_alias_scan(early_init_dt_alloc_memory_arch); 
}

我們用struct device_node 來抽象裝置樹中的一個節點,具體解釋如下:

struct device_node { 
    const char *name;----------------------device node name 
    const char *type;-----------------------對應device_type的屬性 
    phandle phandle;-----------------------對應該節點的phandle屬性 
    const char *full_name; ----------------從“/”開始的,表示該node的full path

    struct    property *properties;-------------該節點的屬性列表 
    struct    property *deadprops; ----------如果需要刪除某些屬性,kernel並非真的刪除,而是掛入到deadprops的列表 
    struct    device_node *parent;------parent、child以及sibling將所有的device node連線起來 
    struct    device_node *child; 
    struct    device_node *sibling; 
    struct    device_node *next;  --------通過該指標可以獲取相同型別的下一個node 
    struct    device_node *allnext;-------通過該指標可以獲取node global list下一個node 
    struct    proc_dir_entry *pde;--------開放到userspace的proc介面資訊 
    struct    kref kref;-------------該node的reference count 
    unsigned long _flags; 
    void    *data; 
};

unflatten_device_tree函式的主要功能就是掃描DTB,將device node被組織成:

1、global list。全域性變數struct device_node *of_allnodes就是指向裝置樹的global list

2、tree。

這些功能主要是在__unflatten_device_tree函式中實現,具體程式碼如下(去掉一些無關緊要的程式碼):

static void __unflatten_device_tree(struct boot_param_header *blob,---需要掃描的DTB 
                 struct device_node **mynodes,---------global list指標 
                 void * (*dt_alloc)(u64 size, u64 align))------記憶體分配函式 

    unsigned long size; 
    void *start, *mem; 
    struct device_node **allnextp = mynodes;

    此處刪除了health check程式碼,例如檢查DTB header的magic,確認blob的確指向一個DTB。

    /* scan過程分成兩輪,第一輪主要是確定device-tree structure的長度,儲存在size變數中 */ 
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); 
    size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0); 
    size = ALIGN(size, 4);

    /* 初始化的時候,並不是掃描到一個node或者property就分配相應的記憶體,實際上核心是一次性的分配了一大片記憶體,這些記憶體包括了所有的struct device_node、node name、struct property所需要的記憶體。*/ 
    mem = dt_alloc(size + 4, __alignof__(struct device_node)); 
    memset(mem, 0, size);

    *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);   //用來檢驗後面unflattening是否溢位

    /* 這是第二輪的scan,第一次scan是為了得到儲存所有node和property所需要的記憶體size,第二次就是實打實的要構建device node tree了 */ 
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); 
    unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);  
   

    此處略去校驗溢位和校驗OF_DT_END。 
}

具體的scan是在unflatten_dt_node函式中,如果已經清楚地瞭解DTB的結構,其實程式碼很簡單,這裡就不再細述了。

四、如何併入linux kernel的裝置驅動模型

在linux kernel引入統一裝置模型之後,bus、driver和device形成了裝置模型中的鐵三角。在驅動初始化的時候會將代表該driver的一個數據結構(一般是xxx_driver)掛入bus上的driver連結串列。device掛入連結串列分成兩種情況,一種是即插即用型別的bus,在插入一個裝置後,匯流排可以檢測到這個行為並動態分配一個device資料結構(一般是xxx_device,例如usb_device),之後,將該資料結構掛入bus上的device連結串列。bus上掛滿了driver和device,那麼如何讓device遇到“對”的那個driver呢?那麼就要靠緣分了,也就是bus的match函式。

上面是一段導論,我們還是回到Device Tree。導致Device Tree的引入ARM體系結構的程式碼其中一個最重要的原因的太多的靜態定義的表格。例如:一般程式碼中會定義一個static struct platform_device *xxx_devices的靜態陣列,在初始化的時候呼叫platform_add_devices。這些靜態定義的platform_device往往又需要靜態定義各種resource,這導致靜態表格進一步增大。如果ARM linux中不再定義這些表格,那麼一定需要一個轉換的過程,也就是說,系統應該會根據Device tree來動態的增加系統中的platform_device。當然,這個過程並非只是發生在platform bus上(具體可以參考“Platform Device”的裝置),也可能發生在其他的非即插即用的bus上,例如AMBA匯流排、PCI匯流排。一言以蔽之,如果要併入linux kernel的裝置驅動模型,那麼就需要根據device_node的樹狀結構(root是of_allnodes)將一個個的device node掛入到相應的匯流排device連結串列中。只要做到這一點,匯流排機制就會安排device和driver的約會。

當然,也不是所有的device node都會掛入bus上的裝置連結串列,比如cpus node,memory node,choose node等。

1、cpus node的處理

這部分的處理可以參考setup_arch->arm_dt_init_cpu_maps中的程式碼,具體的程式碼如下:

void __init arm_dt_init_cpu_maps(void) 

    scan device node global list,尋找full path是“/cpus”的那個device node。cpus這個device node只是一個容器,其中包括了各個cpu node的定義以及所有cpu node共享的property。 
    cpus = of_find_node_by_path("/cpus");

    for_each_child_of_node(cpus, cpu) {           遍歷cpus的所有的child node 
        u32 hwid;

        if (of_node_cmp(cpu->type, "cpu"))        我們只關心那些device_type是cpu的node 
            continue;


        if (of_property_read_u32(cpu, "reg", &hwid)) {    讀取reg屬性的值並賦值給hwid 
            return; 
        }

        reg的屬性值的8 MSBs必須設定為0,這是ARM CPU binding定義的。 
        if (hwid & ~MPIDR_HWID_BITMASK)   
            return;

        不允許重複的CPU id,那是一個災難性的設定 
        for (j = 0; j < cpuidx; j++) 
            if (WARN(tmp_map[j] == hwid, "Duplicate /cpu reg " 
                             "properties in the DT\n")) 
                return;

陣列tmp_map儲存了系統中所有CPU的MPIDR值(CPU ID值),具體的index的編碼規則是: tmp_map[0]儲存了booting CPU的id值,其餘的CPU的ID值儲存在1~NR_CPUS的位置。 
        if (hwid == mpidr) { 
            i = 0; 
            bootcpu_valid = true; 
        } else { 
            i = cpuidx++; 
        }

        tmp_map[i] = hwid; 
    }

根據DTB中的資訊設定cpu logical map陣列。

    for (i = 0; i < cpuidx; i++) { 
        set_cpu_possible(i, true); 
        cpu_logical_map(i) = tmp_map[i]; 
    } 
}

要理解這部分的內容,需要理解ARM CUPs binding的概念,可以參考linux/Documentation/devicetree/bindings/arm目錄下的CPU.txt檔案的描述。

2、memory的處理

這部分的處理可以參考setup_arch->setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_memory中的程式碼。具體如下:

int __init early_init_dt_scan_memory(unsigned long node, const char *uname, 
                     int depth, void *data) 

    char *type = of_get_flat_dt_prop(node, "device_type", NULL); 獲取device_type屬性值 
    __be32 *reg, *endp; 
    unsigned long l;

    在初始化的時候,我們會對每一個device node都要呼叫該call back函式,因此,我們要過濾掉那些和memory block定義無關的node。和memory block定義有的節點有兩種,一種是node name是[email protected]形態的,另外一種是node中定義了device_type屬性並且其值是memory。 
    if (type == NULL) { 
        if (depth != 1 || strcmp(uname, "[email protected]") != 0) 
            return 0; 
    } else if (strcmp(type, "memory") != 0) 
        return 0;

    獲取memory的起始地址和length的資訊。有兩種屬性和該資訊有關,一個是linux,usable-memory,不過最新的方式還是使用reg屬性。

reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l); 
    if (reg == NULL) 
        reg = of_get_flat_dt_prop(node, "reg", &l); 
    if (reg == NULL) 
        return 0;

    endp = reg + (l / sizeof(__be32));

reg屬性的值是address,size陣列,那麼如何來取出一個個的address/size呢?由於memory node一定是root node的child,因此dt_root_addr_cells(root node的#address-cells屬性值)和dt_root_size_cells(root node的#size-cells屬性值)之和就是address,size陣列的entry size。

    while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { 
        u64 base, size;

        base = dt_mem_next_cell(dt_root_addr_cells, ®); 
        size = dt_mem_next_cell(dt_root_size_cells, ®);

        early_init_dt_add_memory_arch(base, size);  將具體的memory block資訊加入到核心中。 
    }

    return 0; 
}

3、interrupt controller的處理

初始化是通過start_kernel->init_IRQ->machine_desc->init_irq()實現的。我們用S3C2416為例來描述interrupt controller的處理過程。下面是machine描述符的定義。

DT_MACHINE_START(S3C2416_DT, "Samsung S3C2416 (Flattened Device Tree)") 
…… 
    .init_irq    = irqchip_init, 
…… 
MACHINE_END

在driver/irqchip/irq-s3c24xx.c檔案中定義了兩個interrupt controller,如下:

IRQCHIP_DECLARE(s3c2416_irq, "samsung,s3c2416-irq", s3c2416_init_intc_of);

IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

當然,系統中可以定義更多的irqchip,不過具體用哪一個是根據DTB中的interrupt controller node中的compatible屬性確定的。在driver/irqchip/irqchip.c檔案中定義了irqchip_init函式,如下:

void __init irqchip_init(void) 

    of_irq_init(__irqchip_begin); 
}

__irqchip_begin就是所有的irqchip的一個列表,of_irq_init函式是遍歷Device Tree,找到匹配的irqchip。具體的程式碼如下:

void __init of_irq_init(const struct of_device_id *matches) 

    struct device_node *np, *parent = NULL; 
    struct intc_desc *desc, *temp_desc; 
    struct list_head intc_desc_list, intc_parent_list;

    INIT_LIST_HEAD(&intc_desc_list); 
    INIT_LIST_HEAD(&intc_parent_list);

    遍歷所有的node,尋找定義了interrupt-controller屬性的node,如果定義了interrupt-controller屬性則說明該node就是一箇中斷控制器。

    for_each_matching_node(np, matches) { 
        if (!of_find_property(np, "interrupt-controller", NULL) || 
                !of_device_is_available(np)) 
            continue; 
       

分配記憶體並掛入連結串列,當然還有根據interrupt-parent建立controller之間的父子關係。對於interrupt controller,它也可能是一個樹狀的結構。 
        desc = kzalloc(sizeof(*desc), GFP_KERNEL); 
        if (WARN_ON(!desc)) 
            goto err;

        desc->dev = np; 
        desc->interrupt_parent = of_irq_find_parent(np); 
        if (desc->interrupt_parent == np) 
            desc->interrupt_parent = NULL; 
        list_add_tail(&desc->list, &intc_desc_list); 
    }

    正因為interrupt controller被組織成樹狀的結構,因此初始化的順序就需要控制,應該從根節點開始,依次遞進到下一個level的interrupt controller。 
    while (!list_empty(&intc_desc_list)) {  intc_desc_list連結串列中的節點會被一個個的處理,每處理完一個節點就會將該節點刪除,當所有的節點被刪除,整個處理過程也就是結束了。 
         
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { 
            const struct of_device_id *match; 
            int ret; 
            of_irq_init_cb_t irq_init_cb;

            最開始的時候parent變數是NULL,確保第一個被處理的是root interrupt controller。在處理完root node之後,parent變數被設定為root interrupt controller,因此,第二個迴圈中處理的是所有parent是root interrupt controller的child interrupt controller。也就是level 1(如果root是level 0的話)的節點。

            if (desc->interrupt_parent != parent) 
                continue;

            list_del(&desc->list);      -----從連結串列中刪除 
            match = of_match_node(matches, desc->dev);-----匹配並初始化 
            if (WARN(!match->data,----------match->data是初始化函式 
                "of_irq_init: no init function for %s\n", 
                match->compatible)) { 
                kfree(desc); 
                continue; 
            }

            irq_init_cb = (of_irq_init_cb_t)match->data; 
            ret = irq_init_cb(desc->dev, desc->interrupt_parent);-----執行初始化函式 
            if (ret) { 
                kfree(desc); 
                continue; 
            }

           處理完的節點放入intc_parent_list連結串列,後面會用到 
            list_add_tail(&desc->list, &intc_parent_list); 
        }

        對於level 0,只有一個root interrupt controller,對於level 1,可能有若干個interrupt controller,因此要遍歷這些parent interrupt controller,以便處理下一個level的child node。 
        desc = list_first_entry_or_null(&intc_parent_list, 
                        typeof(*desc), list); 
        if (!desc) { 
            pr_err("of_irq_init: children remain, but no parents\n"); 
            break; 
        } 
        list_del(&desc->list); 
        parent = desc->dev; 
        kfree(desc); 
    }

    list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { 
        list_del(&desc->list); 
        kfree(desc); 
    } 
err: 
    list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { 
        list_del(&desc->list); 
        kfree(desc); 
    } 
}

只有該node中有interrupt-controller這個屬性定義,那麼linux kernel就會分配一個interrupt controller的描述符(struct intc_desc)並掛入佇列。通過interrupt-parent屬性,可以確定各個interrupt controller的層次關係。在scan了所有的Device Tree中的interrupt controller的定義之後,系統開始匹配過程。一旦匹配到了interrupt chip列表中的項次後,就會呼叫相應的初始化函式。如果CPU是S3C2416的話,匹配到的是irqchip的初始化函式是s3c2416_init_intc_of。

OK,我們已經通過compatible屬性找到了適合的interrupt controller,那麼如何解析reg屬性呢?我們知道,對於s3c2416的interrupt controller而言,其#interrupt-cells的屬性值是4,定義為。每個域的解釋如下:

(1)ctrl_num表示使用哪一種型別的interrupt controller,其值的解釋如下:

      - 0 ... main controller 
      - 1 ... sub controller 
      - 2 ... second main controller

(2)parent_irq。對於sub controller,parent_irq標識了其在main controller的bit position。

(3)ctrl_irq標識了在controller中的bit位置。

(4)type標識了該中斷的trigger type,例如:上升沿觸發還是電平觸發。

為了更順暢的描述後續的程式碼,我需要簡單的介紹2416的中斷控制器,其block diagram如下:

2416intc

53個Samsung2416的中斷源被分成兩種型別,一種是需要sub暫存器進行控制的,例如DMA,系統中的8個DMA中斷是通過兩級識別的,先在SRCPND暫存器中得到是DMA中斷的資訊,具體是哪一個channel的DMA中斷需要繼續查詢SUBSRC暫存器。那些不需要sub暫存器進行控制的,例如timer,5個timer的中斷可以直接從SRCPND中得到。 
中斷MASK暫存器可以控制產生的中斷是否要報告給CPU,當一箇中斷被mask的時候,雖然SRCPND暫存器中,硬體會set該bit,但是不會影響到INTPND暫存器,從而不會向CPU報告該中斷。對於SUBMASK暫存器,如果該bit被set,也就是該sub中斷被mask了,那麼即便產生了對應的sub中斷,也不會修改SRCPND暫存器的內容,只是修改SUBSRCPND中暫存器的內容。

不過隨著硬體的演化,更多的HW block加入到SOC中,這使得中斷源不夠用了,因此中斷暫存器又被分成兩個group,一個是group 1(開始地址是0X4A000000,也就是main controller了),另外一個是group2(開始地址是0X4A000040,叫做second main controller)。group 1中的sub暫存器的起始地址是0X4A000018(也就是sub controller)。

瞭解了上面的內容後,下面的定義就比較好理解了:

static struct s3c24xx_irq_of_ctrl s3c2416_ctrl[] = { 
    { 
        .name = "intc", -----------main controller 
        .offset = 0, 
    }, { 
        .name = "subintc", ---------sub controller 
        .offset = 0x18, 
        .parent = &s3c_intc[0], 
    }, { 
        .name = "intc2", ----------second main controller 
        .offset = 0x40, 
    } 
};

對於s3c2416而言,irqchip的初始化函式是s3c2416_init_intc_of,s3c2416_ctrl作為引數傳遞給了s3c_init_intc_of,大部分的處理都是在s3c_init_intc_of函式中完成的,由於這個函式和中斷子系統非常相關,這裡就不詳述了,後續會有一份專門的文件描述之。

4、GPIO controller的處理

暫不描述,後續會有一份專門的文件描述GPIO sub system。

5、machine初始化

machine初始化的程式碼可以沿著start_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup->do_initcalls路徑尋找。在do_initcalls函式中,kernel會依次執行各個initcall函式,在這個過程中,會呼叫customize_machine,具體如下:

static int __init customize_machine(void) 


    if (machine_desc->init_machine) 
        machine_desc->init_machine(); 
    else 
        of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); 

    return 0; 

arch_initcall(customize_machine);

在這個函式中,一般會呼叫machine描述符中的init_machine callback函式來把各種Device Tree中定義各個裝置節點加入到系統。如果machine描述符中沒有定義init_machine函式,那麼直接呼叫of_platform_populate把所有的platform device加入到kernel中。對於s3c2416,其machine描述符中的init_machine callback函式就是s3c2416_dt_machine_init,程式碼如下:

static void __init s3c2416_dt_machine_init(void) 

    of_platform_populate(NULL, --------傳入NULL引數表示從root node開始scan

of_default_bus_match_table, s3c2416_auxdata_lookup, NULL);

    s3c_pm_init(); --------power management相關的初始化 
}

由此可見,最終生成platform device的程式碼來自of_platform_populate函式。該函式的邏輯比較簡單,遍歷device node global list中所有的node,並呼叫of_platform_bus_create處理,of_platform_bus_create函式程式碼如下:

static int of_platform_bus_create(struct device_node *bus,-------------要建立的那個device node 
                  const struct of_device_id *matches,-------要匹配的list 
                  const struct of_dev_auxdata *lookup,------附屬資料 
                  struct device *parent, bool strict)---------------parent指向父節點。strict是否要求完全匹配 

    const struct of_dev_auxdata *auxdata; 
    struct device_node *child; 
    struct platform_device *dev; 
    const char *bus_id = NULL; 
    void *platform_data = NULL; 
    int rc = 0;

刪除確保device node有compatible屬性的程式碼。

    auxdata = of_dev_lookup(lookup, bus);  在傳入的lookup table尋找和該device node匹配的附加資料 
    if (auxdata) { 
        bus_id = auxdata->name;-----------------如果找到,那麼就用附加資料中的靜態定義的內容 
        platform_data = auxdata->platform_data; 
    }

ARM公司提供了CPU core,除此之外,它設計了AMBA的匯流排來連線SOC內的各個block。符合這個匯流排標準的SOC上的外設叫做ARM Primecell Peripherals。如果一個device node的compatible屬性值是arm,primecell的話,可以呼叫of_amba_device_create來向amba總線上增加一個amba device。

    if (of_device_is_compatible(bus, "arm,primecell")) { 
        of_amba_device_create(bus, bus_id, platform_data, parent); 
        return 0; 
    }

    如果不是ARM Primecell Peripherals,那麼我們就需要向platform bus上增加一個platform device了

    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); 
    if (!dev || !of_match_node(matches, bus)) 
        return 0;

    一個device node可能是一個橋裝置,因此要重複呼叫of_platform_bus_create來把所有的device node處理掉。

    for_each_child_of_node(bus, child) { 
        pr_debug("   create child: %s\n", child->full_name); 
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); 
        if (rc) { 
            of_node_put(child); 
            break; 
        } 
    } 
    return rc; 
}

具體增加platform device的程式碼在of_platform_device_create_pdata中,程式碼如下:

static struct platform_device *of_platform_device_create_pdata( 
                    struct device_node *np, 
                    const char *bus_id, 
                    void *platform_data, 
                    struct device *parent) 

    struct platform_device *dev;

    if (!of_device_is_available(np))---------check status屬性,確保是enable或者OK的。 
        return NULL;

    of_device_alloc除了分配struct platform_device的記憶體,還分配了該platform device需要的resource的記憶體(參考struct platform_device 中的resource成員)。當然,這就需要解析該device node的interrupt資源以及memory address資源。

    dev = of_device_alloc(np, bus_id, parent); 
    if (!dev) 
        return NULL;

設定platform_device 中的其他成員 
    dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); 
    if (!dev->dev.dma_mask) 
        dev->dev.dma_mask = &dev->dev.coherent_dma_mask; 
    dev->dev.bus = &platform_bus_type; 
    dev->dev.platform_data = platform_data;

    if (of_device_add(dev) != 0) {------------------把這個platform device加入統一裝置模型系統中 
        platform_device_put(dev); 
        return NULL; 
    }

    return dev; 
}

轉自:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html