1. 程式人生 > >BeagleBone Black 移植U-Boot (2 MLO、U-Boot)

BeagleBone Black 移植U-Boot (2 MLO、U-Boot)

原文:jexbat.com/categories/BeagleBone/

什麼是 U-Boot

熟悉嵌入式開發的應該都聽過它,U-boot 就是啟動系統前的一段載入程式,雖然是載入程式,但是功能非常強大。

這一篇主要講解如何從無到有執行 U-Boot,關於 U-Boot 引導 Linux 的部分放在另外一篇文章講解。

U-Boot 之前的版本以版本號命名如:0.1.0, 0.2.0 這幾年改為了以時間和日期命名:U-Boot 2016.03。

使用 git 獲得 U-Boot 的原始碼:

git clone git://git.denx.de/u-boot.git

目前我使用的是 2016.02 的版本。

MLO 及其啟動過程

上一篇文章,我們瞭解了 BeagleBone 有個 SPL 過程,就在這個時候讀取 MLO 檔案,MLO 檔案其實是個精簡版的 U-Boot,也是由 U-Boot 生成,但是功能有限,只初始化了部分資源如 DDR,然後啟動 U-Boot。

MLO 檔案是如何編譯出來的

分析 MLO 的編譯過程之前需要知道編譯原理和 Makefile 等相關知識。
我們先找找 Makefile 看看能不能找到什麼。建議使用 Sublime 編輯器。用全域性查詢功能查詢 MLO 關鍵字。

找到 u-boot/scripts/Makefile.spl

 檔案 117行

u-boot/scripts/Makefile.spl

MLO MLO.byteswap: $(obj)/u-boot-spl.bin FORCE
	$(call if_changed,mkimage)

可以看到 MLO 檔案是由 u-boot-spl.bin 檔案通過 mkimage 命令生成的。
再查到 u-boot/Makefile 檔案 1310 行

u-boot/Makefile

spl/u-boot-spl.bin: spl/u-boot-spl
	@:
spl/u-boot-spl: tools prepare $(if $(CONFIG_OF_SEPARATE),dts/dt.dtb)
	$(Q)$(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all

u-boot-spl.bin 檔案是還是由 u-boot/scripts/Makefile.spl 檔案生成。
檔案 u-boot/scripts/Makefile.spl 168 行 定義了 u-boot-spl.bin 的生成:

u-boot/scripts/Makefile.spl

ifeq ($(CONFIG_SPL_OF_CONTROL),y)
$(obj)/$(SPL_BIN)-dtb.bin: $(obj)/$(SPL_BIN)-nodtb.bin $(obj)/$(SPL_BIN)-pad.bin \
		$(obj)/$(SPL_BIN).dtb FORCE
	$(call if_changed,cat)

$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-dtb.bin FORCE
	$(call if_changed,copy)
else
$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-nodtb.bin FORCE
	$(call if_changed,copy)
endif

因為 SPL_BIN 在 第32行 定義為 u-boot-spl:

u-boot/scripts/Makefile.spl

ifeq ($(CONFIG_TPL_BUILD),y)
SPL_BIN := u-boot-tpl
else
SPL_BIN := u-boot-spl
endi

由 168 行 上面的定義可以知道 u-boot-spl.bin 和 u-boot-spl-nodtb.bin 有關係。

接著查詢到第223行

u-boot/scripts/Makefile.spl

$(obj)/$(SPL_BIN)-nodtb.bin: $(obj)/$(SPL_BIN) FORCE
	$(call if_changed,objcopy)

u-boot-spl-nodtb.bin 是通過 objcopy 命令由 u-boot-spl 生成。

再看第246行:

u-boot/scripts/Makefile.spl

$(obj)/$(SPL_BIN): $(u-boot-spl-init) $(u-boot-spl-main) $(obj)/u-boot-spl.lds FORCE
	$(call if_changed,u-boot-spl)

所以u-boot-spl 是由 u-boot-spl.lds 連結檔案生成的 ,但是目錄下面有幾個u-boot-spl.lds檔案,到底是哪個 lds 檔案呢,上面是 $(obj)/u-boot-spl.lds, obj 在 1310 行 編譯 u-boot-spl.bin 的時候賦值為 obj=spl,所以我們需要看 u-boot/spl/u-boot-spl.lds 這個檔案,但是如果你之前沒有編譯過這個檔案是沒有的。這個檔案是如何生成的呢?我們稍後再看,先看 lds 檔案的內容:

u-boot/spl/u-boot-spl.lds

MEMORY { .sram : ORIGIN = 0x402F0400, LENGTH = (0x4030B800 - 0x402F0400) }
MEMORY { .sdram : ORIGIN = 0x80a00000, LENGTH = 0x80000 }
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 .text :
 {
  __start = .;
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text)
  *(.text*)
 } >.sram
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
 . = ALIGN(4);
 .data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 } >.sram
 . = ALIGN(4);
 __image_copy_end = .;
 .end :
 {
  *(.__end)
 } >.sram
 .bss :
 {
  . = ALIGN(4);
  __bss_start = .;
  *(.bss*)
  . = ALIGN(4);
  __bss_end = .;
 } >.sdram
}

