005@ arm-linux之uboot向内核传递参数(setup_arch分析)

arm-linux之uboot向内核传递参数(setup_arch分析)
抛开 uboot 不谈,先看看 uboot 给内核传递的参数是什么样的东西,在 arch/arm/kernel/setup.h     文件中的 struct tag 结构体:
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;
               /*
        * Marvell specific
      */
              struct tag_mv_uboot     mv_uboot;
             // board info
              struct tag_board_info board_info;
              } u;
};
Uboot 传递给内核的参数,都是一些对一个个的设备参数的描述,用于对内核进行相应的初始化, 参数的具体内容暂时无需关心,
有个大致印象就行,下面一步步看内核是怎么接收 uboot 参数的,在 setup_arch 函数中首先定义 struct tag * 型指针变量 tags
struct tag *tags = (struct tag *)& init_tags ;
tags 是重点,它就是内核接收 uboot 参数的东西!
init_tags 是个 全局静态变量 ,就在本文件 (arch/arm/kernel/setup.c) 定义如下:
static struct init_tags {
       struct tag_header hdr1;
       struct tag_core   core;
       struct tag_header hdr2;
       struct tag_mem32  mem;
       struct tag_header hdr3;
} init_tags __initdata = {
       { tag_size(tag_core), ATAG_CORE },
       { 1, PAGE_SIZE, 0xff },
       { tag_size(tag_mem32), ATAG_MEM },
       { MEM_SIZE, PHYS_OFFSET },
       { 0, ATAG_NONE }
};
在定义结构体 init_tags 的同时声明了静态全局变量      init_tags ,并且对其结构体中的5个形参做了初始化赋值,第一个形参是最关键的,就是struct tag_header hdr1;
记住这个静态全局变量不重要 ,继续往下看,还在 setup_arch 函数中接下来几行:
if (__atags_pointer)
       tags = phys_to_virt( __atags_pointer );
else if (mdesc->boot_params)
       tags = phys_to_virt(mdesc->boot_params);
这个是重点,这里根据情况判断 tags 接收 uboot 参数的来源, if 中说明来源是 uboot 传递, else if 说明是由内核部分的代码 ( 即代码写死,不是从 uboot) ,这个是重点:
先看看这个 __atags_pointer 是什么:
__atags_pointer ,定义在汇编文件 arch/arm/kernel/head-common.S      __switch_data 子程序
( 可参考“ ARM 架构 内核启动分析 -head.S (1.4、stext分析之打开MMU并跳到start kernel”一文   ) )
在内核代码源头 stext 运行前,由 arm 寄存器 R2 保存要传递给内核的参数的地址,当 stext 运行到子程序 __switch_data 时,
定义变量 __atags_pointer 保存这个地址,即 __atags_pointer 保存了    uboot 要传递给内核的参数的地 址,所以这里让 tags 获取 __atags_pointer 的转换后的虚拟地址,即可访问。
有的时候,可能不需要从 uboot 传递参数到内核,也就是说这些参数写死在内核里而不是在 uboot 里,
那么就可以写死,写死是写死在 machine_desc     变量的 boot_params 成员,可以把地址值赋给这个成员,即可访问。
一般来说还是从 __atags_pointer    传递,即从 uboot 传递的几率比较大,我手头这个 marvell 设备就是如此,
毕竟在内核代码的 machine_desc    变量写死本质还是 uboot 里存放这些参数的物理地址,
一旦 uboot 里这些参数的物理地址变动,同时还要改 machine_desc 变量的这个值,不如自动传递方便。
搞明白了参数的传递,下面看内核代码如何使用这些参数,接着 setup_arch    函数往下看,如下:
if (tags->hdr.tag != ATAG_CORE)
       convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
        tags = (struct tag *)&init_tags;
