U-Boot移植與總結 P2020篇

本文章首發地址:https://www.lgfccl.xyz 隨時down 機,轉載註明出處謝謝

UBOOT 概述

1 U-Boot簡介

U-Boot,全稱 Universal Boot Loader,是遵循 GPL 條款的開放源碼項目。從 FADSROM、8xxROM、PPCBOOT 逐步發展演化而來。其源碼目錄、編譯形式 與 Linux 內核很相似,事實上,不少 U-Boot 源碼就是相應的 Linux 內核源程序的簡化,尤其是一些設備的驅動程序,如網絡設備、flash、ddr等,這從 U-Boot 源碼的註釋中能體現這一點。

U-Boot 不僅僅支持嵌入式 Linux 系統的引導,它還支持如NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS 嵌入式OS。硬件支持包括 MIPS、 x86、ARM、Powerpc、Zynq 等諸多常用處理器。 這兩個特點正是 U-Boot 項目的開發目標,即支持儘可能多的嵌入式處理器和嵌 入式操作系統。
這個項目起源於 Magnus Damm.[1] 在 8xx PowerPC 架構下寫的引導加載程序8xxROM。1999 年十月,Wolfgang Denk 將項目移轉到 SourceForge.net,但 SourceForge.net 不允許數字開頭的項目名稱,所以改名爲 PPCBoot。PPCBoot 在 2000年 7 月 19 日第一次公開發布 0.4.1 版。也正是因此,U-Boot對PPC支持最爲豐富。

  • 選擇 u-boot 的理由:

​ 開放源碼
​ 支持多種嵌入式OS,如 Linux、NetBSD、VxWorks、QNX等
​ 支持多種處理器架構,如PowerPC、ARM、x86、MIPS、XScale、Zynq(ARM);
​ 較高的可靠性和穩定性;
​ 豐富的設備驅動源碼,如串口、以太網、SDRAM、FLASH等;
​ 較爲豐富的開發調試文檔與強大的網絡技術支持;

2 目錄結構

  • board 目標板相關文件,主要包含SDRAM、FLASH驅動;
  • common 獨立於處理器體系結構的通用代碼,如內存大小探測與故障檢測;
  • cpu 與處理器相關的文件。如mpc8xx子目錄下含串口、網口、LCD驅動及中斷初始化等文件;
  • driver 通用設備驅動,如CFI FLASH驅動(目前對INTEL FLASH支持較好)
  • doc U-Boot的說明文檔;
  • examples可在U-Boot下運行的示例程序;如hello_world.c,timer.c;
  • include U-Boot頭文件;尤其configs子目錄下與目標板相關的配置頭文件是移植過程中經常要修改的文件;
  • lib_xxx 處理器體系相關的文件,如lib_ppc, lib_arm目錄分別包含與PowerPC、ARM體系結構相關的文件;
  • net 與網絡功能相關的文件目錄,如bootp,nfs,tftp;
  • post 上電自檢文件目錄。尚有待於進一步完善;
  • rtc RTC驅動程序
  • tools 用於創建U-Boot S-RECORD和BIN鏡像文件的工具;


以上關於目錄結構的說明只適應u-boot-2010.06之前版本。u-boot-2010.06之後目錄結構改變
cpu與lib_arch合二爲一,命名arch
增加include folder
分離出通用庫文件夾lib

3 主要功能

U-Boot 可支持的主要功能如下:

  • 系統引導,Linux 支持最爲強勁;
  • 支持NFS掛載、RAMDISK(壓縮或非壓縮)形 式的根文件系統;
  • 支持NFS掛載、從FLASH中引導壓縮或非壓縮系統內核;
  • 支持目標板環境參數多種存儲方式,如FLASH、NVRAM、EEPROM;
  • CRC32 校驗,可校驗 FLASH 中內核、RAMDISK 鏡像文件是否完好;
  • 設備驅動 串口、SDRAM、FLASH、以太網 、USB、PCMCIA、PCI、RTC 等驅動支持;
  • 上電自檢功能 SDRAM、FLASH 大小自動檢測;SDRAM 故障檢測;CPU型號;
  • 特殊功能 XIP 內核引導;

