【ARM-Linux开发】内核3.x版本之后设备树机制
內(nèi)核3.x版本之后設(shè)備樹(shù)機(jī)制
Based??on??Linux??3.10.24??source??code?
參考/documentation/devicetree/Booting-without-of.txt文檔
目錄
內(nèi)核3.x版本之后設(shè)備樹(shù)機(jī)制 1
一、設(shè)備樹(shù)(Device??Tree)基本概念及作用 3
二、設(shè)備樹(shù)的組成和使用 4
①DTS和DTSI 4
②DTC 4
③DTB 5
④Bootloader 5
三、設(shè)備樹(shù)中dts、dtsi文件的基本語(yǔ)法 5
㈠chosen?node 8
㈡aliases?node 8
㈢memory?node 9
㈣ 其他節(jié)點(diǎn) 10
四、DTB相關(guān)結(jié)構(gòu) 13
㈠Header 14
㈢字符串塊 16
㈣ memory?reserve?map 16
五、解析DTB的函數(shù)及相關(guān)數(shù)據(jù)結(jié)構(gòu) 17
㈠machine_desc結(jié)構(gòu) 17
㈡設(shè)備節(jié)點(diǎn)結(jié)構(gòu)體 18
㈢屬性結(jié)構(gòu)體 19
㈣ uboot下的相關(guān)結(jié)構(gòu)體 19
六、DTB加載及解析過(guò)程 21
七、OF的API接口 32
一、設(shè)備樹(shù)(Device??Tree)基本概念及作用
在內(nèi)核源碼中,存在大量對(duì)板級(jí)細(xì)節(jié)信息描述的代碼。這些代碼充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目錄,對(duì)內(nèi)核而言這些platform設(shè)備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data絕大多數(shù)純屬垃圾冗余代碼。為了解決這一問(wèn)題,ARM內(nèi)核版本3.x之后引入了原先在Power?PC等其他體系架構(gòu)已經(jīng)使用的Flattened?Device?Tree。
“A?data?structure?by?which?bootloaders?pass?hardware?layout?to?Linux?in?a?device-independent?manner,?simplifying?hardware?probing.”開(kāi)源文檔中對(duì)設(shè)備樹(shù)的描述是,一種描述硬件資源的數(shù)據(jù)結(jié)構(gòu),它通過(guò)bootloader將硬件資源傳給內(nèi)核,使得內(nèi)核和硬件資源描述相對(duì)獨(dú)立。
Device?Tree可以描述的信息包括CPU的數(shù)量和類(lèi)別、內(nèi)存基地址和大小、總線和橋、外設(shè)連接、中斷控制器和中斷使用情況、GPIO控制器和GPIO使用情況、Clock控制器和Clock使用情況。
另外,設(shè)備樹(shù)對(duì)于可熱插拔的熱備不進(jìn)行具體描述,它只描述用于控制該熱插拔設(shè)備的控制器。
設(shè)備樹(shù)的主要優(yōu)勢(shì):對(duì)于同一SOC的不同主板,只需更換設(shè)備樹(shù)文件.dtb即可實(shí)現(xiàn)不同主板的無(wú)差異支持,而無(wú)需更換內(nèi)核文件。
注:要使得3.x之后的內(nèi)核支持使用設(shè)備樹(shù),除了內(nèi)核編譯時(shí)需要打開(kāi)相對(duì)應(yīng)的選項(xiàng)外,bootloader也需要支持將設(shè)備樹(shù)的數(shù)據(jù)結(jié)構(gòu)傳給內(nèi)核。
二、設(shè)備樹(shù)的組成和使用
設(shè)備樹(shù)包含DTC(device?tree?compiler),DTS(device?tree?source和DTB(device?tree?blob)。其對(duì)應(yīng)關(guān)系如圖1-1所示:
圖1-1?DTS、DTC、DTB之間的關(guān)系
①DTS和DTSI
.dts文件是一種ASCII文本對(duì)Device?Tree的描述,放置在內(nèi)核的/arch/arm/boot/dts目錄。一般而言,一個(gè).dts文件對(duì)應(yīng)一個(gè)ARM的machine。
由于一個(gè)SOC可能有多個(gè)不同的電路板,而每個(gè)電路板擁有一個(gè)?.dts。這些dts勢(shì)必會(huì)存在許多共同部分,為了減少代碼的冗余,設(shè)備樹(shù)將這些共同部分提煉保存在.dtsi文件中,供不同的dts共同使用。.dtsi的使用方法,類(lèi)似于C語(yǔ)言的頭文件,在dts文件中需要進(jìn)行include?.dtsi文件。當(dāng)然,dtsi本身也支持include?另一個(gè)dtsi文件。
②DTC
DTC為編譯工具,它可以將.dts文件編譯成.dtb文件。DTC的源碼位于內(nèi)核的scripts/dtc目錄,內(nèi)核選中CONFIG_OF,編譯內(nèi)核的時(shí)候,主機(jī)可執(zhí)行程序DTC就會(huì)被編譯出來(lái)。?即scripts/dtc/Makefile中
hostprogs-y :=?dtc
always :=?$(hostprogs-y)?
在內(nèi)核的arch/arm/boot/dts/Makefile中,若選中某種SOC,則與其對(duì)應(yīng)相關(guān)的所有dtb文件都將編譯出來(lái)。在linux下,make?dtbs可單獨(dú)編譯dtb。以下截取了TEGRA平臺(tái)的一部分。
ifeq?($(CONFIG_OF),y)
dtb-$(CONFIG_ARCH_TEGRA)?+=?tegra20-harmony.dtb?\
tegra30-beaver.dtb?\
tegra114-dalmore.dtb?\
tegra124-ardbeg.dtb?
③DTB
DTC編譯.dts生成的二進(jìn)制文件(.dtb),bootloader在引導(dǎo)內(nèi)核時(shí),會(huì)預(yù)先讀取.dtb到內(nèi)存,進(jìn)而由內(nèi)核解析。
④Bootloader
Bootloader需要將設(shè)備樹(shù)在內(nèi)存中的地址傳給內(nèi)核。在ARM中通過(guò)bootm或bootz命令來(lái)進(jìn)行傳遞。bootm?[kernel_addr]?[initrd_address]?[dtb_address],其中kernel_addr為內(nèi)核鏡像的地址,initrd為initrd的地址,dtb_address為dtb所在的地址。若initrd_address為空,則用“-”來(lái)代替。
三、設(shè)備樹(shù)中dts、dtsi文件的基本語(yǔ)法
DTS的基本語(yǔ)法范例,如圖3-1?所示。
它包括一系列節(jié)點(diǎn),以及描述節(jié)點(diǎn)的屬性。
“/”為root節(jié)點(diǎn)。在一個(gè).dts文件中,有且僅有一個(gè)root節(jié)點(diǎn);在root節(jié)點(diǎn)下有“node1”,“node2”子節(jié)點(diǎn),稱(chēng)root為“node1”和“node2”的parent節(jié)點(diǎn),除了root節(jié)點(diǎn)外,每個(gè)節(jié)點(diǎn)有且僅有一個(gè)parent;其中子節(jié)點(diǎn)node1下還存在子節(jié)點(diǎn)“child-nodel1”和“child-node2”。
注:如果看過(guò)內(nèi)核/arch/arm/boot/dts目錄的讀者看到這可能有一個(gè)疑問(wèn)。在每個(gè).dsti和.dts中都會(huì)存在一個(gè)“/”根節(jié)點(diǎn),那么如果在一個(gè)設(shè)備樹(shù)文件中include一個(gè).dtsi文件,那么豈不是存在多個(gè)“/”根節(jié)點(diǎn)了么。其實(shí)不然,編譯器DTC在對(duì).dts進(jìn)行編譯生成dtb時(shí),會(huì)對(duì)node進(jìn)行合并操作,最終生成的dtb只有一個(gè)root?node。Dtc會(huì)進(jìn)行合并操作這一點(diǎn)從屬性上也可以得到驗(yàn)證。這個(gè)稍后做講解。
在節(jié)點(diǎn)的{}里面是描述該節(jié)點(diǎn)的屬性(property),即設(shè)備的特性。它的值是多樣化的:
1.它可以是字符串string,如①;也可能是字符串?dāng)?shù)組string-list,如②
2.它也可以是32?bit?unsigned?integers,如cell⑧,用<>表示
3.它也可以是binary?data,如③,用[]表示
4.它也可能是空,如⑦
圖3-1??DTS的基本語(yǔ)法范例
在/arch/arm/boot/dts/目錄中有一個(gè)文件skeleton.dtsi,該文件為各ARM?vendor共用的一些硬件定義信息。以下為skeleton.dtsi的全部?jī)?nèi)容。
/?{
#address-cells?=?<1>;
#size-cells?=?<1>;
chosen?{?};
aliases?{?};
memory?{?device_type?=?"memory";?reg?=?<0?0>;?};
};
如上,屬性#?address-cells的值為1,它代表以“/”根節(jié)點(diǎn)為parent的子節(jié)點(diǎn)中,reg屬性中存在一個(gè)address值;#size-cells的值為1,它代表以“\”?根節(jié)點(diǎn)為parent的子節(jié)點(diǎn)中,reg屬性中存在一個(gè)size值。即父節(jié)點(diǎn)的#?address-cells和#size-cells決定了子節(jié)點(diǎn)的address和size的長(zhǎng)度;Reg的組織形式為reg?=?
下面列舉例子,對(duì)一些典型節(jié)點(diǎn)進(jìn)行具體描述。
㈠chosen?node
chosen?{
bootargs?=?"tegraid=40.0.0.00.00?vmalloc=256M?video=tegrafb?console=ttyS0,115200n8?earlyprintk";
};
chosen?node?主要用來(lái)描述由系統(tǒng)指定的runtime?parameter,它并沒(méi)有描述任何硬件設(shè)備節(jié)點(diǎn)信息。原先通過(guò)tag?list傳遞的一些linux?kernel運(yùn)行的參數(shù),可以通過(guò)chosen節(jié)點(diǎn)來(lái)傳遞。如command?line可以通過(guò)bootargs這個(gè)property來(lái)傳遞。如果存在chosen?node,它的parent節(jié)點(diǎn)必須為“/”根節(jié)點(diǎn)。
㈡aliases?node
aliases?{
i2c6?=?&pca9546_i2c0;
i2c7?=?&pca9546_i2c1;
i2c8?=?&pca9546_i2c2;
i2c9?=?&pca9546_i2c3;
};
aliases?node用來(lái)定義別名,類(lèi)似C++中引用。上面是一個(gè)在.dtsi中的典型應(yīng)用,當(dāng)使用i2c6時(shí),也即使用pca9546_i2c0,使得引用節(jié)點(diǎn)變得簡(jiǎn)單方便。例:當(dāng).dts??include?該.dtsi時(shí),將i2c6的status屬性賦值為okay,則表明該主板上的pca9546_i2c0處于enable狀態(tài);反之,status賦值為disabled,則表明該主板上的pca9546_i2c0處于disenable狀態(tài)。如下是引用的具體例子:
&i2c6?{
status?=?"okay";
};
㈢memory?node
memory?{
device_type?=?"memory";
reg?=?<0x00000000?0x20000000>;?/*?512?MB?*/
};
對(duì)于memory?node,device_type必須為memory,由之前的描述可以知道該memory?node是以0x00000000為起始地址,以0x20000000為結(jié)束地址的512MB的空間。
一般而言,在.dts中不對(duì)memory進(jìn)行描述,而是通過(guò)bootargs中類(lèi)似521M@0x00000000的方式傳遞給內(nèi)核。
㈣ 其他節(jié)點(diǎn)
由于其他設(shè)備節(jié)點(diǎn)依據(jù)屬性進(jìn)行描述,具有類(lèi)似的形式。接下來(lái)的部分主要分析各種屬性的含義及作用,并結(jié)合相關(guān)的例子進(jìn)行闡述。
㈠?Reg屬性
在device?node?中,reg是描述memory-mapped?IO?register的offset和length。子節(jié)點(diǎn)的reg屬性address和length長(zhǎng)度取決于父節(jié)點(diǎn)對(duì)應(yīng)的#address-cells和#size-cells的值。例:
在上述的aips節(jié)點(diǎn)中,存在子節(jié)點(diǎn)spda。spda中的中reg為<0x70000000?0x40000?>,其0x700000000為address,0x40000為size。這一點(diǎn)在圖3-1下有作介紹。
這里補(bǔ)充的一點(diǎn)是:
設(shè)備節(jié)點(diǎn)的名稱(chēng)格式node-name@unit-address,節(jié)點(diǎn)名稱(chēng)用node-name唯一標(biāo)識(shí),為一個(gè)ASCII字符串。其中@unit-address為可選項(xiàng),可以不作描述。unit-address的具體格式和設(shè)備掛載在哪個(gè)bus上相關(guān)。如:cpu的unit-address從0開(kāi)始編址,以此加1;本例中,aips為0x70000000。
㈡?compatible屬性
在①中,compatible屬性為string?list,用來(lái)將設(shè)備匹配對(duì)應(yīng)的driver驅(qū)動(dòng),優(yōu)先級(jí)為從左向右。本例中spba的驅(qū)動(dòng)優(yōu)先考慮“fsl,aips-bus”驅(qū)動(dòng);若沒(méi)有“fsl,aips-bus”驅(qū)動(dòng),則用字符串“simple-bus”來(lái)繼續(xù)尋找合適的驅(qū)動(dòng)。即compatible實(shí)現(xiàn)了原先內(nèi)核版本3.x之前,platform_device中.name的功能,至于具體的實(shí)現(xiàn)方法,本文后面會(huì)做講解。
注:對(duì)于“/”root節(jié)點(diǎn),它也存在compatible屬性,用來(lái)匹配machine?type。具體說(shuō)明將在后面給出。
㈢?interrupts屬性
設(shè)備節(jié)點(diǎn)通過(guò)interrupt-parent來(lái)指定它所依附的中斷控制器,當(dāng)節(jié)點(diǎn)沒(méi)有指定interrupt-parent時(shí),則從parent節(jié)點(diǎn)中繼承。上面例子中,root節(jié)點(diǎn)的interrupt-parent?=?<&mic>。這里使用了引用,即mic引用了②中的inrerrupt-controller?@40008000;root節(jié)點(diǎn)的子節(jié)點(diǎn)并沒(méi)有指定interrupt-controller,如ahb、fab,它們均使用從根節(jié)點(diǎn)繼承過(guò)來(lái)的mic,即位于0x40008000的中斷控制器。
若子節(jié)點(diǎn)使用到中斷(中斷號(hào)、觸發(fā)方法等等),則需用interrupt屬性來(lái)指定,該屬性的數(shù)值長(zhǎng)度受中斷控制器中#inrerrupt-controller值③控制,即interrupt屬性<>中數(shù)值的個(gè)數(shù)為#inrerrupt-controller的值;本例中#inrerrupt-controller=<2>,因而④中interrupts的值為<0x3d?0>形式,具體每個(gè)數(shù)值的含義由驅(qū)動(dòng)實(shí)現(xiàn)決定。
㈣?ranges屬性
ranges屬性為地址轉(zhuǎn)換表,這在pcie中使用較為常見(jiàn),它表明了該設(shè)備在到parent節(jié)點(diǎn)中所對(duì)用的地址映射關(guān)系。ranges格式長(zhǎng)度受當(dāng)前節(jié)點(diǎn)#address-cell、parent節(jié)點(diǎn)#address-cells、當(dāng)前節(jié)點(diǎn)#size-cell所控制。順序?yàn)閞anges=<前節(jié)點(diǎn)#address-cell,?parent節(jié)點(diǎn)#address-cells?,?當(dāng)前節(jié)點(diǎn)#size-cell。在本例中,當(dāng)前節(jié)點(diǎn)#address-cell=<1>,對(duì)應(yīng)于⑤中的第一個(gè)0x20000000;parent節(jié)點(diǎn)#address-cells=<1>,對(duì)應(yīng)于⑤中的第二個(gè)0x20000000;當(dāng)前節(jié)點(diǎn)#size-cell=<1>,對(duì)應(yīng)于⑤中的0x30000000。即ahb0節(jié)點(diǎn)所占空間從0x20000000地址開(kāi)始,對(duì)應(yīng)于父節(jié)點(diǎn)的0x20000000地址開(kāi)始的0x30000000地址空間大小。
注:對(duì)于相同名稱(chēng)的節(jié)點(diǎn),dtc會(huì)根據(jù)定義的先后順序進(jìn)行合并,其相同屬性,取后定義的那個(gè)。
四、DTB相關(guān)結(jié)構(gòu)
本節(jié)講下.dts編譯生成的dtb文件,其布局結(jié)構(gòu)。
DTB由三部分組成:頭(Header)、結(jié)構(gòu)塊(device-tree?structure)、字符串塊(string?block)。下面將詳細(xì)介紹這三部分的內(nèi)容。
㈠Header
在\kernel\include\linux\of_fdt.h文件中有相關(guān)定義
設(shè)備樹(shù)結(jié)構(gòu)塊是一個(gè)線性化的結(jié)構(gòu)體,是設(shè)備樹(shù)的主體,以節(jié)點(diǎn)的形式保存了主板上的設(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?(0x00000009)結(jié)束。在\kernel\include\linux\of_fdt.h中有相關(guān)定義,我們把這些宏稱(chēng)之為token。
(1)FDT_BEGIN_NODE?(0x00000001)。該token描述了一個(gè)node的開(kāi)始位置,緊挨著該token的就是node?name(包括unit?address)
(2)FDT_END_NODE?(0x00000002)。該token描述了一個(gè)node的結(jié)束位置。
(3)FDT_PROP?(0x00000003)。該token描述了一個(gè)property的開(kāi)始位置,該token之后是兩個(gè)u32的數(shù)據(jù),分別是length和name?offset。length表示該property?value?data的size。name?offset表示該屬性字符串在device?tree?strings?block的偏移值。length和name?offset之后就是長(zhǎng)度為length具體的屬性值數(shù)據(jù)。
(4)FDT_NOP?(0x00000004)。
(5)FDT_END?(0x00000009)。該token標(biāo)識(shí)了一個(gè)DTB的結(jié)束位置。
一個(gè)節(jié)點(diǎn)的結(jié)構(gòu)如下:
(1)節(jié)點(diǎn)開(kāi)始標(biāo)志:一般為OF_DT_BEGIN_NODE(0x00000001)。
(2)節(jié)點(diǎn)路徑或者節(jié)點(diǎn)的單元名(version<3以節(jié)點(diǎn)路徑表示,version>=0x10以節(jié)點(diǎn)單元名表示)
(3)填充字段(對(duì)齊到四字節(jié))
(4)節(jié)點(diǎn)屬性。每個(gè)屬性以宏OF_DT_PROP(0x00000003)開(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(0x00000002)。
㈢字符串塊
通過(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)在字符串塊中的偏移量。
㈣?memory?reserve?map
這個(gè)區(qū)域包括了若干的reserve?memory描述符。每個(gè)reserve?memory描述符是由address和size組成。其中address和size都是用U64來(lái)描述。
有些系統(tǒng),我們也許會(huì)保留一些memory有特殊用途(例如DTB或者initrd?image),或者在有些DSP+ARM的SOC?platform上,有些memory被保留用于ARM和DSP進(jìn)行信息交互。這些保留內(nèi)存不會(huì)進(jìn)入內(nèi)存管理系統(tǒng)。
五、解析DTB的函數(shù)及相關(guān)數(shù)據(jù)結(jié)構(gòu)
㈠machine_desc結(jié)構(gòu)
內(nèi)核將機(jī)器信息記錄為machine_desc結(jié)構(gòu)體(該定義在/arch/arm/include/asm/mach/arch.h),并保存在_arch_info_begin到_arch_info_end之間(_arch_info_begin,_arch_info_end為虛擬地址,是編譯內(nèi)核時(shí)指定的,此時(shí)mmu還未進(jìn)行初始化。它其實(shí)通過(guò)匯編完成地址偏移操作)
machine_desc結(jié)構(gòu)體用宏MACHINE_START進(jìn)行定義,一般在/arch/arm/子目錄,與板級(jí)相關(guān)的文件中進(jìn)行成員函數(shù)及變量的賦值。由linker將machine_desc聚集在.arch.info.init節(jié)區(qū)形成列表。
bootloader引導(dǎo)內(nèi)核時(shí),ARM寄存器r2會(huì)將.dtb的首地址傳給內(nèi)核,內(nèi)核根據(jù)該地址,解析.dtb中根節(jié)點(diǎn)的compatible屬性,將該屬性與內(nèi)核中預(yù)先定義machine_desc結(jié)構(gòu)體的dt_compat成員做匹配,得到最匹配的一個(gè)machine_desc。
在代碼中,內(nèi)核通過(guò)在start_kernel->setup_arch中調(diào)用setup_machine_fdt來(lái)實(shí)現(xiàn)上述功能,該函數(shù)的具體實(shí)現(xiàn)可參見(jiàn)/arch/arm/kernel/devtree.c。?
㈡設(shè)備節(jié)點(diǎn)結(jié)構(gòu)體
1.
記錄節(jié)點(diǎn)信息的結(jié)構(gòu)體。.dtb經(jīng)過(guò)解析之后將以device_node列表的形式存儲(chǔ)節(jié)點(diǎn)信息。
㈢屬性結(jié)構(gòu)體
device_node結(jié)構(gòu)體中的成員結(jié)構(gòu)體,用于描述節(jié)點(diǎn)屬性信息。
㈣?uboot下的相關(guān)結(jié)構(gòu)體
首先我們看下uboot用于記錄os、initrd、fdt信息的數(shù)據(jù)結(jié)構(gòu)bootm_headers,其定義在/include/image.h中,這邊截取了其中與dtb相關(guān)的一小部分。
fit_hdr_fdt指向DTB設(shè)備樹(shù)鏡像的頭。
lmb為uboot下的一種內(nèi)存管理機(jī)制,全稱(chēng)為logical?memory?blocks。用于管理鏡像的內(nèi)存。lmb所記錄的內(nèi)存信息最終會(huì)傳遞給kernel。這里對(duì)lmb不做展開(kāi)描述。在/include/lmb.h和/lib/lmb.c中有對(duì)lmb的接口和定義的具體描述。有興趣的讀者可以看下,所包含的代碼量不多。
六、DTB加載及解析過(guò)程
先從uboot里的do_bootm出發(fā),根據(jù)之前描述,DTB在內(nèi)存中的地址通過(guò)bootm命令進(jìn)行傳遞。在bootm中,它會(huì)根據(jù)所傳進(jìn)來(lái)的DTB地址,對(duì)DTB所在內(nèi)存做一系列操作,為內(nèi)核解析DTB提供保證。上圖為對(duì)應(yīng)的函數(shù)調(diào)用關(guān)系圖。
在do_bootm中,主要調(diào)用函數(shù)為do_bootm_states,第四個(gè)參數(shù)為bootm所要處理的階段和狀態(tài)。?
在do_bootm_states中,bootm_start會(huì)對(duì)lmb進(jìn)行初始化操作,lmb所管理的物理內(nèi)存塊有三種方式獲取。起始地址,優(yōu)先級(jí)從上往下:
1.?環(huán)境變量“bootm_low”
2.?宏CONFIG_SYS_SDRAM_BASE(在tegra124中為0x80000000)
3.?gd->bd->bi_dram[0].start
大小:
1.?環(huán)境變量“bootm_size”
2.?gd->bd->bi_dram[0].size
經(jīng)過(guò)初始化之后,這塊內(nèi)存就歸lmb所管轄。接著,調(diào)用bootm_find_os進(jìn)行kernel鏡像的相關(guān)操作,這里不具體闡述。
還記得之前講過(guò)bootm的三個(gè)參數(shù)么,第一個(gè)參數(shù)內(nèi)核地址已經(jīng)被bootm_find_os處理,而接下來(lái)的兩個(gè)參數(shù)會(huì)在bootm_find_other中執(zhí)行操作。
首先,bootm_find_other根據(jù)第二個(gè)參數(shù)找到ramdisk的地址,得到ramdisk的鏡像;然后根據(jù)第三個(gè)參數(shù)得到DTB鏡像,同檢查kernel和ramdisk鏡像一樣,檢查DTB鏡像也會(huì)進(jìn)行一系列的校驗(yàn)工作,如果校驗(yàn)錯(cuò)誤,將無(wú)法正常啟動(dòng)內(nèi)核。另外,uboot在確認(rèn)DTB鏡像無(wú)誤之后,會(huì)將該地址保存在環(huán)境變量“fdtaddr”中。
接著,uboot會(huì)把DTB鏡像reload一次,使得DTB鏡像所在的物理內(nèi)存歸lmb所管理:①boot_fdt_add_mem_rsv_regions會(huì)將原先的內(nèi)存DTB鏡像所在的內(nèi)存置為reserve,保證該段內(nèi)存不會(huì)被其他非法使用,保證接下來(lái)的reload數(shù)據(jù)是正確的;②boot_relocate_fdt會(huì)在bootmap區(qū)域中申請(qǐng)一塊未被使用的內(nèi)存,接著將DTB鏡像內(nèi)容復(fù)制到這塊區(qū)域(即歸lmb所管理的區(qū)域)
注:若環(huán)境變量中,指定“fdt_high”參數(shù),則會(huì)根據(jù)該值,調(diào)用lmb_alloc_base函數(shù)來(lái)分配DTB鏡像reload的地址空間。若分配失敗,則會(huì)停止bootm操作。因而,不建議設(shè)置fdt_high參數(shù)。
接下來(lái),do_bootm會(huì)根據(jù)內(nèi)核的類(lèi)型調(diào)用對(duì)應(yīng)的啟動(dòng)函數(shù)。與linux對(duì)應(yīng)的是do_bootm_linux。
①?boot_prep_linux
為啟動(dòng)后的kernel準(zhǔn)備參數(shù)
②?boot_jump_linux
以上是boot_jump_linux的片段代碼,可以看出:若使用DTB,則原先用來(lái)存儲(chǔ)ATAG的寄存器R2,將會(huì)用來(lái)存儲(chǔ).dtb鏡像地址。
boot_jump_linux最后將調(diào)用kernel_entry,將.dtb鏡像地址傳給內(nèi)核。
下面我們來(lái)看下內(nèi)核的處理部分:
在arch/arm/kernel/head.S中,有這樣一段:
_vet_atags定義在/arch/arm/kernel/head-common.S中,它主要對(duì)DTB鏡像做了一個(gè)簡(jiǎn)單的校驗(yàn)。
真正解析處理dbt的開(kāi)始部分,是setup_arch->setup_machine_fdt。這部分的處理在第五部分的machine_mdesc中有提及。
如圖,是setup_machine_fdt中的解析過(guò)程。
解析chosen節(jié)點(diǎn)將對(duì)boot_command_line進(jìn)行初始化。
解析根節(jié)點(diǎn)的{size,address}將對(duì)dt_root_size_cells,dt_root_addr_cells進(jìn)行初始化。為之后解析memory等其他節(jié)點(diǎn)提供依據(jù)。
解析memory節(jié)點(diǎn),將會(huì)把節(jié)點(diǎn)中描述的內(nèi)存,加入memory的bank。為之后的內(nèi)存初始化提供條件。
解析設(shè)備樹(shù)在函數(shù)unflatten_device_tree中完成,它將.dtb解析成device_node結(jié)構(gòu)(第五部分有其定義),并構(gòu)成單項(xiàng)鏈表,以供OF的API接口使用。
下面主要結(jié)合代碼分析:/drivers/of/fdt.c
總的歸納為:
①?kernel入口處獲取到uboot傳過(guò)來(lái)的.dtb鏡像的基地址
②?通過(guò)early_init_dt_scan()函數(shù)來(lái)獲取kernel初始化時(shí)需要的bootargs和cmd_line等系統(tǒng)引導(dǎo)參數(shù)。
③?調(diào)用unflatten_device_tree函數(shù)來(lái)解析dtb文件,構(gòu)建一個(gè)由device_node結(jié)構(gòu)連接而成的單向鏈表,并使用全局變量of_allnodes保存這個(gè)鏈表的頭指針。
④?內(nèi)核調(diào)用OF的API接口,獲取of_allnodes鏈表信息來(lái)初始化內(nèi)核其他子系統(tǒng)、設(shè)備等。
七、OF的API接口
OF的接口函數(shù)在/drivers/of/目錄下,有of_i2c.c、of_mdio.c、of_mtd.c、Adress.c等等
這里將列出幾個(gè)常用的API接口。
1.?用來(lái)查找在dtb中的根節(jié)點(diǎn)
unsigned?long?__init?of_get_flat_dt_root(void)
2.?根據(jù)deice_node結(jié)構(gòu)的full_name參數(shù),在全局鏈表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中根據(jù)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.?根據(jù)設(shè)備類(lèi)型查找相應(yīng)的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.?根據(jù)compatible字符串查找device_node
struct?device_node?*of_find_compatible_node(struct?device_node?*from,const?char?*type,?const?char?*compatible)
6.?根據(jù)節(jié)點(diǎn)屬性的name查找device_node
struct?device_node?*of_find_node_with_property(struct?device_node?*from,const?char?*prop_name)
7.?根據(jù)phandle查找device_node
struct?device_node?*of_find_node_by_phandle(phandle?handle)
8.?根據(jù)alias的name獲得設(shè)備id號(hào)
int?of_alias_get_id(struct?device_node?*np,?const?char?*stem)
9.?device?node計(jì)數(shù)增加/減少
struct?device_node?*of_node_get(struct?device_node?*node)
void?of_node_put(struct?device_node?*node)
10.?根據(jù)property結(jié)構(gòu)的name參數(shù),在指定的device?node中查找合適的property
struct?property?*of_find_property(const?struct?device_node?*np,const?char?*name,int?*lenp)
11.?根據(jù)property結(jié)構(gòu)的name參數(shù),返回該屬性的屬性值
const?void?*of_get_property(const?struct?device_node?*np,?const?char?*name,int?*lenp)
12.?根據(jù)compat參數(shù)與device?node的compatible匹配,返回匹配度
int?of_device_is_compatible(const?struct?device_node?*device,const?char?*compat)
13.?獲得父節(jié)點(diǎn)的device?node
struct?device_node?*of_get_parent(const?struct?device_node?*node)
14.?將matches數(shù)組中of_device_id結(jié)構(gòu)的name和type與device?node的compatible和type匹配,返回匹配度最高的of_device_id結(jié)構(gòu)
const?struct?of_device_id?*of_match_node(const?struct?of_device_id?*matches,const?struct?device_node?*node)
15.?根據(jù)屬性名propname,讀出屬性值中的第index個(gè)u32數(shù)值給out_value
int?of_property_read_u32_index(const?struct?device_node?*np,const?char?*propname,u32?index,?u32?*out_value)
16.?根據(jù)屬性名propname,讀出該屬性的數(shù)組中sz個(gè)屬性值給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.?根據(jù)屬性名propname,讀出該屬性的u64屬性值
int?of_property_read_u64(const?struct?device_node?*np,?const?char?*propname,u64?*out_value)
18.?根據(jù)屬性名propname,讀出該屬性的字符串屬性值
int?of_property_read_string(struct?device_node?*np,?const?char?*propname,const?char?**out_string)
19.?根據(jù)屬性名propname,讀出該字符串屬性值數(shù)組中的第index個(gè)字符串
int?of_property_read_string_index(struct?device_node?*np,?const?char?*propname,int?index,?const?char?**output)
20.?讀取屬性名propname中,字符串屬性值的個(gè)數(shù)
int?of_property_count_strings(struct?device_node?*np,?const?char?*propname)
21.?讀取該設(shè)備的第index個(gè)irq號(hào)
unsigned?int?irq_of_parse_and_map(struct?device_node?*dev,?int?index)
22.?讀取該設(shè)備的第index個(gè)irq號(hào),并填充一個(gè)irq資源結(jié)構(gòu)體
int?of_irq_to_resource(struct?device_node?*dev,?int?index,?struct?resource?*r)
23.?獲取該設(shè)備的irq個(gè)數(shù)
int?of_irq_count(struct?device_node?*dev)
24.?獲取設(shè)備寄存器地址,并填充寄存器資源結(jié)構(gòu)體
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.?獲取經(jīng)過(guò)映射的寄存器虛擬地址
void?__iomem?*of_iomap(struct?device_node?*np,?int?index)
24.?根據(jù)device_node查找返回該設(shè)備對(duì)應(yīng)的platform_device結(jié)構(gòu)
struct?platform_device?*of_find_device_by_node(struct?device_node?*np)
25.?根據(jù)device?node,bus?id以及父節(jié)點(diǎn)創(chuàng)建該設(shè)備的platform_device結(jié)構(gòu)
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中的節(jié)點(diǎn)掛接到of_platform_bus_type總線上,由于此時(shí)of_platform_bus_type總線上還沒(méi)有驅(qū)動(dòng),所以此時(shí)不進(jìn)行匹配
int?of_platform_bus_probe(struct?device_node?*root,const?struct?of_device_id?*matches,struct?device?*parent)
轉(zhuǎn)載于:https://www.cnblogs.com/huty/p/8517543.html
總結(jié)
以上是生活随笔為你收集整理的【ARM-Linux开发】内核3.x版本之后设备树机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C语言重难点:内存对齐和位段
- 下一篇: Linux系统编程36:多线程之线程控制