U-BOOT针对性开发
——乐百事
2017-4-19
1、 选择boot版本:拿到一个平台之前如何正确的选择合适的boot版本?
2、 选择好boot之后,如何选择交叉编译版本?
3、 boot编译如何指定交叉编译器
4、 如何针对平台指定CPU及外设并编译
5、 Uboot第一个启动位置
6、 给内核用的mkimage工具
7、 精简代码的阅读性,删除无关程序
8、如何增加自己的命令到uboot中?
一、 选择boot版本
版本不是越新越好,因为越新越臃肿,所以选择的版本只要含有我们板子上的cpu相关的代码即可。如果平台上的CPU用了一些比较特殊的芯片,可以在uboot上针对性的开发或者移植该芯片的支持。也可以搜索这款CPU已经成熟的平台提供的uboot源码或者uboot版本。
此处,我们用米尔科技的MYD-C4378开发板开始uboot的学习之路,官方提供的uboot版本是2013.10的版本,我们到官网上下载纯净的uboot-2013.10版本,下载地址如下:
ftp://ftp.denx.de/pub/u-boot/
二、 选择交叉编译版本
uboot的交叉编译器可以用与内核同样的交叉编译器,但是如果uboot还不支持这个编译器的话,需要针对uboot另外选择编译器,这个没有强制要求。具体的内核选择交叉编译器版本的参考“linux交叉环境搭建”一文。
三、 指定交叉编译器
uboot指定交叉编译器的方法和内核指定编译器方法一致,参考“linux交叉环境搭建”一文。
四、 针对平台指定CPU
Uboot源码中根目录下有boards.cfg文件,里面有各个平台的CPU支持,在board目录中有对应的CPU源码,我们的AM4378的在/board/ti/am43xx中,在boards.cfg中有am43xx的对应关系,编译时候make am43xx_evm,这里也可以自己添加或修改名称,比如am43xx_evm_nor或者am43xx_evm_nand等等。
下面开始执行make distclean,然后执行make am43xx_evm,有报错如下:
从报错信息中可以看出boot源码缺失compiler-gcc5.h,如下图所示:
这个原因是因为我安装的ubuntu系统太新,尝试一下把uboot里面的gcc4.h备份出一个gcc5.h。继续执行依然有报错如下:
从这里可以看出缺少很多libstdc++.so.6和libz.so.1的库,通过查找库的关联关系可以看出来系统需要安装apt-getinstall lib32z1和lib32stdc++6两个库,查找方法参考如下:
Step1、安装apt-file:apt-get install apt-file
Step2、安装更新:apt-file update
Step3、查找关联库:apt-file searchlibz.so.1
Step4、查找出来的库会有x86等库文件,找到完全一样的,例如lib32z1: /usr/lib32/libz.so.1.2.8
Step5、安装缺失的库文件:apt-get installlib32z1
五、 Uboot第一个启动位置
从上一章可以看到boards.cfg如下图
其中armv7就是arch/arm/cpu/armv7和spl/arch/arm/cpu/armv7目录,里面都有start.S文件。
还有一种办法,make am43xx_evm成功之后,通过find ./ -name start.o查找到对应的启动目录和start.S,如下图所示:
找到了uboot启动的第一个文件之后,还需要找到u-boot.lds文件,可以看一下start.S同级目录的Makefile入下(一般在start.S的上一级目录,u-boot-spl.lds为什么不看,参考下文):
可以看到include了当前目录的config.mk文件,我们看一下config.mk文件如下图所示,
这里加了很多宏定义决定是否同时编译u-boot-spl,这个和编译u-boot.bin不冲突,至于SPL作用,在这里也做个简单说明,如果需要u-boot-spl功能的可以修改宏定义编译出u-boot-spl.bin,这个spl的作用就是引导u-boot.bin,这里可能会让人奇怪,u-boot.bin不就是系统上电第一个运行的么,负责硬件初始化工作等,为什么还要用spl来引导。
我也科普了一下,简单说明就是微处理器的发展,很多芯片制造商固化了一些程序,让CPU自动从nand等处理器上读取部分大小的数据到内存执行,比如三星的S3C2440。
上电时S3C2440内部固化的程序自动把NandFlash的前4KB程序拷贝到片内SRAM,然后执行IRAM中的程序,同时要保证这4KB中的程序是位置无关码,在这4KB程序完成了内存的初始化,栈的设置,NandFlash的初始化,将u-boot镜像从NandFlash中拷贝到内存中,将PC跳转到内存中执行。
随着u-boot的更新,在u-boot的前4K已经无法完成上面这些事,在前4KB会执行位置相关码,导致u-boot无法正常运行。
为了解决这个问题,u-boot提供了SPL,用spl来引导u-boot,spl的体积很小,只完成将u-boot从NandFlash中拷贝到内存中,然后跳转到内存。
这里我们暂时没有用到就不做深入研究了,只需要研究spl以外的start.S相关程序即可。
注解:start.S作为第一个要看的启动程序,board/ti/am43xx/board.c作为第二个要看的启动程序,我们开发一款平台的时候这些是一定要看的,网上start.S的文章很多,想了解更透彻的可以参考http://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/
六、 Mkimage工具
编译uboot之后会在uboot的tool目录里面生成mkimage命令,将此拷贝到/usr/bin里面,即可在编译内核时候直接make uImage来编译uImage文件,或者可以apt-get install uboot-mkimage来安装mkimage的命令。在内核中make uImage可能会报错没有loadaddr地址,这个地址可以在Makefile中填入,具体的地址根据实际情况填写,编译时候make LOADADDR=0x(addr) uImage来生成uImage,也可以修改arch/arm/boot/Makefile填入默认的地址。
注解:之前一直用uboot里面生成的mkimage文件,让我原本以为mkimage是和uboot源码有某种联系的,每次编译出来的mkimage才是完全配对的,直到我看人用apt-get直接安装的mkimage也能用时候才了解了一下,其实uImage和zImage之间的区别就是uImage只是在zImage的文件头增加了64byte头信息而已,这些头信息又都是固定的,所以mkimage只是一个通用的命令而已,怎么获取到都无所谓。
七、 源码分析
1、u-boot.lds
/*
* Copyright (c) 2004-2008 Texas Instruments
*
* (C) Copyright 2002
* Gary Jennejohn, DENX Software Engineering,
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include
#include
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm")
/*指定输出可执行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm)
ENTRY(_start)
/*指定输出可执行文件的起始代码段为_start*/
SECTIONS
{
#ifndefCONFIG_CMDLINE
/DISCARD/ : { *(.u_boot_list_2_cmd_*) }
#endif
#ifdefined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)
/*
* IfCONFIG_ARMV7_SECURE_BASE is true, secure code will not
*bundle with u-boot, and code offsets are fixed. Secure zone
* onlyneeds to be copied from the loading address to
*CONFIG_ARMV7_SECURE_BASE, which is the linking and running
*address for secure code.
*
* IfCONFIG_ARMV7_SECURE_BASE is undefined, the secure zone will
* beincluded in u-boot address space, and some absolute address
* wereused in secure code. The absolute addresses of the secure
* codealso needs to be relocated along with the accompanying u-boot
*code.
*
* SoDISCARD is only for CONFIG_ARMV7_SECURE_BASE.
*/
/DISCARD/ : { *(.rel._secure*) }
#endif
. = 0x00000000;
/*指定可执行文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成*/
. = ALIGN(4);
/*代码以4字节对齐*/
.text :
{
*(.__image_copy_start)
/*u-boot将自己copy到RAM,此为需要copy的程序的start*/
*(.vectors)
/*./arch/arm/lib/vectors.S,异常向量表*/
CPUDIR/start.o (.text*)
/*./arch/arm/cpu/armv7/start.S*/
*(.text*)
/*其他的代码段放在这里,即start.S/vector.S之后*/
}
#ifdefCONFIG_ARMV7_NONSEC
/* Align the secure section only if we're going to use itin situ
只有在我们准备使用它的时候,才会对齐安全区域。 */
.__secure_start :
#ifndefCONFIG_ARMV7_SECURE_BASE
ALIGN(CONSTANT(COMMONPAGESIZE))
#endif
{
KEEP(*(.__secure_start))
}
#ifndefCONFIG_ARMV7_SECURE_BASE
#defineCONFIG_ARMV7_SECURE_BASE
#define__ARMV7_PSCI_STACK_IN_RAM
#endif
.secure_text CONFIG_ARMV7_SECURE_BASE :
AT(ADDR(.__secure_start) + SIZEOF(.__secure_start))
{
*(._secure.text)
}
.secure_data : AT(LOADADDR(.secure_text) +SIZEOF(.secure_text))
{
*(._secure.data)
}
#ifdefCONFIG_ARMV7_PSCI
.secure_stack ALIGN(ADDR(.secure_data) +SIZEOF(.secure_data),
CONSTANT(COMMONPAGESIZE)) (NOLOAD) :
#ifdef__ARMV7_PSCI_STACK_IN_RAM
AT(ADDR(.secure_stack))
#else
AT(LOADADDR(.secure_data) +SIZEOF(.secure_data))
#endif
{
KEEP(*(.__secure_stack_start))
/* Skip addreses for stack */
. = . + CONFIG_ARMV7_PSCI_NR_CPUS *ARM_PSCI_STACK_SIZE;
/* Align end of stack section to pageboundary */
. = ALIGN(CONSTANT(COMMONPAGESIZE));
KEEP(*(.__secure_stack_end))
#ifdefCONFIG_ARMV7_SECURE_MAX_SIZE
/*
*We are not checking (__secure_end - __secure_start) here,
*as these are the load addresses, and do not include the
*stack section. Instead, use the end of the stack section
*and the start of the text section.
*/
ASSERT((. - ADDR(.secure_text)) <=CONFIG_ARMV7_SECURE_MAX_SIZE,
"Error: secure section exceeds secure memory size");
#endif
}
#ifndef__ARMV7_PSCI_STACK_IN_RAM
/* Reset VMA but don't allocate space if wehave secure SRAM */
. = LOADADDR(.secure_stack);
#endif
#endif
.__secure_end : AT(ADDR(.__secure_end)) {
*(.__secure_end)
LONG(0x1d1071c); /* Must output something to reset LMA */
}
#endif
. = ALIGN(4);
/*代码段结束后,有可能4bytes不对齐了,此时做好4bytes对齐,以开始后面 的.rodata段*/
.rodata : {*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
/*在代码段之后,存放read only数据段*/
. = ALIGN(4);
/*和前面一样,4bytes对齐,以开始接下来的.data段*/
.data : {
*(.data*)
/*可读写数据段*/
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
/*.data段结束后,紧接着存放u-boot自有的一些function,例如u-boot command等*/
}
. = ALIGN(4);
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(efi_runtime_text)
*(efi_runtime_data)
}
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.relefi_runtime_text)
*(.relefi_runtime_data)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
/*至此,u-boot需要自拷贝的内容结束,总结一下,包括代码段,数据段,以及u_boot_list*/
}
.rel_dyn_start :
/*在老的uboot中,如果我们想要uboot启动后把自己拷贝到内存中的某个地方,只要把要拷贝的地址写给TEXT_BASE即可,然后boot启动后就会把自己拷贝到TEXT_BASE内的地址处运行,在拷贝之前的代码都是相对的,不能出现绝对的跳转,否则会跑飞。在新版的uboot里(2013.07),TEXT_BASE的含义改变了。它表示用户要把这段代码加载到哪里,通常是通过串口等工具。然后搬移的时候由uboot自己计算一个地址来进行搬移。新版的uboot采用了动态链接技术,在lds文件中有__rel_dyn_start和__rel_dyn_end,这两个符号之间的区域存放着动态链接符号,只要给这里面的符号加上一定的偏移,拷贝到内存中代码的后面相应的位置处,就可以在绝对跳转中找到正确的函数。*/
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
/*动态链接符存放在的段*/
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
/*动态链接符段结束*/
}
.end :
{
*(.__end)
}
_image_binary_end = .;
/*bin文件结束*/
/*
* Deprecated: this MMU section isused by pxa at present but
* should not be used by newboards/CPUs.
*/
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
/*
* Compiler-generated __bss_startand __bss_end, see arch/arm/lib/bss.c
编译器生成的__bss_start和__bss_end,参见arch/arm/lib/bss.c
* __bss_base and __bss_limit arefor linker only (overlay ordering)
__bss_base和__bss_limit仅用于链接器(覆盖排序)
*/
/*bss段的描述*/
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. =ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
/*bss段的描述结束*/
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : {*(.gnu.linkonce.armexidx.*) }
}
从u-boot.lds的L15行ENTRY(_start)可以看出开机时候的入口执行函数是__start,那么这个__start到底是先执行start.S里面的呢还是其他地方的,还是看u-boot.lds如下图
开机执行地址0后,对齐字节后,u-boot将自己拷贝到RAM,然后先执行的vectors.S,__start里面做了什么可以从vectors.S中看到如下图:
跳转到reset,也就是start.S里面的reset如下图:
在这里设置了SVC工作模式,关闭了FIQ和IRQ中断。接着往下看。
注:详细的跳转是__start(vectors.S)--->reset(start.S)--->save_boot_params(start.S)--->save_boot_params_ret(start.S)--->cpu_init_cp15(start.S)--->cpu_init_crit(start.S)--->lowlevel_init(忽略)--->_main(crt0.S)--->board_init_f(直接搜索函数在c中)
Board_init_f中会有初始化的一系列函数,例如serial_init,console_init_f,dram_init等等如下图所示
八、 新源码尝试
于2018年1月到TI官网上看到有关SDK的更新,建议的源码包也对应的支持更高版本的了,具体如下:
下载u-boot-2017.01.tar.bz2这个版本,较之前的版本有部分改动,具体先从boards.cfg说起,新的版本里面已经找不到这个boards.cfg的文件了,多了一个configs的文件夹,里面包含了各个板卡的配置文件。
可以看出类似内核的make menuconfig的一些配置,那么应该可以直接make am43xx_evm_defconfig这样生成.config文件进行make编译。
九、 精简U-BOOT源码
有时候我们需要查看源码来定制U-BOOT的开发,但是下载下来的U-BOOT源码往往支持很多平台和CPU架构,我们需要把我们有关的程序保留下来,删除其他平台的程序,直接删除平台的目录,会同时删除掉目录中的kconfig等文件,影响编译,所以我们想办法把我们编译过程中涉及到的源码都保留,没有涉及的源码都删除,编译成.o文件的c源码都保留,其他的删除。
大致通过find找出所以编译生成.o的文件列表记录到tmp.txt文件,然后sed删除tmp.txt每行最后的o转换成tmp2.txt,在通过sed在文件后面增加*,最后如下图所示:
先通过find ./ -name *.o > tmp.txt将找到编译生成的.o文件列表记录成tmp.txt文件,然后把.o都改成.*通过sed 's/.$//' tmp.txt > tmp2.txt生成一个tmp2.txt文件,去除每行最后的.o,然后通过sed's/$/&*/g' tmp2.txt > tmp3.txt生成一个tmp3.txt在最后增加.*,如下图所示
备份好之后就可以在arch目录下通过find ./-name *.S | xargs rm -rf删除所有.c和.S文件,删除完直接解压bk.tar.gz,make distclean && makeam43xx_evm_defconfig && make如下结果:
关闭SPL功能后可以make成功,至此所有编译无关的程序都被删除了。
将以上步骤做成src-clean.sh脚本如下:
#!/bin/bash
echo "All uncompiled programs will be deleted!"
cd ${PWD} || exit
read -r -p "Have you confirmed thecompilation,Invalid input is NO! [Y/n] " input
case $inputin
[yY][eE][sS]|[yY])
echo "Yes"
;;
[nN][oO]|[nN])
echo "No"
exit
;;
*)
echo "No"
exit
;;
esac
find ./arch ./board -name *.o > tmp.txt
sed 's/.$//' tmp.txt > tmp2.txt || exit
sed 's/$/&*/g' tmp2.txt > tmp3.txt || exit
cat tmp3.txt
tar -zcvf bk.tar.gz `cat tmp3.txt` || exit
find ./arch ./board -name *.S | xargs rm -rf
find ./arch ./board -name *.c | xargs rm -rf
rm -rf tmp.txt tmp2.txt tmp3.txt
tar -zxvf bk.tar.gz
rm -rf bk.tar.gz
十、 常用命令
调试以太网phy的mii命令:
mii device 查看当前所有phy设备
mii device
mii info
mii read
mii write
mii dump
SD卡mmc命令:
mmc info 查看当前设备的信息
mmc rescan 重新查找设备
mmc dev [dev] 选中设备
mmc list 列举所有设备
mmc part 列举当前设备的分区情况
mmc read
mmc write
注:addr:读取到内存的位置
blk#:是mmc内的块号,这个位置是mmc的0地址的偏移量,16进制,block单位是512字节
cnt:读取block个数,16进制
NAND Flash命令:
nand info 查看可以使用的Nand Flash
nand device [dev] 显示或设定当前使用的Nand Flash
nand read
的数据到RAM的addr地址
nand write
烧写到Nand的off偏移地址
nand erase
nand bad 显示Nand Flash的坏块
nand erase.chip 擦除整块Nand Flash中的数据
SPI命令:
sf probe
选择spi flash
sf read
从spi flash的偏移地址offset开始读取len个字节的内容到内存addr处
sf write
将内存addr开始的len个字节的内容写到spi flash的偏移地址offset处
sf erase
擦除spi flash的偏移地址offset处开始的len个字节的数据
内存操作命令:
nm [.b, .w, .l]
修改内存值 (指定地址)mm [.b, .w, .l]
修改内存值 (地址自动加一)md [.b, .w, .l]
显示内存值mw [.b, .w, .l]
cp [.b, .w, .l]
十一、 增加自定义命令
添加自己的新命令
l 在cmd目录下增加源码;
l 在cmd/Makefile中添加该命令的编译;
l 在cmd/Kconfig中添加该命令的编译配置;
l 编译uboot。
使能或修改原支持的命令
l 在cmd/Kconfig中添加该命令的编译配置;
l 在menuconfig中选中该命令;
l 编译uboot;
l 如果编译出错,根据提示,在include/configs/xxx_evm.h中增加相应的宏定义。