一、U-boot基本概念
1、U-boot本质上就是一个单片机程序,所以对应不同的硬件设计和CPU。U-BOOT必须按需修改。
2、U-boot用来引导启动操作系统(也是程序)
3、U-boot的官方网址: SourceCode < U-Boot < DENX
4、U-boot其实是个很庞大的单片机工程,就为了干一件事:引导加载Linux
5、u-boot一般从FLASH或SD读出内核可执行程序放到SRAM里,然后执行内核,在SRAM里面。
6、u-boot为了实现终极功能,必须具备的基本功能:
①读写FLASH或SD卡
②写SDRAM
③从SDRAM中启动内核
④初始化时钟
⑤初始化串口甚至网口
7、U-boot的结构与启动过程
       u-boot启动过程可分为单阶段与多阶段两种;通常,多阶段的u-boot可以提供更加复杂的功能和更好的可移植性。从固态存储器设备上启动的Bootloader大多都是两阶段的启动过程;第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化( 就是xxx.s文件) ,并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
       一般而言,这两个阶段完成的功能可以如下分类:
     (1)Bootloader第一阶段的功能。( xxx.s文件!!!
       ·  硬件设备初始化
       ·  为加载Bootload的第二阶段代码准备RAM空间
       ·  复制Bootload的第二阶段代码到RAM空间中
       ·  设置好代码运行的堆栈空间
       ·  跳转到第二阶段代码的C入口点。
     (2)Bootloader启动第二阶段:
       · 配置关闭看门狗、关闭各类中断、设置CPU的速度和初始化系统时钟
       · 初始化本阶段要使用到的硬件设备
       · 检测系统内存映射
       · 将内核镜像和根文件系统镜像从FLASH上读到RAM空间中去运行
       · 为内核设置启动参数。
       · 调用并运行内核
      检查内存映射:就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。
       内核被存放在合适的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足:
      (1)CPU寄存器的设置。
        · R0=0
        · R1=机器类型ID;
        · R2=启动参数标记列表在RAM中起始基地址。
      (2)CPU工作模式
        · 必须禁止中断(IRQs和FIQs)
        · CPU必须为SVC模式(SVC:管理员模式,CPU有权访问一些受保护的寄存器)
     (3)Cache和MMU的设置(高速缓存寄存器和内存管理单元)
        · MMU必须关闭
        · 指令Cache可以打开也可以关闭
        · 数据Cache必须关闭
8、Bootloader与内核的数据交互
       Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于他们不能同时运行,传递参数方法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
       除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。标记,就是一种 数据结构;标记列表,就是 挨着存放的多个标记。标记列表以ATAG_CORE开始,以标记ATAG_NONE结束。
      标记的数据解构为tag,它由一个tag_header结构和一个联合体组成。tag_header结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合体内的对应数据格式。
9、常用Bootloader的介绍
 Vivi是Mizi公司针对SAMSUNG的ARM架构CPU专门设计的,基本上可以直接使用,命令简单。不过其初始版本只支持串口下载,速度较慢。在网上出现了各种改进版本:支持网络功能、USB功能、烧写YAFFS文件系统映像等。

二、基于U-boot源码Makefile文件,结构分析
注意:这里使用u-boot版本为:u-boot-1.1.6
u-boot目录下,文件的结构:(从readme文件中可以查询)
以下截图文件目录格式,基于正点原子提供的 U-Boot 2016.03
通过Makefile分析了解U-BOOT的文件架构
从Makefile文件中,大概可以了解到:
1、在配置阶段,根据传入的参数,调用对应板子目录下的config.mk在板子的目录下生成一些列与CPU架构和板子有关的软链接并初始化环境变量参数
2、再次输入make指令后,开始编译,系统根据之前的环境变量开始从CPU对应的启动文件开始,编译出.o和.a文件;
3、最后根据板子下对应的u-boot.lds文件,将编译器出来的这些二进制文件按照一定的顺序存放到FLASH指定位置
Makefile索引文件目录的流程:( 基于正点原子NXP I.MX6ULL
① 判断make命令所携带的参数,设置编译的方式( eg:make V=1   显示详细的编译过程信息
② 获取当前PC机的CPU架构和使用的操作系统第225行~第242行,有些变量不知道内容,可以echo打印出来
③ 设置编译的目标CPU架构、使用的交叉编译器以及使用的配置文件( 第244行~第325行,如果在输入命令时,不加ARCH=cpu CROSS_COMPILE=gcc,这里就需要直接指定
④ 调用u-boot目录下./scripts/Kbuild.include文件,文件内定义了许多编译过程会用到的变量。()
⑤ 设置一系列交叉编译工具( 第334行~第348行
⑥ 导出一些变量给子目录makefile,顶层Makefile应用了config.mk( 第366行~第377行。其中,这些变量:ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR,被定义在文件./config.mk里面这里形成了一条链:Kconfig-->.config-->config.mk--->Makefile, 在VSCODE里,通过全局索引就可以找到线索
根据我们编译U-BOOT的命令顺序分析,接下来这一步是 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  xxx_defconfig;所以我们来看看Make是如何实现将xxx_defconfig转变为.config的。( 经过对Makefile的分析,重点关注./scripts/makefile.build; 指令: make mx6ull_alientek_emmc_defconfig V=1就可以看到真相!!
⑧ 对文件 ./scripts/makefile.build的分析;第一部分是执行这句话 make -f ./scripts/Makefile.build obj=scripts/basic,这句话通过分析得到最终执行 include ./scripts/basic/Makefile、 __build: scripts/basic/fixdep,就是为了生成一个软件fixdep;第二部分是执行这句话 make -f ./scripts/Makefile.build obj=scripts/kconfig  xxx_defconfig,这句话通过分析,最终得到一句命令 scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig,就是通过主机软件conf调用目标defconfig和Kconfig来生成.config。
⑨ 接下来是正式的编译指令: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1 -j2,具体分析整个编译流程。整个编译流程成默认从all这里开始, all-->u-boot.bin-->u-boot_nodtb.bin-->u-boot;而u-boot根据根目录下的u-boot.lds将arch/arm/cpu/armv7/start.o以及build_in.o链接起来。 build_in.o文件是由同等目录下的 .autoboot.o.cmd来编译源码生成的链接地址:( 命令行:grep -nR "87800000"
include/configs/mx6_common.h:86:#define CONFIG_SYS_TEXT_BASE    0x87800000
arch/arm/cpu/armv7/start.o
arch/arm/cpu/built-in.o
arch/arm/cpu/armv7/built-in.o
arch/arm/imx-common/built-in.o
arch/arm/lib/built-in.o
board/freescale/common/built-in.o
board/freescale/mx6ullevk/built-in.o
cmd/built-in.o
common/built-in.o
disk/built-in.o
drivers/built-in.o
drivers/dma/built-in.o
drivers/gpio/built-in.o
drivers/i2c/built-in.o
drivers/mmc/built-in.o
drivers/mtd/built-in.o
drivers/mtd/onenand/built-in.o
drivers/mtd/spi/built-in.o
drivers/net/built-in.o
drivers/net/phy/built-in.o
drivers/pci/built-in.o
drivers/power/built-in.o
drivers/power/battery/built-in.o
drivers/power/fuel_gauge/built-in.o
drivers/power/mfd/built-in.o
drivers/power/pmic/built-in.o
drivers/power/regulator/built-in.o
drivers/serial/built-in.o
drivers/spi/built-in.o
drivers/usb/dwc3/built-in.o
drivers/usb/emul/built-in.o
drivers/usb/eth/built-in.o
drivers/usb/gadget/built-in.o
drivers/usb/gadget/udc/built-in.o
drivers/usb/host/built-in.o
drivers/usb/musb-new/built-in.o
drivers/usb/musb/built-in.o
drivers/usb/phy/built-in.o
drivers/usb/ulpi/built-in.o
fs/built-in.o
lib/built-in.o
net/built-in.o
test/built-in.o
test/dm/built-in.o
总结:对于后面修改移植U-BOOT,我们需要关注的,是那些被编译为.o的文件。

///

 

三、基于U-BOOT源码的Makefile的具体代码分析
U-BOOT的编译链接脚本u-boot.lds分析基于正点原子NXP IMX6ULL开发板
1、由lds文件可知,整个u-boot工程的入口地址就是_start,那么这个_start在哪里呢?
2、通过在vscode里,全局搜索".globl _start",就可以找到, 这个_start在 arch\arm\lib\vectors.S里面( 之所以要搜索".globl _start",是因为一般来说,作为汇编启动文件的 标准开头 都是全局变量的_start
3、在vectors.S里面可以看到,_start后面就是中断向量表,但在中断向量表之前,定义了这个语句:.section ".vectors" , "ax";这个是啥意思呢? 不懂,先回到u-boot.lds上看看
4、在u-boot.lds上,我们可以看到在.text这里,先有一个变量.__image_copy_start,然后是.vectors这个变量;那么我们全局搜索一下(grep -nR "__image_copy_start"),可以找到一个文件u-boot.map。
5、在u-boot.map文件里,第925行开始,就可以看到十分有趣的内容了;可以看到,.__image_copy_start这个变量所代表的就是u-boot工程的起始地址0x87800000,紧跟在后面的就是.vectors,也就是vectors.S文件里定义的中断向量表。 在中断向量表之后,才是armv7的start.s文件。
6、从上面的分析可以知道,u-boot工程对代码位置要求是: 中断向量表-->内核启动文件-->其他驱动代码且位置任意
7、继续阅读u-boot.lds可以找到一个 关键变量.__image_copy_end 。这个变量代表着u-boot整个程序代码段的结束地址。
8、接着就是 __rel_dyn_start和 __rel_dyn_end,叫做rel段,其实在u-boot.map里面可以看出,这一段里存放的内容与armv7架构的start.o有关。
9、接下来,就是end段,包含变量 _image_binary_end;还有 bss_start和bss_end段。综上所述,这些段是接下来U-BOOT源码分析的重点。
10、接下来,我们从u-boot.lds和u-boot.map中可以得知,程序开始执行从reset中断处理函数这里进入, reset在./arch/arm/armv7/start.s中被实现
1、 u-boot启动第一阶段代码
CPU及其硬件初始化:
(1)硬件设备初始化:
    /*
     * set the cpu to SVC32 mode    设置cpu进入管理员模式,并且关闭FIQ和IRQ
     */
    mrs    r0,cpsr
    bic    r0,r0,#0x1f-
    orr    r0,r0,#0xd3
    msr    cpsr,r0
/*
  • 清零CP15协处理器VBAR寄存器,允许重定位中断向量表。并写入新的中断向量表首地址
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc p150r0c1c00   @ Read CP15 SCTLR Register
    bic r0#CR_V       @ V = 0
    mcr p150r0c1c00   @ Write CP15 SCTLR Register
    /* Set vector address in CP15 VBAR register */
    ldr r0, =_start
    mcr p150r0c12c00  @Set VBAR
#endif
/* t urn off the watchdog   关闭看门狗*/
#if defined(CONFIG_S3C2400)
# define pWTCON        0x15300000
# define INTMSK        0x14400008    /* Interupt-Controller base addresses */
# define CLKDIVN    0x14800014    /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON        0x53000000
# define INTMSK        0x4A000008    /* Interupt-Controller base addresses */
# define INTSUBMSK    0x4A00001C
# define CLKDIVN    0x4C000014    /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    ldr     r0, =pWTCON
    mov     r1, #0x0
    str     r1, [r0]     //关闭看门狗//
  
    /*
     * mask all IRQs by setting all bits in the INTMR - default
     * 设置所有中断配置为默认配置
     */
    mov    r1, #0xffffffff
    ldr    r0, =INTMSK
    str    r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr    r1, =0x3ff3
    ldr    r0, =INTSUBMSK12
2、u-boot启动第二阶段代码分析
先看流程图
//
1、第二阶段的函数入口: start_armboot(void)
void start_armboot (void)
{
    init_fnc_t **init_fnc_ptr;
    char *s;
#ifndef CFG_NO_FLASH
    ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
    unsigned long addr;
#endif
    /* Pointer is writable since we allocated a register for it */
    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));  //gd结构体内所有信息,最终会传递给Linux内核//
    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");
    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd->bd, 0, sizeof (bd_t));
    monitor_flash_len = _bss_start - _armboot_start;
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {  / /这里for循环的是一个函数接口数组:
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }
/*板子初始化函数数组,函数被按照顺序调用*/
init_fnc_t *init_sequence[] = {
    cpu_init,        /* basic cpu dependent setup */
    board_init,        /* basic board dependent setup */
    interrupt_init,        /* set up exceptions */
    env_init,        /* initialize environment */
    init_baudrate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,        /* stage 1 init of console */
    display_banner,        /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,        /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,        /* display board info */
#endif
    dram_init,        /* configure available RAM banks */
    display_dram_config,
    NULL,
};
/
2、cpu_init()对CPU的IRQ和FIQ堆栈初始化
此函数在./cpu/armxxx/cpu.c里
int cpu_init (void)
{
    /*
     * setup up stacks if necessary
     */
#ifdef CONFIG_USE_IRQ
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
    return 0;
}
//
3、 board_init()对CPU的系统时钟、GPIO口和串口的初始化
此函数在./board/xxx/xxx.上
int board_init (void)
{
    DECLARE_GLOBAL_DATA_PTR;
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
    S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
    /* to reduce PLL lock time, adjust the LOCKTIME register */
    clk_power->LOCKTIME = 0xFFFFFF;
    /* configure MPLL */
    clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
    /* some delay between MPLL and UPLL */
    delay (4000);
    /* configure UPLL */
    clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
    /* some delay between MPLL and UPLL */
    delay (8000);
    /* set up the I/O ports */
    gpio->GPACON = 0x007FFFFF;
    gpio->GPBCON = 0x00044556;
    gpio->GPBUP = 0x000007FF;
    gpio->GPCCON = 0xAAAAAAAA;
    gpio->GPCUP = 0x0000FFFF;
    gpio->GPDCON = 0xAAAAAAAA;
    gpio->GPDUP = 0x0000FFFF;
    gpio->GPECON = 0xAAAAAAAA;
    gpio->GPEUP = 0x0000FFFF;
    gpio->GPFCON = 0x000055AA;
    gpio->GPFUP = 0x000000FF;
    gpio->GPGCON = 0xFF95FF3A;
    gpio->GPGUP = 0x0000FFFF;
    gpio->GPHCON = 0x0016FAAA;
    gpio->GPHUP = 0x000007FF;
    gpio->EXTINT0=0x22222222;
    gpio->EXTINT1=0x22222222;
    gpio->EXTINT2=0x22222222;
    /* arch number of SMDK2410-Board */
    gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
    /* adress of boot parameters */
    gd->bd->bi_boot_params = 0x30000100;
    icache_enable();     //地址总线高速缓存区使能//
    dcache_enable();   //数据总线高速缓存区使能//
    return 0;
}
串口通信初始化,函数在/cpu/armxxx/xxx/serial.c里
void serial_setbrg (void)
{
    S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
    int i;
    unsigned int reg = 0;
    /* value is calculated so : (int)(PCLK/16./baudrate) -1 */
    reg = get_PCLK() / (16 * gd->baudrate) - 1;
    /* FIFO enable, Tx/Rx FIFO clear */
    uart->UFCON = 0x07;
    uart->UMCON = 0x0;
    /* Normal,No parity,1 stop,8 bit */
    uart->ULCON = 0x3;
    /*
     * tx=level,rx=edge,disable timeout int.,enable rx error int.,
     * normal,interrupt or polling
     */
    uart->UCON = 0x245;
    uart->UBRDIV = reg;
#ifdef CONFIG_HWFLOW
    uart->UMCON = 0x1; /* RTS up */
#endif
    for (i = 0; i < 100; i++);
}
/*
* Initialise the serial port with the given baudrate. The settings
* are always 8 data bits, no parity, 1 stop bit, no start bits.
*
*/
int serial_init (void)
{
    serial_setbrg ();
    return (0);
}
//
4、 interrupt_init()配置启动定时器4中断,10ms一次
此函数在./cpu/armxxx/xxx/interupts.c上
int interrupt_init (void)
{
    S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();
    /* use PWM Timer 4 because it has no output */
    /* prescaler for Timer 4 is 16 */
    timers->TCFG0 = 0x0f00;
    if (timer_load_val == 0)
    {
        /*
         * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
         * (default) and prescaler = 16. Should be 10390
         * @33.25MHz and 15625 @ 50 MHz
         */
        timer_load_val = get_PCLK()/(2 * 16 * 100);
    }
    /* load value for 10 ms timeout */
    lastdec = timers->TCNTB4 = timer_load_val;
    /* auto load, manual update of Timer 4 */
    timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
    /* auto load, start Timer 4 */
    timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
    timestamp = 0;
    return (0);
}
//
5、 env_init()配置检查可用的FLASH
此函数在./common/env_flash.c里
int  env_init(void)
{
    int crc1_ok = 0, crc2_ok = 0;
    uchar flag1 = flash_addr->flags;          //用来判断FLASH是否是空的
    uchar flag2 = flash_addr_new->flags; .
    ulong addr_default = (ulong)&default_environment[0];
    ulong addr1 = (ulong)&(flash_addr->data);
    ulong addr2 = (ulong)&(flash_addr_new->data);
#ifdef CONFIG_OMAP2420H4
    int flash_probe(void);
    if(flash_probe() == 0)
        goto bad_flash;
#endif
    /*对待用的新地址进行CRC校验*/
    crc1_ok = (crc32(0, flash_addr->data, ENV_SIZE) == flash_addr->crc);
    crc2_ok = (crc32(0, flash_addr_new->data, ENV_SIZE) == flash_addr_new->crc);
    if (crc1_ok && ! crc2_ok) {
        gd->env_addr  = addr1;
        gd->env_valid = 1;
    } else if (! crc1_ok && crc2_ok) {
        gd->env_addr  = addr2;
        gd->env_valid = 1;
    } else if (! crc1_ok && ! crc2_ok) {
        gd->env_addr  = addr_default;
        gd->env_valid = 0;
    } else if (flag1 == ACTIVE_FLAG && flag2 == OBSOLETE_FLAG) {
        gd->env_addr  = addr1;
        gd->env_valid = 1;
    } else if (flag1 == OBSOLETE_FLAG && flag2 == ACTIVE_FLAG) {
        gd->env_addr  = addr2;
        gd->env_valid = 1;
    } else if (flag1 == flag2) {
        gd->env_addr  = addr1;
        gd->env_valid = 2;
    } else if (flag1 == 0xFF) {
        gd->env_addr  = addr1;
        gd->env_valid = 2;
    } else if (flag2 == 0xFF) {
        gd->env_addr  = addr2;
        gd->env_valid = 2;
    }
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
    return (0);
}
//
6、 init_baudrate()初始化配置串口波特率,递交给内核启动变量
此函数位置在./lib_xxx/board.c
static int init_baudrate (void)
{
    char tmp[64];    /* long enough for environment variables */
    int i = getenv_r ("baudrate", tmp, sizeof (tmp));
    gd->bd->bi_baudrate = gd->baudrate = (i > 0)
            ? (int) simple_strtoul (tmp, NULL, 10)
            : CONFIG_BAUDRATE;
    return (0);
}
//
7、 console_init_f()向Linux内核递交串口控制台信息
此函数在./common/console.c
int console_init_f (void)
{
    gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE
    if (getenv("silent") != NULL)
        gd->flags |= GD_FLG_SILENT;
#endif
    return (0);
}
///
8、 dram_init()函数定义了板子的内存地址与大小等信息,并向内核递交
此函数在./board/sbc2410x/sbc2410x.c
int dram_init (void)
{
    DECLARE_GLOBAL_DATA_PTR;
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
    return 0;
}
///
9、 main_loop()引导启动Linux内核的真正函数
此函数在./common/main.c
这里面其实是启动了U-BOOT的控制台指令集,提供u-boot的各种功能包括引导启动内核
3、u-boot命令集的实现
run_commed的分析、字符串内关键字的提取技巧、命令集结构体、那个代码查看工具
1、强大的代码查看工具:source Insight
///
2、 run_commed的分析之命令字符关键字的提取
        for (inquotes = 0, sep = str; *sep; sep++) {
            if ((*sep=='\'') &&
                (*(sep-1) != '\\'))
                inquotes=!inquotes;
            if (!inquotes &&
                (*sep == ';') &&    /* separator        */
                ( sep != str) &&    /* past string start    */
                (*(sep-1) != '\\'))    /* and NOT escaped    */
                break;
        }     //命令里若带了;号,会自动当两个指令来处理
   /*对具体指令在指令结构体中查找是否存在*/
        /* Extract arguments */
        if ((argc = parse_line (finaltoken, argv)) == 0) {
            rc = -1;    /* no command at all */
            continue;
        }  // parse_line解析指令,拆分为argv[0]和argv[1]。指令与指令的参数
        /* Look up command in command table */
        if ((cmdtp = find_cmd(argv[0])) == NULL) {
            printf ("Unknown command '%s' - try 'help'\n", argv[0]);
            rc = -1;    /* give up after bad command */
            continue;
        }  //在u-boot命令结构体数组中查找到对应结构体并返回。 find_cmd
struct cmd_tbl_s {
    char        *name;        /* 命令的名字           */
    int        maxargs;    /* 命令最大参数值  */
    int        repeatable;    /* 命令是否可重复 */
                    /* 命令对应的函数接口 */
    int        (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
    char        *usage;        /* 该命令短的帮助信息   */
#ifdef    CFG_LONGHELP
    char        *help;        /* 该命令长的帮助信息  */
#endif
#ifdef CONFIG_AUTO_COMPLETE
    /* do auto completion on the arguments */
    int        (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};.//
/* find_cmd函数的实现
cmd_tbl_t *find_cmd (const char *cmd)
{
    cmd_tbl_t *cmdtp;
    cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start;    /*Init value */
    const char *p;
    int len;
    int n_found = 0;
    /*
     * Some commands allow length modifiers (like "cp.b");
     * compare command name only until first dot.
     */
    len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
    for (cmdtp = &__u_boot_cmd_start;
         cmdtp != &__u_boot_cmd_end;
         cmdtp++) {
        if (strncmp (cmd, cmdtp->name, len) == 0) {
            if (len == strlen (cmdtp->name))
                return cmdtp;    /* full match */
            cmdtp_temp = cmdtp;    /* abbreviated command ? */
            n_found++;
        }
    }
    if (n_found == 1) {            /* exactly one match */
        return cmdtp_temp;
    }
    return NULL;    /* not found or ambiguous command */
}
=============================*/
        /* found - check max args */
        if (argc > cmdtp->maxargs) {
            printf ("Usage:\n%s\n", cmdtp->usage);
            rc = -1;
            continue;
        }
if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
        /* avoid "bootd" recursion */
        if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
            printf ("[%s]\n", finaltoken);
#endif
            if (flag & CMD_FLAG_BOOTD) {
                puts ("'bootd' recursion detected\n");
                rc = -1;
                continue;
            } else {
                flag |= CMD_FLAG_BOOTD;
            }
        }
#endif    /* CFG_CMD_BOOTD */
        /* OK - call function to do the command */
        if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
            rc = -1;
        }
        repeatable &= cmdtp->repeatable;
        /* Did the user stop this? */
        if (had_ctrlc ())
            return 0;    /* if stopped then not repeatable */
    }
    return rc ? rc : repeatable;
///
3、u-boot如何自动索引并执行指令
第一步:在./common/main.c中调用函数run_command (const char *cmd, int flag)开始;
第二步:run_command中,会调用函数cmd_tbl_t *find_cmd (const char *cmd)函数在指定内存查找匹配的指令; 每个指令是一个结构体( struct cmd_tbl_s
struct cmd_tbl_s {
    char        *name;        /* 指令的名称        */
    int        maxargs;    /* 命令最大的参数个数  */
    int        repeatable;    /* 是否可重复       */
                    /* 实现命令具体功能的函数接口    */
    int        (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
    char        *usage;        /* 该命令的简明帮助信息    */
#ifdef    CFG_LONGHELP
    char        *help;        /*该命令的详细帮助信息*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
    /* do auto completion on the arguments */
    int        (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
第三步:在函数cmd_tbl_t *find_cmd (const char *cmd)中,实现方案是,循环从指令结构体所在内存扫描并与目标命令进行比对:
    for (cmdtp = &__u_boot_cmd_start;
         cmdtp != &__u_boot_cmd_end;
         cmdtp++) {
        if (strncmp (cmd, cmdtp->name, len) == 0) {
            if (len == strlen (cmdtp->name))
                return cmdtp;    /* full match */
            cmdtp_temp = cmdtp;    /* abbreviated command ? */
            n_found++;
        }
这里面有两个宏:__u_boot_cmd_start 和 __u_boot_cmd_end。这两个宏被定义在板子对应目录下的u-boot.lds上:
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;
这里我们关注一下内存段u_boot_cmd在哪里被用到:./include/common.h里
#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
#ifdef  CFG_LONGHELP
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
第四步:这里可以看到,有个关键的宏U_BOOT_CMD,猜想应该是用来定义一个命令的,找一个例子来看看:
U_BOOT_CMD(
    go, CFG_MAXARGS, 1,    do_go,
    "go      - start application at address 'addr'\n",
    "addr [arg ...]\n    - start application at address 'addr'\n"
    "      passing 'arg' as arguments\n"
);
这个例子结合宏定义展开,那么结论就很明显了
cmd_tbl_t __u_boot_cmd_go __attribute__ ((unused,section (".u_boot_cmd")))= {go, maxargs, rep, cmd, usage}
第五步:如果我们要实现自己的一个command指令,操作步骤如下:
① 在u-boot目录下的./common里新建一个.c文件,修改目录下的Makefile内容添加编译该文件
②在这个.c文件里,模仿其他命令添加相关头文件并实现命令函数
③ 使用宏定义U_BOOT_CMD定义一个命令
///
4、u-boot如何使用命令系统加载并启动Linux内核
第一步:在main_loop函数中,内核启动主要依赖两条语句
s = getenv ("bootcmd");      //获取环境变量bootcmd
run_command (s, 0);            //执行环境变量里的命令
s = nand read .jffs2 0x30007fc0 kernel ; bootm 0x30007fc0
     //这里面有两个指令:以;号为界。第一个指令的意思是,从kernel所指定的nand falsh内存地址中读取规定的长度到SDRAM(内存)的0x30007fc0地址上;第二个指令的意思是,从0x30007fc0位置启动Linux内核//
第二步:nand命令对应的命令实现函数是int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])    /* read write */
     if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {   //这里处理到nand指令的read或write参数
        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"))) {    //这里进入读指令,判断读FLASH使用的文件格式为 jffs2;这里所谓的文件格式指的是读写FLASH时,数据长度的地址对齐方式, jffs2则可以随意读写,无需对齐
            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 */
                nand_write_options_t opts;
                memset(&opts, 0, sizeof(opts));
                opts.buffer    = (u_char*) addr;
                opts.length    = size;
                opts.offset    = off;
                /* opts.forcejffs2 = 1; */
                opts.pad    = 1;
                opts.blockalign = 1;
                opts.quiet      = quiet;
                ret = nand_write_opts(nand, &opts);
            }
第三步:第二条指令bootm的执行函数int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
typedef struct image_header {
    uint32_t    ih_magic;    /* 内核镜像模数    */
    uint32_t    ih_hcrc;    /* 内核镜像的头信息的CRC校验和    */
    uint32_t    ih_time;    /* 该内核镜像创建的时间    */
    uint32_t    ih_size;    /* 内核镜像的数据大小       */
    uint32_t    ih_load;    /* 内核在内存中的加载地址        */
    uint32_t    ih_ep;        /* 内核运行入口函数的接口地址 */
    uint32_t    ih_dcrc;    /*  内核镜像的数据的CRC校验和*/
    uint8_t        ih_os;        /* 内核操作系统类型*/
    uint8_t        ih_arch;    /*CPU架构  */
    uint8_t        ih_type;    /* 镜像的格式*/
    uint8_t        ih_comp;    /* 镜像的压缩类型 */
    uint8_t        ih_name[IH_NMLEN];    /* 内核镜像的名字  */
} image_header_t;  //这个就是uimage的完整格式!!
其中需要特别注意的就是ih_koad和in_ep;bootm指令在执行中,会判断配置的内核地址与ih_load是否一致,如果不一致,则将内核拷贝到ih_load对应的地址上。
①bootm第一步是检查内核镜像加载地址,将其移动到内核镜像结构体内所申明的位置下
    case IH_COMP_NONE:
         if(ntohl(hdr->ih_load) == addr) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;
            printf ("   Loading %s ... ", name);
            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else    /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
             memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif    /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;
② 调用函数void do_bootm_linux(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int   verify)启动Linux内核
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
         do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;
第四步:函数void do_bootm_linux(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int   verify)正式启动Linux
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
     setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
     setup_serial_tag (&params);    //1 这里配置内核启动的参数
#endif
#ifdef CONFIG_REVISION_TAG
     setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
     setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
     setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
         setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
     setup_videolfb_tag ((gd_t *) gd);
#endif
     setup_end_tag (bd);
#endif
    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
    {
        extern void udc_disconnect (void);
        udc_disconnect ();
    }
#endif
    cleanup_before_linux ();
    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);   //2 调用内核的入口函数,启动内核
}
       其中,设置Linux启动参数的 有这些函数:  启动参数被放在u-boot与linux约定好的指定FLASH内存地址上,约定的格式连续紧密存放。
struct tag {
    struct tag_header hdr;
    union {
        struct tag_core        core;
        struct tag_mem32    mem;
        struct tag_videotext    videotext;
        struct tag_ramdisk    ramdisk;
        struct tag_initrd    initrd;
        struct tag_serialnr    serialnr;
        struct tag_revision    revision;
        struct tag_videolfb    videolfb;
        struct tag_cmdline    cmdline;
        /*
         * Acorn specific
         */
        struct tag_acorn    acorn;
        /*
         * DC21285 specific
         */
        struct tag_memclk    memclk;
    } u;
};   //启动参数结构体
       函数 void setup_start_tag (bd_t *bd)创建一个启动参数开始标志,Linux内核启动该时,从这个启动参数开始,逐步去解析u-boot传递给它的启动参数
static void setup_start_tag (bd_t *bd)                                 //创建一个描述起始的启动参数
{
    params = (struct tag *) bd->bi_boot_params;   //0x300100  这个就是u-boot与Linux约定好的启动参数存放的基地址
    params->hdr.tag = ATAG_CORE;   //0x54410001  启动参数的起始tag标志
    params->hdr.size = tag_size (tag_core);
    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;
    params = tag_next (params);  //指向下一个tag
}
        函数 static void setup_memory_tags (bd_t *bd)递交板子DRAM内存分布情况的启动参数:
static void setup_memory_tags (bd_t *bd)     //创建多个描述板子内存空间的启动参数
{
    int i;
    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
        params->hdr.tag = ATAG_MEM;   //0x54410002
        params->hdr.size = tag_size (tag_mem32);  //
        params->u.mem.start = bd->bi_dram[i].start;
        params->u.mem.size = bd->bi_dram[i].size;
        params = tag_next (params);
    }
}
      函数 static void setup_commandline_tag (bd_t *bd, char *commandline)创建一个基本命令行启动参数,向Linux传输内核启动的基本命令
static void setup_commandline_tag (bd_t *bd, char *commandline)   //配置命令行信息的启动参数
{
    char *p;
    if (!commandline)
        return;
    /* eat leading white space */
    for (p = commandline; *p == ' '; p++);
    /* skip non-existent command lines so the kernel will still
     * use its default command line.
     */
    if (*p == '\0')
        return;
    params->hdr.tag = ATAG_CMDLINE;   //0x54410009
    params->hdr.size =
        (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
    strcpy (params->u.cmdline.cmdline, p);
    params = tag_next (params);
}
char *commandline = getenv ("bootargs");   //命令行信息来源于u-boot环境变量bootargs
bootargs = noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySACO
            /* 内核核心盘位于FALSH的第4个分区 开机启动的第一个应用程序是linuxrc,控制台是设备tty0*/
      函数static void setup_end_tag (bd_t *bd)启动参数结束标志
static void setup_end_tag (bd_t *bd)   //标志启动参数结束位置
{
    params->hdr.tag = ATAG_NONE;   //0x00000000
    params->hdr.size = 0;
}
    最后,通过函数指针调用Linux内核启动函数接口,传入必要参数,启动Linux内核:
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);   //调用内核启动的接口
bd->bi_boot_params == 启动参数所在基地址
bd->bi_arch_number == #define MACH_TYPE_SMDK2410             193  //机器ID,
///

 

/

四、U-BOOT常用的命令使用详解
1、信息查询命令
bdinfo:用于查询板子的信息,直接输入指令即可。
printenv:输出环境变量信息,直接输入指令就好
version:输出u-boot的版本信息
///
2、环境变量操作命令
saveenv:将环境变量保存到FLASH中
setenv xxx data: 设置/修改xxx这个环境变量的值为data
注意:修改的环境变量如果是字符串,或字符,需要单引号('')
             setenv也可以用于新建环境变量
             setenv xxx 这个指令格式可以将环境变量xxx删除,因为xxx被赋值为空
///
3、内存操作命令
这里的内存指DDR3L的内存和寄存器
md[.b .w .l] address [# of objects]:查询内存address位置指定长度的内容 。
解释:命令中的[.b .w .l]对应 byte、word 和 long,也就是分别以 1 个字节、2 个字节、4 个字节 来显示内存值。address 就是要查看的内存起始地址,[# of objects]表示要查看的数据长度,这个数据长度单位不是字节,而是跟你所选择的显示格式有关。比如你设置要查看的内存长度为 20(十六进制为 0x14),如果显示格式为.b 的话那就表示 20 个字节;如果显示格式为.w 的话就 表示 20 个 word,也就是 20*2=40 个字节;如果显示格式为.l 的话就表示 20 个 long,也就是 20*4=80 个字节。 另外要注意: uboot 命令中的数字都是十六进制的!不是十进制的!
举个例子:md.b 80000000 14  //查看以 0X80000000 开始的 20 个字节的内存值,显示格式为字节
nm[.b .w .l] address:用于修改指定内存地址的值。
解释:nm 命令同样可以以.b、.w 和.l 来指定操作格式。
例子: nm.l 0209c000   //查看修改GPIO1_DR寄存器的值
mm[.b .w .l] address:地址自动递增的修改内存的值
解释:mm 命令也是修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用命 令 nm 的话地址不会自增。 这里自增的宽度取决于你访问的格式(.b .w .l)
mw[.b .w .l] address value [count] :命令 mw 用于使用一个指定的数据(vlaue)填充一段内存address*count
解释:mw 命令同样可以以.b、.w 和.l 来指定操作格式,address 表示要填充的内存起始地址,value 为要填充的数据,count 是填充的长度。
例子 :mw.l 80000000 0A0A0A0A 10  //起始地址0x80000000开始,连续填充0a0a0a0a到0x10*4个字节
c p[.b .w .l] source target count:将source内存地址的数据拷贝到target内存地址,连续拷贝count个类型数据
解释:cp 命令同样可以以.b、.w 和.l 来指定操作格式,source 为源地址,target 为目的地址,count 为拷贝的长度。
例子 :cp.l 80000000 80000100 10  //我们使用.l 格式将 0x80000000 处的地址拷贝到 0X80000100 处,长度为 0x10 个 内存块(0x10 * 4=64 个字节)
cmp[.b .w .l] addr1 addr2 count:比较addr1和addr2两段内存的值是否相等
解释:cmp 命令同样可以以.b、.w 和.l 来指定操作格式,addr1 为第一段内存首地址,addr2 为第 二段内存首地址,count 为要比较的长度。
例子 :cmp.l 80000000 80000100 10  //使用.l 格式来比较 0x80000000 和 0X80000100 这 两个地址数据是否相等,比较长度为 0x10 个内存块(16 * 4=64 个字节)
///
4、网络操作命令
ping xxx.xxx.xxx.xxx:板子ping目标IP主机是否存在
dhcp:向路由器申请一个IP地址
nfs [loadAddress] [[hostIPaddr:]bootfilename]:从nfs服务器上下载数据到指定内存位置
解释:loadAddress 是要保存的 DRAM 地址,[[hostIPaddr:]bootfilename]是要下载的文件地址。
例子:nfs 80800000 192.168.1.100:/usr/my_ubuntu/mx6u_Learn/linux/nfs/zImage  从我的电脑的nfs服务器上下载zImage到内存0x80800000
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]:从tftp服务器上下载文件到指定的内存中
解释:tftp 命令的作用和 nfs 命令一样,都是用于通过网络下载东西到 DRAM 中,只是 tftp 命令 使用的 TFTP 协议,Ubuntu 主机作为 TFTP 服务器。 指令不再需要指定详细的文件路径,只要告诉目标文件名即可
例子 :tftpboot 80800000 192.168.1.100:zImage  //从192.168.1.100这个服务器上下载zImage文件到内存0x80800000中
///
5、EMMC和SD卡操作命令
        uboot 支持 EMMC 和 SD 卡,因此也要提供 EMMC 和 SD 卡的操作命令。 一般认为 EMMC 和 SD 卡是同一个东西,所以没有特殊说明,本教程统一使用 MMC 来代指 EMMC 和 SD 卡。 uboot 中常用于操作 MMC 设备的命令为“mmc”。 mmc 是一系列的命令,其后可以跟不同的参数,输入“?mmc”即可查看 mmc 有关的命
mmc info:输出当前使用的mmc内存卡的信息
mmc rescan:扫描当前开发板上所有的mmc卡
mmc list:查看目前所有可用的mmc卡
mmc dev:切换当前使用的mmc卡与分区
解释:[dev]用来设置要切换的 MMC 设备号,[part]是分区号。如果不写分区号的话默认为分区0。( 分区0通常是无法识别的
mmc part:获取当前选中的mmc卡设备的分区情况
解释:这里列出来的是可以被识别的分区,任何一张新的mmc卡都有默认分区0,无法被识别。 如果当前选择的是具体的某个分区(mmc dev xx yy),则指令显示的结果只是当前分区的信息
mmc read addr blk# cnt:读取mmc设备的数据
解释:addr 是数据读取到 DRAM 中的地址,blk 是要读取的块起始地址( 十六进制),一个块是 512 字节, 这里的块和扇区是一个意思 ,在 MMC 设备中我们通常说扇区,cnt 是要读取的块数量( 六进制 )。
例子 :mmc read 80800000 600 10  //从第0x600块读取0x10块数据到内存0x80800000中, 这里其实就是读取了u-boot的环境变量
mmc write addr blk# cnt:将数据写入mmc中去
解释:addr 是要写入 MMC 中的数据在 DRAM 中的起始地址,blk 是要写入 MMC 的块起始地址 ( 十六进制 ),cnt 是要写入的块大小,一个块为 512 字节。
例子 :我们可以使用命令“mmc write”来升 级 uboot,也就是在 uboot 中更新 uboot。
mmc erase blk# cnt:擦除mmc指定的块内容
解释:blk 为要擦除的起始块,cnt 是要擦除的数量。 没事不要用 mmc erase 来擦除 MMC 设备!!!
///
6、FAT格式文件系统操作命令
fatinfo <interface> [<dev[:part]>]:查询指定设备分区的FAT文件系统信息
解释:interface 表示接口,比如 mmc,dev 是查询的设备号,part 是要查询的分区。
例子:fatinfo mmc 1:1  //查询mmc设备1的第一个分区的文件系统信息
fatls <interface> [<dev[:part]>] [directory]:用于查询 FAT 格式设备的目录和文件信息
解释:interface 是要查询的接口,比如 mmc,dev 是要查询的设备号,part 是要查询的分区,directory 是要查询的目录。
例子 :fatls mmc 1:1  //查询mmc第1个设备的第1个分区的目录文件
fstype <interface> <dev>:<part>:查询指定设备分区的文件系统类型
解释:interface 是要查询的接口,比如 mmc,dev 是要查询的设备号,part 是要查询的分区。
例子: fstype mmc 1:1  //查询mmc设备1的第1个分区文件系统类型
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]:用于将指定的文件读取到 DRAM 中
解释:interface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是保存在 DRAM 中的起始 地址,filename 是要读取的文件名字。bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省 略的话表示读取整个文件。pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的 话表示从文件首地址开始读取。
例子 :fatload mmc 1:1 80800000 zimage  //从第一号MMC设备中的第1个分区中读取整个zimage到DRAM的0x80800000上
fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>:将内存数据写入MMC指定的位置中, 文件格式为fat
解释:interface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是要写入的数据在 DRAM 中的起始地址,filename 是写入的数据文件名字,bytes 表示要写入多少字节的数据。
例子 :fatwrite mmc 1:1 80800000 zImage 6788f8  //将0x80800000内存中的数据写入到第1个mmc设备的第1个分区中,写入大小为0x6788f8字节。
///
7、EXT格式文件系统操作命令
      uboot 有 ext2 和 ext4 这两种格式的文件系统的操作命令,常用的就四个命令,分别为: ext2load、ext2ls、ext4load、ext4ls 和 ext4write。 这些命令的含义和使用与 fatload、fatls 和 fatwrite 一样,只是 ext2 和 ext4 都是针对 ext 文件系统的 。关于 ext 格式文件系统其他命令的操作参考fat指令即可。
///
8、NAND操作命令
nand info:查看板子当前使用的nand flash的基本信息
nand device xx:切换当前使用的nand flash设备为xx
解释:如果你的板子支持多片 NAND 的话就可以使用此命令 来设置当前所使用的 NAND。 这个需要你的 CPU 有两个 NAND 控制器,并且两个 NAND 控制 器各接一片 NAND Flash
nand erase offset size:擦除FLASH指定地址的指定字节大小的数据
解释:nand erase 命令用于擦除 NAND Flash,NAND Flash 的特性决定了在向 NAND Flash 写数据 之前一定要先对要写入的区域进行擦除。 “nand erase”命令有三种形式
① nand erase offset size         //从offset地址开始,擦除size字节大小数据
② nand erase.part partition   //擦除指定的分区partition
③ nand erase.chip                //全片擦除
nand write addr off size:将内存addr的数据写入flash的off地址处,写入size个字节
解释:addr 是要写入的数据首地址,off 是 NAND 中的目的地址,size 是要写入的数据大小。
nand read addr off size:用于从 NAND 中的指定地址读取指定大小的数据到 DRAM 中
解释:addr 是目的地址,off 是要读取的 NAND 中的数据源地址,size 是要读取的数据大小。
///
9、BOOT操作命令
      uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常 用的跟 boot 有关的命令有:bootz、bootm 和 boot。
bootz [addr [initrd[:size]] [fdt]]:从addr加载 zImage内核,从fdt加载设备树并启动
解释:命令 bootz 有三个参数,addr 是 Linux 镜像文件在 DRAM 中的位置,initrd 是 initrd 文件在 DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可,fdt 就是设备树文件在 DRAM 中 的地址。
例子 :bootz 80800000 - 83000000  //从DRAM的0x80800000加载内核,从DRAM的0x83000000加载设备树并启动LINUX
bootm [addr [initrd[:size]] [fdt]]:加载启动 uImage内核
解释:其中 addr 是 uImage 在 DRAM 中的首地址,initrd 是 initrd 的地址,fdt 是设备树(.dtb)文件 在 DRAM 中的首地址,如果 initrd 为空的话,同样是用“-”来替代。
boot使用环境变量bootcmd 来执行对应操作并启动内核
解释:boot 命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系 统,bootcmd 是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引导”和“命 令”,说明这个环境变量保存着引导命令,其实就是启动的命令集合,具体的引导命令内容是可 以修改的。
例子:使用 tftp 命令从网络启动 Linux 那么就可以设置 bootcmd 为“tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb; bootz 80800000 - 83000000”,然后使用 saveenv 将 bootcmd 保存起来。然后直接输入 boot 命令即可从网络启动 Linux 系统( 这就是网络启动!!!
///
10、其他常用的命令
reset:重启u-boot
go addr [arg ...]跳转执行addr内存地址下执行裸机程序
解释:addr 是应用在 DRAM 中的首地址,我们可以编译一下裸机例程的实验 13_printf,然后将编 译出来的 printf.bin 拷贝到 Ubuntu 中的 tftpboot 文件夹里面, 注意,这里要拷贝 printf.bin 文件, 不需要在前面添加 IVT 信息,因为 uboot 已经初始化好了 DDR 了还有,一旦跳转到那里执行,程序没reurn就回不来了。
例子
run xxx:运行环境变量xxx
mtest [start [end [pattern [iterations]]]]:DDR内存读写测试命令
解释:start是要测试的DRAM开始地址,end是结束地址,比如我们测试0X80000000~0X80001000 这段内存,输入“mtest 80000000 80001000”。 测试会破坏内存的数据!!!
例子 :mtest 80800000 80800100  //读写测试内存0x80800000~0x80800100,Ctrl+C结束
///

/

五、U-BOOT的修改与移植
基于正点原子的NXP:imx6ull
学习目标:无论任何厂家CPU,都可以自己设计底板然后移植u-boot(基于CPU厂家的u-boot模板
提示:1、通常,自己设计底板,需要参考CPU厂家的官方开发板,且一些基本外设:DDR、EMMC、FLASH、UART这些在硬件上通常一模一样。
在U-BOOT模板中,添加自己的板子
① 在./configs目录下,添加自己的默认配置文件xxx_defconfig(这里拷贝了nxp默认的mx6ull_14x14_evk_emmc_defconfig为mx6ull_14x14_alientek_emmc_defconfig);在里面根据具体的CPU和配置文件信息,添加一些内容。不同厂家的CPU,默认配置文件的内容是不同的,但基本的结构是相同的
② 在./include/configs目录下,创建对应的配置信息头文件xxx.h(这里也是拷贝了nxp默认的mx6ullevk.h为mx6ull_14x14_alientek_emmc.h);一般具体的配置信息是放在这个头文件里的,其他厂家的CPU也是一样,只不过有些是细化到具体的底板,有些是具体到某一类
③ 在./board/目录下,找到CPU厂家的目录,添加具体底板的文件夹或者修改里面的一些文件(NXP的是需要根据具体底板建立文件夹,然后修改文件夹里面的一些文件);这里以NXP为例子,修改目录下的xxx.c文件文件名与当前目录名相同,修改Makefile里编译的对象,因为xxx.c已经改了名字了,修改imximage.cfg里面编译出plugin.bin的路径为当前目录,修改Kconfig里面的TARGET编译条件判断,并将里面涉及到的底板名称的全部改为对应的底板名字,修改MAINTAINERS内容,同样是为了匹配底板的名字。
④ 在./arch/arm/cpu/armv7/mx6目录下,修改Kconfig,添加底板的配置选项,以及索引到底板目录的Kconfig(这是NXP的修改要求,sunxi的似乎又没有修改??);这个涉及到U-BOOT图形配置界面,后面会详细讲解
///
根据具体的底板,修改外设驱动以正点原子NXP驱动网卡、LCD和LED为例
① LCD液晶显示屏驱动修改;实际上u-boot模仿了linux下的外设驱动挂载,为LCD提供了基本的结构体struct fb_videomode在目录include\linux\fb.h里);NXP则基于这个结构体,在他们自己的目录arch\arm\include\asm\imx-common\video.h下,二次封装了结构体struct dispaly_info_t,我们在xxx.c文件里,申明一个对应得结构体对象来描述自己的LCD屏幕,然后实现一个初始化函数挂载在结构体对应的enable上;初始化函数利用另一个NXP标准的GPIO配置结构体struct iomux_v3_cfg_t来描述需要配置哪些IO和寄存器;修改环境变量指定对应名字的LCD。(不同的CPU,配置LCD屏幕具体的驱动方式不一样,NXP有专门的LCD寄存器外设,sunxi则只能使用I2C同信;但描述LCD屏幕基本参数是一样的
② 网卡的驱动修改:NXP内置有MAC的网络外设,只需要再加一个PHY芯片就可以实现有线网络PHY芯片无论是哪个厂家的,他的前32个寄存器配置方式是一样的,且被规定默认配置即可正常使用,所以U-BOOT已经官方集成了对PHY的驱动,只需要修改结构体和具体的主机引脚定义即可其他CPU的厂家如果未提供MAC外设,就需要采用外置MAC+PHY的方案)。基于NXP的修改流程大概就是在xxx.h文件下,修改宏定义选择使用的网卡,并告诉主机PHY的设备ID;再在xxx.c里,使用NXP提供的结构体iomux_v3_cfg对象配置CPU的MAC外设与PHY引脚的电器属性,实现函数对PHY做硬件复位。(个别PHY芯片还需要做软件复位,像LAN8720,这就需要修改u-boot源码 drivers/net/phy/phy.c里面,找到函数genphy_update_link,在里面添加软件复位操作)。
③ NXP上,开机点亮LED1:同样是在xxx.h和xxx.c上做修改;在xxx.h上添加宏定义,申明使用的GPIO口及其复用功能;在xxx.c上使用NXP提供结构体iomux_v3_cfg_t申明一个结构体变量,描述GPIO的电器属性,同时是想函数,调用NXP提供的GPIO输出控制函数,让GPIO输出低电平,在board_init函数里,使用结构体来配置GPIO并调用函数来点亮LED。(不同的CPU厂家具体的结构体这些会有不同,但思想是一样的)。
/
修改bootcmd指令相关环境变量
① 对xxx.h文件作用的解释:任何CPU厂家,更多U-BOOT详细的配置选项都放在这里./include/configs/xxx.h,而./configs/xxx_defconfig反而很少;这些配置决定了某些初始化函数与是否执行,某些外设是否被驱动;同时在这些头文件中,也提供了U-BOOT的默认环境变量(不同CPU厂家,环境变量的定义方式和文件位置可能不同);所谓的源码裁剪,其实就是删除或取消这里面的某些不用的配置,不编译进入u-boot
② 默认环境变量,bootcmd和bootargs:无论那一款CPU,u-boot目录下,都会调用./include/env_default.h这个文件,而这个文件就会给u-boot配置各种默认环境变量,具体的环境变量内容则在对应底板的xxx.h里面以shell脚本语言的格式来定义;bootcmd变量确定了启动Linux内核的方式以及内核镜像、设备树在内存中的位置;bootargs则是传递给Linux内核的重要参数,告诉Linux,终端控制台使用的UART和波特率以及根文件系统所在磁盘分区以及分区的读写权限
③ 针对NXP这款CPU这DDR初始化文件修改:NXP内部有集成的bootload,用来引导u-boot从mmc拷贝到DDR并运行;所以,它势必需要先初始化时钟和DDR配置校准,使用不同容量的DDR,这里需要修改底板目录下./board/freescale/xxx/的一个配置文件-->imximage.cfg;这里面存放的是提供给NXP内部bootload读取的一些寄存器配置信息(官方默认512MB的);具体的校准方法,NXP官方提供了一个工具软件ddr_stress_tester,在pc端通过串口启动方式与板子链接,自动校准并生成ini文件,将文件内的数据写入cfg即可具体操作方式看正点原子的教程)。
//
全志香橙派的u-boot修改与移植
① 在./configs目录下,新建orangepi_linux_defconfig文件(我直接复制了里面的一个plus模板),里面的配置信息需要根据实际底板做修改;其中有个俩个配置选项十分重要,CONFIG_MACH_SUN8I_H3这个配置选项定义了核心板架构,CONFIG_DEFAULT_DEVICE_TREE="sun8i-h3-orangepi-plus"用来指定u-boot编译过程中读取的设备数(设备树文件在./arch/arm/dts里面,有些CPU厂家会在u-boot上引入设备树这个概念,暂时未学习)。
② 在./arch/arm/dts下面添加设备树文件,可以复制模板的设备树文件xxx.dts然后做修改,之后需要一个CPU厂家提供的软件来将其编译为dtb文件格式。(具体操作配置步骤这里暂时待学习
③ 进入./board/sunxi目录,打开board.c文件,可以在类似board_init函数内根据提供的标准函数接口,添加对一些外设的驱动(在本例程中,我在sunxi_board_init()函数里,添加点亮底板上的两LED灯)。
④ 最后,就可以按照标准流程来编译了,针对全志的CPU,我觉得重点在设备树和defconfig上我需要进一步学习:u-boot的设备树、某款CPU的defconfig配置选项含义、某款CPU的整个uboot编译流程
//
技术总结
① u-boot的编译流程和执行流程十分重点。
② 支持设备树和不支持设备树的原理有差别。
③ Kconfig图像界面配置与文件管理结构要学习。 
④ 实际工作中,通常会与CPU厂家合作,基于厂家的开发板来做修改,U-boot改动不大且有模板。
//

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