連結檔案裡面說明了記憶體佈局,arch/arm/cpu/armv7/start.o 程式碼段都放在 SRAM 中,所以 arch/arm/cpu/armv7/start.S 就是我們要找的東西了。

lds 連結檔案的生成

u-boot/spl/u-boot-spl.lds 這個檔案的生成在 u-boot/scripts/Makefile.spl 有解釋:

u-boot/scripts/Makefile.spl

$(obj)/u-boot-spl.lds: $(LDSCRIPT) FORCE
	$(call if_changed_dep,cpp_lds)

LDSCRIPT 的定義:

u-boot/scripts/Makefile.spl

# Linker Script
ifdef CONFIG_SPL_LDSCRIPT
# need to strip off double quotes
LDSCRIPT := $(addprefix $(srctree)/,$(CONFIG_SPL_LDSCRIPT:"%"=%))
endif

ifeq ($(wildcard $(LDSCRIPT)),)
	LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
	LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
	LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
$(error could not find linker script)
endif

可見 Makefile.spl 檔案中先是判斷有沒有指定的 lds 檔案,如果沒有指定的,就查詢 board 資料夾中目標板目錄下面有沒有 lds 檔案,如果沒有就查詢相應的 cpu 目錄,因為我們目標器件是 am335x,所以發現有 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 再通過 cpp_lds 命令編譯成,cpp_lds 是一組命令的集合,具體定義還是在 Makefile.spl 檔案中,我們檢視 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 也發現 MLO 檔案程式碼是在 start.S 檔案中。

MLO 程式分析

檢視 start.S 分析下 MLO 程式具體的執行流程,MLO 的 makefile 會根據 CONFIG_SPL_BUILD 編譯不同的原始檔,同樣的在原始碼內也通過 CONFIG_SPL_BUILD 控制不同的程式碼執行,前面一部分 MLO 檔案和 U-Boot 是類似的,進入到 _main 函式中兩個程式的功能就開始出現差異了:

reset //(arch/arm/cpu/armv7/start.S)
save_boot_params_ret //(arch/arm/cpu/armv7/start.S)
  |- disable interrupts 
  |- cpu_init_cp15 //(arch/arm/cpu/armv7/start.S)
  |   |- Invalidate L1 I/D
  |   |- disable MMU stuff and caches
  |- cpu_init_crit //(arch/arm/cpu/armv7/start.S)
  |   |- lowlevel_init //(arch/arm/cpu/armv7/lowlevel_init.S)
  |       |- Setup a temporary stack
  |       |- Set up global data 
  |       |- s_init //(arch/arm/cpu/armv7/am33xx/board.c)
  |           |- watchdog_disable
  |           |- set_uart_mux_conf
  |           |- setup_clocks_for_console
  |           |- uart_soft_reset
  |- _main //(arch/arm/lib/crt0.S)
  	  
      |(MLO)如果是 MLO 檔案
      |- board_init_f //(arch/arm/cpu/armv7/am33xx/board.c)
      |   |- board_early_init_f //(arch/arm/cpu/armv7/am33xx/board.c)
      |   |   |- prcm_init
      |   |   |- set_mux_conf_regs
      |   |- sdram_init //(board/ti/am335x/board.c) 初始化 DDR
      |- spl_relocate_stack_gd
      |- board_init_r //(common/spl/spl.c)
          |- ...
          |- spl_load_image //根據不同的啟動方式載入 u-boot 映象,
          |- jump_to_image_no_args //進入u-boot程式碼執行
  	  
  
      |(U-Boot)如果是U-Boot 映象
      |- board_init_f //(common/board_f.c)
      |   |- ...
      |   |- initcall_run_list(init_sequence_f)   
      |   |- ...   
      |   
      |- relocate_code //(arch/arm/lib/relocate.S) 程式碼重定位
      |- relocate_vectors //(arch/arm/lib/relocate.S) 向量表重定義
      |- Set up final (full) environment 
      |- board_init_r //(common/board_r.c)
          |- initcall_run_list(init_sequence_r)//初始化各種外設
              |- main_loop()

