u-boot fdt的應用


簡單的說,如果要使用Device Tree,首先用戶要了解自己的硬件配置和系統運行參數,並把這些信息組織成Device Tree source file。通過DTC(Device Tree Compiler),可以將這些適合人類閱讀的Device Tree source file變成適合機器處理的Device Tree binary file(有一個更好聽的名字,DTB,device tree blob)。在系統啟動的時候,boot program(例如:firmware、bootloader)可以將保存在flash中的DTB copy到內存(當然也可以通過其他方式,例如可以通過bootloader的交互式命令加載DTB,或者firmware可以探測到device的信息,組織成DTB保存在內存中),並把DTB的起始地址傳遞給client program(例如OS kernel,bootloader或者其他特殊功能的程序)。對於計算機系統(computer system),一般是firmware->bootloader->OS,對於嵌入式系統,一般是bootloader->OS。

1. u-boot對fdt(flattened device tree)的支持

實現:只要加入

#define CONFIG_OF_LIBFDT               /* Device Tree support */

重新編譯u-boot,就可以實現對device tree的支持。

2、dtb在uboot中的位置

dtb可以以兩種形式編譯到uboot的鏡像中。

    • dtb和uboot的bin文件分離

    • dtb集成到uboot的bin文件內部

      • 如何使能 
        需要打開CONFIG_OF_EMBED宏來使能。
      • 編譯說明 
        在這種方式下,在編譯uboot的過程中,也會編譯dtb。
      • 最終位置 
        注意:最終dtb是包含到了uboot的bin文件內部的。 
        dtb會位於uboot的.dtb.init.rodata段中,並且在代碼中可以通過__dtb_dt_begin符號獲取其符號。 
        因為這種方式不夠靈活,文檔上也不推薦,所以后續也不具體研究,簡單了解一下即可。
    • 另外,也可以通過fdtcontroladdr環境變量來指定dtb的地址 
      可以通過直接把dtb加載到內存的某個位置,並在環境變量中設置fdtcontroladdr為這個地址,達到動態指定dtb的目的。

 

u-boot中如何獲取dtb

1、整體說明

在u-boot初始化過程中,需要對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; }

驗證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; }

 

 

boot中對FDT的支持

如果要使用tag,需boot和kernel同時支持,那么用FDT取代TAG也同樣如此.

uboot中對FDT的支持:

1) uboot代碼中已經對fdt有支持,可以通過開啟CONFIG_OF_LIBFDT配置,實現FDT支持,如此會省掉一部分工作。例如:fdt調試命令可以直接使用。
2) 如前所述dtb文件被燒錄到flash上。在Uboot引導內核啟動之前會將dtb拷貝到內存上,並對dtb的內容進行修改,這一步是為了支持對環境變量的動態修改。

以下介紹u-boot中如何修改dts文件中定義的內容:重點觀察example_plat_version.dts文件里chosen節點中bootargs參數的修改,關鍵函數fdt_chosen:

int fdt_chosen(void *fdt, int force)
{
  int nodeoffset;
  char *str;
  const char *path;
  nodeoffset= fdt_path_offset(fdt, "/chosen");
  if(nodeoffset<0)
  {
    nodeoffset=fdt_add_subnode(fdt,0,"chosen");
    if(nodeoffset<0)
    {
      return nodeoffset;
    }
  }
  str = getenv("bootargs");
  path = fdt_set_prop(fdt,nodeoffset,"bootargs",str,strlen(str)+1);
  return 0;
} 

上述函數的fdt_path_offset(fdt,”chosen”)獲取的是dts文件中的:


chosen{
bootargs = "console=ttyS0,115200 mem=100M root=/dev/mtdblock0 init=/linuxrc";
};
要想修改bootargs則調用fdt_setprop_string;
Ps:其中fdt_setprop_string等接口是FDT提供的標准接口,boot和kernel源碼中都可調用。
3)將dtb從flash拷貝到dram上。假設拷貝到dram=0x200100的地址,則uboot跳轉到kernel時再通過cpu的通用寄存器r2告知系統這個地址,這一點與tag沒有分別。至此完成boot下對fdt的支持。

Uboot下調試FDT

Uboot支持標准的fdt命令修改dtb的內容。

1 舉例:常用調試命令
1>fdt list :列出系統中所有節點。即上面dts文件中的節點。

#fdt list
/{
#address-cells = <0x1000000>;
#size-cells = <0x1000000>;
compatible = "example,**";
model = "example plat evm Board";
chosen{
};
aliases{
};
memory{
};
cpus{
};
apb@80000000{
};
};
2>列出aliases節點:
#fdt list /aliases
aliases{
serial0 = "/apb@80000000/uart@8005000";
nand = "/apb@90000000/nand@9001000";
i2c0 = "/ahb@70000000/i2c@7001000";
};
3>查看boot分區信息:
#fdt list nand
nand@9001000{
compatible = "example,nand";
#address-cells = <0x1000000>;
#size-cells = <0x1000000>;
partition@0{
};
partition@1{
};
};
#fdt list nand/partition@0
partition@0{
reg = <0x0 0x40000>;
label = "uboot";
};
2 修改dtb舉例:
#fdt list /memory
memory{
device_type = "memory";
reg = <0x2000,0xe007>;
};
#fdt set /memory reg <0x200000 0x6e00000>
#
#fdt list /memory
memory{
device_type = "memory";
reg = <0x2000,0xe006>;
};
1>修改前 reg= <0x2000 0xe007>(字節序反了):實際表示linux 系統內存從0x200000開始大小為0x7e00000
2>修改后 reg=<0x2000 0xe006>:實際表示linux 系統內存從0x200000開始大小為0x6e00000

 

