//Based?on?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;?//next?in?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(" Error: unrecognized/unsupported ""device tree compatible list: [ ");
//找到設備樹的根節點,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("] ");
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;
//參數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! ",?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 ",?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 ",?(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 ",(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 ! ");
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 ",?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 ",?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;
}?else?if?(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, ",
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 ",?(unsigned long long)base,(unsigned long long)size);
//添加物理內存到memblock中進行管理,這里不再展開
early_init_dt_add_memory_arch(base,?size);
}
return 0;
}
2.?通過比較根節點屬性compatible值指明和目標板為同一系列的兼容的開發板名稱
//compatible制定系統的名稱。它包含","格式的字符串。準確地確定器件型號是非常重要的,并且我們需要包含廠商的名字來避免名字空間沖突。因為操作系統會使用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_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;
}
//查找設備樹的根節點
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?=?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() ");
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));
//檢查設備樹magic
if?(be32_to_cpu(blob->magic)?!=?OF_DT_HEADER)?{
pr_err("Invalid device tree blob header ");
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... ",?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... ",?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 ",?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指向設備樹的設備塊起始地址
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 ",?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?=?'