當 U-Boot 重定位好程式碼、向量表之後,執行 board_init_r 函式,此函式會呼叫 init_sequence_r 列表裡面的函式初始化各種外設驅動,最後在 main_loop() 函式中執行,U-Boot 有個 bootdelay 延時啟動,如果不手動停止 U-Boot 會自動執行 bootcmd 包含的命令。

核心引導這部分放在另外一篇文章詳細講解。

U-Boot 編譯

編譯 U-Boot

編譯 U-Boot 前我們需要安裝交叉編譯器:

# sudo apt-get install gcc-arm-linux-gnueabihf

下載 U-Boot 原始碼:

# git clone git://git.denx.de/u-boot.git

因為 U-Boot 官方已經支援了 Beaglebone Black 所以配置檔案也已經自帶了,編譯輸入如下命令:

# make distclean
# make am335x_boneblack_defconfig
# ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make

片刻後會生成 MLO 和 u-boot.img 檔案。

配置 U-Boot 引數

有兩種方式可以配置 U-Boot 的一些引數,分別是 uEnv.txt 和 boot.src 檔案。
U-Boot 啟動的時候會在啟動分割槽尋找這兩個檔案。

boot.scr: This file is a U-Boot script. It contains instructions for U-Boot. Using these instruction, the kernel is loaded into memory, and (optionally) a ramdisk is loaded. boot.scr can also pass parameters to the kernel. This file is a compiled script, and cannot be edited directly. In some cases, boot.scr loads further instructions and configuration parameters from a text file.

uEnv.txt: A file with additional boot parameters. This file can be read by boot.scr, or by the boot sequence if there is no script file. uEnv.txt is a regular text file that can be edited. This file should have Unix line ending, so a compatible program must be used when editing this file.

U-Boot 啟動的時候如果不打斷會呼叫 bootcmd 包含的命令來執行,通常 bootcmd 會呼叫 bootscript 指令碼也就是 boot.scr裡面的命令進行執行, boot.scr 通常也會先讀取 uEnv.txt 確定額外引數,因為 boot.src 檔案必須通過 boot.cmd 檔案編譯而來, uEnv.txt 則是可以任意編輯,這樣可配置性就大大提高了。如果沒有 boot.src 檔案,U-Boot 有預設配置的 bootcmd 命令。

在 Beagelbone Black 中我們不需要額外的 boot.scr 檔案,用預設的命令即可,預設的命令為:

#define CONFIG_BOOTCOMMAND \
	"run findfdt; " \
	"run distro_bootcmd"

run distro_bootcmd 最終會呼叫 run mmcboot 命令載入 uEnv.txt 檔案,並且會執行 uEnv.txt 檔案裡面 uenvcmd 指代的命令。

uEnv.txt 從網路啟動例子:

console=ttyO0,115200n8
ipaddr=192.168.23.2
serverip=192.168.23.1
rootpath=/exports/rootfs
netargs=setenv bootargs console=${console} ${optargs} root=/dev/nfs nfsroot=${serverip}:${rootpath},${nfsopts} rw ip=${ipaddr}:${serverip}:192.168.23.1:255.255.255.0:beaglebone:eth0:none:192.168.23.1
netboot=echo Booting from network ...; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; run netargs; bootz ${loadaddr} - ${fdtaddr}
uenvcmd=run netboot

製作 U-Boot 的 SD 啟動卡

製作 SD 啟動卡之前首先需要為 SD 卡分割槽, ROM Code 啟動的時候如果是從 MMC 裝置載入啟動程式碼,ROM Code 會從第一個活動分割槽尋找名為 “MLO” 的檔案,並且此分割槽必須為 FAT檔案系統。所以製作 U-Boot 的啟動卡只需要一個帶有 MLO 和 U-Boot 映象的 FAT 格式的 SD 卡,如果需要啟動 Linux 核心還需要別的分割槽,我們以後再講。

有兩種方式可以製作包含 U-Boot 的可啟動的 SD 卡,一種是用 RAW Mode 的方式,還有一種是用 FTA 的方式。

RAW Mode 和燒寫方式在這篇文章裡面有講:解析 BeagleBone Black 官方映象

FTA 模式下只要建立一個 FTA 分割槽再把 MLO 和 uboot.img 檔案拷貝進去即可。

我是使用的 USB 讀卡器,插入後 Linux /dev/ 目錄會顯示 /dev/sd* 裝置,我這裡多出兩個裝置分別顯示 /dev/sdb 和 /dev/sdb1 ,其中 /dev/sdb 表示一整個物理磁碟, /dev/sdb1 表示的是具體的分割槽。