fdt調試和驗證的工具方法:

驅動開發時與設備注冊、設備樹相關的調試方法,彼此間沒有優先級之分,每種方法不一定是最優解,但可以作為一種debug查找問題的手段,快速定位問題原因。例如在芯片驗證時,不同時鍾頻率下系統啟動情況摸底時,U-Boot fdt命令可以方便快捷的幫助我們完成這個實驗。

#1. dtc工具

dtc可以使用宿主機提供的亦可以使用kernel提供的。這個工具是將已編譯的dtb文件反匯編。

 

 

#2. U-Boot fdt command

驅動代碼在debug期間,若希望更改外設模塊的設備樹屬性時,在不改變存儲設備中dtb文件的前提下,進入到U-Boot的命令行界面,通過U-Boot的fdt命令來實現。例如修改外設時鍾源、修改外設時鍾名、status屬性等。為了使U-Boot支持fdt命令需要打開CONFIG_OF_LIBFDT。

 

 

 

 

1、fdtdec_setup 函數

 fdtdec_setup:如果u-boot中使用設備樹,則需處理一些相關工作
第一篇文章查看.config配置文件知道關於設備樹就有下面幾個定義:

CONFIG_OF_CONTROL=y
CONFIG_OF_SEPARATE=y
CONFIG_OF_TRANSLATE=y
CONFIG_OF_LIBFDT=y

所以去掉宏定義之后的函數定義就是:

/* file: lib/fdtdec.c */
int fdtdec_setup(void)
{
int ret;

/* Allow the board to override the fdt address. */
gd->fdt_blob = board_fdt_blob_setup();

/* Allow the early environment to override the fdt address */
gd->fdt_blob = map_sysmem
(env_get_ulong("fdtcontroladdr", 16,
(unsigned long)map_to_sysmem(gd->fdt_blob)), 0);
ret = fdtdec_prepare_fdt();
if (!ret)
ret = fdtdec_board_setup(gd->fdt_blob);
return ret;
}

函數先是調用board_fdt_blob_setup來設置gd結構體的fdt_blob成員,簡化宏定義之后函數如下:

/* file: lib/fdtdec.c */
__weak void *board_fdt_blob_setup(void)
{
void *fdt_blob = NULL;
/* FDT is at end of image */
fdt_blob = (ulong *)&_end;
return fdt_blob;
}

由前面的鏡像組成可以知道u-boot設備樹文件是放在u-boot.bin的末尾,所以取它的地址返回即可。 后面繼續調用env_get_ulong從環境變量中獲取u-boot設備樹的地址,如果該環境變量沒有被設置則返回原本的默認值(unsigned long)map_to_sysmem(gd->fdt_blob)。最終繼續調用fdtdec_prepare_fdt函數來打印一些信息:

/* file: lib/fdtdec.c */
int fdtdec_prepare_fdt(void)
{
if (!gd->fdt_blob || ((uintptr_t)gd->fdt_blob & 3) ||
fdt_check_header(gd->fdt_blob)) {
#ifdef CONFIG_SPL_BUILD
puts("Missing DTB\n");
#else
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");
# ifdef DEBUG
if (gd->fdt_blob) {
printf("fdt_blob=%p\n", gd->fdt_blob);
print_buffer((ulong)gd->fdt_blob, gd->fdt_blob, 4,
32, 0);
}
# endif
#endif
return -1;
}
return 0;
}

節點node

{}包圍起來的結構稱之為節點,dts中最開頭的/ {},稱為根節點。
在節點中,以 key = value 代表節點屬性。
樹中每個表示一個設備的節點都需要一個 compatible 屬性。

節點名 name

  • 節點名稱:每個節點名格式為:<name>[@<unit_address>],其中:
    • :設備名,就是一個不超過31位的簡單 ascii 字符串,節點的命名應該根據它所體現的是什么樣的設備。
    • <unit_address> :設備地址,用來唯一標識一共節點。沒有指定<unit_address>時,同級節點命名必須是唯一的;但只要<unit_address>不同,多個節點也可以使用一樣的通用名稱。

下面是典型節點名的寫法:

/ {
        model = "Freescale i.MX23 Evaluation Kit";
        compatible = "fsl,imx23-evk", "fsl,imx23";

        memory {
                reg = <0x40000000 0x08000000>;
        };
        // 注意這里
        apb@80000000 {
                ...
        };
}

上面的節點名是apb,節點路徑是/apb@80000000 ,這點要注意,因為根據節點名查找節點的API的參數是不能有"@xxx"這部分的。

