uboot启动流程概述_uboot启动第二阶段分析

本文详细介绍了UBoot启动的第二阶段,包括start_armboot函数、内存分配、外设初始化、环境变量重定位、控制台和网卡初始化等关键步骤。在启动过程中,UBoot会进行CPU、DDR、网卡等硬件的初始化,并将环境变量移到DDR中,最后进入命令行模式等待用户输入或启动内核。
摘要由CSDN通过智能技术生成

之前介绍了 uboot 启动第一阶段,现在介绍启动的第二阶段

启动阶段的工作

启动第一阶段的主要工作是对处理器的内部资源(如时钟、串口)、内存(ddr)初始化,并进行 uboot 的重定位,并跳转到启动第二阶段

启动第二阶段的主要工作则是对处理器的外部资源(iNand、网卡芯片…)、uboot环境(uboot命令、环境变量..)等初始化,并等待命令输入

工作流程

正常情况下,在 uboot 的初始化工作完毕后,会启动内核,在启动内核后结束 uboot 程序。

但是用户可以阻止 uboot 的结束,进入 uboot 的命令行模式,就是一个 uboot 中的死循环;uboot 在死循环中不断接受命令、解析命令、执行命令

start_armboot

如果说启动第一阶段主要工作是 lowlevel_init 完成的,那么启动第二阶段的主要工作是 start_armboot 完成的

相关变量介绍

init_fnc_ptr

它是一个 init_fnc_t 类型的函数指针数组,通过 typedef int (init_fnc_t) (void); 定义

gd

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

gd 这个变量通过 DECLARE_GLOBAL_DATA_PTR 定义,这个变量的作用是用来存储 uboot 需要使用到的全局变量,这样可以减少全局变量的数量,方便他人阅读代码。具体的作用可以从结构体成员中得知

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
    void        **jt;       /* jump table */
} gd_t;

gd 变量的结构体成员的功能基本可以从名字和注释中得知,不过对第一个成员 bd 的描述很少,这里再从 bd_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;

从 bd_t 的定义可以看出, bd 这个成员变量的主要作用是用来保存一些和板级相关的信息,如波特率、ip 地址等

因为 gd 这个变量需要被频繁访问,所以使用了 volatile ,避免编译器做出不适当的优化;另外使用 register asm ("r8") 加速访问

内存排布

上面提到的 gd bd 指针变量只是声明,此时还不能使用,需要给他们分配内存空间

分配内存的原则就是够用、紧凑,所以首先需要做的是为 gd bd 分配基地址

在 uboot 中计算 gd 和 bd 的基地址的方式如下

gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
    gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
...
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

其中 CFG_UBOOT_BASE=0x33e00000 CFG_UBOOT_SIZE=2MB CFG_MALLOC_LEN=912KB CFG_STACK_SIZE=512KB gd_t=36字节 bd_t≈44字节

从而可以得到内存分配如图

f1636b736b155cc57460c2320d0bdb68.png

部分外设初始化工作

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

这里初始化工作是通过一个 for 循环完成的,使用先前定义的 init_fnc_ptr 变量去指向一个预先定义的函数指针数组 init_sequence ,在 init_sequence 中存储了用来初始化外设的函数指针。通过 for 循环依次执行每一个初始化函数,一旦返回的值不为0,就停止程序的运行并报错

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
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,      /* configure available RAM banks */
    display_dram_config,
    NULL,
};

cpu_init

cpu_init 只有当允许了中断的情况下才会做中断栈的设置工作,因为我们 uboot 中没有允许中断,所以这里相当于什么都没做

board_init

这里进行了网卡的初始化,本人使用的开发板使用的网卡芯片是 dm9000 ,所以使用了 dm9000_pre_init 进行预初始化操作

网卡的驱动是不需要修改的,关键是移植,dm9000_pre_init 需要做的事就是移植相关,就像 stm32 一样,使用 GPIO 前,需要进行初始化的工作,这里就是做这些的工作

除了网卡的初始化,这里还在 bd 中添加了机器码和参数内存地址

机器码是用来软件和开发板之间配对用的,避免将软件下载到错误的开发板导致损失。机器码是唯一的,需要向 uboot 进行申请,当然学习 uboot 的时候可以随意填写,只要 uboot 中配置的机器码和 linux 内核中的机器码一致即可

这里的参数地址中存放的是需要想内核中传送的字符串参数(bootargs)的地址, uboot 启动的时候是通过 r0 r1 r2 来传递参数,其中一个寄存器放的就是 bd->bi_boot_params 中的值

interrupt_init

这里是对定时器4做的相关初始化,进行了10ms的定时

env_init