使用命令 sudo fdisk /dev/sdb 管理磁碟:

a : toggle a bootable flag(設定或取消啟動表示)

b : edit bsd disklabel(編輯 bsd disklabel)

c : toggle the dos compatibility flag

d : delete a partition (刪除一個分割槽)

l : list known partition types (列出已知的分割槽型別)

m : print this menu (列印次列表)

n : add a new partition (增加一個新分割槽)

o : create a new empty DOS partition table (建立一個新的空 DOS 分割槽表)

p : print the partition table (列印分割槽表)

q : quit without saving changes (不儲存退出)

s : create a new empty Sun disklabel

t : change a partition’s system id

u : change display/entry units

v : verify the partition table (驗證分割槽表)

w : write table to disk and exit (把分割槽表寫入磁碟)

x : extra functionality (experts only) (額外的功能)

新建啟動分割槽:

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *        2048    15130623     7564288    c  W95 FAT32 (LBA)

Command (m for help): m
Command action
   a   toggle a bootable flag
   b   edit bsd disklabel
   c   toggle the dos compatibility flag
   d   delete a partition
   l   list known partition types
   m   print this menu
   n   add a new partition
   o   create a new empty DOS partition table
   p   print the partition table
   q   quit without saving changes
   s   create a new empty Sun disklabel
   t   change a partition's system id
   u   change display/entry units
   v   verify the partition table
   w   write table to disk and exit
   x   extra functionality (experts only)

Command (m for help): d
Selected partition 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 
Using default value 1
First sector (2048-15130623, default 2048): 
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-15130623, default 15130623): 
Using default value 15130623

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048    15130623     7564288   83  Linux

Command (m for help): t
Selected partition 1
Hex code (type L to list codes): c
Changed system type of partition 1 to c (W95 FAT32 (LBA))

Command (m for help): a
Partition number (1-4): 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *        2048    15130623     7564288    c  W95 FAT32 (LBA)

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: If you have created or modified any DOS 6.x
partitions, please see the fdisk manual page for additional
information.
Syncing disks.

建立好新的分割槽之後需要命名並格式化:

# sudo mkfs.vfat -F 32 -n boot /dev/sdb1

格式化之後掛載磁碟並把 MLO 檔案和 u-boot.img 檔案拷貝進去:

# sudo mount /dev/sdb1 /media/jg/boot 
# sudo cp MLO /media/jg/boot/MLO
# ls /media/jg/boot             
MLO
# sudo cp u-boot.img /media/jg/boot/u-boot.img
# ls /media/jg/boot 
MLO  u-boot.img
# sync 
# sudo umount /media/jg/boot

接著把 SD 卡插入 Beaglebone Black 並且按著 S2 按鈕上電,從串列埠打印出的資訊我們可以看到 U-Boot 已經可以正常啟動了:

U-Boot SPL 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20)
Trying to boot from MMC
Card doesn't support part_switch
MMC partition switch failed
*** Warning - MMC partition switch failed, using default environment

reading u-boot.img
reading u-boot.img

U-Boot 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20 +0800)

       Watchdog enabled
I2C:   ready
DRAM:  512 MiB
MMC:   OMAP SD/MMC: 0, OMAP SD/MMC: 1
*** Warning - bad CRC, using default environment

Net:   <ethaddr> not set. Validating first E-fuse MAC
cpsw, usb_ether
Press SPACE to abort autoboot in 2 seconds
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
reading boot.scr
** Unable to read file boot.scr **
reading uEnv.txt
** Unable to read file uEnv.txt **
** File not found /boot/zImage **
switch to partitions #0, OK
mmc1(part 0) is current device
Scanning mmc 1:1...
switch to partitions #0, OK
mmc1(part 0) is current device
SD/MMC found on device 1
reading boot.scr
** Unable to read file boot.scr **
reading uEnv.txt
** Unable to read file uEnv.txt **
** File not found /boot/zImage **
## Error: "bootcmd_nand0" not defined
cpsw Waiting for PHY auto negotiation to complete......... TIMEOUT !
BOOTP broadcast 1
BOOTP broadcast 2
BOOTP broadcast 3
BOOTP broadcast 4

 

啟動之後,前面一段列印資訊是 MLO 程式打印出來的,讀取 U-Boot 之後開始執行完整的 U-Boot,之後程式掃描各個裝置讀取 boot.scr 和 uEnv.txt 檔案,接著再讀取是否有 Linux 核心可以執行。

參考資料