U-BOOT的针对性开发

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也能用时候才了解了一下,其实uImagezImage之间的区别就是uImage只是在zImage的文件头增加了64byte头信息而已,这些头信息又都是固定的,所以mkimage只是一个通用的命令而已,怎么获取到都无所谓。

 

七、 源码分析

1u-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格式,32ARM指令,小端*/

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将自己copyRAM,此为需要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.ldsL15ENTRY(_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等等如下图所示

 

八、 新源码尝试

20181月到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

 

十、 常用命令

调试以太网phymii命令:

mii device                 查看当前所有phy设备

mii device            选中设备

mii info           查看设备信息

mii read               读取phy的寄存器

mii write        配置phy的寄存器

mii dump             列举选中的寄存器的配置(具体到bit),只支持reg0-5

 

SDmmc命令:

mmc info                   查看当前设备的信息

mmc rescan               重新查找设备

mmc dev  [dev]               选中设备

mmc list                          列举所有设备

mmc part                   列举当前设备的分区情况

mmc read      读取mmc内容到内存

mmc write      将内存内容写入到mmc中

注:addr:读取到内存的位置

blk#:是mmc内的块号,这个位置是mmc0地址的偏移量,16进制,block单位是512字节

cnt:读取block个数,16进制

 

NAND Flash命令:

nand info                   查看可以使用的Nand Flash

nand device  [dev]                  显示或设定当前使用的Nand Flash

nand read           从Nand 的 off 偏移地址处读取size字节

                                   的数据到RAM的addr地址

nand write         将RAM的 addr 地址处的size字节的数据

                                   烧写到Nand的off偏移地址

nand erase              擦除Nand Flash的off偏移地址处的size字节的数据

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]

[count]    用指定的数据填充内存

cp [.b, .w, .l] [count]       内存的拷贝

 

十一、      增加自定义命令

添加自己的新命令

l  在cmd目录下增加源码;

l  在cmd/Makefile中添加该命令的编译;

l  在cmd/Kconfig中添加该命令的编译配置;

l  编译uboot。

使能或修改原支持的命令

l  在cmd/Kconfig中添加该命令的编译配置;

l  在menuconfig中选中该命令;

l  编译uboot;

l  如果编译出错,根据提示,在include/configs/xxx_evm.h中增加相应的宏定义。

 

你可能感兴趣的:(uboot,linux)