结合本文最前面的 struct tags     结构体,它的第一个成员如下:
struct tag_header hdr
记住, struct tag_header   的成员 tag ,如果不等于宏 ATAG_CORE 的值 ( arch/arm/kernel/setup.h     定义 )
说明是旧式的参数,需要转换成新格式的参数,所以调用函数 convert_to_tag_list ;如果转换后依然是旧格式的,
那么就没法使用这个参数了, 改为使用默认参数,就是本文开始时描述的那个不重要的    init_tags    静态全局变量
convert_to_tag_list     这个函数内容可以不看,因为一个正常的 uboot 是不会传递旧格式的参数,这里重在理解道理即可。
后面的 fixup 部分,其实可以不关注了, fixup 用于内核代码固定的写死 meminfo ,而不是由 uboot 传递参数配置 meminfo ,应该说很少有使用 fixup 成员写死 meminfo 的情况。
言归正传,看下面的代码:
if (tags->hdr.tag == ATAG_CORE ) {
       if (meminfo.nr_banks != 0)
              squash_mem_tags(tags);
       save_atags (tags);
    parse_tags(tags);
}
首先全局变量 meminfo 在这时候还没有被初始化,其用于指示物理内存 bank 个数的成员 nr_banks 肯定为 0
继续往下看, save_atags 将把 tags 里的内容拷贝给全局变量 atags_copy ,重点是下面的 parse_tags
观察 parse_tags 函数的实现,这时必须要搞懂 tags     指针变量里面的内容是什么了, tags 指针变量实际上指向了多个的 struct tags 型变量,
观察 struct tags 结构体即可发现,它是一个 struct tags_header   加一个联合的结构,这就很明确了,
再看 parse_tags 的实现,它就是对每一个它指向的 struct tags     型变量调用函数 parse_tag ,这个函数实际解析 struct tags 型变量,终于到重点了,看它的实现:
static int __init parse_tag (const struct tag *tag)
{
       extern struct tagtable __tagtable_begin, __tagtable_end;
       struct tagtable *t;
  
       for (t = &__tagtable_begin; t < & __tagtable_end ; t++)
              if (tag->hdr.tag == t->tag) {
                     t->parse(tag);
                     break;
              }
       return t < &__tagtable_end;
}
先看“ extern struct tagtable __tagtable_begin, __tagtable_end; ”,可参考 内核汇编启动阶段的文章
可以立即感觉到这两个东西是在链接脚本 vmlinux.lds.S   中定义的,并且是卡住某一代码段便于给 C 函数调用;
首先看这两个变量在哪里定义,卡住了哪部分内容:
__tagtable_begin = .;
       *(.taglist.init)
