http://blog.csdn.net/ooonebook/article/details/53206623
以下例子都以project X項目tiny210(s5pv210平台,armv7架構)為例
[uboot] uboot流程系列:
[project X] tiny210(s5pv210)上電啟動流程(BL0-BL2)
[project X] tiny210(s5pv210)從存儲設備加載代碼到DDR
[uboot] (第一章)uboot流程——概述
[uboot] (第二章)uboot流程——uboot-spl編譯流程
[uboot] (第三章)uboot流程——uboot-spl代碼流程
[uboot] (第四章)uboot流程——uboot編譯流程
[uboot] (第五章)uboot流程——uboot啟動流程
[uboot] (番外篇)global_data介紹
[uboot] (番外篇)uboot relocation介紹
建議先看《[[uboot] (番外篇)uboot relocation介紹》和《[uboot] (第四章)uboot流程——uboot編譯流程》
=================================================================================
因為在學習uboot的driver module,發現有必要先把uboot的fdt整明白點。所以這里就先學習一下fdt咯。
一、介紹
FDT,flatted device tree,扁平設備樹。熟悉Linux的人對這個概念應該不陌生。
簡單理解為將部分設備信息結構存放到device tree文件中。
uboot最終將其device tree編譯成dtb文件,使用過程中通過解析該dtb來獲取板級設備信息。
uboot的dtb和kernel中的dtb是一致的。這部分建議直接參考wowo的dtb的文章
Device Tree(一):背景介紹
Device Tree(二):基本概念
Device Tree(三):代碼分析
關於uboot的fdt,可以參考doc/README.fdt-control。
二、dtb介紹
1、dtb結構介紹
結構體如下 |
---|
DTB header |
alignment gap |
memory reserve map |
alignment gap |
device-tree structure |
alignment gap |
device-tree string |
dtb header結構如下:
結構體如下 |
---|
magic |
totalsize |
off_dt_struct |
off_dt_strings |
off_mem_rsvmap |
version |
…… |
其中,magic是一個固定的值,0xd00dfeed(大端)或者0xedfe0dd0(小端)。
以s5pv210-tiny210.dtb為例:
執行”hexdump -C s5pv210-tiny210.dtb | more”命令
@:dts$ hexdump -C s5pv210-tiny210.dtb | more
00000000 d0 0d fe ed 00 00 5a cc 00 00 00 38 00 00 58 14 |......Z....8..X.| 00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............|
可以看到dtb的前面4個字節就是0xd00dfeed,也就是magic。
綜上,我們只要提取待驗證dtb的地址上的數據的前四個字節,與0xd00dfeed(大端)或者0xedfe0dd0(小端)進行比較,如果匹配的話,就說明對應待驗證dtb就是一個合法的dtb。
2、dtb在uboot中的位置
dtb可以以兩種形式編譯到uboot的鏡像中。
-
dtb和uboot的bin文件分離
- 如何使能
需要打開CONFIG_OF_SEPARATE宏來使能。 - 編譯說明
在這種方式下,uboot的編譯和dtb的編譯是分開的,先生成uboot的bin文件,然后再另外生成dtb文件。
具體參考《[uboot] (第四章)uboot流程——uboot編譯流程》。 - 最終位置
dtb最終會追加到uboot的bin文件的最后面。也就是uboot.img的最后一部分。
因此,可以通過uboot的結束地址符號,也就是_end符號來獲取dtb的地址。
具體參考《[uboot] (第四章)uboot流程——uboot編譯流程》。
- 如何使能
-
dtb集成到uboot的bin文件內部
- 如何使能
需要打開CONFIG_OF_EMBED宏來使能。 - 編譯說明
在這種方式下,在編譯uboot的過程中,也會編譯dtb。 - 最終位置
注意:最終dtb是包含到了uboot的bin文件內部的。
dtb會位於uboot的.dtb.init.rodata段中,並且在代碼中可以通過__dtb_dt_begin符號獲取其符號。
因為這種方式不夠靈活,文檔上也不推薦,所以后續也不具體研究,簡單了解一下即可。
- 如何使能
-
另外,也可以通過fdtcontroladdr環境變量來指定dtb的地址
可以通過直接把dtb加載到內存的某個位置,並在環境變量中設置fdtcontroladdr為這個地址,達到動態指定dtb的目的。
在調試中使用。
三、uboot中如何支持fdt
1、相關的宏
-
CONFIG_OF_CONTROL
用於配置是否使能FDT。
./source/configs/tiny210_defconfig:312:CONFIG_OF_CONTROL=y -
CONFIG_OF_SEPARATE、CONFIG_OF_EMBED
配置dtb是否集成到uboot的bin文件中。具體參考上述。一般都是使用分離的方式。
2、如何添加一個dtb
以tiny210為例,具體可以參考project X項目中uboot的Git記錄:8a371676710cc0572a0a863255e25c35c82bb928
(1)在Makefile中添加對應的目標dtb
arch/arm/dts/Makefile
dtb-$(CONFIG_TARGET_TINY210) += \
s5pv210-tiny210.dtb
(2)創建對應的dts文件
arch/arm/dts/s5pv210-tiny210.dts,注意文件名要和Makefile中的dtb名一致
/dts-v1/;
/{
};
(3)打開對應的宏
configs/tiny210_defconfig
CONFIG_OF_CONTROL=y
CONFIG_OF_SEPARATE=y ##其實這里不用配,arm默認就是指定這種方式
(4)因為最終的編譯出來的dtb可能會多個,這里需要為tiny210指定一個dtb
configs/tiny210_defconfig
CONFIG_DEFAULT_DEVICE_TREE="s5pv210-tiny210"
編譯,解決一些編譯錯誤,就可以發現最終生成了u-boot.dtb文件。
通過如下“hexdump -C u-boot.dtb | more”命令可以查看我們的dtb文件,得到部分內容如下:
hlos@node4:u-boot$ hexdump -C u-boot.dtb | more
00000000 d0 0d fe ed 00 00 01 a4 00 00 00 38 00 00 01 58 |...........8...X| 00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............| 00000020 00 00 00 4c 00 00 01 20 00 00 00 00 00 00 00 00 |...L... ........| 00000030 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 |................|
四、uboot中如何獲取dtb
1、整體說明
在uboot初始化過程中,需要對dtb做兩個操作:
- 獲取dtb的地址,並且驗證dtb的合法性
- 因為我們使用的dtb並沒有集成到uboot的bin文件中,也就是使用的CONFIG_OF_SEPARATE方式。因此,在relocate uboot的過程中並不會去relocate dtb。因此,這里我們還需要自行為dtb預留內存空間並進行relocate。關於uboot relocate的內容請參考《[uboot] (番外篇)uboot relocation介紹》。
- relocate之后,還需要重新獲取一次dtb的地址。
這部分過程是在init_board_f中實現,參考《[uboot] (第五章)uboot流程——uboot啟動流程》。
對應代碼common/board_f.c
static init_fnc_t init_sequence_f[] = { ... #ifdef CONFIG_OF_CONTROL fdtdec_setup, // 獲取dtb的地址,並且驗證dtb的合法性 #endif ... reserve_fdt, // 為dtb分配新的內存地址空間 ... reloc_fdt, // relocate dtb ... }
后面進行具體函數的分析。
2、獲取dtb的地址,並且驗證dtb的合法性(fdtdec_setup)
對應代碼如下:
lib/fdtdec.c
int fdtdec_setup(void) { #if CONFIG_IS_ENABLED(OF_CONTROL) // 確保CONFIG_OF_CONTROL宏是打開的 # ifdef CONFIG_OF_EMBED /* Get a pointer to the FDT */ gd->fdt_blob = __dtb_dt_begin; // 當使用CONFIG_OF_EMBED的方式時,也就是dtb集成到uboot的bin文件中時,通過__dtb_dt_begin符號來獲取dtb地址。 # elif defined CONFIG_OF_SEPARATE /* FDT is at end of image */ gd->fdt_blob = (ulong *)&_end; //當使用CONFIG_OF_SEPARATE的方式時,也就是dtb追加到uboot的bin文件后面時,通過_end符號來獲取dtb地址。 # endif /* Allow the early environment to override the fdt address */ gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob); // 可以通過環境變量fdtcontroladdr來指定gd->fdt_blob,也就是指定fdt的地址。 #endif // 最終都把dtb的地址存儲在gd->fdt_blob中 return fdtdec_prepare_fdt(); // 在fdtdec_prepare_fdt中檢查fdt的合法性 } /* fdtdec_prepare_fdt實現如下 */ int fdtdec_prepare_fdt(void) { if (!gd->fdt_blob || ((uintptr_t)gd->fdt_blob & 3) || fdt_check_header(gd->fdt_blob)) { puts("No valid device tree binary found - please append one to U-Boot binary, use u-boot-dtb.bin or define CONFIG_OF_EMBED. For sandbox, use -d <file.dtb>\n"); return -1; // 判斷dtb是否存在,以及是否有四個字節對齊。 // 然后再調用fdt_check_header看看頭部是否正常。fdt_check_header主要是檢查dtb的magic是否正確。 } return 0; }
驗證dtb的部分可以參考《[kernel 啟動流程] (第四章)第一階段之——dtb的驗證》。
3、為dtb分配新的內存地址空間(reserve_fdt)
relocate的內容請參考《[uboot] (番外篇)uboot relocation介紹》。
common/board_f.c中
static int reserve_fdt(void) { #ifndef CONFIG_OF_EMBED // 當使用CONFIG_OF_EMBED方式時,也就是dtb集成在uboot中的時候,relocate uboot過程中也會把dtb一起relocate,所以這里就不需要處理。 // 當使用CONFIG_OF_SEPARATE方式時,就需要在這里地方進行relocate if (gd->fdt_blob) { gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32); // 獲取dtb的size gd->start_addr_sp -= gd->fdt_size; gd->new_fdt = map_sysmem(gd->start_addr_sp, gd->fdt_size); // 為dtb分配新的內存空間 debug("Reserving %lu Bytes for FDT at: %08lx\n", gd->fdt_size, gd->start_addr_sp); } #endif return 0; }
4、relocate dtb(reloc_fdt)
relocate的內容請參考《[uboot] (番外篇)uboot relocation介紹》。
common/board_f.c中
static int reloc_fdt(void) { #ifndef CONFIG_OF_EMBED // 當使用CONFIG_OF_EMBED方式時,也就是dtb集成在uboot中的時候,relocate uboot過程中也會把dtb一起relocate,所以這里就不需要處理。 // 當使用CONFIG_OF_SEPARATE方式時,就需要在這里地方進行relocate if (gd->flags & GD_FLG_SKIP_RELOC) // 檢查GD_FLG_SKIP_RELOC標識 return 0; if (gd->new_fdt) { memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size); // relocate dtb空間 gd->fdt_blob = gd->new_fdt; // 切換gd->fdt_blob到dtb的新的地址空間上 } #endif return 0; }
五、uboot中dtb解析的常用接口
gd->fdt_blob已經設置成了dtb的地址了。
注意,fdt提供的接口都是以gd->fdt_blob(dtb的地址)為參數的。
1、接口功能
以下只簡單說明幾個接口的功能,沒有深究到實現原理。先說明幾個,后續繼續補充。
另外,用節點在dtb中的偏移地址來表示一個節點。也就是節點變量node中,存放的是節點的偏移地址
-
lib/fdtdec.c中
-
fdt_path_offset
int fdt_path_offset(const void *fdt, const char *path)
eg:node = fdt_path_offset(gd->fdt_blob, “/aliases”);
功能:獲得dtb下某個節點的路徑path的偏移。這個偏移就代表了這個節點。 -
fdt_getprop
const void *fdt_getprop(const void *fdt, int nodeoffset, const char *name, int *lenp)
eg: mac = fdt_getprop(gd->fdt_blob, node, “mac-address”, &len);
功能:獲得節點node的某個字符串屬性值。 -
fdtdec_get_int_array、fdtdec_get_byte_array
int fdtdec_get_int_array(const void *blob, int node, const char *prop_name, u32 *array, int count)
eg: ret = fdtdec_get_int_array(blob, node, “interrupts”, cell, ARRAY_SIZE(cell));
功能:獲得節點node的某個整形數組屬性值。 -
fdtdec_get_addr
fdt_addr_t fdtdec_get_addr(const void *blob, int node, const char *prop_name)
eg:fdtdec_get_addr(blob, node, “reg”);
功能:獲得節點node的地址屬性值。 -
fdtdec_get_config_int、fdtdec_get_config_bool、fdtdec_get_config_string
功能:獲得config節點下的整形屬性、bool屬性、字符串等等。 -
fdtdec_get_chosen_node
int fdtdec_get_chosen_node(const void *blob, const char *name)
功能:獲得chosen下的name節點的偏移 -
fdtdec_get_chosen_prop
const char *fdtdec_get_chosen_prop(const void *blob, const char *name)
功能:獲得chosen下name屬性的值
-
-
lib/fdtdec_common.c中
-
fdtdec_get_int
int fdtdec_get_int(const void *blob, int node, const char *prop_name, int default_val)
eg: bus->udelay = fdtdec_get_int(blob, node, “i2c-gpio,delay-us”, DEFAULT_UDELAY);
功能:獲得節點node的某個整形屬性值。 -
fdtdec_get_uint
功能:獲得節點node的某個無符號整形屬性值。
-