嵌入式学习笔记102-uboot_1.1.6移植(2)

  1. 接下来分析uboot的第二阶段,在第一阶段的start.S的末尾有:
    ldr pc, _start_armboot

_start_armboot: .word start_armboot

而start_armboot()就是第二阶段的入口,首先我们要认识两个重要的结构体,start_armboot()函数的一系列初始化可以说都是以这个结构体为主线的:

typedef struct  global_data {
    bd_t        *bd;
    unsigned long   flags;
    unsigned long   baudrate;
    unsigned long   have_console;   /* serial_init() was called */
    unsigned long   reloc_off;  /* Relocation Offset */
    unsigned long   env_addr;   /* Address  of Environment struct */
    unsigned long   env_valid;  /* Checksum of Environment valid? */
    unsigned long   fb_base;    /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char   vfd_type;   /* display type */
#endif
#if 0
    unsigned long   cpu_clk;    /* CPU clock in Hz!     */
    unsigned long   bus_clk;
    unsigned long   ram_size;   /* RAM size */
    unsigned long   reset_status;   /* reset status register at boot */
#endif
    void        **jt;       /* jump table */
} gd_t;


typedef struct bd_info {
    int         bi_baudrate;    /* serial console baudrate */
    unsigned long   bi_ip_addr; /* IP Address */
    unsigned char   bi_enetaddr[6]; /* Ethernet adress */
    struct environment_s           *bi_env;
    ulong           bi_arch_number; /* unique id for this board */
    ulong           bi_boot_params; /* where this board expects params */
    struct              /* RAM configuration */
    {
    ulong start;
    ulong size;
    }           bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

这里会申明一个全局寄存器指针变量gd,指定r8为存储的寄存器,并且为volatile,即告知编译器不要优化,使CPU每次都到指定的内存去内容。每个.c若要使用这个gd,只需要在文件的前面加以 DECLARE_GLOBAL_DATA_PTR 申明即可。
那这个指针变量指的是哪里的内存的,函数start_armboot()的前部分有如下code:

    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
    /* 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));

其中存放全局配置内容的gd在_armboot_start 偏下CFG_MALLOC_LEN 下紧随其下的即使bd的存储空间了,内存分布图为:
这里写图片描述
这就要求我们在设置TEXT_BASE一定要远大于SDRAM的基址0x30000000才行,同时后续会讲到cpu_init()会设置IRQ和FRQ的栈,这会占用bd的内存空间,所幸的是uboot并没有使能中断,所以bd的内存不会被破坏。
接下来是一系列基本的初始化:

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }
//其中init_sequence是一个函数指针数组
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,
};

我们依次分析里面的每一个函数:
a. cpu_init()设置IRQ和FRQ的栈,不过没用到,也不应该用到,否则其指向的内存刚好和bd的重叠导致数据被破坏

b. board_init(),该函数明显和具体的板子有关,到board/tq2440下的tq2440.c有该函数的实现

int board_init (void)
{
    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 = 0x00044555;
    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 = 0xFF95FFBA;
    gpio->GPGUP = 0x0000FFFF;
    gpio->GPHCON = 0x002AFAAA;
    gpio->GPHUP = 0x000007FF;

    /* 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;
}

主要是设置时钟的频率,GPIO,以及gd arch_number boot_params,这两个参数到时候会传给kernel, arch_number 用于校验kernel是否合法,boot_params是传给kernel的参数的起始地址。因为uboot传给kernel是单向的,所以由于不可能同时运行uboot和kernel,所以uboot只能事先将要传递的参数放置某地址处,然后启动kernel并将该地址作为参数传给kernel,后面我们会讲解到的。

c. interrupt_init(),中断初始化,啥都没做,即不使能中断

d. env_init(),环境变量的初始化,无非就是设置gd->env_addr和gd->env_valid,env_init()在env_nand.c env_flash.c均有实现,其主要思想就是判断gd->env_addr指向哪,当通过crc校验tq2440.h指定的换环境地址CFG_ENV_ADDR后,如果错误,则使用默认的环境变量default_environment,通常uboot初次在板子启动的时候还没有将环境变量保存在存储介质上,所以都会指向default_environment,default_environment是一个数组,存储在内存当中,当uboot跑起来后执行save 环境变量指令后才会保存到指定的存储位置处,到下次启动时进过crc校验发现有数据了才设置gd->env_addr指向该处。所以板子初次是使用default_environment,直到save保存到存储介质后下次才会从该处获取,这样的好处是当我们更改了环境变变量原有的值或者新增的值可以得以保存。

uchar default_environment[] = {
#ifdef  CONFIG_BOOTARGS
    "bootargs=" CONFIG_BOOTARGS         "\0"
#endif
#ifdef  CONFIG_BOOTCOMMAND
    "bootcmd="  CONFIG_BOOTCOMMAND      "\0"
#endif
#ifdef  CONFIG_RAMBOOTCOMMAND
    "ramboot="  CONFIG_RAMBOOTCOMMAND       "\0"
#endif
#ifdef  CONFIG_NFSBOOTCOMMAND
    "nfsboot="  CONFIG_NFSBOOTCOMMAND       "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    "bootdelay="    MK_STR(CONFIG_BOOTDELAY)    "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
    "baudrate=" MK_STR(CONFIG_BAUDRATE)     "\0"
#endif
#ifdef  CONFIG_LOADS_ECHO
    "loads_echo="   MK_STR(CONFIG_LOADS_ECHO)   "\0"
#endif
#ifdef  CONFIG_ETHADDR
    "ethaddr="  MK_STR(CONFIG_ETHADDR)      "\0"
#endif
#ifdef  CONFIG_ETH1ADDR
    "eth1addr=" MK_STR(CONFIG_ETH1ADDR)     "\0"
#endif
#ifdef  CONFIG_ETH2ADDR
    "eth2addr=" MK_STR(CONFIG_ETH2ADDR)     "\0"
#endif
#ifdef  CONFIG_ETH3ADDR
    "eth3addr=" MK_STR(CONFIG_ETH3ADDR)     "\0"
#endif
#ifdef  CONFIG_IPADDR
    "ipaddr="   MK_STR(CONFIG_IPADDR)       "\0"
#endif
#ifdef  CONFIG_SERVERIP
    "serverip=" MK_STR(CONFIG_SERVERIP)     "\0"
#endif
#ifdef  CFG_AUTOLOAD
    "autoload=" CFG_AUTOLOAD            "\0"
#endif
#ifdef  CONFIG_PREBOOT
    "preboot="  CONFIG_PREBOOT          "\0"
#endif
#ifdef  CONFIG_ROOTPATH
    "rootpath=" MK_STR(CONFIG_ROOTPATH)     "\0"
#endif
#ifdef  CONFIG_GATEWAYIP
    "gatewayip="    MK_STR(CONFIG_GATEWAYIP)    "\0"
#endif
#ifdef  CONFIG_NETMASK
    "netmask="  MK_STR(CONFIG_NETMASK)      "\0"
#endif
#ifdef  CONFIG_HOSTNAME
    "hostname=" MK_STR(CONFIG_HOSTNAME)     "\0"
#endif
#ifdef  CONFIG_BOOTFILE
    "bootfile=" MK_STR(CONFIG_BOOTFILE)     "\0"
#endif
#ifdef  CONFIG_LOADADDR
    "loadaddr=" MK_STR(CONFIG_LOADADDR)     "\0"
#endif
#ifdef  CONFIG_CLOCKS_IN_MHZ
    "clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
    "pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY)    "\0"
#endif
#ifdef  CONFIG_EXTRA_ENV_SETTINGS
    CONFIG_EXTRA_ENV_SETTINGS
#endif
    "\0"
};

从上面我们可以知道如果要设置指定的环境变量的话,只需要在tq2440.h里定义该宏即可

e. init_baudrate(),直接获取环境变量的配置,所以该函数必须在env_init()之后。

f. serial_init(),串口的初始化,在u-boot-1.1.6\cpu\arm920t\s3c24x0\serial.c里实现的,里面直接调用serial_setbrg,主要就是设置波特率等一些设置,注意的是

    /* value is calculated so : (int)(PCLK/16./baudrate) -1 */
    reg = get_PCLK() / (16 * gd->baudrate) - 1;

由于get_PCLK() -> get_HCLK() ->get_FCLK(), 而2410 和2440对这三个频率的获取公式不一样,所以在移植的时候要注意这点!

g. dram_init(),SDRAM的初始化,也在board/tq2440下的tq2440.c下

int dram_init (void)
{
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

    return 0;
}
#define PHYS_SDRAM_1        0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE   0x04000000 /* 64 MB */

这个函数指针数组基本就是这样子了,
我们接着往下看,
如果有nor flash的话会有flash_init(),明显这是跟板子有关的,在u-boot-1.1.6\board\tq2440\flash.c里,具体的实现请读者自行分析,如果在tq2440.h里申明了CFG_CMD_NAND 的话 还会初始化nand_init(); 里面最终会调用board_nand_init(nand); 这个是extern,即如果用户想使用nand flash的话 那么得自己完成相应的code,当然是最底层的code的实现,上层的driver已经基本是完成的。
继续往下走会发现有个env_relocate ();这个是环境变量的重定向,为什么要重定向呢,主要是前面只是指向了环境的基址,但如果指向的是存储在存储介质入flash或nand的话,那么访问速度就会慢,所以干脆搬到SDRAM来以便快速的访问!只是奇怪如果是指向default_environment的话已经在SDRAM了不知道为何还要搬移,估计是为了统一,即不管之前到底是在default_environment的ram内存还是非易失性存储介质都统一搬移到指定处,而这个“指定”的内存是使用malloc,即前面事先预留的,

#define CFG_MALLOC_LEN      (CFG_ENV_SIZE + 128*1024)

显然这个malloc特意在128K基础上再开辟CFG_ENV_SIZE来存放环境变量。
最后 函数调用 main_loop (); 下面大概讲诉这个 main_loop ();

  1. main_loop ()
    该函数简化后如下:
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    s = getenv ("bootdelay");
    bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
        s = getenv ("bootcmd");

    debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    run_command (s, 0);
    ...........................................

也就是说板子在delay时间内没有任何按键的话就会执行bootcmd,而正是这个命令将会启动内核!

3.接下来重点讲解bootcmd,一般其命令格式如下:

#define CONFIG_BOOTCOMMAND "nand read 0x32000000 0x200000 0x300000; bootm
0x32000000"

该‘bootcmd’分为两个命令 一个是nand read 0x32000000 0x200000 0x300000;,即从nand地址为0x200000 copy size为0x300000 到0x32000000处,其实就是实现将kernel的code从存储介质拷贝到内存当中去,第二个命令则是调用bootm命令并将刚才拷贝的kernel基址作为参数传进去,看一下bootm:

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
);