__tagtable_end = .;
可见是卡住了“ .taglist.init ”段的全部内容,那这个段里是什么东西呢?在这里, arch/include/asm/setup.h 文 件中,有这么些内容:
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
第一行的意思是:宏 __tag ,定义为 __used __attribute__((__section__(".taglist.init")))
第二、三行的意思是:定义宏 __tagtable(tag, fn) static struct tagtable __tagtable_##fn __tag = { tag, fn }
意思是:在 ".taglist.init" 段中,创建 struct tagtable 的静态变量 __tagtable_##fn(fn 是什么由参数指定,
后面的 __tag 起变量描述符的作用,它真正指定了这个静态变量是在 ".taglist.init" 段中链接 ) ,并赋初值,赋的值就是宏函数定义时的参数 tag fn
说白了就是,以 __tagtable(tag, fn) 形式定义的宏,实际是在 ".taglist.init" 段中,
创建 struct tagtable 的静态变量,并赋初值给这个变量,赋的值就是这个宏的两个参数 tag fn
arch/arm/mach-XXX/core.c
arch/arm/mm/init.c
arch/arm/kernel/setup.c    
文件中,定义了很多 __tagtable(XXX XXX) 这样的宏,这些宏干什么的?
就是解析 uboot 传递给内核的参数用的 ( 当然不仅它们解析,后面还有别的代码解析 ) ,现在回到 parse_tag 函数,应该很好理解了,
它对传递进来的参数,利用 __tagtable_begin __tagtable_end ,使用所有定义的 __tagtable(tag, fn) 对其进行遍历,
一旦发现 __tagtable(tag, fn) tag 和传递进来的参数的参数头的 tag( struct tags_header tag 成员 ) 一样,
就用该 __tagtable(tag, fn) fn 即解析函数进行解析,这里的 marvell 设备的实现一共定义了 11 个这样的 __tagtable(tag, fn) ,就不一列出了,这里具体描述两个比较重要的:
parse_tag_mem32
这个是初始化 meminfo 即让内核了解设备的物理内存情况的,它将调用函数 arm_add_memory
参数就是 uboot 传递的相应参数的 mem 结构,包括 start 成员和 size 成员即物理内存起始地址和内存大小,
arm_add_memory 这个函数比较简单,它把这两个参数写进 meminfo bank 中,并更新 benk 个数值 nr_banks
对于很多小型 arm 嵌入式应用,一般只有一个物理内存,也就只调用该函数一次。重中之重的 meminfo 就是在这里初始化的!
parse_tag_cmdline
它把 uboot 传递的“命令行参数”赋给静态全局变量 default_command_line ,这个变量是干什么用的?它很重要,先看看这里的 marvell 设备的情况:
ttyS0,115200 ubi.mtd=3 root=ubi0:rootfs rootfstype=ubifs mtdparts=nand_flash:0x200000@0x0(uboot)ro,0x200000@0x200000(env)rw,0x500000@0x400000(kernel0)ro,0x2800000@0x900000(rootfs0)ro,0x500000@0x3100000(kernel1)ro,0x2800000@0x3600000(rootfs1)ro,0x2200000@0x5e00000(config)rw rw)
想必会明白这个 default_command_line 是干什么的了,这里只是拷贝到这个变量中去,后面还有其他代码进一步解析它。
看完这一部分,接下来是另一部分的解析,接着 setup_arch     函数往下看:
parse_cmdline(cmdline_p, from);
form     指针变量在    setup_arch 函数一开始就指向了 default_command_line
到这应该能感觉到现在要解析 default_command_line 了,看 parse_cmdline 的实现:
无需仔细看里边关于用空格定位每一个参数等细节,重点是看它是由谁来解析的,可以看到两个变量    __early_begin __early_end
和上面类似,故伎重演,在    vmlinux.lds.S     中它俩卡住了“ .early_param.init ”段,这个段所链接的内容同样是在    arch/arm/kernel/setup.h    中规定,
道理完全一样具体就不描述了,最终是由 __early_param   宏解析。
具体都在解析些什么内容即解析后如何操作,也都是一些和设备参数相关的诸如内存、缓存等等的信息,在这里也不详细描述了。

总之,对于uboot向内核传递参数,需要理解的一个是内核对uboot所传参数的接收、解析的机制和方法,

另外需要了解下所解析和操作的内容,尤其对于一些重要参数典型如内存参数的解析和操作需要细致理解。

