uboot启动流程源码分析(一)

本文详细介绍了U-Boot Bootloader的BL1和BL2阶段,重点剖析了BL2的启动过程,涉及内存规划、初始化函数执行、硬件设备配置等内容,适合对Bootloader内核加载感兴趣的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实前面我学习韦神的书的时候,学习过uboot,知道分为两个阶段。期间对这个uboot的源码没有了解深入。最近坐AVB校验,需要uboot到kernel的这个过程。这里再学习一下。

与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段。

BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现;

BL2阶段主要是对外部设备如网卡、Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现。

(这里不要和ATF的BL1搞混了哈)

1、BL1阶段

uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:

(1) 指定uboot的入口。在链接脚本uboot.lds中指定uboot的入口为start.S中的_start。

(2)设置异常向量(exception vector)

(3)关闭IRQ、FIQ,设置SVC模式

(4)关闭L1 cache、设置L2 cache、关闭MMU

(5)根据OM引脚确定启动方式

(6)在SoC内部SRAM中设置栈

(7)lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)

(8)设置开发板供电锁存

(9)设置SDRAM中的栈

(10)将uboot从SD卡拷贝到SDRAM中

(11)设置并开启MMU

(12)通过对SDRAM整体使用规划,在SDRAM中合适的地方设置栈

(13)清除bss段,远跳转到start_armboot执行,BL1阶段执行完

2、BL2阶段

start_armboot函数位于lib_arm/board.c中,是C语言开始的函数,也是BL2阶段代码中C语言的 主函数,同时还是整个u-boot(armboot)的主函数,BL2阶段的主要功能如下:

(1)规划uboot的内存使用

(2)遍历调用函数指针数组init_sequence中的初始化函数

(3)初始化uboot的堆管理器mem_malloc_init

(4)初始化SMDKV210开发板的SD/MMC控制器mmc_initialize

(5)环境变量重定位env_relocate

(6)将环境变量中网卡地址赋值给全局变量的开发板变量

(7)开发板硬件设备的初始化devices_init

(8)跳转表jumptable_init

(9)控制台初始化console_init_r

(10)网卡芯片初始化eth_initialize

(11)uboot进入主循环main_loop

这里主要对第二个阶段BL2进行一个分析。

3、start_armboot函数分析

start_armboot函数的主要功能如下:

(1)遍历调用函数指针数组init_sequence中的初始化函数

依次遍历调用函数指针数组init_sequence中的函数,如果有函数执行出错,则执行hang函数,打印出”### ERROR ### Please RESET the board ###”,进入死循环。

(2)初始化uboot的堆管理器mem_malloc_init

(3)初始化SMDKV210的SD/MMC控制器mmc_initialize

(4)环境变量重定位env_relocate

(5)将环境变量中网卡地址赋值给全局变量的开发板变量

(6)开发板硬件设备的初始化devices_init

(7)跳转表jumptable_init

(8)控制台初始化console_init_r

(9)网卡芯片初始化eth_initialize

(10)uboot进入主循环main_loop

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的各种功能包括引导启动内核

main_loop()引导启动Linux内核的真正函数,这个main_loop()才是我最关注的函数。

这一步找到了我想要的关注点,就是main_loop()函数。

参考链接:
https://blog.youkuaiyun.com/weixin_40639467/article/details/122506413
https://blog.51cto.com/u_15169172/2710568

所有资料来源网上,与朋友分享 u-boot-1.1.6之cpu/arm920t/start.s分析 2 u-boot中.lds连接脚本文件的分析 12 分享篇我总结的uboot学习笔记(转) 15 U-BOOT内存布局及启动过程浅析 22 u-boot中的命令实现 25 U-BOOT环境变量实现 28 1.相关文件 28 2.数据结构 28 3.ENV 的初始化 30 3.1env_init 30 3.2 env_relocate 30 3.3*env_relocate_spec 31 4. ENV 的保存 31 U-Boot环境变量 32 u-boot代码链接的问题 35 ldr和adr在使用标号表达式作为操作数的区别 40 start_armboot浅析 42 1.全局数据结构的初始化 42 2.调用通用初始化函数 43 3.初始化具体设备 44 4.初始化环境变量 44 5.进入主循环 44 u-boot编译过程 44 mkconfig文件的分析 47 从NAND闪存中启动U-BOOT的设计 50 引言 50 NAND闪存工作原理 51 从NAND闪存启动U-BOOT的设计思路 51 具体设计 51 支持NAND闪存的启动程序设计 51 支持U-BOOT命令设计 52 结语 53 参考文献 53 U-boot给kernel传参数和kernel读取参数—struct tag (以及补充) 53 1 、u-boot 给kernel 传RAM 参数 54 2 、Kernel 读取U-boot 传递的相关参数 56 3 、关于U-boot 中的bd 和gd 59 U-BOOT源码分析及移植 60 、u-boot工程的总体结构: 61 1、源代码组织 61 2.makefile简要分析 61 3、u-boot的通用目录是怎么做到与平台无关的? 63 4、smkd2410其余重要的文件 : 63 二、u-boot的流程、主要的数据结构、内存分配 64 1、u-boot的启动流程: 64 2、u-boot主要的数据结构 66 3、u-boot重定位后的内存分布: 68 三、u-boot的重要细节 。 68 关于U-boot中命令相关的编程 : 73 四、U-boot在ST2410的移植,基于NOR FLASH和NAND FLASH启动。 76 1、从smdk2410到ST2410: 76 2、移植过程: 76 3、移植要考虑的问题: 77 4、SST39VF1601: 77 5、我实现的flash.c主要部分: 78 6、增加从Nand 启动的代码 : 82 7、添加网络命令。 87
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值