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