Linux设备树4

Linux设备树4(基于Linux6.6)---内核head.S对uboot传参的处理

 


一、uboot传参启动

boot启动内核是通过传入三个参数来启动的,arch/arc/lib/bootm.c

/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
	void (*kernel_entry)(int zero, int arch, uint params);
	unsigned int r0, r2;
	int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

	kernel_entry = (void (*)(int, int, uint))images->ep;

	debug("## Transferring control to Linux (at address %08lx)...\n",
	      (ulong) kernel_entry);
	bootstage_mark(BOOTSTAGE_ID_RUN_OS);

	printf("\nStarting kernel ...%s\n\n", fake ?
	       "(fake run for tracing)" : "");
	bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");

	cleanup_before_linux();

	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
		r0 = 2;
		r2 = (unsigned int)images->ft_addr;
	} else {
		r0 = 1;
		r2 = (unsigned int)env_get("bootargs");
	}

	smp_set_core_boot_addr((unsigned long)kernel_entry, -1);
	smp_kick_all_cpus();

	if (!fake)
		kernel_entry(r0, 0, r2);
}

kernel_entry为内核zImage在内存的首地址。

之前我们传的三个参数分别是:

0,芯片的机器ID,uboot给内核参数tag在内存的首地址r2。

查看上面代码,可以看到r2也可以是另设备树文件(dtb)在在内存的地址。

对于使用tag传参给内核,机器的ID是必须要和内核中配置的芯片一致的。

而对于使用设备树来启动内核,机器ID则不是必须的。

分析的linux6.6内核默认支持设备树传参启动。

二、uboot传参过程分析

2.1、启动入口

head.S文件中,ARM64架构的启动入口通常是_start符号。这是内核被加载到内存中后的第一个执行点。

.globl    _start
    .type    _start, %function
_start:
    adr_l    x0, __PHYS_OFFSET            // 获取物理内存偏移(通常是内核加载的物理地址)
    bl       el3_setup                    // 跳转到EL3(Exception Level 3)设置代码

2.2、EL3 设置

el3_setup函数进行一些基本的EL3级别的设置,包括禁用中断、配置栈指针等。

el3_setup:
    // 禁用中断,配置栈指针等
    // ...
    bl       el2_setup                    // 跳转到EL2设置代码

2.3、EL2 设置

el2_setup函数进一步配置EL2级别的环境,并准备跳转到内核的C语言启动代码。

el2_setup:
    // 配置EL2环境
    // ...
    adr_l    x1, __dtb_start_addr         // 获取设备树基地址(可能由U-Boot传递)
    adr_l    x2, __dtb_end_addr           // 获取设备树结束地址
    mov      x3, #0                       // 初始化参数计数器
    // 检查U-Boot是否传递了设备树地址(通过ATAGS或FDT)
    ldr      x4, =__atags_ptr             // ATAGS指针位置
    ldr      w5, [x4]                     // 读取ATAGS的第一个字(标识)
    cmp      w5, #ATAG_NONE               // 检查是否为空(无ATAGS)
    b.eq     check_fdt                    // 如果是空,则检查FDT

// 处理ATAGS
process_atags:
    // 解析ATAGS并保存到内核数据结构中
    // ...
    b        done_parsing

