轉自:http://blog.csdn.net/Tommy_wxie/article/details/42806457
//Basedon Linux v3.14 source code Linux設備樹機制(Device Tree) 一、描述 ARM Device Tree起源於OpenFirmware (OF),在過去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代碼,相當多數的代碼只是在描述板級細節,而這些板級細節對於內核來講,不過是垃圾,如板上的platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data。為了改變這種局面,Linux社區的大牛們參考了PowerPC等體系架構中使用的Flattened Device Tree(FDT),也采用了Device Tree結構,許多硬件的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗余編碼。 Device Tree是一種描述硬件的數據結構,由一系列被命名的結點(node)和屬性(property)組成,而結點本身可包含子結點。所謂屬性,其實就是成對出現的name和value。在Device Tree中,可描述的信息包括(原先這些信息大多被hard code到kernel中):CPU的數量和類別,內存基地址和大小,總線和橋,外設連接,中斷控制器和中斷使用情況,GPIO控制器和GPIO使用情況,Clock控制器和Clock使用情況。 通常由.dts文件以文本方式對系統設備樹進行描述,經過Device Tree Compiler(dtc)將dts文件轉換成二進制文件binary device tree blob(dtb),.dtb文件可由Linux內核解析,有了device tree就可以在不改動Linux內核的情況下,對不同的平台實現無差異的支持,只需更換相應的dts文件,即可滿足。 二、相關結構體 1.U-Boot需要將設備樹在內存中的存儲地址傳給內核。該樹主要由三大部分組成:頭(Header)、結構塊(Structure block)、字符串塊(Strings block)。設備樹在內存中的存儲布局圖。 ------------------------------ base ->| struct boot_param_header | ------------------------------ | (alignment gap)(*)| ------------------------------ | memory reserve map | ------------------------------ | (alignment gap)| ------------------------------ | | | device-tree structure| | | ------------------------------ | (alignment gap)| ------------------------------ | | | device-tree strings| | | ----->------------------------------ | | ---(base + totalsize) 1.1 頭(header) 頭主要描述設備樹的一些基本信息,例如設備樹大小,結構塊偏移地址,字符串塊偏移地址等。偏移地址是相對於設備樹頭的起始地址計算的。 struct boot_param_header { __be32 magic; //設備樹魔數,固定為0xd00dfeed __be32 totalsize; //整個設備樹的大小 __be32 off_dt_struct; //保存結構塊在整個設備樹中的偏移 __be32 off_dt_strings; //保存的字符串塊在設備樹中的偏移 __be32 off_mem_rsvmap; //保留內存區,該區保留了不能被內核動態分配的內存空間 __be32 version; //設備樹版本 __be32 last_comp_version; //向下兼容版本號 __be32 boot_cpuid_phys; //為在多核處理器中用於啟動的主cpu的物理id __be32 dt_strings_size; //字符串塊大小 __be32 dt_struct_size; //結構塊大小 }; 1.2 結構塊(struct block) 設備樹結構塊是一個線性化的結構體,是設備樹的主體,以節點node的形式保存了目標單板上的設備信息。 在結構塊中以宏OF_DT_BEGIN_NODE標志一個節點的開始,以宏OF_DT_END_NODE標識一個節點的結束,整個結構塊以宏OF_DT_END結束。一個節點主要由以下幾部分組成。 (1)節點開始標志:一般為OF_DT_BEGIN_NODE。 (2)節點路徑或者節點的單元名(ersion<3以節點路徑表示,version>=0x10以節點單元名表示) (3)填充字段(對齊到四字節) (4)節點屬性。每個屬性以宏OF_DT_PROP開始,后面依次為屬性值的字節長度(4字節)、屬性名稱在字符串塊中的偏移量(4字節)、屬性值和填充(對齊到四字節)。 (5)如果存在子節點,則定義子節點。 (6)節點結束標志OF_DT_END_NODE。 1.3 字符串塊 通過節點的定義知道節點都有若干屬性,而不同的節點的屬性又有大量相同的屬性名稱,因此將這些屬性名稱提取出一張表,當節點需要應用某個屬性名稱時直接在屬性名字段保存該屬性名稱在字符串塊中的偏移量。 1.4 設備樹源碼 DTS 表示 設備樹源碼文件(.dts)以可讀可編輯的文本形式描述系統硬件配置設備樹,支持 C/C++方式的注釋,該結構有一個唯一的根節點“/”,每個節點都有自己的名字並可以包含多個子節點。設備樹的數據格式遵循了 Open Firmware IEEE standard 1275。這個設備樹中有很多節點,每個節點都指定了節點單元名稱。每一個屬性后面都給出相應的值。以雙引號引出的內容為 ASCII 字符串,以尖括號給出的是 32 位的16進制值。這個樹結構是啟動 Linux 內核所需節點和屬性簡化后的集合,包括了根節點的基本模式信息、CPU 和物理內存布局,它還包括通過/chosen 節點傳遞給內核的命令行參數信息。 1.5 machine_desc結構 內核提供了一個重要的結構體struct machine_desc ,這個結構體在內核移植中起到相當重要的作用,內核通過machine_desc結構體來控制系統體系架構相關部分的初始化。machine_desc結構體通過MACHINE_START宏來初始化,在代碼中, 通過在start_kernel->setup_arch中調用setup_machine_fdt來獲取。 struct machine_desc { unsigned int nr;/* architecture number*/ const char *name; /* architecture name*/ unsigned long atag_offset; /* tagged list (relative) */ const char *const *dt_compat;/* array of device tree* 'compatible' strings */ unsigned int nr_irqs;/* number of IRQs*/ #ifdef CONFIG_ZONE_DMA phys_addr_t dma_zone_size; /* size of DMA-able area*/ #endif unsigned int video_start;/* start of video RAM*/ unsigned int video_end;/* end of video RAM */ unsigned char reserve_lp0 :1;/* never has lp0*/ unsigned char reserve_lp1 :1;/* never has lp1*/ unsigned char reserve_lp2 :1;/* never has lp2*/ enum reboot_mode reboot_mode; /* default restart mode */ struct smp_operations *smp;/* SMP operations*/ bool (*smp_init)(void); void (*fixup)(struct tag*, char **,struct meminfo*); void (*init_meminfo)(void); void (*reserve)(void);/* reserve mem blocks */ void (*map_io)(void);/* IO mapping function */ void (*init_early)(void); void (*init_irq)(void); void (*init_time)(void); void (*init_machine)(void); void (*init_late)(void); #ifdef CONFIG_MULTI_IRQ_HANDLER void (*handle_irq)(struct pt_regs*); #endif void (*restart)(enum reboot_mode,const char *); }; 1.6 設備節點結構體 struct device_node { const char *name; //設備name const char *type; //設備類型 phandle phandle; const char *full_name; //設備全稱,包括父設備名 struct property *properties; //設備屬性鏈表 struct property *deadprops; //removed properties struct device_node *parent;//指向父節點 struct device_node *child;//指向子節點 struct device_node *sibling;//指向兄弟節點 struct device_node *next;//相同設備類型的下一個節點 struct device_node *allnext;//nextin list of all nodes struct proc_dir_entry *pde;//該節點對應的proc struct kref kref; 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 }; 1.7 屬性結構體 struct property { char *name; //屬性名 int length; //屬性值長度 void *value; //屬性值 struct property *next;//指向下一個屬性 unsigned long _flags; //標志 unsigned int unique_id; }; 三、設備樹初始化及解析 分析Linux內核的源碼,可以看到其對扁平設備樹的解析流程如下: (1)首先在內核入口處將從u-boot傳遞過來的鏡像基地址。 (2)通過調用early_init_dt_scan()函數來獲取內核前期初始化所需的bootargs,cmd_line等系統引導參數。 (3)根據bootargs,cmd_line等系統引導參數進入start_kernel()函數,進行內核的第二階段初始化。 (4)調用unflatten_device_tree()函數來解析dtb文件,構建一個由device_node結構連接而成的單項鏈表,並使用全局變量of_allnodes指針來保存這個鏈表的頭指針。 (5)內核調用OF提供的API函數獲取of_allnodes鏈表信息來初始化內核其他子系統、設備等。 //kernel 初始化的代碼(init/main.c) asmlinkage void __init start_kernel(void) { ... //這個setup_arch就是各個架構自己的設置函數,哪個參與了編譯就調用哪個,arm架構應當是arch/arm/kernel/setup.c中的 setup_arch。 setup_arch(&command_line); ... } void __init setup_arch(char **cmdline_p) { const struct machine_desc *mdesc; setup_processor(); //setup_machine_fdt函數獲取內核前期初始化所需的bootargs,cmd_line等系統引導參數 mdesc = setup_machine_fdt(__atags_pointer);//__atags_pointer是bootloader傳遞參數的物理地址 if (!mdesc) mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); machine_desc = mdesc; machine_name = mdesc->name; if (mdesc->reboot_mode!= REBOOT_HARD) reboot_mode = mdesc->reboot_mode; init_mm.start_code = (unsigned long) _text; init_mm.end_code = (unsigned long) _etext; init_mm.end_data = (unsigned long) _edata; init_mm.brk = (unsigned long) _end; strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE); *cmdline_p = cmd_line; parse_early_param(); sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL); early_paging_init(mdesc, lookup_processor_type(read_cpuid_id())); setup_dma_zone(mdesc); sanity_check_meminfo(); arm_memblock_init(&meminfo, mdesc); paging_init(mdesc); request_standard_resources(mdesc); if (mdesc->restart) arm_pm_restart = mdesc->restart; //解析設備樹 unflatten_device_tree(); ...... } (一)函數獲取內核前期初始化所需的bootargs,cmd_line等系統引導參數 1. setup_machine_fdt()函數獲取內核前期初始化所需的bootargs,cmd_line等系統引導參數。 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { const struct machine_desc *mdesc, *mdesc_best= NULL; #ifdef CONFIG_ARCH_MULTIPLATFORM DT_MACHINE_START(GENERIC_DT,"Generic DT based system") MACHINE_END mdesc_best = &__mach_desc_GENERIC_DT; #endif //bootloader傳遞參數的物理地址不為空,並將物理地址轉化為虛擬地址, //通過函數early_init_dt_scan從設備樹中讀出bootargs,cmd_line等系統引導參數。 if (!dt_phys|| !early_init_dt_scan(phys_to_virt(dt_phys))) return NULL; //根據設備樹中根節點屬性"compatible"的屬性描述,找到系統中定義的最匹配的machine_desc結構,該結構控制系統體系架構相關部分的初始化 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); if (!mdesc){ const char *prop; long size; unsigned long dt_root; early_print("\nError: unrecognized/unsupported ""device tree compatible list:\n[ "); //找到設備樹的根節點,dt_root指向根節點的屬性地址處 dt_root = of_get_flat_dt_root(); //讀出根節點的"compatible"屬性的屬性值 prop = of_get_flat_dt_prop(dt_root,"compatible",&size); //將根節點的"compatible"屬性的屬性值打印出來 while (size > 0){ early_print("'%s' ", prop); size -= strlen(prop)+ 1; prop += strlen(prop)+ 1; } early_print("]\n\n"); dump_machine_table();/* does not return */ } //Change machine numberto match the mdesc we're using __machine_arch_type = mdesc->nr; return mdesc; } struct boot_param_header *initial_boot_params; bool __init early_init_dt_scan(void *params) { if (!params) return false; //參數params是bootloader傳遞參數的物理地址轉化為的虛擬地址,保存設備樹起始地址 initial_boot_params = params; //驗證設備樹的magic if (be32_to_cpu(initial_boot_params->magic)!= OF_DT_HEADER){ initial_boot_params = NULL; return false; } //從設備樹中讀取chosen節點的信息,包括命令行boot_command_line,initrd location及size of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); //得到根節點的{size,address}-cells信息 of_scan_flat_dt(early_init_dt_scan_root,NULL); //讀出設備樹的系統內存設置 of_scan_flat_dt(early_init_dt_scan_memory,NULL); return true; } int __init of_scan_flat_dt(int(*it)(unsigned long node,const char*uname, int depth,void *data),void*data) { //找到設備樹中結構塊的地址 unsigned long p = ((unsigned long)initial_boot_params)+ be32_to_cpu(initial_boot_params->off_dt_struct); int rc = 0; int depth = -1; do { //獲得節點起始標志,即OF_DT_BEGIN_NODE,OF_DT_PROP等 u32 tag = be32_to_cpup((__be32*)p); const char *pathp; p += 4;//跳過節點起始標志 //如果是OF_DT_END_NODE標志,表示該節點結束,繼續下一結點 if (tag== OF_DT_END_NODE){ depth--; continue; } //OF_DT_NOP標志代表空節點 if (tag== OF_DT_NOP) continue; //OF_DT_END標志整個結構塊結束 if (tag== OF_DT_END) break; //OF_DT_PROP標示屬性,Property:屬性值的字節長度、屬性名稱在字符串塊中的偏移量、屬性值和填充。 if (tag== OF_DT_PROP){ //屬性值的字節大小 u32 sz = be32_to_cpup((__be32*)p); p += 8;//跳過屬性值的字節長度、屬性名稱在字符串塊中的偏移量 if (be32_to_cpu(initial_boot_params->version)< 0x10) p = ALIGN(p, sz>= 8 ? 8 : 4); //跳過該屬性值的大小 p += sz; //地址對齊 p = ALIGN(p, 4); //表示一個屬性節點遍歷完成,因為這里並不是尋找屬性節點,而是找OF_DT_BEGIN_NODE開始的節點 continue; } //若都不是以上節點類型,也不是節點開始標示(OF_DT_BEGIN_NODE),則出錯返回 if (tag!= OF_DT_BEGIN_NODE){ pr_err("Invalid tag %x in flat device tree!\n", tag); return -EINVAL; } //執行到這里,標示tag=OF_DT_BEGIN_NODE,表示一個節點的開始,探索深度加1 depth++; //節點路徑或者節點名 pathp = (char *)p; //節點地址四字節對齊,然后p指向節點屬性地址 p = ALIGN(p+ strlen(pathp)+ 1, 4); //如果是節點路徑,則返回路徑名的最后一段,假如為/root/my_root,則返回my_root,即獲得節點名 if (*pathp== '/') pathp = kbasename(pathp); //調用相應的節點處理函數,p指向節點屬性地址 rc = it(p, pathp, depth, data); if (rc!= 0) break; } while(1); return rc; } 1.1 chosen節點 //chosen 節點並不代表一個真實的設備,只是作為一個為固件和操作系統之間傳遞數據的地方,比如引導參數。chosen 節點里的數據也不代表硬件。通常,chosen 節點在.dts 源文件中為空,並在啟動時填充。在我們的示例系統中,固件可以往 chosen 節點添加以下信息: //chosen{ // bootargs= "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";//節點屬性 // linux,initrd-start= <0x85500000>;//節點屬性 // linux,initrd-end= <0x855a3212>;//節點屬性 //}; int __init early_init_dt_scan_chosen(unsigned long node,const char *uname,int depth, void*data) { unsigned long l; char *p; pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname); //depth深度要為1,表示在根節點下(一般根節點/的depth為0) //data表示系統啟動命令行boot_command_line要分配空間 //檢查節點名是否為chosen節點 if (depth!= 1 || !data|| (strcmp(uname,"chosen") != 0 && strcmp(uname,"chosen@0")!= 0)) return 0; //從設備樹的chosen節點中讀出initrd的起始、結束地址 early_init_dt_check_for_initrd(node); //設備樹的chosen節點中讀取bootargs屬性的屬性值,並拷貝給boot_command_line p = of_get_flat_dt_prop(node,"bootargs",&l); if (p!= NULL && l> 0) strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE)); pr_debug("Command line is: %s\n",(char*)data); return 1; } static void __init early_init_dt_check_for_initrd(unsigned long node) { u64 start, end; unsigned long len; __be32 *prop; pr_debug("Looking for initrd properties... "); //返回該chosen節點中屬性名為"linux,initrd-start"的地址 prop = of_get_flat_dt_prop(node,"linux,initrd-start",&len); if (!prop) return; //從該地址讀出initrd-start的地址 start = of_read_number(prop,len/4); //返回該chosen節點中屬性名為"linux,initrd-end"的地址 prop = of_get_flat_dt_prop(node,"linux,initrd-end",&len); if (!prop) return; //從該地址讀出initrd-end的地址 end = of_read_number(prop,len/4); //將讀出的地址賦值給全局變量initrd_start和initrd_end,用於跟文件系統的掛載 initrd_start = (unsigned long)__va(start); initrd_end = (unsigned long)__va(end); initrd_below_start_ok = 1; pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n",(unsigned long long)start,(unsigned long long)end); } void *__init of_get_flat_dt_prop(unsigned long node,const char *name,unsigned long*size) { return of_fdt_get_property(initial_boot_params, node, name, size); } void *of_fdt_get_property(struct boot_param_header*blob,unsigned long node,const char *name,unsigned long*size) { //p指向該chosen節點的節點屬性地址 unsigned long p = node; //因為一個節點中可能包含多個屬性,所以這里遍歷chosen節點中的所有屬性,找到屬性名為name的屬性 do { //取得該節點屬性的起始標志OF_DT_PROP u32 tag = be32_to_cpup((__be32*)p); u32 sz, noff; const char *nstr; p += 4;//跳過節點屬性的起始標志 //空節點則繼續 if (tag== OF_DT_NOP) continue; //非屬性標志則返回NULL if (tag!= OF_DT_PROP) return NULL; //運行到這里表示為屬性OF_DT_PROP //取得該節點屬性的的屬性值size sz = be32_to_cpup((__be32*)p); //取得該屬性名的在字符串塊中的偏移值 noff = be32_to_cpup((__be32*)(p+ 4)); p += 8; //跳過對齊填充字段 if (be32_to_cpu(blob->version)< 0x10) p = ALIGN(p, sz>= 8 ? 8 : 4); //在字符串塊取出該屬性的名稱 nstr = of_fdt_get_string(blob, noff); if (nstr== NULL) { pr_warning("Can't find property index name !\n"); return NULL; } //若名稱一致,表示找到我們要找的屬性 if (strcmp(name, nstr)== 0){ if (size) *size = sz;//返回該屬性的屬性值size //返回該屬性值所在的地址 return (void *)p; } //否則繼續下一個屬性 p += sz; p = ALIGN(p, 4); } while(1); } char *of_fdt_get_string(struct boot_param_header*blob, u32 offset) { //從設備樹的字符串塊的offset處讀出name return ((char*)blob)+ be32_to_cpu(blob->off_dt_strings)+ offset; } static inline u64 of_read_number(const __be32*cell, int size) { u64 r = 0; //讀出屬性值,屬性值大小為size while (size--) r = (r<< 32)| be32_to_cpu(*(cell++)); return r; } 1.2 根節點"/" //設備樹有且僅有一個根節點,即“/”,根節點下包含很多子節點,例入下圖,根節點為"/",根節點的子節點為"chosen",根節點的屬性包含"compatible","#address-cells","#size-cells","interrupt-parent"等。屬性model指明了目標板平台或模塊的名稱,屬性compatible值指明和目標板為同一系列的兼容的開發板名稱。對於大多數32位平台,屬性#address-cells和#size-cells的值一般為1。#address-cells= <1>; 1表示地址32位,2表示地址64位。#size-cells= <1>;1表示rangs的每部分占一個cell,依此類推 /* / { compatible = "sprd,spx15"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&gic>; chosen { bootargs = "loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw"; linux,initrd-start= <0x85500000>; linux,initrd-end= <0x855a3212>; }; } */ //所以本函數就是讀取根節點的"#address-cells","#size-cells"屬性 int __init early_init_dt_scan_root(unsigned long node,const char *uname,int depth, void*data) { __be32 *prop; //根節點的探索深度depth一定為0,否則不是根節點"/",node為根節點的屬性地址 if (depth!= 0) return 0; dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT; dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT; //返回根節點節點屬性名為"#size-cells"的地址 prop = of_get_flat_dt_prop(node,"#size-cells",NULL); if (prop) dt_root_size_cells = be32_to_cpup(prop);//從該屬性獲得root size pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells); //返回根節點節點屬性名為"#address-cells"的地址 prop = of_get_flat_dt_prop(node,"#address-cells",NULL); if (prop) dt_root_addr_cells = be32_to_cpup(prop);//從該屬性獲得root address pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells); return 1; } 1.3 memory節點 //memory節點用於描述目標板上物理內存范圍,一般稱作/memory節點,可以有一個或多個。當有多個節點時,需要后跟單元地址予以區分;只有一個單元地址時,可以不寫單元地址,默認為0。此節點包含板上物理內存的屬性,一般要指定device_type(固定為"memory")和reg屬性。其中reg的屬性值以<起始地址 空間大小>的形式給出,如下示例中目標板內存起始地址為0x80000000,大小為0x20000000字節。 //memory{ // device_type= "memory"; // reg= <0x80000000 0x20000000>; //}; int __init early_init_dt_scan_memory(unsigned long node,const char *uname,int depth, void*data) { //獲取該節點中屬性"device_type"的屬性值 char *type = of_get_flat_dt_prop(node,"device_type",NULL); __be32 *reg,*endp; unsigned long l; //檢查"device_type"的屬性值,確定該節點是否為memory節點 if (type== NULL) { if (depth!= 1 || strcmp(uname,"memory@0")!= 0) return 0; } elseif (strcmp(type,"memory") != 0) return 0; //從該memory節點中獲取屬性"linux,usable-memory"的屬性值,及屬性值大小l 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; //reg為屬性值的起始地址,endp為結束地址 endp = reg + (l / sizeof(__be32)); pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x,\n", uname, l, reg[0], reg[1], reg[2], reg[3]); while ((endp- reg) >= (dt_root_addr_cells+ dt_root_size_cells)){ u64 base, size; //讀出物理內存的起始地址以及size base = dt_mem_next_cell(dt_root_addr_cells,®); size = dt_mem_next_cell(dt_root_size_cells,®); if (size== 0) continue; pr_debug(" - %llx , %llx\n",(unsigned long long)base,(unsigned long long)size); //添加物理內存到memblock中進行管理,這里不再展開 early_init_dt_add_memory_arch(base, size); } return 0; } 2. 通過比較根節點屬性compatible值指明和目標板為同一系列的兼容的開發板名稱 //compatible制定系統的名稱。它包含"<manufacture>,<model>"格式的字符串。准確地確定器件型號是非常重要的,並且我們需要包含廠商的名字來避免名字空間沖突。因為操作系統會使用compatible這個值來決定怎樣在這個機器上運行,所以在這個屬性中放入正確的值是非常重要的。 const void * __init of_flat_dt_match_machine(const void*default_match, const void* (*get_next_compat)(const char* const**)) { const void *data = NULL; const void *best_data = default_match; const char *const *compat; unsigned long dt_root; unsigned int best_score = ~1, score= 0; //讀出設備樹的根節點,dt_root指向根節點的屬性地址處 dt_root = of_get_flat_dt_root(); //調用arch_get_next_mach,遍歷該系統中的所有machine_desc結構,返回給data,並且返回該結構的compatible while ((data= get_next_compat(&compat))){ //將系統中的所有machine_desc結構的compatible字符串與設備樹根節點的compatible屬性進行match score = of_flat_dt_match(dt_root, compat); //返回與根節點屬性compatible屬性值最匹配的machine_desc結構 if (score> 0 && score< best_score){ best_data = data; best_score = score; } } if (!best_data){ const char *prop; long size; pr_err("\n unrecognized device tree list:\n[ "); prop = of_get_flat_dt_prop(dt_root,"compatible",&size); if (prop){ while (size > 0){ printk("'%s' ", prop); size -= strlen(prop)+ 1; prop += strlen(prop)+ 1; } } printk("]\n\n"); return NULL; } pr_info("Machine model: %s\n", of_flat_dt_get_machine_name()); return best_data; } //查找設備樹的根節點 unsigned long __init of_get_flat_dt_root(void) { //找到設備樹的設備塊起始地址 unsigned long p = ((unsigned long)initial_boot_params)+ be32_to_cpu(initial_boot_params->off_dt_struct); //跳過空節點 while (be32_to_cpup((__be32*)p)== OF_DT_NOP) p += 4; BUG_ON(be32_to_cpup((__be32*)p)!= OF_DT_BEGIN_NODE); p += 4; //第一個節點就是根節點,p指向根節點的屬性地址處 return ALIGN(p + strlen((char*)p)+ 1, 4); } //arch/arm/kernel/devtree.c __arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中: 00034: __arch_info_begin = .; 00035: *(.arch.info.init) 00036: __arch_info_end = .; 這里是聲明了兩個變量:__arch_info_begin 和 __arch_info_end,其中等號后面的"."是location counter。在__arch_info_begin 的位置上,放置所有文件中的".arch.info.init" 段的內容,然后緊接着是 __arch_info_end 的位置.".arch.info.init" 段中定義了設備的machine_desc結構。 //這里就是取出一個machine_desc結構 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++;//指針后移,確保下次取出下一個machine_desc結構 *match = m->dt_compat; return m;//返回當前的machine_desc結構 } //與設備樹根節點進行match int __init of_flat_dt_match(unsigned long node,const char *const*compat) { //initial_boot_params指向設備樹起始地址 //node指向根節點的屬性地址 //compat為系統中machine_desc結構的compatible字符串 return of_fdt_match(initial_boot_params, node, compat); } int of_fdt_match(struct boot_param_header*blob, unsigned long node,const char*const *compat) { unsigned int tmp, score= 0; if (!compat) return 0; //遍歷compatible字符串數組 while (*compat){ //返回compatible的匹配值 tmp = of_fdt_is_compatible(blob, node,*compat); if (tmp&& (score == 0|| (tmp < score))) score = tmp;//返回最大的匹配值 compat++;//下一個字符串 } return score; } int of_fdt_is_compatible(struct boot_param_header*blob,unsigned long node,const char *compat) { const char *cp; unsigned long cplen, l, score= 0; //從根節點中讀出屬性"compatible"的屬性值 cp = of_fdt_get_property(blob, node,"compatible",&cplen); if (cp== NULL) return 0; //比較compatible的指定的屬性字符串的一致性 while (cplen> 0) { score++; if (of_compat_cmp(cp, compat, strlen(compat))== 0) return score; l = strlen(cp)+ 1; cp += l; cplen -= l; } return 0; } (二)、解析設備樹 //unflatten_device_tree()函數來解析dtb文件,構建一個由device_node結構連接而成的單項鏈表,並使用全局變量of_allnodes指針來保存這個鏈表的頭指針。內核調用OF提供的API函數獲取of_allnodes鏈表信息來初始化內核其他子系統、設備。 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(structproperty),__alignof__(structproperty)); //第一次調用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(structproperty) + sz,__alignof__(structproperty)); 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/ethernet@0,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”或者“/chosen@0”的節點,賦值給全局變量of_chosen of_chosen = of_find_node_by_path("/chosen"); if (of_chosen== NULL) of_chosen = of_find_node_by_path("/chosen@0"); //找到的話,則在該節點查找"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); } } 四、OF提供的常用API函數 //OF提供的函數主要集中在drivers/of/目錄下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等 1. 用來查找在dtb中的根節點 unsigned long __init of_get_flat_dt_root(void) 2. 根據deice_node結構的full_name參數,在全局鏈表of_allnodes中,查找合適的device_node struct device_node *of_find_node_by_path(const char *path) 例如: struct device_node *cpus; cpus=of_find_node_by_path("/cpus"); 3. 若from=NULL,則在全局鏈表of_allnodes中根據name查找合適的device_node struct device_node *of_find_node_by_name(struct device_node *from,const char *name) 例如: struct device_node *np; np = of_find_node_by_name(NULL,"firewire"); 4. 根據設備類型查找相應的device_node struct device_node *of_find_node_by_type(struct device_node *from,const char *type) 例如: struct device_node *tsi_pci; tsi_pci= of_find_node_by_type(NULL,"pci"); 5. 根據compatible字符串查找device_node struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible) 6. 根據節點屬性的name查找device_node struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name) 7. 根據phandle查找device_node struct device_node *of_find_node_by_phandle(phandle handle) 8. 根據alias的name獲得設備id號 int of_alias_get_id(struct device_node *np, const char *stem) 9. device node計數增加/減少 struct device_node *of_node_get(struct device_node *node) void of_node_put(struct device_node *node) 10. 根據property結構的name參數,在指定的device node中查找合適的property struct property *of_find_property(const struct device_node *np,const char *name,int *lenp) 11. 根據property結構的name參數,返回該屬性的屬性值 const void *of_get_property(const struct device_node *np, const char *name,int *lenp) 12. 根據compat參數與device node的compatible匹配,返回匹配度 int of_device_is_compatible(const struct device_node *device,const char *compat) 13. 獲得父節點的device node struct device_node *of_get_parent(const struct device_node *node) 14. 將matches數組中of_device_id結構的name和type與device node的compatible和type匹配,返回匹配度最高的of_device_id結構 const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node) 15. 根據屬性名propname,讀出屬性值中的第index個u32數值給out_value int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value) 16. 根據屬性名propname,讀出該屬性的數組中sz個屬性值給out_values int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz) int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz) int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz) 17. 根據屬性名propname,讀出該屬性的u64屬性值 int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value) 18. 根據屬性名propname,讀出該屬性的字符串屬性值 int of_property_read_string(struct device_node *np, const char *propname,const char **out_string) 19. 根據屬性名propname,讀出該字符串屬性值數組中的第index個字符串 int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output) 20. 讀取屬性名propname中,字符串屬性值的個數 int of_property_count_strings(struct device_node *np, const char *propname) 21. 讀取該設備的第index個irq號 unsigned int irq_of_parse_and_map(struct device_node *dev, int index) 22. 讀取該設備的第index個irq號,並填充一個irq資源結構體 int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) 23. 獲取該設備的irq個數 int of_irq_count(struct device_node *dev) 24. 獲取設備寄存器地址,並填充寄存器資源結構體 int of_address_to_resource(struct device_node *dev, int index,struct resource *r) const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags) 25. 獲取經過映射的寄存器虛擬地址 void __iomem *of_iomap(struct device_node *np, int index) 24. 根據device_node查找返回該設備對應的platform_device結構 struct platform_device *of_find_device_by_node(struct device_node *np) 25. 根據device node,bus id以及父節點創建該設備的platform_device結構 struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent) static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id, void *platform_data,struct device *parent) 26. 遍歷of_allnodes中的節點掛接到of_platform_bus_type總線上,由於此時of_platform_bus_type總線上還沒有驅動,所以此時不進行匹配 int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent) 27. 遍歷of_allnodes中的所有節點,生成並初始化platform_device結構 int of_platform_populate(struct device_node *root,const struct of_device_id *matches, const struct of_dev_auxdata *lookup,struct device *parent) { struct device_node *child; int rc = 0; //獲得設備樹的根節點 root = root ? of_node_get(root) : of_find_node_by_path("/