//Based?on?Linux v3.14 source code
Linux設(shè)備樹(shù)機(jī)制(Device Tree)
一、描述
ARM Device Tree起源于OpenFirmware?(OF),在過(guò)去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥著大量的垃圾代碼,相當(dāng)多數(shù)的代碼只是在描述板級(jí)細(xì)節(jié),而這些板級(jí)細(xì)節(jié)對(duì)于內(nèi)核來(lái)講,不過(guò)是垃圾,如板上的platform設(shè)備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data。為了改變這種局面,Linux社區(qū)的大牛們參考了PowerPC等體系架構(gòu)中使用的Flattened Device Tree(FDT),也采用了Device Tree結(jié)構(gòu),許多硬件的細(xì)節(jié)可以直接透過(guò)它傳遞給Linux,而不再需要在kernel中進(jìn)行大量的冗余編碼。
Device Tree是一種描述硬件的數(shù)據(jù)結(jié)構(gòu),由一系列被命名的結(jié)點(diǎn)(node)和屬性(property)組成,而結(jié)點(diǎn)本身可包含子結(jié)點(diǎn)。所謂屬性,其實(shí)就是成對(duì)出現(xiàn)的name和value。在Device Tree中,可描述的信息包括(原先這些信息大多被hard code到kernel中):CPU的數(shù)量和類(lèi)別,內(nèi)存基地址和大小,總線和橋,外設(shè)連接,中斷控制器和中斷使用情況,GPIO控制器和GPIO使用情況,Clock控制器和Clock使用情況。
通常由.dts文件以文本方式對(duì)系統(tǒng)設(shè)備樹(shù)進(jìn)行描述,經(jīng)過(guò)Device Tree Compiler(dtc)將dts文件轉(zhuǎn)換成二進(jìn)制文件binary device tree blob(dtb),.dtb文件可由Linux內(nèi)核解析,有了device tree就可以在不改動(dòng)Linux內(nèi)核的情況下,對(duì)不同的平臺(tái)實(shí)現(xiàn)無(wú)差異的支持,只需更換相應(yīng)的dts文件,即可滿(mǎn)足。
二、相關(guān)結(jié)構(gòu)體
1.U-Boot需要將設(shè)備樹(shù)在內(nèi)存中的存儲(chǔ)地址傳給內(nèi)核。該樹(shù)主要由三大部分組成:頭(Header)、結(jié)構(gòu)塊(Structure block)、字符串塊(Strings block)。設(shè)備樹(shù)在內(nèi)存中的存儲(chǔ)布局圖。
------------------------------
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)
頭主要描述設(shè)備樹(shù)的一些基本信息,例如設(shè)備樹(shù)大小,結(jié)構(gòu)塊偏移地址,字符串塊偏移地址等。偏移地址是相對(duì)于設(shè)備樹(shù)頭的起始地址計(jì)算的。
struct boot_param_header?{
__be32 magic;????????????????//設(shè)備樹(shù)魔數(shù),固定為0xd00dfeed
__be32 totalsize;????????????//整個(gè)設(shè)備樹(shù)的大小
__be32 off_dt_struct;????????//保存結(jié)構(gòu)塊在整個(gè)設(shè)備樹(shù)中的偏移
__be32 off_dt_strings;????????//保存的字符串塊在設(shè)備樹(shù)中的偏移
__be32 off_mem_rsvmap;????????//保留內(nèi)存區(qū),該區(qū)保留了不能被內(nèi)核動(dòng)態(tài)分配的內(nèi)存空間
__be32 version;????????????//設(shè)備樹(shù)版本
__be32 last_comp_version;????//向下兼容版本號(hào)
__be32 boot_cpuid_phys;????//為在多核處理器中用于啟動(dòng)的主cpu的物理id
__be32 dt_strings_size;????//字符串塊大小
__be32 dt_struct_size;?????//結(jié)構(gòu)塊大小
};
1.2 結(jié)構(gòu)塊(struct block)
設(shè)備樹(shù)結(jié)構(gòu)塊是一個(gè)線性化的結(jié)構(gòu)體,是設(shè)備樹(shù)的主體,以節(jié)點(diǎn)node的形式保存了目標(biāo)單板上的設(shè)備信息。
在結(jié)構(gòu)塊中以宏OF_DT_BEGIN_NODE標(biāo)志一個(gè)節(jié)點(diǎn)的開(kāi)始,以宏OF_DT_END_NODE標(biāo)識(shí)一個(gè)節(jié)點(diǎn)的結(jié)束,整個(gè)結(jié)構(gòu)塊以宏OF_DT_END結(jié)束。一個(gè)節(jié)點(diǎn)主要由以下幾部分組成。
(1)節(jié)點(diǎn)開(kāi)始標(biāo)志:一般為OF_DT_BEGIN_NODE。
(2)節(jié)點(diǎn)路徑或者節(jié)點(diǎn)的單元名(ersion<3以節(jié)點(diǎn)路徑表示,version>=0x10以節(jié)點(diǎn)單元名表示)
(3)填充字段(對(duì)齊到四字節(jié))
(4)節(jié)點(diǎn)屬性。每個(gè)屬性以宏OF_DT_PROP開(kāi)始,后面依次為屬性值的字節(jié)長(zhǎng)度(4字節(jié))、屬性名稱(chēng)在字符串塊中的偏移量(4字節(jié))、屬性值和填充(對(duì)齊到四字節(jié))。
(5)如果存在子節(jié)點(diǎn),則定義子節(jié)點(diǎn)。
(6)節(jié)點(diǎn)結(jié)束標(biāo)志OF_DT_END_NODE。
1.3 字符串塊
通過(guò)節(jié)點(diǎn)的定義知道節(jié)點(diǎn)都有若干屬性,而不同的節(jié)點(diǎn)的屬性又有大量相同的屬性名稱(chēng),因此將這些屬性名稱(chēng)提取出一張表,當(dāng)節(jié)點(diǎn)需要應(yīng)用某個(gè)屬性名稱(chēng)時(shí)直接在屬性名字段保存該屬性名稱(chēng)在字符串塊中的偏移量。
1.4 設(shè)備樹(shù)源碼 DTS 表示
設(shè)備樹(shù)源碼文件(.dts)以可讀可編輯的文本形式描述系統(tǒng)硬件配置設(shè)備樹(shù),支持 C/C++方式的注釋,該結(jié)構(gòu)有一個(gè)唯一的根節(jié)點(diǎn)“/”,每個(gè)節(jié)點(diǎn)都有自己的名字并可以包含多個(gè)子節(jié)點(diǎn)。設(shè)備樹(shù)的數(shù)據(jù)格式遵循了 Open Firmware IEEE standard 1275。這個(gè)設(shè)備樹(shù)中有很多節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都指定了節(jié)點(diǎn)單元名稱(chēng)。每一個(gè)屬性后面都給出相應(yīng)的值。以雙引號(hào)引出的內(nèi)容為 ASCII 字符串,以尖括號(hào)給出的是 32 位的16進(jìn)制值。這個(gè)樹(shù)結(jié)構(gòu)是啟動(dòng) Linux 內(nèi)核所需節(jié)點(diǎn)和屬性簡(jiǎn)化后的集合,包括了根節(jié)點(diǎn)的基本模式信息、CPU 和物理內(nèi)存布局,它還包括通過(guò)/chosen 節(jié)點(diǎn)傳遞給內(nèi)核的命令行參數(shù)信息。
1.5 machine_desc結(jié)構(gòu)
內(nèi)核提供了一個(gè)重要的結(jié)構(gòu)體struct machine_desc ,這個(gè)結(jié)構(gòu)體在內(nèi)核移植中起到相當(dāng)重要的作用,內(nèi)核通過(guò)machine_desc結(jié)構(gòu)體來(lái)控制系統(tǒng)體系架構(gòu)相關(guān)部分的初始化。machine_desc結(jié)構(gòu)體通過(guò)MACHINE_START宏來(lái)初始化,在代碼中, 通過(guò)在start_kernel->setup_arch中調(diào)用setup_machine_fdt來(lái)獲取。
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 設(shè)備節(jié)點(diǎn)結(jié)構(gòu)體
struct device_node?{
const?char?*name;????//設(shè)備name
const?char?*type;?//設(shè)備類(lèi)型
phandle phandle;
const?char?*full_name;?//設(shè)備全稱(chēng),包括父設(shè)備名
struct?property?*properties;?//設(shè)備屬性鏈表
struct?property?*deadprops;?//removed properties
struct device_node?*parent;?//指向父節(jié)點(diǎn)
struct device_node?*child;?//指向子節(jié)點(diǎn)
struct device_node?*sibling;?//指向兄弟節(jié)點(diǎn)
struct device_node?*next;?//相同設(shè)備類(lèi)型的下一個(gè)節(jié)點(diǎn)
struct device_node?*allnext;?//next?in?list of all nodes
struct proc_dir_entry?*pde;?//該節(jié)點(diǎn)對(duì)應(yīng)的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 屬性結(jié)構(gòu)體
struct?property?{
char?*name;????????//屬性名
int?length;????????//屬性值長(zhǎng)度
void?*value;????????//屬性值
struct?property?*next;?//指向下一個(gè)屬性
unsigned long _flags;?//標(biāo)志
unsigned?int?unique_id;
};
三、設(shè)備樹(shù)初始化及解析
分析Linux內(nèi)核的源碼,可以看到其對(duì)扁平設(shè)備樹(shù)的解析流程如下:
(1)首先在內(nèi)核入口處將從u-boot傳遞過(guò)來(lái)的鏡像基地址。
(2)通過(guò)調(diào)用early_init_dt_scan()函數(shù)來(lái)獲取內(nèi)核前期初始化所需的bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)。
(3)根據(jù)bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)進(jìn)入start_kernel()函數(shù),進(jìn)行內(nèi)核的第二階段初始化。
(4)調(diào)用unflatten_device_tree()函數(shù)來(lái)解析dtb文件,構(gòu)建一個(gè)由device_node結(jié)構(gòu)連接而成的單項(xiàng)鏈表,并使用全局變量of_allnodes指針來(lái)保存這個(gè)鏈表的頭指針。
(5)內(nèi)核調(diào)用OF提供的API函數(shù)獲取of_allnodes鏈表信息來(lái)初始化內(nèi)核其他子系統(tǒng)、設(shè)備等。
//kernel 初始化的代碼(init/main.c)
asmlinkage void __init start_kernel(void)
{
...
//這個(gè)setup_arch就是各個(gè)架構(gòu)自己的設(shè)置函數(shù),哪個(gè)參與了編譯就調(diào)用哪個(gè),arm架構(gòu)應(yīng)當(dāng)是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函數(shù)獲取內(nèi)核前期初始化所需的bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)
mdesc?=?setup_machine_fdt(__atags_pointer);//__atags_pointer是bootloader傳遞參數(shù)的物理地址
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;
//解析設(shè)備樹(shù)
unflatten_device_tree();
......
}
(一)函數(shù)獲取內(nèi)核前期初始化所需的bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)
1.?setup_machine_fdt()函數(shù)獲取內(nèi)核前期初始化所需的bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)。
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傳遞參數(shù)的物理地址不為空,并將物理地址轉(zhuǎn)化為虛擬地址,
//通過(guò)函數(shù)early_init_dt_scan從設(shè)備樹(shù)中讀出bootargs,cmd_line等系統(tǒng)引導(dǎo)參數(shù)。
if?(!dt_phys?||?!early_init_dt_scan(phys_to_virt(dt_phys)))
return?NULL;
//根據(jù)設(shè)備樹(shù)中根節(jié)點(diǎn)屬性"compatible"的屬性描述,找到系統(tǒng)中定義的最匹配的machine_desc結(jié)構(gòu),該結(jié)構(gòu)控制系統(tǒng)體系架構(gòu)相關(guān)部分的初始化
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(" Error: unrecognized/unsupported ""device tree compatible list: [ ");
//找到設(shè)備樹(shù)的根節(jié)點(diǎn),dt_root指向根節(jié)點(diǎn)的屬性地址處
dt_root?=?of_get_flat_dt_root();
//讀出根節(jié)點(diǎn)的"compatible"屬性的屬性值
prop?=?of_get_flat_dt_prop(dt_root,?"compatible",?&size);
//將根節(jié)點(diǎn)的"compatible"屬性的屬性值打印出來(lái)
while?(size?>?0)?{
early_print("'%s' ",?prop);
size?-=?strlen(prop)?+?1;
prop?+=?strlen(prop)?+?1;
}
early_print("] ");
dump_machine_table();?/*?does?not?return?*/
}
//Change machine number?to?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;
//參數(shù)params是bootloader傳遞參數(shù)的物理地址轉(zhuǎn)化為的虛擬地址,保存設(shè)備樹(shù)起始地址
initial_boot_params?=?params;
//驗(yàn)證設(shè)備樹(shù)的magic?
if?(be32_to_cpu(initial_boot_params->magic)?!=?OF_DT_HEADER)?{
initial_boot_params?=?NULL;
return?false;
}
//從設(shè)備樹(shù)中讀取chosen節(jié)點(diǎn)的信息,包括命令行boot_command_line,initrd location及size
of_scan_flat_dt(early_init_dt_scan_chosen,?boot_command_line);
//得到根節(jié)點(diǎn)的{size,address}-cells信息
of_scan_flat_dt(early_init_dt_scan_root,?NULL);
//讀出設(shè)備樹(shù)的系統(tǒng)內(nèi)存設(shè)置
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)
{
//找到設(shè)備樹(shù)中結(jié)構(gòu)塊的地址
unsigned long p?=?((unsigned long)initial_boot_params)?+?be32_to_cpu(initial_boot_params->off_dt_struct);
int?rc?=?0;
int?depth?=?-1;
do?{
//獲得節(jié)點(diǎn)起始標(biāo)志,即OF_DT_BEGIN_NODE,OF_DT_PROP等
u32 tag?=?be32_to_cpup((__be32?*)p);
const?char?*pathp;
p?+=?4;//跳過(guò)節(jié)點(diǎn)起始標(biāo)志
//如果是OF_DT_END_NODE標(biāo)志,表示該節(jié)點(diǎn)結(jié)束,繼續(xù)下一結(jié)點(diǎn)
if?(tag?==?OF_DT_END_NODE)?{
depth--;
continue;
}
//OF_DT_NOP標(biāo)志代表空節(jié)點(diǎn)
if?(tag?==?OF_DT_NOP)
continue;
//OF_DT_END標(biāo)志整個(gè)結(jié)構(gòu)塊結(jié)束
if?(tag?==?OF_DT_END)
break;
//OF_DT_PROP標(biāo)示屬性,Property:屬性值的字節(jié)長(zhǎng)度、屬性名稱(chēng)在字符串塊中的偏移量、屬性值和填充。
if?(tag?==?OF_DT_PROP)?{
//屬性值的字節(jié)大小
u32 sz?=?be32_to_cpup((__be32?*)p);
p?+=?8;//跳過(guò)屬性值的字節(jié)長(zhǎng)度、屬性名稱(chēng)在字符串塊中的偏移量
if?(be32_to_cpu(initial_boot_params->version)?0x10)
p?=?ALIGN(p,?sz?>=?8???8?:?4);
//跳過(guò)該屬性值的大小
p?+=?sz;
//地址對(duì)齊
p?=?ALIGN(p,?4);
//表示一個(gè)屬性節(jié)點(diǎn)遍歷完成,因?yàn)檫@里并不是尋找屬性節(jié)點(diǎn),而是找OF_DT_BEGIN_NODE開(kāi)始的節(jié)點(diǎn)
continue;
}
//若都不是以上節(jié)點(diǎn)類(lèi)型,也不是節(jié)點(diǎn)開(kāi)始標(biāo)示(OF_DT_BEGIN_NODE),則出錯(cuò)返回
if?(tag?!=?OF_DT_BEGIN_NODE)?{
pr_err("Invalid tag %x in flat device tree! ",?tag);
return?-EINVAL;
}
//執(zhí)行到這里,標(biāo)示tag=OF_DT_BEGIN_NODE,表示一個(gè)節(jié)點(diǎn)的開(kāi)始,探索深度加1
depth++;
//節(jié)點(diǎn)路徑或者節(jié)點(diǎn)名
pathp?=?(char?*)p;
//節(jié)點(diǎn)地址四字節(jié)對(duì)齊,然后p指向節(jié)點(diǎn)屬性地址
p?=?ALIGN(p?+?strlen(pathp)?+?1,?4);
//如果是節(jié)點(diǎn)路徑,則返回路徑名的最后一段,假如為/root/my_root,則返回my_root,即獲得節(jié)點(diǎn)名
if?(*pathp?==?'/')
pathp?=?kbasename(pathp);
//調(diào)用相應(yīng)的節(jié)點(diǎn)處理函數(shù),p指向節(jié)點(diǎn)屬性地址
rc?=?it(p,?pathp,?depth,?data);
if?(rc?!=?0)
break;
}?while?(1);
return rc;
}
1.1 chosen節(jié)點(diǎn)
//chosen 節(jié)點(diǎn)并不代表一個(gè)真實(shí)的設(shè)備,只是作為一個(gè)為固件和操作系統(tǒng)之間傳遞數(shù)據(jù)的地方,比如引導(dǎo)參數(shù)。chosen 節(jié)點(diǎn)里的數(shù)據(jù)也不代表硬件。通常,chosen 節(jié)點(diǎn)在.dts 源文件中為空,并在啟動(dòng)時(shí)填充。在我們的示例系統(tǒng)中,固件可以往 chosen 節(jié)點(diǎn)添加以下信息:
//chosen?{
//????bootargs?=?"root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";?//節(jié)點(diǎn)屬性
//????linux,initrd-start?=?<0x85500000>;?//節(jié)點(diǎn)屬性
//????linux,initrd-end?=?<0x855a3212>;?//節(jié)點(diǎn)屬性
//};
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 ",?depth,?uname);
//depth深度要為1,表示在根節(jié)點(diǎn)下(一般根節(jié)點(diǎn)/的depth為0)
//data表示系統(tǒng)啟動(dòng)命令行boot_command_line要分配空間
//檢查節(jié)點(diǎn)名是否為chosen節(jié)點(diǎn)????
if?(depth?!=?1?||?!data?||?(strcmp(uname,?"chosen")?!=?0?&&?strcmp(uname,?"chosen@0")?!=?0))
return 0;
//從設(shè)備樹(shù)的chosen節(jié)點(diǎn)中讀出initrd的起始、結(jié)束地址
early_init_dt_check_for_initrd(node);
//設(shè)備樹(shù)的chosen節(jié)點(diǎn)中讀取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 ",?(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節(jié)點(diǎn)中屬性名為"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節(jié)點(diǎn)中屬性名為"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,用于跟文件系統(tǒng)的掛載
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 ",(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節(jié)點(diǎn)的節(jié)點(diǎn)屬性地址
unsigned long p?=?node;
//因?yàn)橐粋€(gè)節(jié)點(diǎn)中可能包含多個(gè)屬性,所以這里遍歷chosen節(jié)點(diǎn)中的所有屬性,找到屬性名為name的屬性
do?{
//取得該節(jié)點(diǎn)屬性的起始標(biāo)志OF_DT_PROP
u32 tag?=?be32_to_cpup((__be32?*)p);
u32 sz,?noff;
const?char?*nstr;
p?+=?4;//跳過(guò)節(jié)點(diǎn)屬性的起始標(biāo)志
//空節(jié)點(diǎn)則繼續(xù)
if?(tag?==?OF_DT_NOP)
continue;
//非屬性標(biāo)志則返回NULL
if?(tag?!=?OF_DT_PROP)
return?NULL;
//運(yùn)行到這里表示為屬性O(shè)F_DT_PROP
//取得該節(jié)點(diǎn)屬性的的屬性值size
sz?=?be32_to_cpup((__be32?*)p);
//取得該屬性名的在字符串塊中的偏移值
noff?=?be32_to_cpup((__be32?*)(p?+?4));
p?+=?8;
//跳過(guò)對(duì)齊填充字段
if?(be32_to_cpu(blob->version)?0x10)
p?=?ALIGN(p,?sz?>=?8???8?:?4);
//在字符串塊取出該屬性的名稱(chēng)
nstr?=?of_fdt_get_string(blob,?noff);
if?(nstr?==?NULL)?{
pr_warning("Can't find property index name ! ");
return?NULL;
}
//若名稱(chēng)一致,表示找到我們要找的屬性
if?(strcmp(name,?nstr)?==?0)?{
if?(size)
*size?=?sz;//返回該屬性的屬性值size
//返回該屬性值所在的地址
return?(void?*)p;
}
//否則繼續(xù)下一個(gè)屬性
p?+=?sz;
p?=?ALIGN(p,?4);
}?while?(1);
}
char?*of_fdt_get_string(struct boot_param_header?*blob,?u32 offset)
{
//從設(shè)備樹(shù)的字符串塊的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 根節(jié)點(diǎn)"/"
//設(shè)備樹(shù)有且僅有一個(gè)根節(jié)點(diǎn),即“/”,根節(jié)點(diǎn)下包含很多子節(jié)點(diǎn),例入下圖,根節(jié)點(diǎn)為"/",根節(jié)點(diǎn)的子節(jié)點(diǎn)為"chosen",根節(jié)點(diǎn)的屬性包含"compatible","#address-cells","#size-cells","interrupt-parent"等。屬性model指明了目標(biāo)板平臺(tái)或模塊的名稱(chēng),屬性compatible值指明和目標(biāo)板為同一系列的兼容的開(kāi)發(fā)板名稱(chēng)。對(duì)于大多數(shù)32位平臺(tái),屬性#address-cells和#size-cells的值一般為1。#address-cells?=?<1>;?1表示地址32位,2表示地址64位。#size-cells?=?<1>;1表示rangs的每部分占一個(gè)cell,依此類(lèi)推?
/*
/?{
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>;
};
}
*/
//所以本函數(shù)就是讀取根節(jié)點(diǎn)的"#address-cells","#size-cells"屬性
int?__init early_init_dt_scan_root(unsigned long node,?const?char?*uname,int?depth,?void?*data)
{
__be32?*prop;
//根節(jié)點(diǎn)的探索深度depth一定為0,否則不是根節(jié)點(diǎn)"/",node為根節(jié)點(diǎn)的屬性地址????
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;
//返回根節(jié)點(diǎn)節(jié)點(diǎn)屬性名為"#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 ",?dt_root_size_cells);
//返回根節(jié)點(diǎn)節(jié)點(diǎn)屬性名為"#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 ",?dt_root_addr_cells);
return 1;
}
1.3 memory節(jié)點(diǎn)
//memory節(jié)點(diǎn)用于描述目標(biāo)板上物理內(nèi)存范圍,一般稱(chēng)作/memory節(jié)點(diǎn),可以有一個(gè)或多個(gè)。當(dāng)有多個(gè)節(jié)點(diǎn)時(shí),需要后跟單元地址予以區(qū)分;只有一個(gè)單元地址時(shí),可以不寫(xiě)單元地址,默認(rèn)為0。此節(jié)點(diǎn)包含板上物理內(nèi)存的屬性,一般要指定device_type(固定為"memory")和reg屬性。其中reg的屬性值以<起始地址 空間大小>的形式給出,如下示例中目標(biāo)板內(nèi)存起始地址為0x80000000,大小為0x20000000字節(jié)。?
//memory?{
//????device_type?=?"memory";
//????reg?=?<0x80000000 0x20000000>;
//};
int?__init early_init_dt_scan_memory(unsigned long node,?const?char?*uname,int?depth,?void?*data)
{
//獲取該節(jié)點(diǎn)中屬性"device_type"的屬性值
char?*type?=?of_get_flat_dt_prop(node,?"device_type",?NULL);
__be32?*reg,?*endp;
unsigned long l;
//檢查"device_type"的屬性值,確定該節(jié)點(diǎn)是否為memory節(jié)點(diǎn)
if?(type?==?NULL)?{
if?(depth?!=?1?||?strcmp(uname,?"memory@0")?!=?0)
return 0;
}?else?if?(strcmp(type,?"memory")?!=?0)
return 0;
//從該memory節(jié)點(diǎn)中獲取屬性"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為結(jié)束地址
endp?=?reg?+?(l?/?sizeof(__be32));
pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x, ",
uname,?l,?reg[0],?reg[1],?reg[2],?reg[3]);
while?((endp?-?reg)?>=?(dt_root_addr_cells?+?dt_root_size_cells))?{
u64 base,?size;
//讀出物理內(nèi)存的起始地址以及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 ",?(unsigned long long)base,(unsigned long long)size);
//添加物理內(nèi)存到memblock中進(jìn)行管理,這里不再展開(kāi)
early_init_dt_add_memory_arch(base,?size);
}
return 0;
}
2.?通過(guò)比較根節(jié)點(diǎn)屬性compatible值指明和目標(biāo)板為同一系列的兼容的開(kāi)發(fā)板名稱(chēng)
//compatible制定系統(tǒng)的名稱(chēng)。它包含","格式的字符串。準(zhǔn)確地確定器件型號(hào)是非常重要的,并且我們需要包含廠商的名字來(lái)避免名字空間沖突。因?yàn)椴僮飨到y(tǒng)會(huì)使用compatible這個(gè)值來(lái)決定怎樣在這個(gè)機(jī)器上運(yùn)行,所以在這個(gè)屬性中放入正確的值是非常重要的。
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;
//讀出設(shè)備樹(shù)的根節(jié)點(diǎn),dt_root指向根節(jié)點(diǎn)的屬性地址處
dt_root?=?of_get_flat_dt_root();
//調(diào)用arch_get_next_mach,遍歷該系統(tǒng)中的所有machine_desc結(jié)構(gòu),返回給data,并且返回該結(jié)構(gòu)的compatible
while?((data?=?get_next_compat(&compat)))?{
//將系統(tǒng)中的所有machine_desc結(jié)構(gòu)的compatible字符串與設(shè)備樹(shù)根節(jié)點(diǎn)的compatible屬性進(jìn)行match
score?=?of_flat_dt_match(dt_root,?compat);
//返回與根節(jié)點(diǎn)屬性compatible屬性值最匹配的machine_desc結(jié)構(gòu)
if?(score?>?0?&&?score?
best_data?=?data;
best_score?=?score;
}
}
if?(!best_data)?{
const?char?*prop;
long size;
pr_err(" unrecognized device tree list: [ ");
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("] ");
return?NULL;
}
pr_info("Machine model: %s ",?of_flat_dt_get_machine_name());
return best_data;
}
//查找設(shè)備樹(shù)的根節(jié)點(diǎn)
unsigned long __init of_get_flat_dt_root(void)
{
//找到設(shè)備樹(shù)的設(shè)備塊起始地址
unsigned long p?=?((unsigned long)initial_boot_params)?+
be32_to_cpu(initial_boot_params->off_dt_struct);
//跳過(guò)空節(jié)點(diǎn)?
while?(be32_to_cpup((__be32?*)p)?==?OF_DT_NOP)
p?+=?4;
BUG_ON(be32_to_cpup((__be32?*)p)?!=?OF_DT_BEGIN_NODE);
p?+=?4;
//第一個(gè)節(jié)點(diǎn)就是根節(jié)點(diǎn),p指向根節(jié)點(diǎn)的屬性地址處
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?=?.;
這里是聲明了兩個(gè)變量:__arch_info_begin 和 __arch_info_end,其中等號(hào)后面的"."是location counter。在__arch_info_begin 的位置上,放置所有文件中的?".arch.info.init"?段的內(nèi)容,然后緊接著是 __arch_info_end 的位置.".arch.info.init"?段中定義了設(shè)備的machine_desc結(jié)構(gòu)。
//這里就是取出一個(gè)machine_desc結(jié)構(gòu)
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++;//指針后移,確保下次取出下一個(gè)machine_desc結(jié)構(gòu)
*match?=?m->dt_compat;
return m;//返回當(dāng)前的machine_desc結(jié)構(gòu)
}
//與設(shè)備樹(shù)根節(jié)點(diǎn)進(jìn)行match
int?__init of_flat_dt_match(unsigned long node,?const?char?*const?*compat)
{
//initial_boot_params指向設(shè)備樹(shù)起始地址
//node指向根節(jié)點(diǎn)的屬性地址
//compat為系統(tǒng)中machine_desc結(jié)構(gòu)的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字符串?dāng)?shù)組????
while?(*compat)?{
//返回compatible的匹配值
tmp?=?of_fdt_is_compatible(blob,?node,?*compat);
if?(tmp?&&?(score?==?0?||?(tmp?
score?=?tmp;//返回最大的匹配值
compat++;//下一個(gè)字符串
}
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;
//從根節(jié)點(diǎn)中讀出屬性"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;
}
(二)、解析設(shè)備樹(shù)
//unflatten_device_tree()函數(shù)來(lái)解析dtb文件,構(gòu)建一個(gè)由device_node結(jié)構(gòu)連接而成的單項(xiàng)鏈表,并使用全局變量of_allnodes指針來(lái)保存這個(gè)鏈表的頭指針。內(nèi)核調(diào)用OF提供的API函數(shù)獲取of_allnodes鏈表信息來(lái)初始化內(nèi)核其他子系統(tǒng)、設(shè)備。
void __init unflatten_device_tree(void)
{
//解析設(shè)備樹(shù),將所有的設(shè)備節(jié)點(diǎn)鏈入全局鏈表????of_allnodes中
__unflatten_device_tree(initial_boot_params,?&of_allnodes,early_init_dt_alloc_memory_arch);
//設(shè)置內(nèi)核輸出終端,以及遍歷“/aliases”節(jié)點(diǎn)下的所有的屬性,掛入相應(yīng)鏈表
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() ");
if?(!blob)?{
pr_debug("No device tree pointer ");
return;
}
pr_debug("Unflattening device tree: ");
pr_debug("magic: %08x ",?be32_to_cpu(blob->magic));
pr_debug("size: %08x ",?be32_to_cpu(blob->totalsize));
pr_debug("version: %08x ",?be32_to_cpu(blob->version));
//檢查設(shè)備樹(shù)magic
if?(be32_to_cpu(blob->magic)?!=?OF_DT_HEADER)?{
pr_err("Invalid device tree blob header ");
return;
}
//找到設(shè)備樹(shù)的設(shè)備節(jié)點(diǎn)起始地址
start?=?((void?*)blob)?+?be32_to_cpu(blob->off_dt_struct);
//第一次調(diào)用mem傳0,allnextpp傳NULL,實(shí)際上是為了計(jì)算整個(gè)設(shè)備樹(shù)所要的空間
size?=?(unsigned long)unflatten_dt_node(blob,?0,?&start,?NULL,?NULL,?0);
size?=?ALIGN(size,?4);//4字節(jié)對(duì)齊
pr_debug(" size is %lx, allocating... ",?size);
//調(diào)用early_init_dt_alloc_memory_arch函數(shù),為設(shè)備樹(shù)分配內(nèi)存空間
mem?=?dt_alloc(size?+?4,?__alignof__(struct device_node));
memset(mem,?0,?size);
//設(shè)備樹(shù)結(jié)束處賦值0xdeadbeef,為了后邊檢查是否有數(shù)據(jù)溢出
*(__be32?*)(mem?+?size)?=?cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p... ",?mem);
//再次獲取設(shè)備樹(shù)的設(shè)備節(jié)點(diǎn)起始地址
start?=?((void?*)blob)?+?be32_to_cpu(blob->off_dt_struct);
//mem為設(shè)備樹(shù)分配的內(nèi)存空間,allnextp指向全局變量of_allnodes,生成整個(gè)設(shè)備樹(shù)
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 ",?be32_to_cpup(start));
if?(be32_to_cpup(mem?+?size)?!=?0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x ",be32_to_cpup(mem?+?size));
*allnextp?=?NULL;
pr_debug(" <- unflatten_device_tree() ");
}
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指向設(shè)備樹(shù)的設(shè)備塊起始地址
tag?=?be32_to_cpup(*p);
//每個(gè)有孩子的設(shè)備節(jié)點(diǎn),其tag一定是OF_DT_BEGIN_NODE
if?(tag?!=?OF_DT_BEGIN_NODE)?{
pr_err("Weird tag at start of node: %x ",?tag);
return mem;
}
*p?+=?4;//地址+4,跳過(guò)tag,這樣指向節(jié)點(diǎn)的名稱(chēng)或者節(jié)點(diǎn)路徑名
pathp?=?*p;//獲得節(jié)點(diǎn)名或者節(jié)點(diǎn)路徑名
l?=?allocl?=?strlen(pathp)?+?1;//該節(jié)點(diǎn)名稱(chēng)的長(zhǎng)度
*p?=?PTR_ALIGN(*p?+?l,?4);//地址對(duì)齊后,*p指向該節(jié)點(diǎn)屬性的地址
//如果是節(jié)點(diǎn)名則進(jìn)入,若是節(jié)點(diǎn)路徑名則(*pathp)?==?'/'
if?((*pathp)?!=?'/')?{
new_format?=?1;
if?(fpsize?==?0)?{//fpsize=0
fpsize?=?1;
allocl?=?2;
l?=?1;
*pathp?=?'