<think>好的,我现在需要详细说明U-Boot及其启动流程。首先,我应该先了解U-Boot是什么,它的主要功能和用途。U-Boot,全称是Das U-Boot,是一个开源的引导加载程序,主要用于嵌入式系统。它支持多种架构,如ARM、MIPS、PowerPC等,常用于引导Linux内核。 接下来,我需要理解U-Boot的启动流程。用户可能想知道从设备上电到加载内核的整个过程。启动流程通常分为几个阶段,每个阶段的具体任务可能有所不同,但大致可以分为以下几个步骤: 1. **ROM Bootloader**:设备上电后,首先执行的是芯片内部ROM中的引导代码。这部分代码通常是固化在硬件中的,负责初始化最基本的硬件,如时钟、内存控制器,并加载下一阶段的引导程序(如SPL)。 2. **SPL (Secondary Program Loader)**:由于内部ROM空间有限,SPL作为第二阶段引导加载程序,负责进一步初始化硬件(如DRAM),并加载完整的U-Boot到内存中。SPL通常是U-Boot的一部分,经过编译后生成的小型镜像。 3. **U-Boot主程序**:SPL加载完成后,跳转到U-Boot的主程序。U-Boot会进行更全面的硬件初始化,包括串口、网卡、存储设备等,然后读取环境变量,根据配置加载内核镜像、设备树文件(DTB)和根文件系统。 4. **加载内核**:U-Boot将内核镜像、设备树和可能的initramfs加载到内存中的指定地址,然后通过bootm或bootz命令启动内核传递必要的参数。 在这个过程中,需要考虑到不同硬件平台的差异,比如启动介质(NOR Flash、eMMC、SD卡等)、内存布局、环境变量的存储位置(如EEPROM、Flash中的特定分区)等。 另外,U-Boot还支持交互式命令,用户可以在启动过程中打断自动流程,进入命令行界面,手动执行命令进行调试或配置。环境变量在U-Boot中非常重要,它们决定了启动参数和流程,比如bootcmd定义了自动执行的命令序列。 可能还需要解释U-Boot的编译和配置过程,比如通过make menuconfig配置目标板,选择所需的驱动和功能,然后编译生成u-boot.bin等文件。此外,不同开发板的配置文件位于configs目录下,如mx6ullevk_defconfig对应特定的开发板。 在启动流程中,设备树(Device Tree)的作用也很重要,它描述了硬件配置,使得内核无需硬编码硬件信息。U-Boot负责将设备树二进制文件(DTB)加载到内存,并传递内核。 还要注意U-Boot的启动阶段可能涉及多个文件,比如SPL、U-Boot proper,以及如何将它们烧写到存储设备的正确位置。例如,在SD卡启动时,SPL可能需要存放在特定的偏移量位置,以便ROM Bootloader能够找到并加载它。 可能存在的疑问点包括:SPL和U-Boot的关系,如何配置环境变量,如何处理不同的启动介质,U-Boot如何与内核交互,以及如何调试启动问题。需要确保这些点在回答中都有所覆盖,并且结构清晰,步骤明确。 最后,需要验证这些信息是否正确,比如确认U-Boot的启动阶段划分是否准确,SPL的作用是否描述正确,以及加载内核的具体命令和流程是否符合实际。如果有不确定的地方,可能需要查阅U-Boot的官方文档或源码进行确认,确保回答的准确性。</think>--- ### **U-Boot详解与启动流程分析** --- #### **1. U-Boot概述** **定义**: U-Boot(Universal Boot Loader)是嵌入式系统领域使用最广泛的开源引导加载程序,支持**ARM、MIPS、PowerPC、RISC-V**等30+处理器架构,兼容**NOR Flash、eMMC、NAND、SD卡**等多种存储介质。 **核心功能**: - 硬件初始化(时钟/DDR/外设) - 多阶段引导加载 - 固件升级(USB DFU/TFTP) - 环境变量管理 - 操作系统映像加载(Linux/FreeRTOS/VxWorks) - 安全启动(Verified Boot) --- #### **2. 典型启动流程** ```mermaid sequenceDiagram participant ROM Code participant SPL participant U-Boot participant Kernel ROM Code->>SPL: 加载SPL到SRAM SPL->>U-Boot: 初始化DRAM,加载U-Boot到内存 U-Boot->>U-Boot: 外设初始化(网卡/USB/存储) U-Boot->>Kernel: 加载内核镜像、设备树、initramfs Kernel->>Kernel: 解压并启动操作系统 ``` --- #### **3. 关键阶段详解** **3.1 ROM Bootloader** - **硬件行为**:芯片上电后自动执行固化在ROM中的代码 - **主要任务**: - 初始化基础时钟(CPU/总线) - 检测启动介质(通过GPIO或OTP配置) - 加载SPL到内部SRAM(典型大小:64-256KB) **示例代码(i.MX6ULL)**: ```c /* arch/arm/mach-imx/imx6ull/lowlevel_init.S */ bl setup_pll // 配置PLL时钟 bl setup_dcdr // DDR控制器初始化 ldr r0, =SPL_LOAD_ADDR bl load_image // 从SD卡加载SPL ``` --- **3.2 SPL阶段** - **二进制文件**:`u-boot-spl.bin` - **内存限制**:通常在SRAM中运行(无DRAM支持) - **核心任务**: 1. 初始化DRAM控制器 2. 设置内存映射表 3. 加载完整U-Boot到DRAM 4. 验证U-Boot镜像签名(安全启动场景) **关键配置选项**: ```makefile # configs/mx6ullevk_defconfig CONFIG_SPL=y CONFIG_SPL_DM=y CONFIG_SPL_LOAD_FIT=y ``` --- **3.3 U-Boot主程序** - **入口函数**:`lib/init/board_init.c`中的`board_init_f()` - **启动阶段划分**: ```c board_init_f() // 初始化全局数据结构(gd_t) ↓ relocate_code() // 重定位到DRAM高端地址 ↓ board_init_r() // 完整外设初始化 ``` **硬件初始化顺序**: 1. 串口调试输出(`DEBUG_PORT`) 2. 内存控制器配置 3. 存储设备初始化(MMC/SATA) 4. 网络接口(PHY初始化) 5. 环境变量加载(`env_relocate()`) --- **3.4 环境变量管理** - **存储位置**:Flash专用分区(如eMMC的ENV分区) - **关键变量**: ```bash bootcmd=mmc dev 0; ext4load mmc 0:1 0x80800000 zImage; \ ext4load mmc 0:1 0x83000000 imx6ull.dtb; \ bootz 0x80800000 - 0x83000000 bootargs=console=ttymxc0,115200 root=/dev/mmcblk0p2 ro ``` **操作命令**: ```bash # 查看环境变量 printenv # 修改并保存 setenv ipaddr 192.168.1.100 saveenv ``` --- #### **4. 内核加载过程** **典型bootm命令流程**: ```c do_bootm() ↓ bootm_start() // 解析镜像头信息 ↓ bootm_load_os() // 加载内核到指定地址 ↓ boot_fn = boot_os[os] // 选择启动方法(Linux/NetBSD等) ↓ do_bootm_linux() // 设置ATAGs/FDT,跳转到内核入口 ``` **ARM平台内存布局示例**: | 地址范围 | 用途 | |------------------|-----------------------| | 0x80000000 | 内核加载地址 | | 0x81000000 | 设备树地址 | | 0x82000000 | initramfs地址 | --- #### **5. 安全启动实现** **数字签名验证流程**: ```mermaid graph LR A[SPL] --> B{验证U-Boot签名} B -->|有效| C[加载U-Boot] B -->|无效| D[进入恢复模式] C --> E[U-Boot验证内核签名] E -->|有效| F[启动内核] E -->|无效| G[终止启动] ``` **关键配置**: ```makefile CONFIG_SPL_FIT_SIGNATURE=y # 启用FIT镜像签名 CONFIG_SPL_CRYPTO_SUPPORT=y # 加密算法支持 CONFIG_SPL_RSA_VERIFY=y # RSA验证 ``` --- #### **6. 调试与优化** **6.1 常用调试手段**: - 串口日志输出(`CONFIG_DEBUG_UART`) - LED指示灯控制(`led_set_state()`) - 内存检测(`mtest`命令) - 启动暂停(`CONFIG_AUTOBOOT_KEYED`) **6.2 性能优化技巧**: ```c /* 加速存储设备访问 */ #define CONFIG_SYS_MMC_ENV_DEV 0 #define CONFIG_SYS_MMC_MAX_BLK_COUNT 1024 // 增大块输数量 /* 压缩镜像尺寸 */ #define CONFIG_LZMA // 使用LZMA压缩算法 ``` --- #### **7. 典型移植步骤** 1. **创建板级目录**: ```bash board/<vendor>/<board>/ ├── Kconfig ├── Makefile └── <board>.c ``` 2. **配置设备树**: ```dts /dts-v1/; / { model = "Custom Board"; memory@80000000 { reg = <0x80000000 0x20000000>; }; }; ``` 3. **实现关键驱动**: - 串口(`drivers/serial/serial_xxx.c`) - 存储设备(`drivers/mmc/mmc_xxx.c`) - GPIO控制 --- #### **8. 启动时间分析** **典型时间分布(ARM Cortex-A9@1GHz)**: | 阶段 | 耗时(ms) | |----------------|-----------| | ROM Code | 12 | | SPL执行 | 58 | | U-Boot初始化 | 320 | | 内核加载 | 92 | | **总计** | **482** | **优化方向**: - 启用SPL框架(减少初始化步骤) - 预计算CRC32(替代运行时计算) - 并行初始化外设 --- 通过这种层次化的启动架构,U-Boot实现了从硬件初始化到操作系统引导的完整控制流程,其模块化设计和可移植性使其成为嵌入式系统开发的核心组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值