// 检查FDT(设备树扁平化格式)
check_fdt:
    ldr      x6, =fdt_addr                // FDT地址(可能由U-Boot设置)
    ldr      x7, [x6, #FDT_SIZE_OFFSET]   // 读取FDT大小
    cmp      x7, #0
    b.eq     no_fdt_or_atags              // 如果没有FDT,则报错
    mov      x1, x6                       // 将FDT地址保存到x1
    mov      x2, x7                       // 将FDT大小保存到x2
    b        done_parsing

// 如果没有ATAGS或FDT
no_fdt_or_atags:
    // 错误处理
    // ...
    b        hang

// 完成解析
done_parsing:
    // 保存解析后的参数(如设备树地址)
    // ...
    adr_l    x8, __primary_arch_data      // 获取主架构数据指针
    str      x1, [x8, #__arch_data_fdt_addr] // 保存FDT地址
    // 跳转到C语言启动代码
    mov      x0, #0
    bl       kernel_entry                 // 跳转到内核入口

2.4、参数解析

在上面的代码中,内核通过检查__atags_ptrfdt_addr来确定U-Boot是否传递了ATAGS或FDT。

  • ATAGS:旧的启动参数传递方式,通过一系列的标签传递各种信息。
  • FDT:现代的启动参数传递方式,使用设备树扁平化格式(Flattened Device Tree)传递复杂的硬件配置信息。

2.5、跳转到C语言启动代码

一旦解析完启动参数,内核将跳转到C语言启动代码,通常是kernel_entry函数。这个函数会进一步初始化内核环境,包括内存管理、调度器、设备树解析等。

kernel_entry:
    // C语言启动代码入口
    // ...

2.6、start_kernel 函数的执行

start_kernel 函数是 Linux 内核的初始化入口,它位于 init/main.c 文件中。在内核启动的第一阶段,start_kernel 会处理与架构相关的初始化工作,诸如:

  • 解析设备树
  • 配置硬件
  • 初始化内存管理
  • 启动系统进程

以下是 start_kernel 函数的大致执行流程:

void __init start_kernel(void)
{
    // 调用 setup_arch 函数进行架构相关的初始化
    setup_arch(&command_line);

    // 内存管理初始化
    mm_init();

    // 解析设备树(r1 中存储设备树地址)
    if (early_init_dt_scan())
        pr_err("DTB: Failed to initialize device tree");

    // 启动内核进程
    kernel_thread();
}

2.7、内核命令行参数处理

内核命令行参数存储在 r2 中,内核会通过 setup_arch 等函数对其进行解析。命令行参数用于定制内核的启动行为,例如指定根文件系统的路径、启用调试模式等。

例如,在 start_kernel 函数中,命令行参数会传递给 setup_arch,以便进行相应的配置:

setup_arch(&command_line);  // 解析并配置内核架构相关设置

command_line 中存储了内核启动时的命令行参数,通常是 U-Boot 启动时传递的内容。

2.8、设备树的解析

设备树(Device Tree)描述了硬件平台的详细信息(如 CPU 类型、内存布局、外设等)。内核通过解析设备树来获取硬件信息。设备树的地址由 U-Boot 传递,并存储在 r1 中。

内核会在初始化过程中读取设备树,并根据设备树中的信息来配置硬件。early_init_dt_scan 函数就是用于扫描和初始化设备树的:

if (early_init_dt_scan())
    pr_err("DTB: Failed to initialize device tree");

设备树的解析是内核硬件初始化的一部分,它允许 Linux 支持多种硬件平台,而不需要在内核源码中硬编码硬件信息。

 

 三、head.S 处理 U-Boot 传参的流程图

+-------------------------------------------+
|            U-Boot 启动阶段               |
|   (加载内核、设备树、命令行参数等)        |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|  ARM64 内核启动:进入 head.S             |
|   (此时在 EL3,进行初始化设置)            |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|   获取 U-Boot 传递的参数 (如设备树、      |
|   内核镜像地址、命令行参数等)             |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|   获取传递的 `atags` 或设备树地址        |
|   (根据 U-Boot 配置,可能是设备树或 ATAGs)|
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|   配置 MMU (内存管理单元),启用虚拟地址   |
|   映射,设置 EL1 运行环境                |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|   初始化中断控制器 (GIC)                 |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|   跳转到内核 C 语言代码入口 `start_kernel` |
|   (将控制权交给 C 语言内核初始化代码)     |
+-------------------------------------------+
                    |
                    v
+-------------------------------------------+
|        继续进行内核初始化,启动用户进程    |
+-------------------------------------------+

具体步骤说明:

  1. U-Boot 启动阶段

    • 在 U-Boot 中,内核镜像、设备树和启动参数被加载到内存中。U-Boot 会根据配置启动内核,并将相关参数传递给内核启动。
  2. 进入 head.S

    • 内核的启动代码 head.S 在 ARM64 系统中位于引导流程的最前端,通常执行一些硬件相关的初始化任务。此时,内核处于 EL3(最高的异常级别),执行最基础的系统设置。
  3. 获取 U-Boot 传递的参数

    • head.S 通过读取从 U-Boot 传递的启动参数,主要是通过 atags(或者设备树)获取内核镜像的位置、设备树的地址、以及命令行参数等。不同的启动配置可能会采用不同的方式来传递这些参数(如设备树地址、ATAGs)。
  4. 配置 MMU 和虚拟地址映射

    • 由于 ARM64 使用虚拟内存机制,在内核启动时需要配置 MMU 来创建虚拟到物理地址的映射。这一过程通过 head.S 完成,确保内核在 EL1 中能够正确地访问内存。
  5. 初始化中断控制器

    • head.S 还会初始化 GIC(Generic Interrupt Controller),以便处理器能够正确地处理硬件中断。
  6. 跳转到 C 语言入口 start_kernel

    • 完成上述初始化后,head.S 会将控制权交给内核的 C 语言入口函数 start_kernel,这是内核的核心初始化逻辑开始执行的地方。start_kernel 函数将继续执行内核初始化、调度器设置、内存管理等任务。
  7. 继续进行内核初始化,启动用户进程

    • start_kernel 中,内核会初始化更多的硬件、驱动和内核子系统,最终进入调度器,准备启动用户空间的进程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值