显然最后会调用do_bootm函数:

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    addr = simple_strtoul(argv[1], NULL, 16);
    memmove (&header, (char *)addr, sizeof(image_header_t));
    data = (ulong)&header;
    len  = sizeof(image_header_t);

    checksum = ntohl(hdr->ih_hcrc);
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {

    data = addr + sizeof(image_header_t);
    len  = ntohl(hdr->ih_size);

    if (verify) {
        puts ("   Verifying Checksum ... ");
        if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {


    **memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);**
**if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                (uchar *)data, &len) != 0)** 
**BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
                        &unc_len, (char *)data, len,
                        CFG_MALLOC_LEN < (4096 * 1024), 0);**


 do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);

该函数的基本作用就是分析该kernel的头部信息(所以必须编译出来的kernel要经过uboot的mkimage对image添加头部信息成uimage),头部信息如下:

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;

其中ih_load;是最终kernel的基址,所以不管前面的nand read 0x32000000 0x200000 0x300000;到0x32000000还是其他地址,都会通过解析这个头部信息来获取最终的加载地址,在将code搬移到指定的ih_load前会先判断该kernel是否是压缩的,如果没压缩直接memmove ((void ) ntohl(hdr->ih_load), (uchar )data, len); 到指定的ih_load处,如果是压缩的就解压到指定ih_load处。同时会对头部信息和kernel进行crc校验 其中ih_hcrc ih_dcrc两个校验值又mkimage工具生成。
所以do_bootm()函数就是将kernel经过解压(如果需要)搬移到指定的ih_load处,并校验数据的完整性,最后调用do_bootm_linux ()函数