Linux中的設備樹還包括幾個特殊的節點:比如chosen,chosen節點不描述一個真實設備,而是用於firmware傳遞一些數據給OS,比如bootloader傳遞內核啟動參數給內核

/include/ "zynq-7000.dtsi"

/ {
        model = "Zynq ZC702 Development Board";
        compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";

        ...

        chosen {
                bootargs = "console=ttyPS1,115200 earlyprintk";
        };
};

引用

當我們找一個節點的時候,我們必須書寫完整的節點路徑,這樣當一個節點嵌套比較深的時候就不是很方便。所以,設備樹允許我們用下面的形式為節點標注引用(起別名),借以省去冗長的路徑。
標號引用常常還作為節點的重寫方式,用於修改節點屬性。

  • 格式:
    • 聲明別名: 別名 : 節點名
    • 訪問 : &別名

編譯設備樹的時候,相同的節點的不同屬性信息都會被合並,相同節點的相同的屬性會被重寫(覆蓋前值),使用引用可以避免移植者四處找節點,直接在板級.dts增改即可。

/include/ "imx53.dtsi"

/ {
        model = "Freescale i.MX53 Automotive Reference Design Board";
        compatible = "fsl,imx53-ard", "fsl,imx53";

        memory {
                reg = <0x70000000 0x40000000>;
        };

        eim-cs1@f4000000 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "fsl,eim-bus", "simple-bus";
                reg = <0xf4000000 0x3ff0000>;

                lan9220@f4000000 {
                        compatible = "smsc,lan9220", "smsc,lan9115";
                        reg = <0xf4000000 0x2000000>;
                        phy-mode = "mii";
                        interrupt-parent = <&gpio2>; // 直接使用引用

                        vdd33a-supply = <&reg_3p3v>;
                };
        };

        regulators {
                compatible = "simple-bus";

                reg_3p3v: 3p3v {                     // 定義一個引用
                        compatible = "regulator-fixed";
                        regulator-name = "3P3V";
                };
        };

        ...
        // 引用一個節點,新增/修改其屬性。
        &reg_3p3v {
            regulator-always-on;
        }

 

 

 

 

1.2 設備樹語法

在上一小節,我們將設備樹的概念有基本的認知,下面更重要的就是DTS語法了,這里我么結合實際的代碼區理解設備樹語法。

1.2.1 #include語法

DTS中#include語法和C語言中類似,支持將包裹的文件直接放置在#include位置從而訪問到其它文件的數據,如官方設備樹內使用的

#include "nuc9xx.dtsi"

另外,也可以用來包含dts文件,如下

#include "imx6ull-14x14-evk.dts"


解析:
(1) 根節點
compatible: 內核通過root節點"/"的compatible屬性來判斷它啟動的是哪個machine。
#address-cells : 子結點(reg屬性)需要多少個cell描述地址。
#size-cells : 子結點(reg屬性)需要多少個cell描述長度。
interrupt-parent : 標示該節點屬於哪個中斷控制器,如果沒有該屬性,則依附於父節點。

(2) cpus節點
#address-cells : 同上
#size-cells : 同上

(3) cpu節點
@unit-address: 可選項,設備地址,節點名相同時可以通過這個來區分不同節點。unit-address地址也經常在其對應的reg屬性中給出。
reg : region,描述設備地址
格式: reg = <address1 length1 [address2 length2] [address3 length3]>

(4) serial節點
compatible: 外設節點上的compatible屬性用於驅動和設備的綁定(匹配)
reg: 外設基地址和偏移量 ,比如:reg = <0x101f1000 0x1000 >
interrupts: 中斷號和標識(上升沿,下降沿等), 里面多少個值要根據中斷控制器的#interrupt-cells屬性來決定。而#interrupt-cells屬性值要由中斷控制器的類型決定。

中斷控制器類型:
GIC: Generic Interrupt Controller(通用中斷控制器)
中斷類型,中斷號,標識(上升沿,下降沿等)
VIC : Vectored Interrupt Controller(向量中斷控制器)
中斷號
NVIC:Nested Vectored Interrupt Controller(內嵌向量中斷控制器)
中斷號,中斷優先級

參考: Documentation\devicetree\bindings\interrupt-controller

(5) interrupt-controller節點
compatible: 中斷控制器類型,查看上面路徑下的文件來獲知。
reg:同上
interrupt-controller:空屬性,用來聲明這個節點接收中斷信號。
#interrupt-cells:標識該控制器需要幾個cell來描述中斷,其實就是決定了interrupts屬性需要幾個cell

(6) external-bus節點
ranges: 地址轉換表,每一行都包含子地址、父地址、在子地址空間內的區域大小。
ranges屬性值為空的話,表示1:1映射。

ranges屬性值的格式 <local地址, parent地址, size>
local地址的個數取決於當前含有ranges屬性的節點的#address-cells屬性的值。
parent地址的個數取決於父節點的#address-cells的值。
size取決於當前含有ranges屬性的節點的#size-cells屬性的值。

(7) rtc節點
reg: i2c設備地址




 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM