Exynos4412 核心移植(六)—— 裝置樹解析
阿新 • • 發佈:2019-01-09
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); } }