这里是环境变量的初始化,通过 x210_sd.h 中定义的宏 CFG_ENV_IS_IN_AUTO 分析可以知道,env_init 应该使用的是 env_auto.c 中定义的 env_init

init_baudrate

首先是从 uboot 环境变量中读取波特率,如果环境变量中没有定义波特率就使用 x210_sd.h 定义的波特率

uboot 环境变量可以在 uboot 中使用 print 查看

serial_init

串口初始化,主要工作在汇编中完成了,这里没有做什么事

console_init_f

控制台初始化第一阶段,一般第一阶段后缀为 _f,第二阶段后缀为 _r

display_banner

用来输出 uboot 的 logo

const char version_string[] =
    U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

uboot 启动时输出的 version_string 是定义在主 Makefile 中的,在编译时自动生成的 include/version_autogenerated.h 就会包含 U_BOOT_VERSION 相关信息

print_cpuinfo

这里可以输出 CPU 的相关信息

CPU:  S5PV210@1000MHz(OK)
        APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
        MPLL = 667MHz, EPLL = 96MHz
                       HclkDsys = 166MHz, PclkDsys = 83MHz
                       HclkPsys = 133MHz, PclkPsys = 66MHz
                       SCLKA2M  = 200MHz

dram_init

这里做的是 ddr 软件方面的初始化

因为嵌入式设备是定制性的,不像 PC 机都是标准化的,可以自动获取 ddr 的片数、大小

因此在 uboot 中,需要人为添加 ddr 的相关配置信息,相当于是将 ddr 的配置信息写入 gd->bd->bi_dram 这个数组中

display_dram_config

打印 dram 信息

堆管理器初始化

mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);

这样堆的地址如下图

f423276e442ae031041cb1b94e473852.png

开发板专有初始化

为了保证 uboot 的通用性,三星使用了宏来判断当前的开发板的型号,并做对应的初始化

这里的初始化是针对 MMC 的,通过 Makefile 中的脚本,配置开发板是 sd 还是 onenand 启动;然后对 MMC 的初始化根据宏做相应的处理

环境变量重定位

uboot 的环境变量需要重定位到 ddr 中才能使用

在 env_relocate 中,先是通过宏判读环境变量是否嵌入在代码段中;本人的开发板的环境变量在 SD 卡中,所以直接跳过,直接分配一个缓存准备用来存放环境变量

uboot 第一次启动时,因为没有烧录 env 分区,gd->env_valid 应当为0,这时候会使用 uboot 的默认系统变量,并写入到 SD 卡的 env 分区中

控制台初始化

console_init_r 做的是控制台的第二阶段的初始化,主要是进行控制台的软件方面的配置

重定位了 stdin stdout stderr

网卡芯片初始化

eth_initialize 对网卡芯片本身进行初始化

开机logo显示

x210_preboot_init 进行开发板启动前的初始化,开启 lcd 显示开机 logo

logo 一般是使用软件制作的数组

通过sd卡烧录系统

通过 check_menu_update_from_sd 会判断开发部的 left 按键是否按下,开发板会在按键按下的时候,进入更新模式,读取 SD 卡中的镜像烧录系统

使用 SD 卡烧录比 fastboot 的方式简单、高效,一般用于量产的场景

死循环

主要是有三个功能

  • 命令解析器
  • 开机倒计时
  • 命令补全

总结

以下来自朱老师总结的 uboot 的工作

init_sequence
    cpu_init    空的
    board_init  网卡、机器码、内存传参地址
        dm9000_pre_init         网卡
        gd->bd->bi_arch_number  机器码
        gd->bd->bi_boot_params  内存传参地址
    interrupt_init  定时器
    env_init
    init_baudrate   gd数据结构中波特率
    serial_init     空的
    console_init_f  空的
    display_banner  打印启动信息
    print_cpuinfo   打印CPU时钟设置信息
    checkboard      检验开发板名字
    dram_init       gd数据结构中DDR信息
    display_dram_config 打印DDR配置信息表
mem_malloc_init        初始化uboot自己维护的堆管理器的内存
mmc_initialize        inand/SD卡的SoC控制器和卡的初始化
env_relocate        环境变量重定位
gd->bd->bi_ip_addr    gd数据结构赋值
gd->bd->bi_enetaddr    gd数据结构赋值
devices_init        空的
jumptable_init        不用关注的
console_init_r        真正的控制台初始化
enable_interrupts    空的
loadaddr、bootfile     环境变量读出初始化全局变量
board_late_init        空的
eth_initialize        空的
x210_preboot_init    LCD初始化和显示logo
check_menu_update_from_sd    检查自动更新
main_loop            主循环

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值