(二)從解析DTS到創建device_DTS的匹配和解析(結合源碼)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章