title: u-boot分析与使用
date: 2019-08-12 19:16:50
categories:
打开u-boot-1.1.6/Makefile文件,发现:
SRCTREE    := $(CURDIR) *CURDIR是make的内嵌变量, 为当前目录
......
MKCONFIG    := $(SRCTREE)/mkconfig
......
100ask24x0_config    :    unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0
上述$(SRCTREE)等于$(CURDIR),也就是当前目录u-boot-1.1.6,所以MKCONFIG=./mkconfig
上述$(@:_config=)的结果就是将 “100ask24x0_config” 中的 “_config” 去掉, 结果为 “100ask24x0” 。
实际执行:
mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
  $0         $1     $2    $3        $4      $5     $6 
显然这里是调用的mkconfig,打开当前目录u-boot-1.1.6下的该文件(用的linux_shell语法,可以参考《精通linux_shell编程教程pdf完整版》以及Linux应用开发手册第264页U-Boot配置过程),在第6行中给出了mkconfig的用法:
# Parameters: Target Architecture CPU Board [VENDOR] [SOC]
刚好对应mkconfig(参数) 100ask24x0(目标) arm(架构) arm920t(cpu)100ask24x0(开发板选型) NULL(供应商) s3c24x0(片上系统/芯片) 。
通过分析,精简后的mkconfig文件:
#!/bin/sh -e
# Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]
# mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
APPEND=no	# Default: Create new config file
BOARD_NAME=""	# Name to print in make output
while [ $# -gt 0 ] ; do
	#不运行
done
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"#BOARD_NAME=100ask24x0
# $#:参数的个数
[ $# -lt 4 ] && exit 1#不运行
[ $# -gt 6 ] && exit 1#不运行
echo "Configuring for ${BOARD_NAME} board..."
#
# Create link to architecture specific headers
#
if [ "$SRCTREE" != "$OBJTREE" ] ; then#通过Makefile发现相等
	#不运行
else
	cd ./include#进入include目录
	rm -f asm
	ln -s asm-$2 asm#ln -s asm-arm asm:建立链接文件asm,指向asm-arm
fi
rm -f asm-$2/arch#rm -f asm-arm/arch:删除文件
if [ -z "$6" -o "$6" = "NULL" ] ; then#第6个参数为空或等于NULL
	ln -s ${LNPREFIX}arch-$3 asm-$2/arch#不运行
else
	ln -s ${LNPREFIX}arch-$6 asm-$2/arch#ln -s arch-s3c24x0 asm-arm/arch:建立链接文件asm-arm/arch,指向arch-s3c24x0
fi
if [ "$2" = "arm" ] ; then
	rm -f asm-$2/proc#删除文件
	ln -s ${LNPREFIX}proc-armv asm-$2/proc#ln -s proc-armv asm-arm/proc:建立链接文件asm-arm/proc,指向proc-armv
fi
#
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk#新建config.mk
echo "CPU    = $3" >> config.mk#追加
echo "BOARD  = $4" >> config.mk
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk#不会执行
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk
#
# Create board specific header file
#
if [ "$APPEND" = "yes" ]	#第五行:APPEND=no
then
	echo >> config.h
else
	> config.h		#新建config.h
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h
exit 0
发现会创建include/config.mk文件:
  ARCH = arm
  CPU = arm920t
  BOARD = 100ask24x0
  SOC = s3c24x0
以及include/config.h
/* Automatically generated - do not edit */
#include <configs/100ask24x0.h>
创建相关的头文件链接:
ln -s asm-arm asm
ln -s arch-s3c24x0 asm-arm/arch
ln -s proc-armv asm-arm/proc
创建顶层makefile包含的文件include/config.mk
  ARCH = arm
  CPU = arm920t
  BOARD = 100ask24x0
  SOC = s3c24x0
创建开发板相关的头文件include/config.h
/* Automatically generated - do not edit */
#include <configs/100ask24x0.h>
u-boot-1.1.6/Makefile文件:
include $(OBJTREE)/include/config.mk//调用config.mk这个文件
...
ifeq ($(ARCH),ppc)//判断config.mk这个文件中ARCH是否等于ppc
CROSS_COMPILE = powerpc-linux- 
endif
ifeq ($(ARCH),arm)//判断config.mk这个文件中ARCH是否等于arm
CROSS_COMPILE = arm-linux- 
endif
...
include $(TOPDIR)/config.mk
最后的config.mk是属于顶层目录的config,主要通过arm,arm920t,100ask24x0,s3c24x0来确定编译器、编译选项等
顶层的config.mk部分为:
...
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
...
这里的LDFLAGS就是下文中的$(LDFLAGS)
$(LDSCRIPT):链接脚本$(TEXT_BASE):board/100ask24x0/config.mk中定义,为链接脚本的起始地址继续看,发现all目标:
...
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)//all的依赖文件
all:    $(ALL)//使用make命令,相当于执行make all 
...
$(obj)u-boot.bin:   $(obj)u-boot//生成u-boot.bin需要elf格式的u-boot,elf也就是通过ld链接文件生成的。
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@//执行:arm-linux-objcopy -O binary u-boot u-boot.bin
...
$(obj)u-boot:   depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)//生成elf格式的u-boot所需要的依赖文件
		UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
		cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \                       
			--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
			-Map u-boot.map -o u-boot
上述10行开始就是开始制作elf格式的u-boot,具体展开的命令可以看make后命令行最后输出的命令,值得注意的是这中间包含的链接脚本,他决定了各种.o文件是怎么组织的(注意$(LDFLAGS))
通过make后命令行输出的命令:
......&&arm-linux-ld -Bstatic -T /work/system/u-boot-1.1.6/board/100ask24x0/u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o......
可以发现链接脚本为board/100ask24x0/u-boot.lds,并且通过**-Ttext**参数确定链接脚本的起始地址为0x33F80000
开发板有64M的SDRAM,SDRAM从0x3000 0000开始,0x33F80000到SDRAM的结束地址刚好为512k
同时,第一个链接的文件为cpu/arm920t/start.o,若链接脚本中没有明确指定那个文件在代码段(text段)的开头,该文件就是第一个被执行的文件
OUTPUT_ARCH(arm)//设置输出文件的体系架构。
ENTRY(_start)//将_start这个全局符号设置成入口地址,进行复位初始化
SECTIONS//输出文件的内容布局
{
    . = 0x00000000;//指定地址0x00000000,最终运行地址在0x33F80000+0x00000000
    . = ALIGN(4);//代码以4字节对齐
    .text ://指定.text section段(位于0x33F80000)
    {
        cpu/arm920t/start.o (.text)//添加第一个目标文件cpu/arm920t/start.o里面的.text代码段
        board/100ask24x0/boot_init.o (.text)//添加第二个目标文件board/100ask24x0/boot_init.o里面的.text代码段
        *(.text)//*(.data) 表示添加剩下的全部文件的.text代码段
    }
    . = ALIGN(4);
    .rodata : { *(.rodata) }//指定.rodata section段(位于0x33F80000+.text section),将所有的.rodata只读数据段合并成一个.rodata只读数据段
    . = ALIGN(4);
    .data : { *(.data) }//指定读写数据段
    . = ALIGN(4);
    .got : { *(.got) }//指定got段,got段是uboot自定义的一个段
    . = .;
    __u_boot_cmd_start = .;//把__u_boot_cmd_start赋值为当前位置, 即起始位置
    .u_boot_cmd : { *(.u_boot_cmd) }// u_boot_cmd段,所有的u-boot命令相关的定义都放在这个位置
    __u_boot_cmd_end = .;// *u_boot_cmd段结束位置
    . = ALIGN(4);
    __bss_start = .;//把__bss_start赋值为当前位置,即bss段的开始位置
    .bss : { *(.bss) }//指定bss段,这里NOLOAD的意思是这段不需装载,仅在执行域中才会有这段
    _end = .;//把_end赋值为当前位置,即bss段的结束位置
}
发现第一个文件是cpu/arm920t/start.S
跳到reset处,执行复位函数
设为SVC(管理员)模式、f关闭看门狗、关闭中断、CPU的相关初始化(SDRAM初始化)
设置堆栈,为之后进入C函数做准备
/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE/*_TEXT_BASE,代码段起始地址,就是之前的0x33F80000*/
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#ifdef CONFIG_USE_IRQ
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub	sp, r0, #12		/* leave 3 words for abort-stack  最后sp指针的位置  */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl clock_init/*跳到C函数初始化时钟(自己写的,一般为汇编写的)*/
#endif
重定位
//搬移代码,将完整的u-boot从flash(NOR、NAND)中搬移到SDRAM中的链接地址去
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:				/* relocate U-Boot to RAM	    */
	adr	r0, _start		/* r0 <- current position of code   */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	beq     clear_bss
	
	ldr	r2, _armboot_start
	ldr	r3, _bss_start
	sub	r2, r3, r2		/* r2 <- size of armboot            */
#if 1
	bl  CopyCode2Ram	/* r0: source, r1: dest, r2: size */
#else
清除BSS段,全部清为0
_bss_start 和 _bss_end由链接脚本决定
clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */
clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l
调用C函数start_armboot
ldr	pc, _start_armboot
_start_armboot:	.word start_armboot
注意:
ldr是位置有关码,将_start_armboot这个标号的值作为地址,取出其中的值(即start_armboot的位置,该位置是通过链接脚本指定的)赋值给pc,所以无论该代码现在运行在哪里(芯片内部SRAM、外部NOR FLASH或者外部SDRAM)都会跳转到已经拷贝到的外部SDRAM中的start_armboot中去
实例:
若此时该汇编代码运行在芯片内部SRAM中,前期通过搬移已经将完整的u-boot搬移至片外SDRAM中,链接脚本指定的链接地址(运行地址)为
33f80000,设此时汇编所对应的反汇编为:#链接地址 机器码 反汇编指令 33f80070: e59ff04c ldr pc, [pc, #76] ; 33f800c4 ...... 33f800c4: 33f805a0 mvnccs r0, #671088640 ; 0x28000000 ...... 33f805a0 <start_armboot>: 33f805a0: e92d4030 stmdb sp!, {r4, r5, lr}此时第一行中的代码肯定是在片内SRAM中运行的,所以该行代码所处的实际物理地址应该是0x33f8 0070-0x33f8 0000=0x70,通过pc+8+76=pc+84=pc+0x54,得出pc应该赋值为地址0x70+0x54=0xc4中的值,即此时pc等于0xc4处所储存的数据33f805a0,正好对应start_armboot的链接地址。即下一刻跳转到片外的SDRAM中的start_armboot函数
总结:
重定位前的代码应该使用位置无关码来写,以确保不同位置都可运行
- 使用相对跳转指令:B、BL
 - 不可使用绝对地址,不可访问全局变量、静态变量(根本方法是看反汇编)
 - 不能访问有初始值的数组,这种数组(指局部数组,全局变量不能使用)本身会存在栈中,但是初始值会在rodata段中,访问这些数据的值会用绝对地址进行访问
 重定位后的跳转到SDRAM代码应该用位置有关码(LDR)来写,以确保可以跳到外部SDRAM中的运行地址去运行
该函数会:
初始化堆栈,用于自定义的malloc和free
拥有读写flash的能力
NOR:Flash_init
NAND:nand_init
跳到main_loop函数
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
    main_loop ();
}
main_loop函数可以等待用户输入相关的命令或者是通过uboot中的环境变量bootcmd中的命令启动linux内核
而main_loop函数的核心在于getenv函数和run_command函数,前者负责从uboot中的环境变量中取出对应的数据,后者负责执行相关的命令
所有命令通过一个结构体进行封装(cmd_tb1_t)
所有命令通过结构体中的name成员进行查找
这些结构体存在一个uboot自定义的段内,在之前的链接脚本中已经进行声明了段的起始和结束地址:
. = .;
__u_boot_cmd_start = .;//把__u_boot_cmd_start赋值为当前位置, 即起始位置
.u_boot_cmd : { *(.u_boot_cmd) }// u_boot_cmd段,所有的u-boot命令相关的定义都放在这个位置
__u_boot_cmd_end = .;// *u_boot_cmd段结束位置
```
通过搜索.u_boot_cmd可以找到怎样将这些结构体放入这个段中(利用__attribute__关键字)
通过搜索常用命令(如:bootm)可以找到相关的定义这些结构体的方法
U_BOOT_CMD(
名字,最大参数,是否可重复,cmd函数,
短字符串,
长字符串
);
例子:
U_BOOT_CMD( bootm, CFG_MAXARGS, 1, do_bootm, "bootm - boot application image from memory\n", "[addr [arg ...]]\n - boot application image stored in memory\n" "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" "\t'arg' can be the address of an initrd image\n" #ifdef CONFIG_OF_FLAT_TREE "\tWhen booting a Linux kernel which requires a flat device-tree\n" "\ta third argument is required which is the address of the of the\n" "\tdevice-tree blob. To boot that kernel without an initrd image,\n" "\tuse a '-' for the second argument. If you do not pass a third\n" "\ta bd_info struct will be passed instead\n" #endif );
uboot启动内核是依赖以下两条命令:
s = getenv ("bootcmd");
run_command (s, 0);
这里的s为环境变量bootcmd中的值,即
nand read.jffs2 0x30007EC0 kernel;//从kernel分区读出内核放到0x30007EC0位置
bootm 0x30007EC0//从0x30007EC0位置启动内核
这里的分区在源码中已经写死,位置为:include/configs/a00ask24x0.h中的MTDPARTS_DEFAULT宏:
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \/*0开始的256k为bootloader*/
                            "128k(params)," \/*接下来的128k为环境变量*/
                            "2m(kernel)," \
                            "-(root)"
在uboot下可以通过mtd命令读取,kernel结果为0x0006 0000起始,大小为0x0020 0000,故原来的指令可以改写为:
nand read.jffs2 0x30007EC0 0x00060000 0x00200000;//从0x00060000读出0x00200000大小数据放到0x30007EC0位置
bootm 0x30007EC0//从0x30007EC0位置启动内核
通过全局搜索nand可以搜索到其命令函数在common/cmd_nand.c下的do_nand函数,简化后:
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
    int i, dev, ret;
    ulong addr, off, size;
    char *cmd, *s;
    nand_info_t *nand;
    int quiet = 0;
    const char *quiet_str = getenv("quiet");
    /* at least two arguments please */
    if (argc < 2)
        goto usage;
    if (quiet_str)
        quiet = simple_strtoul(quiet_str, NULL, 0) != 0;
    cmd = argv[1];
    ......
    /* read write */
    if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
        int read;
        if (argc < 4)
            goto usage;
        addr = (ulong)simple_strtoul(argv[2], NULL, 16);
        read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
        printf("\nNAND %s: ", read ? "read" : "write");
        if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
            return 1;
        s = strchr(cmd, '.');
        if (s != NULL &&
            (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) {
            if (read) {
                /* read */
                nand_read_options_t opts;
                memset(&opts, 0, sizeof(opts));
                opts.buffer = (u_char*) addr;
                opts.length = size;
                opts.offset = off;
                opts.quiet      = quiet;
                ret = nand_read_opts(nand, &opts);
            } else {
                /* write */
                ......
            }
        }
        ......
        printf(" %d bytes %s: %s\n", size,
               read ? "read" : "written", ret ? "ERROR" : "OK");
        return ret == 0 ? 0 : 1;
    }
usage:
    printf("Usage:\n%s\n", cmdtp->usage);
    return 1;
}
最后是调用的nand_read_opts读出的内核
Flash上储存的是uImage=头部+zImage,其中头部为:
typedef struct image_header {
	uint32_t	ih_magic;	/* Image Header Magic Number	*/
	uint32_t	ih_hcrc;	/* Image Header CRC Checksum	*/
	uint32_t	ih_time;	/* Image Creation Timestamp	*/
	uint32_t	ih_size;	/* Image Data Size		*/
	uint32_t	ih_load;	/* Data	 Load  Address		*/
	uint32_t	ih_ep;		/* Entry Point Address		*/
	uint32_t	ih_dcrc;	/* Image Data CRC Checksum	*/
	uint8_t		ih_os;		/* Operating System		*/
	uint8_t		ih_arch;	/* CPU architecture		*/
	uint8_t		ih_type;	/* Image Type			*/
	uint8_t		ih_comp;	/* Compression Type		*/
	uint8_t		ih_name[IH_NMLEN];	/* Image Name		*/
} image_header_t;
这里有以下两个成员值得关注:
bootm命令流程:
读取头部信息,并根据头部信息移动内核到ih_load处
uboot环境变量中的启动命令:
nand read.jffs2 0x30007EC0 0x00060000 0x00200000;
上述ih_load在本文所处的环境中为0x30008000 ,而头部信息刚好64字节,所以这里0x30007EC0=0x30008000 - 64,即提前将linux内核搬移到指定位置,避免bootm命令中重复搬移
通过do_bootm_linux启动内核
设置启动参数(各种tag:开始tag、命令行tag、结束tag…)
这里的命令行tag就是uboot中的环境变量bootargs中的值
本文所处的环境中:
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
跳转到入口地址ih_ep处,并将机器id和启动参数的所在地址一并传入
linux内核会根据不同的机器id来决定是否支持该板卡