1. 程式人生 > >Exynos4412 核心移植(六)—— 裝置樹解析

Exynos4412 核心移植(六)—— 裝置樹解析

void __init unflatten_device_tree(void)
{
    //解析裝置樹,將所有的裝置節點鏈入全域性連結串列    of_allnodes中
    __unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);
     
    //設定核心輸出終端,以及遍歷“/aliases”節點下的所有的屬性,掛入相應連結串列
    of_alias_scan(early_init_dt_alloc_memory_arch);
}

static void __unflatten_device_tree(struct boot_param_header *blob,
                                    struct device_node **mynodes,
                                    void * (*dt_alloc)(u64 size, u64 align))
{
    unsigned long size;
    void *start, *mem;
    struct device_node **allnextp = mynodes;
    
    pr_debug(" -> unflatten_device_tree()\n");
     
    if (!blob) {
        pr_debug("No device tree pointer\n");
        return;
    }
     
    pr_debug("Unflattening device tree:\n");
    pr_debug("magic: %08x\n", be32_to_cpu(blob->magic));
    pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize));
    pr_debug("version: %08x\n", be32_to_cpu(blob->version));
    
    //檢查裝置樹magic
    if (be32_to_cpu(blob->magic) != OF_DT_HEADER) {
        pr_err("Invalid device tree blob header\n");
        return;
    }
    
    //找到裝置樹的裝置節點起始地址
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
    //第一次呼叫mem傳0,allnextpp傳NULL,實際上是為了計算整個裝置樹所要的空間
    size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
    size = ALIGN(size, 4);//4位元組對齊
     
    pr_debug(" size is %lx, allocating...\n", size);
     
    //呼叫early_init_dt_alloc_memory_arch函式,為裝置樹分配記憶體空間
    mem = dt_alloc(size + 4, __alignof__(struct device_node));
    memset(mem, 0, size);
    
    //裝置樹結束處賦值0xdeadbeef,為了後邊檢查是否有資料溢位
    *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); 
    pr_debug(" unflattening %p...\n", mem);
     
    //再次獲取裝置樹的裝置節點起始地址
    start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
    //mem為裝置樹分配的記憶體空間,allnextp指向全域性變數of_allnodes,生成整個裝置樹
    unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
    if (be32_to_cpup(start) != OF_DT_END)
        pr_warning("Weird tag at end of tree: %08x\n", be32_to_cpup(start));
    if (be32_to_cpup(mem + size) != 0xdeadbeef)
        pr_warning("End of tree marker overwritten: %08x\n",be32_to_cpup(mem + size));
    *allnextp = NULL;
     
    pr_debug(" <- unflatten_device_tree()\n");
}

static void * unflatten_dt_node(struct boot_param_header *blob,
                                void *mem,void **p,
                                struct device_node *dad,
                                struct device_node ***allnextpp,
                                unsigned long fpsize)
{
    struct device_node *np;
    struct property *pp, **prev_pp = NULL;
    char *pathp;
    u32 tag;
    unsigned int l, allocl;
    int has_name = 0;
    int new_format = 0;
    
    //*p指向裝置樹的裝置塊起始地址
    tag = be32_to_cpup(*p);
    //每個有孩子的裝置節點,其tag一定是OF_DT_BEGIN_NODE
    if (tag != OF_DT_BEGIN_NODE) {
        pr_err("Weird tag at start of node: %x\n", tag);
        return mem;
    }

    *p += 4;//地址+4,跳過tag,這樣指向節點的名稱或者節點路徑名
    pathp = *p;//獲得節點名或者節點路徑名
    l = allocl = strlen(pathp) + 1;//該節點名稱的長度
    *p = PTR_ALIGN(*p + l, 4);//地址對齊後,*p指向該節點屬性的地址
    
    //如果是節點名則進入,若是節點路徑名則(*pathp) == '/'
    if ((*pathp) != '/') {
        new_format = 1;
        if (fpsize == 0) {//fpsize=0
            fpsize = 1;
            allocl = 2;
            l = 1;
            *pathp = '\0';
        } else {
            fpsize += l;//代分配的長度=本節點名稱長度+父親節點絕對路徑的長度
            allocl = fpsize;
        }
    }
    
    //分配一個裝置節點device_node結構,*mem記錄分配了多大空間,最終會累加計算出該裝置樹總共分配的空間大小
    np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));

    //第一次呼叫unflatten_dt_node時,allnextpp=NULL
    //第一次呼叫unflatten_dt_node時,allnextpp指向全域性變數of_allnodes的地址
    if (allnextpp) {
        char *fn;
        //full_name儲存完整的節點名,即包括各級父節點的名稱
        np->full_name = fn = ((char *)np) + sizeof(*np);
        //若new_format=1,表示pathp儲存的是節點名,而不是節點路徑名,所以需要加上父節點的name
        if (new_format) {
            if (dad && dad->parent) {
                strcpy(fn, dad->full_name);//把父親節點絕對路徑先拷貝
                fn += strlen(fn);
            }
            *(fn++) = '/';
        }
        memcpy(fn, pathp, l);//拷貝本節點的名稱
    
        //prev_pp指向節點的屬性連結串列
        prev_pp = &np->properties;

        //當前節點插入全域性連結串列of_allnodes
        **allnextpp = np;
        *allnextpp = &np->allnext;

        //若父親節點不為空,則設定該節點的parent
        if (dad != NULL) {
            np->parent = dad;//指向父親節點
            if (dad->next == NULL)//第一個孩子
                dad->child = np;//child指向第一個孩子
            else
                dad->next->sibling = np;//把np插入next,這樣孩子節點形成連結串列
            dad->next = np;
        }
        kref_init(&np->kref);
    }

    //分析該節點的屬性
    while (1) {
        u32 sz, noff;
        char *pname;
     
        //前邊已經將*p移到指向節點屬性的地址處,取出屬性標識
        tag = be32_to_cpup(*p);
        //空屬性,則跳過
        if (tag == OF_DT_NOP) {
            *p += 4;
            continue;
        }
        //tag不是屬性則退出,對於有孩子節點退出時為OF_DT_BEGIN_NODE,對於葉子節點退出時為OF_DT_END_NODE
        if (tag != OF_DT_PROP)
            break;
        //地址加4,跳過tag
        *p += 4;
        //獲得屬性值的大小,是以為佔多少整形指標計算的
        sz = be32_to_cpup(*p);
        //獲取屬性名稱在節點的字串塊中的偏移
        noff = be32_to_cpup(*p + 4);
        //地址加8,跳過屬性值的大小和屬性名稱在節點的字串塊中的偏移
        *p += 8;
        //地址對齊後,*P指向屬性值所在的地址
        if (be32_to_cpu(blob->version) < 0x10)
            *p = PTR_ALIGN(*p, sz >= 8 ? 8 : 4);
        
        //從節點的字串塊中noff偏移處,得到該屬性的name
        pname = of_fdt_get_string(blob, noff);
        if (pname == NULL) {
            pr_info("Can't find property name in list !\n");
            break;
        }

        //如果有名稱為name的屬性,表示變數has_name為1
        if (strcmp(pname, "name") == 0)
            has_name = 1;
        //計算該屬性name的大小
        l = strlen(pname) + 1;

        //為該屬性分配一個屬性結構,即struct property,
        //*mem記錄分配了多大空間,最終會累加計算出該裝置樹總共分配的空間大小
        pp = unflatten_dt_alloc(&mem, sizeof(struct property),__alignof__(struct property));

        //第一次呼叫unflatten_dt_node時,allnextpp=NULL
        //第一次呼叫unflatten_dt_node時,allnextpp指向全域性變數of_allnodes的地址
        if (allnextpp) {
            if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) {
                if (np->phandle == 0)
                    np->phandle = be32_to_cpup((__be32*)*p);
            }
            if (strcmp(pname, "ibm,phandle") == 0)
                np->phandle = be32_to_cpup((__be32 *)*p);
            pp->name = pname;//屬性名
            pp->length = sz;//屬性值長度
            pp->value = *p;//屬性值

            //屬性插入該節點的屬性連結串列np->properties
            *prev_pp = pp;
            prev_pp = &pp->next;
        }
        *p = PTR_ALIGN((*p) + sz, 4);//指向下一個屬性
    }
    //至此遍歷完該節點的所有屬性

    //如果該節點沒有"name"的屬性,則為該節點生成一個name屬性,插入該節點的屬性連結串列
    if (!has_name) {
        char *p1 = pathp, *ps = pathp, *pa = NULL;
        int sz;
     
        while (*p1) {
            if ((*p1) == '@')
                pa = p1;
            if ((*p1) == '/')
                ps = p1 + 1;
                p1++;
        }
        if (pa < ps)
            pa = p1;
        sz = (pa - ps) + 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,__alignof__(struct property));
        if (allnextpp) {
            pp->name = "name";
            pp->length = sz;
            pp->value = pp + 1;
            *prev_pp = pp;
            prev_pp = &pp->next;
            memcpy(pp->value, ps, sz - 1);
            ((char *)pp->value)[sz - 1] = 0;
            pr_debug("fixed up name for %s -> %s\n", pathp,(char *)pp->value);
        }
    }

    //若設定了allnextpp指標
    if (allnextpp) {
        *prev_pp = NULL;
        //設定節點的名稱
        np->name = of_get_property(np, "name", NULL);
        //設定該節點對應的裝置型別
        np->type = of_get_property(np, "device_type", NULL);
    
        if (!np->name)
            np->name = "<NULL>";
        if (!np->type)
            np->type = "<NULL>";
    }

    //前邊在遍歷屬性時,tag不是屬性則退出
    //對於有孩子節點退出時tag為OF_DT_BEGIN_NODE,對於葉子節點退出時tag為OF_DT_END_NODE
    while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {
        //空屬性則指向下個屬性
        if (tag == OF_DT_NOP)
            *p += 4;
        else
            //OF_DT_BEGIN_NODE則表明其還有子節點,所以遞迴分析其子節點
            mem = unflatten_dt_node(blob, mem, p, np, allnextpp,fpsize);
        tag = be32_to_cpup(*p);
    }

    //對於葉子節點或者分析完成
    if (tag != OF_DT_END_NODE) {
        pr_err("Weird tag at end of node: %x\n", tag);
        return mem;
    }
    *p += 4;
    //mem返回整個裝置樹所分配的記憶體大小,即裝置樹佔的記憶體空間
    return mem;
}


