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 核心可以執行。