1. 程式人生 > >[uboot] (番外篇)uboot之fdt介紹

[uboot] (番外篇)uboot之fdt介紹

以下例子都以project X專案tiny210(s5pv210平臺,armv7架構)為例

建議先看《[[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檔案,得到部分內容如下:

[email protected]: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的地址。

對應程式碼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;
}

3、為dtb分配新的記憶體地址空間(reserve_fdt)

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)

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的某個無符號整形屬性值。