//從mem分配記憶體空間,*mem記錄分配了多大空間
static void *unflatten_dt_alloc(void **mem, unsigned long size,unsigned long align)
{
    void *res;
     
    *mem = PTR_ALIGN(*mem, align);
    res = *mem;
    *mem += size;
        
    return res;
}

//一個特定的節點通常是以完整的路徑來引用,比如/external-bus/
[email protected]
,0,不過當一個使用者真的想知道“哪個裝置是eth0”時,這將會很繁瑣。aliases節點可以用來為一個完整的裝置路徑分配一個短的別名。比如: //aliases { // serial0 = &uart0; // serial1 = &uart1; // serial2 = &uart2; // serial3 = &uart3; // ethernet0 = ð0; // serial0 = &serial0; //}; //當需要為裝置指定一個標示符時,作業系統歡迎大家使用別名。 //設定核心輸出終端,以及遍歷“/aliases”節點下的所有的屬性,掛入相應連結串列 void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) { struct property *pp; //根據全域性的device_node結構的連結串列of_allnodes,查詢節點名為“/chosen”或者“/
[email protected]
”的節點,賦值給全域性變數of_chosen of_chosen = of_find_node_by_path("/chosen"); if (of_chosen == NULL) of_chosen = of_find_node_by_path("/[email protected]"); //找到的話,則在該節點查詢"linux,stdout-path" 屬性 //"linux,stdout-path"的屬性值,常常為標準終端裝置的節點路徑名,核心會以此作為預設終端 if (of_chosen) { const char *name; //返回屬性"linux,stdout-path"的屬性值 name = of_get_property(of_chosen, "linux,stdout-path", NULL); //根據屬性值查詢裝置節點device_node,即核心預設終端的裝置節點,賦值給全域性變數of_stdout if (name) of_stdout = of_find_node_by_path(name); } //據全域性連結串列of_allnodes,查詢節點名為“/aliases”的節點,賦值給全域性變數of_aliases of_aliases = of_find_node_by_path("/aliases"); if (!of_aliases) return; //遍歷“/aliases”節點下的所有的屬性 for_each_property_of_node(of_aliases, pp) { const char *start = pp->name;//屬性名 const char *end = start + strlen(start);//屬性名結尾 struct device_node *np; struct alias_prop *ap; int id, len; //跳過"name"、"phandle"和"linux,phandle"的屬性 if (!strcmp(pp->name, "name") || !strcmp(pp->name, "phandle") || !strcmp(pp->name, "linux,phandle")) continue; //根據屬性值找到對應的裝置節點 np = of_find_node_by_path(pp->value); if (!np) continue; //去除屬性名中結尾的數字,即裝置id while (isdigit(*(end-1)) && end > start) end--; //len為屬性名去掉結尾數字序號的長度 len = end - start; //此時end指向屬性名中結尾的數字,即開始時start指向“&uart0”,end指向字串結尾。 //經過上步操作,start仍指向“&uart0”字串開始處,而end指向字元‘0’。 //將end字串轉化為10進位制數,賦值給id,作為裝置的id號 if (kstrtoint(end, 10, &id) < 0) continue; //分配alias_prop結構 ap = dt_alloc(sizeof(*ap) + len + 1, 4); if (!ap) continue; memset(ap, 0, sizeof(*ap) + len + 1); ap->alias = start; //將該裝置的aliases指向對應的device_node,並且鏈入aliases_lookup連結串列中 of_alias_add(ap, np, id, start, len); } }