二 UBOOT移植分析與流程

大多數BootLoader都分爲stage1和stage2兩大部分,U-boot也不例外。依賴於cpu體系結構的代碼(如設備初始化代碼等)通常都放在stage1且可以用彙編語言來實現,而stage2則通常用C語言來實現,這樣可以實現複雜的功能,而且有更好的可讀性和移植性。
1、 stage1(start.s代碼結構)
​ U-boot的stage1代碼通常放在start.s文件中,它用彙編語言寫成,其主要代碼部分如下:
(1) 定義入口。由於一個可執行的image必須有一個入口點,並且只能有一個全局入口,通常這個入口放在rom(Flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本來完成。
(2)設置異常向量(exception vector)。
(3)設置CPU的速度、時鐘頻率及中斷控制寄存器
(4)初始化內存控制器 。
(5)將rom中的程序複製到ram中。
(6)初始化堆棧
(7)轉到ram中執行,該工作可使用指令ldrpc來完成。
2、 stage2(C語言代碼部分)
lib_arm/board.c中的start armboot是C語言開始的函數,也是整個啓動代碼中C語言的主函數,同時還是整個u-boot(armboot)的主函數,該函數主要完成如下操作:
(1)調用一系列的初始化函數。
(2)初始化flash設備。
(3)初始化系統內存分配函數。
(4)如果目標系統擁有nand設備,則初始化nand設備。
(5)如果目標系統有顯示設備,則初始化該類設備。
(6)初始化相關網絡設備,填寫ip,c地址等。
(7)進入命令循環(即整個boot的工作循環),接受用戶從串口輸入的命令,然後進行相應的工作。

1 獲取U-Boot源代碼

如下舉例,由於此服務器基本處於爹媽不維護狀態,所以建議直接瀏覽器下載壓縮包,或者wget下載,如果用git clone下載很慢。

  • 官方版本:這個版本是U-Boot開源社區維護的。
  • Powerpc私有Git:這個版本是Freescale(被NXP收購)自己維護的目前已經不更新,不過是最好的選擇。
  • Yocto SDK 工具:bitbake -c compile -f u-boot 獲取,不建議 SDK佔用資源大(50G),且編譯基於bitbake,bitbake又基於python開發效率不高,對於嵌入式編成,優選利用交叉編譯環境。
  > wget http://git.freescale.com/git/cgit.cgi/auto/u-boot.git/snapshot/u-boot-2016.01_bsp14.0.tar.gz
  > tar xvf u-boot-2016.01_bsp14.0.tar.gz
  > cd u-boot-2016.01_bsp14.0
  > ls -alh 
  • 如上獲取了官方支持的最新的U-Boot源代碼,接下來獲取交叉編譯工具(不通內核架構工具不一樣)方式也有3種:
  • Yocto SDK 工具: 可以通過官方工具進行生成,具體方式按照官方[User Guide]([https://freescaleesd.flexnetoperations.com/337170/767/8926767/QorIQ%20SDK%20v1.8%20Info%20Center.pdf?ftpRequestID=7521903397&server=freescaleesd.flexnetoperations.com&dtm=DTM20200114145812NTQ5NzQwNTAy&authparam=1579042692_3dc60c9668a2e35e5b8887f88c5f05e4&ext=.pdf](https://freescaleesd.flexnetoperations.com/337170/767/8926767/QorIQ SDK v1.8 Info Center.pdf?ftpRequestID=7521903397&server=freescaleesd.flexnetoperations.com&dtm=DTM20200114145812NTQ5NzQwNTAy&authparam=1579042692_3dc60c9668a2e35e5b8887f88c5f05e4&ext=.pdf))自行生成即可。
   source ./fsl-setup-poky -h 查看支持的參考Demo板卡
   source ./fsl-setup-poky -m 選擇你的處理器如P2020RDB 
   bitbake fsl-toolchain
   cd build_<machine>_release/tmp/deploy/sdk
   ./fsl-networking-eglibc-<host-system>-<core>-toolchain-<release>.sh

默認安裝/opt/下直接允許即可

  • 尋求技術支持索要:最直接,最快速,推薦。

  • 我分享在github的SDK編譯出的文件,避免下載Yocto SDK工具。

    wget https://raw.githubusercontent.com/fafactx/powerpc-corss-toolchain-all/master/fsl-networking-eglibc-x86_64-ppce500v2-toolchain-QorIQ-SDK-V1.6.sh

2 P2020芯片U-Boot配置

2.1 獲取Demo板卡參考配置

P2020芯片原Freescale出過兩個Demo板卡,我們取其中P2020RDB-PC 進行參考即可

  > cd u-boot-2016.01_bsp14.0
  > source /opt/fsl-networking/QorIQ-SDK-V1.6/environment-setup-ppce500v2-fsl-linux-gnuspe;
  > unset LDFLAGS;
  > make distclean;make P2020RDB-PC_config;make -j8;
  > powerpc-fsl-linux-gnuspe-objdump -j .text -l -C -S u-boot >uboot.map;

到此,你應該可以獲得bin,大小根據頭文件include/configs/p1_p2_rdb_pc.h裏的 #define CONFIG_SYS_TEXT_BASE 0xeff40000決定,這個地址到0xefffffff正好是768K大小。這裏你看你會有疑問,e500V2架構Core第一條指令不是去0xFFFFFFFC去取指令嗎,且一般是跳轉到前4K Page Size處,沒錯這裏就是由於兩個default決定的了。
請參考:P2020RM的4.3.3章節:Boot page tranlation,E500CORERM 1.5.1 章節 Initial Instruction Fetch
​ 我們這裏不展開討論TLB以及MMU,LAW的概念,後續我會根據時間繼續進行介紹。

2.2 自定義板卡配置

如上,你已經獲取了u-boot.bin,通過code warrior jtag或者usb code warrior jtag 燒寫程序到Nor的0xfff40000地址即可啓動,可能是亂碼,根據至於code warrior 的永久license和如何根據自己Nor Flash生成tcl,生成Nor的自定義型號,這裏不展開討論,後續看時間專門出一片code warrior 工具使用介紹(自己看help也夠用了),不過這個cw工具官方已經不在維護,建議不要過度依賴。cw的後臺server是ccs服務程序,這個程序又可以寫一片博客了,看時間進行總結吧。

  • 這裏指列舉幾個看你修改的文件,請自行修改:

    Include/configs/p1_p2_rdb_pc.h —>修改sys clk,ddr clk,nor law reg,ddr2(3) 參數等。

    • board/freescale/p1_p2_rdb/下的law.c tab.c p1_p2_rdb.c 這三個文件就是tlb和law修改,按需修改。
    • 期間可能需要修改Makefile,如編譯遇到libgcc問題,請自行google 搜nxp論壇即可。
  • 到此U-Boot P2020自定義板卡移植結束,當然前提是你板子的硬件設計已經OK,比如啓動位置,各種clk配置陪好。

3 U-Boot 啓動分析

本部分涉及的代碼非常多,所以只能對關鍵代碼進行解釋,還有更多部分涉 及到 POWERPC 體系結構、MMU 工作原理等,這裏對這些內容不再敘述,但是 使用U-Boot過程中,上述內容是必備的,可以參考《POWERPC 體系結構》與 《MMU 工作原理》等手冊相關內容。

3.1 入口函數

打開文件,vim arch/powerpc/cpu/mpc85xx/start.S 文件開始定義了:
CPU 復位後做了三件事情,1清寄存器、2設置異常向量表、3設置更多更大TLB供CPU看見地址:

/*
  * e500 Startup -- after reset only the last 4KB of the effective
  * address space is mapped in the MMU L2 TLB1 Entry0. The .bootpg
  * section is located at THIS LAST page and basically does three
  * things: clear some registers, set up exception tables and
  * add more TLB entries for 'larger spaces'(e.g. the boot rom) to
  * continue the boot procedure.

  * Once the boot rom is mapped by TLB entries we can proceed
  * with normal startup.
  *
  */

     .section .bootpg,"ax"
     .globl _start_e500

 _start_e500:
 /* Enable debug exception */
     li  r1,MSR_DE
     mtmsr   r1

     /*
      * If we got an ePAPR device tree pointer passed in as r3, we need that
      * later in cpu_init_early_f(). Save it to a safe register before we
      * clobber it so that we can fetch it from there later.
      */
     mr  r24, r3

接下來就是根據不同芯片的ERRATUM 進行操作,和關閉L2,L1禁止cache的操作,源碼很簡單這裏不粘貼,只撿最關鍵幾點,接下來是初始化異常向量表,告訴CPU出現下列異常應該怎麼辦:

/* Interrupt vectors do not fit in minimal SPL. */
 #if !defined(MINIMAL_SPL)
     /* Setup interrupt vectors */
     lis r1,CONFIG_SYS_MONITOR_BASE@h
     mtspr   IVPR,r1

     li  r4,CriticalInput@l
     mtspr   IVOR0,r4    /* 0: Critical input */
     li  r4,MachineCheck@l
     mtspr   IVOR1,r4    /* 1: Machine check */
     li  r4,DataStorage@l
     mtspr   IVOR2,r4    /* 2: Data storage */
     li  r4,InstStorage@l
     mtspr   IVOR3,r4    /* 3: Instruction storage */
     li  r4,ExtInterrupt@l
     mtspr   IVOR4,r4    /* 4: External interrupt */
     li  r4,Alignment@l
     mtspr   IVOR5,r4    /* 5: Alignment */
     li  r4,ProgramCheck@l
     mtspr   IVOR6,r4    /* 6: Program check */
     li  r4,FPUnavailable@l
     mtspr   IVOR7,r4    /* 7: floating point unavailable */
     li  r4,SystemCall@l
     mtspr   IVOR8,r4    /* 8: System call */
     /* 9: Auxiliary processor unavailable(unsupported) */
     li  r4,Decrementer@l
     mtspr   IVOR10,r4   /* 10: Decrementer */
     li  r4,IntervalTimer@l
     mtspr   IVOR11,r4   /* 11: Interval timer */
     li  r4,WatchdogTimer@l
     mtspr   IVOR12,r4   /* 12: Watchdog timer */
     li  r4,DataTLBError@l 等

直至後就是cache 當臨時stack供C語言代碼運行,新tlb 映射,u-boot C代碼出事後ddr,基本設置,copy自身代碼到ram(ddr)等等。這部分代碼基本不會出什麼問題,後續就到了最關鍵的relocate 技術了。

3.2 U-Boot中的Relocate技術

所謂代碼重定位技術就是對於鏈接時地址已經固定的數據段與代碼段,在進行代碼搬移後,需要對這些數據段與代碼段的地址進行修改,使程序能夠正確尋 址的技術。在 bootloader 中,通常有 GOT與 PLT兩種實現方法。其基本原理就是利 用數據段與代碼段的相對位置不變實現的。代碼重定位技術也應用於 OS 中,OS 的加載器也實現了 PIC,這裏不做算法研究。
我們知道對於全局變量的訪問,首先要獲取該變量的全局地址,而這個地址是編譯鏈接時生成的一個地址,對於沒有-fpic 選項編譯的程序,此值是一個靜態的地址,當程序從 flash 重新定位到 ram 後,訪問的地址仍然在 Flash 中,那 麼如何讀寫呢?
這就需要一種機制來保證獲取的全局變量的地址是一個動態的值,也是說當 程序重定位到 RAM 中後,這個地址也需要隨之修正。而 U-boot 正是將所有全局 符號的地址保存在一個表中 GOT,程序訪問變量時先從這個表中動態獲取該符 號的地址,而這個地址在重定位時已經修正過了,從而保證能從重定位後的地址 處正確訪問全局變量。

總的來講,U-Boot 依靠維護 GOT 表來實現,在 GOT 表中存放一些全局 label表,這些表項記錄重要的地址。運行在 Flash 時,GOT 表中存放的是編譯時 全局 label 的值(地址);當 U-Boot 運行時檢測 RAM 大小進行代碼搬運之後,利用代碼搬運前後產生的地址偏移對(相對偏移)GOT表中的各個表項值進行更新,使其記錄 RAM 中的相應的地址。 這樣代碼運行時不會出現代碼/變量地址出錯的問題。

  • 代碼運行到 in_ram之後首先進行GOT轉換,其實核心思想就是程序的編譯後的指令,
     .globl  in_ram
 in_ram:

     /*
      * Relocation Function, r12 point to got2+0x8000
      *
      * Adjust got2 pointers, no need to check for 0, this code
      * already puts a few entries in the table.
      */
     li  r0,__got2_entries@sectoff@l
     la  r3,GOT(_GOT2_TABLE_)
     lwz r11,GOT(_GOT2_TABLE_)
     mtctr   r0
     sub r11,r3,r11
     addi    r3,r3,-4
 1:  lwzu    r0,4(r3)
     cmpwi   r0,0
     beq-    2f
     add r0,r0,r11
     stw r0,0(r3)
 2:  bdnz    1b

其實可以這麼理解,一旦有全局變量的地方都需要進行地址換算,這個U-boot原始位置與最新位置,在調用relocate函數之前已經算好,並傳參數給彙編代碼。其實就是公式:
NewGOTPTROldGOTPTR=DestinationAddress+(Offset); New GOTPTR-Old GOTPTR=Destination Address+(Offset);
NewGOTPTROldGOTPTR=UBootUBoot New GOTPTR-Old GOTPTR=UBoot目的地址-UBoot源地址;
這裏Offset可以是負數,具體參考Uboot源代碼。

/*
  * void relocate_code (addr_sp, gd, addr_moni)
  *
  * This "function" does not return, instead it continues in RAM
  * after relocating the monitor code.
  *
  * r3 = dest
  * r4 = src
  * r5 = length in bytes
  * r6 = cachelinesize
  */
     .globl  relocate_code
 relocate_code:
     mr  r1,r3       /* Set new stack pointer        */
     mr  r9,r4       /* Save copy of Init Data pointer   */
     mr  r10,r5      /* Save copy of Destination Address */

     GET_GOT
 #ifndef CONFIG_SPL_SKIP_RELOCATE
     mr  r3,r5               /* Destination Address  */
     lis r4,CONFIG_SYS_MONITOR_BASE@h        /* Source      Address  */
     ori r4,r4,CONFIG_SYS_MONITOR_BASE@l
     lwz r5,GOT(__init_end)
     sub r5,r5,r4
     li  r6,CONFIG_SYS_CACHELINE_SIZE        /* Cache Line Size  */

     /*
      * Fix GOT pointer:
      *
      * New GOT-PTR = (old GOT-PTR - CONFIG_SYS_MONITOR_BASE) + Destination Address
      *
      * Offset:
      */
     sub r15,r10,r4

     /* First our own GOT */
     add r12,r12,r15
       */
     sub r15,r10,r4

     /* First our own GOT */
     add r12,r12,r15
     /* the the one used by the C code */
     add r30,r30,r15

4 U-Boot主要命令

U-Boot發展到現在,已經接近 linux 系統。它具備了豐富的命令,可以幫助 用戶實現調試與系統環境設置等。下面我僅介紹下幾個常用的命令。1、HELP 命令
​ 在終端輸入 help 命令,將會顯示系統支持的命令及簡要說明。

  • help == ?

    ? - alias for ‘help’
    base - print or set address offset
    bdinfo - print Board Info structure
    boot - boot default, i.e., run ‘bootcmd’ - boot default, i.e., run ‘bootcmd’
    flinfo. - print flash bank and erase info

    tftp - download image from serverip via net

    rx - receive by xmodel

    setenv editenv savenv and so on

總結

U-BOOT 是一個非常好用的嵌入式 bootloader。它已經成爲目前嵌入式linux的御用boot,內容非常豐富,但結構混亂,看下common裏面的board_r和board_f就能可見一斑,本文只是從整體上對其啓動部分原理做了簡要陳述。關於 u-boot 程序開發,已經超出了 u-boot 啓動部分內容,並且需要很多外圍硬件的相關知識,所以沒有進行講解。

​ 本文參 考了網絡相關文章,並進行了實踐驗證,以及Powerpc 相關手冊,但由於本人技術水平有限,難免存在 理解錯誤,望大家指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章