4.do_bootm_linux ()分析:
主要code如下:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             ulong addr, ulong *len_ptr, int verify)
{
    void (*theKernel)(int zero, int arch, uint params);
    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

    setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
    setup_serial_tag (&params);
#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

    cleanup_before_linux ();

    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

其实就是调用kernel,申明一个void (*theKernel)(int zero, int arch, uint params); 函数指针,然后指向kernel的入口处,接着传参调用,至于参数是怎么传递,uboot在调用kernel之前先在指定的传参基址处依次赋值,这个传参基址就是在之前board_init()里定义的gd->bd->bi_boot_params = 0x30000100;
当然,除了传参基址外,还要规定传参的数据结构,linux 2.4.x 后采用标记列表(tagged list)的形式来启动参数,具体可以自行上网查询。最后调用theKernel 后会根据这个传参基址自行解析参数,后续会对 linux kernel讲解时提及的!

嵌入式底层开发环境的搭建通常涉及两个关键部分:U-Boot(统一固件加载器)和Linux内核移植。下面是这两个步骤的基本概述: 1. **U-Boot(U-Boot Bootloader)**: U-Boot是一个开源的Bootloader,它负责引导操作系统到内存并初始化硬件。在嵌入式开发,它的作用是为内核提供一个启动平台。搭建过程包括: - **下载源码**:从U-Boot的GitHub仓库或官方网站获取源代码。 - **配置编译**:根据目标硬件平台(如ARM、x86等)和特定需求定制Makefile。 - **编译构建**:执行`make`命令,生成适用于目标板的可执行文件。 - **烧录到目标板**:使用如JTAG或SPI闪存工具将U-Boot加载到设备的非易失性存储。 2. **Linux 内核移植**: Linux内核移植是指将通用的Linux内核配置为适合特定硬件平台的过程。主要步骤如下: - **下载内核源码**:从Linux内核项目的Git仓库获取最新或稳定版本的源代码。 - **配置内核**:使用`make menuconfig`或`make defconfig`选择适合目标板的选项。 - **编译内核**:选择目标架构(如arm, x86等),运行`make`生成内核和设备驱动程序。 - **内核模块和initrd**:可能还需要配置和编译必要的模块(如网络、USB等),以及生成初始ramdisk (initrd)。 - **移植启动加载**:将内核映像和引导参数传递给U-Boot,这通常涉及到修改U-Boot的启动脚本(cmd_line.txt)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值