kernel啓動流程_DTS解析(源碼層面)
此篇博客有很多參考其他文章的內容,由於參考內容繁雜,不一一標註角標了,在末尾會貼上所有參考博客的link,如有侵權,請聯繫本人處理,謝謝。
深入,並且廣泛
-沉默犀牛
我認爲作爲初學者去學習kernel代碼的一個重要方法就是:先知道這些代碼是幹嘛的,然後再找代碼來驗證想法。這樣的探索順序會變得事半功倍,讓我們直接去看繁雜的代碼來分析出代碼用途,是非人道主義的。所以此篇博客會先用文字描述一下大致流程,再帶着讀者到代碼中去驗證。
執行流程
從dts文件的內容來看,系統平臺上掛載了很多總線,i2c,spi,uart等等,每一個總線都被描述爲一個節點,Linux啓動到kernel 入口後,會執行以下操作來加載系統平臺上的總線和設備:start_kernel() ==> setup_arch() ==> unflatten_device_tree()
,執行完unflatten_device_tree()
後,dts的節點信息被解析出來,保存到allnodes鏈表中。隨後啓動到board文件時,調用.init_machine,再調用of_platform_populate(....)
接口,加載平臺總線和平臺設備。至此,系統平臺上的所有已配置的總線和設備將被註冊到系統中。(對這句話更加正確的解釋是:此時所說的設備指的是platform device,此時的總線指的是i2c,spi等,因爲i2c總線和spi總線可以理解爲註冊在platform總線上的device)
注意:不是dtsi文件中所有的節點都會被註冊,在註冊總線和設備時,會對dts節點的狀態作一個判斷,如果節點裏面的status屬性沒有被定義,或者status屬性被定義了並且值被設爲“ok”或者“okay”,其他情況則不被註冊到系統中。
那麼其他設備,例如i2c、spi設備是怎樣註冊到系統中的呢?下面我們就以i2c設備爲例,看看Linux怎樣註冊i2c設備到系統中。以高通平臺爲例,在註冊i2c總線時,會調用到qup_i2c_probe()接口,該接口用於申請總線資源和添加i2c適配器。在成功添加i2c適配器後,會調用of_i2c_register_devices()接口。此接口會解析i2c總線節點的子節點(掛載在該總線上的i2c設備節點),獲取i2c設備的地址、中斷號等硬件信息。然後調用request_module()加載設備對應的驅動文件,調用i2c_new_device(),生成i2c設備。此時設備和驅動都已加載,於是drvier裏面的probe方法將被調用。後面流程就和之前一樣了。
代碼驗證
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer); //根據Device Tree的信息,找到最適合的machine描述符
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", 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;
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
early_fixmap_init();
early_ioremap_init();
parse_early_param();
#ifdef CONFIG_MMU
early_paging_init(mdesc);
#endif
setup_dma_zone(mdesc);
xen_early_init();
efi_init();
/*
* Make sure the calculation for lowmem/highmem is set appropriately
* before reserving/allocating any mmeory
*/
adjust_lowmem_bounds();
arm_memblock_init(mdesc);
/* Memory may have been removed so recalculate the bounds. */
adjust_lowmem_bounds();
early_ioremap_reset();
paging_init(mdesc);
request_standard_resources(mdesc);
if (mdesc->restart)
arm_pm_restart = mdesc->restart;
unflatten_device_tree(); //將DTB轉換成節點是device_node的樹狀結構
我註釋的兩行代碼就是有關DTS解析的重要代碼,如註釋所述,它們分別做了:
1.根據Device Tree的信息,找到最適合的machine描述符
2.將DTB轉換成節點是device_node的樹狀結構
現在分別仔細看一下實現過程:
1.根據Device Tree的信息,找到最適合的machine_desc,先了解一下結構體machine_desc:
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; /*!!!!本文中最重要的成員!!!!!
用於匹配DTS文件*/
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 */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
const struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
long long (*pv_fixup)(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 *);
};
接下來看一看setup_machine_fdt()函數的具體實現:
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
.l2c_aux_val = 0x0,
.l2c_aux_mask = ~0x0,
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) /*early_init_dt_verify()檢查fdt頭部的合法性,然後設置fdt全局變量以
及計算crc,賦值了一個initial_boot_params變量後邊在訪問設備樹的時候還會用到*/
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); //重要函數,下面詳細介紹
if (!mdesc) {
const char *prop;
int size;
unsigned long dt_root;
early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");
dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
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 */
}
/* We really don't want to do this, but sometimes firmware provides buggy data */
if (mdesc->dt_fixup)
mdesc->dt_fixup();
early_init_dt_scan_nodes();
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
爲了講清楚mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
這一句話的作用,我們先分析這個函數的第二個參數arch_get_next_match
:
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++;
*match = m->dt_compat;
return m;
}
這個函數很簡單,注意的是mdesc是靜態局部變量,第一次調用指向__arch_info_begin,後邊每次調用都mdesc++,如果超過了__arch_info_end就返回NULL。以上代碼說明在__arch_info_begin和__arch_info_end兩個地址之間存儲着多個machine_desc變量(也可能是一個),該函數遍歷這些變量,通過match參數返回所有machine_desc結構體的dt_compat變量指針。
那麼__arch_info_begin和__arch_info_end地址是怎麼來的呢?在arch/arm/kernel/vmlinux.lds.S連接腳本中定義了.arch.info.init段,__arch_info_begin和__arch_info_end地址分別是該段的首尾地址。
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
那麼.init.arch.info段的內容怎麼來的呢?這就要參考DT_MACHINE_START和MACHINE_END宏了,arm架構的定義在arch/arm/include/asm/mach/arch.h文件,如下所示:
define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
#endif
從該宏代碼看出它定義了一個machine_desc類型的靜態局部變量,該變量位於.arch.info.init段中。
我們已經知道了arch_get_next_match
指針的具體實現了,現在繼續看of_flat_dt_match_machine。
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 = of_get_flat_dt_root();
/*遍歷.arch.info.init段中所有的dt_compat變量, 然後通過of_flat_dt_match計算一個分數,
並且尋找那個score最小的*/
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
if (!best_data) {
const char *prop;
int 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;
}
of_flat_dt_match_machine的其餘部分代碼都是出錯處理及打印,現在我們看of_flat_dt_match的實現,該函數僅僅是直接調用of_fdt_match而已,不同的是增加了initial_boot_params參數(還記得我們說過前邊說過的這個變量的初始化吧,其實這就是內核中的一個簡單封裝而已)。
int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
return of_fdt_match(initial_boot_params, node, compat);
}
int of_fdt_match(const void *blob, unsigned long node,
const char *const *compat)
{
unsigned int tmp, score = 0;
if (!compat)
return 0;
while (*compat) {
tmp = of_fdt_is_compatible(blob, node, *compat);
if (tmp && (score == 0 || (tmp < score)))
score = tmp;
compat++;
}
return score;
}
這個函數就是遍歷compat數組的每一個字符串,然後通過of_fdt_is_compatible函數計算匹配度(以最小的數值作爲最終的結果)。代碼到這個地方已經很好理解了,compat中的數據來自內核的.arch.info.init段,這個段表示內核支持的平臺,blob是設備樹起始地址,通過node節點指定根節點的compatible屬性,然後計算匹配度。還記得我們前邊說過的compatible屬性包含多個字符串,從前向後範圍越來越大,優先匹配前邊的,這個地方代碼計算分數(score變量)就是這個目的。
繼續看一下of_fdt_is_compatible()函數:
int of_fdt_is_compatible(const void *blob,
unsigned long node, const char *compat)
{
const char *cp;
int cplen;
unsigned long l, score = 0;
cp = fdt_getprop(blob, node, "compatible", &cplen);
if (cp == NULL)
return 0;
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;
}
這個函數就是比較compatible屬性值了,等到while中的if條件滿足,返回score,即代表找到了最匹配的DTS,再總結一下就是:內核通過"compatible"屬性找到對應的平臺描述信息,按照範圍從小到大盡量匹配範圍最小的,如果匹配不到,那麼說明內核不支持該平臺,系統將在初始化的時候就出錯。
那麼到這裏,我們就整個完成了我們這一小節的主題:根據Device Tree的信息,找到最適合的machine_desc。
2.將DTB轉換成節點是device_node的樹狀結構:
在此之前,我們先了解一下device_node結構體:
struct device_node {
const char *name;----------------------device node name
const char *type;-----------------------對應device_type的屬性
phandle phandle;-----------------------對應該節點的phandle屬性
const char *full_name; ----------------從“/”開始的,表示該node的full path
struct property *properties;-------------該節點的屬性列表
struct property *deadprops; ----------如果需要刪除某些屬性,kernel並非真的刪除,而是掛入到deadprops的列表
struct device_node *parent;------parent、child以及sibling將所有的device node連接起來
struct device_node *child;
struct device_node *sibling;
struct device_node *next; --------通過該指針可以獲取相同類型的下一個node
struct device_node *allnext;-------通過該指針可以獲取node global list下一個node
struct proc_dir_entry *pde;--------開放到userspace的proc接口信息
struct kref kref;-------------該node的reference count
unsigned long _flags;
void *data;
};
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);
}
分析以上代碼,在unflatten_device_tree()
中,調用函數__unflatten_device_tree()
,參數initial_boot_params
指向Device Tree在內存中的首地址,of_root
在經過該函數處理之後,會指向根節點,early_init_dt_alloc_memory_arch
是一個函數指針,爲struct device_node和struct property結構體分配內存的回調函數(callback)。
在__unflatten_device_tree()
函數中,兩次調用unflatten_dt_node()
函數,第一次是爲了得到Device Tree轉換成struct device_node和struct property結構體需要分配的內存大小,這時候可能遞歸調用自身(如有子node的話)。第二次調用纔是具體填充每一個struct device_node和struct property結構體。
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");
}
可以看到主要起作用的函數是 unflatten_dt_node()
,接下來進入這個函數看一下:
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;
}
至此已經將DTB轉換成節點是device_node的樹狀結構了。
下一篇文章會補充device_node是如何成爲註冊到各個bus上的device
參考文章:
我眼中的linux設備數系列 :https://www.cnblogs.com/targethero/p/5086085.html
Device Tree(三)代碼分析:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html
宋牧春:Linux設備樹文件結構與解析深度分析:http://www.360doc.com/content/18/0926/15/60139132_789843621.shtml
linux device tree源代碼解析 :https://www.cnblogs.com/sky-heaven/p/6742033.html
Linux DTS(Device Tree Source)設備樹詳解之二(dts匹配及發揮作用的流程篇):https://blog.csdn.net/radianceblau/article/details/74722395
(DT系列五)Linux kernel 是怎麼將 devicetree中的內容生成plateform_device:https://blog.csdn.net/lichengtongxiazai/article/details/38942033