uboot copy_from_nand代码详解

本文详细解析了U-Boot启动过程中从NAND Flash复制U-Boot二进制镜像到内存的具体步骤,包括判断NAND Flash类型、读取芯片ID、数据块拷贝等关键技术点。

uboot copy_from_nand代码详解

copy_from_nand函数简介:

       函数将会把nand flash中的uboot二进制程序镜像拷贝到内存中,通常这一步出现问题,uboot是不可能引导起来的,串口也不会有信息吐出(当然啦,因为uboot不在内存,如何执行啊

 

代码分析:

       1.先贴函数代码,在后面再分析,copy_from_nand函数的代码我们可以在cpu/s3c64xx/start.S的278行看到,上图:

图(1)

273~277行:解释很给力,273行告诉你了这个函数的功能就是把nand flash中存放的uboot镜像文件拷贝到内存中,然后再到内存中去执行uboot,这样我们的uboot就可以在内存中运行起来了。274行告诉我们r0是会被比较的大小。293行把0x0c000000里面的值赋给r0了,我们要比较拷贝到内存的uboot和steppingstone里面是否一样啊?所以我们要比较一下咯(295和297行)

288行:copy_uboot_to_ram这个函数是在C语言里面写的,C语言的程序在编译器编译的时候肯定会有出入栈的操作,会用到sp指针,所以我们在进入这个函数之前是必须设置sp的,所以288行前的不解释了。我们直接进入这个copy_uboot_to_ram函数吧:

图(2)

这个函数的实现在cpu/s3c64xx/nand_cp.c的133行,注意的是这个函数必须在整个uboot.bin文件的前4KB,所以我们看到u-boot.lds中会把nand_cp.o链接到uboot二进制文件靠前的位置

139~148行:主要想做的事情就是判断我们的nand flash是大页得还是小页的,现在主流的的nand flash有512页和2048页两种,在后续的代码操作中有区别,所以在进行nand操作前,必须先判断出是大页还是小页。139行是在使能芯片,代码是(NFCONT_REG &= ~(1 << 1)),即会把0x70200004这个地址的[1]位清零,那么为什么清零[1]位就能够使能nand flash芯片了呢,我们看下s3c6410 datasheet才能够明白:

图(3)

我们看到图3中的[1]位为0的含义是让Xm0CSn2变成低电平,因为我们的nand flash是接在SRAM2上的,

当Xm0CSn2为低电平时,将能访问我们的nand flash芯片了。

140行:在给nand_flash发送命令,发送的值为0x90,含义是读取芯片的ID等厂商信息,话说对nand flash的控制都是这个套路,因为这就是硬件协议,先使能芯片->发送命令->发送地址序列->读或写数据寄存器->判断准备就绪状态->禁止芯片,这是对nand flash操作的大体过程,根据发送命令的不同还有些区别。我们这次发送的是0x90,代表读取ID信息。发送命令的含义通过读取你nand flash datasheet来查看,你需要看你的nand flash芯片是哪个厂家生产的,下载它的datasheet,如下图:


图(4)

你会发现图(2)在发送命令后,会读两次寄存器。这是因为我们发送完0x90,nand flash会返回5次(好像)值给我们,我们需要的值是第二次返回的值,接着148行判断是否id大于0x80,大于的话就是大页2048字节的。我的芯片是K9F84G08U0B,是大页的,所以这个读出来的值是大于0x80的;

155行:拷贝功能主函数,CFG_PHY_UBOOT_BASE第一个代表要拷贝到内存的地址(0x57e00000),第二个参数0x3c000代表要从nand flash中拷贝的大小,我们进入这个函数:

图(5)

图(5)直接看126行,这个循环会执行0x3c000>>page_shift次,如果是9代表除以512,如果是11代表除以2048,这下明白这个for循环的意思了吧,nand flash硬件一次会读取一个页的大小给我们,那么我们就进入127行的读一页吧,注意会读到buf中,buf可就是我们指定的0x57e00000哦:

图(6)

图6函数会读一页的数据到buf中,即内存中。然后一直读0x3c000/512(2048)次,看你是大页还是小页nand,我的是大页。

图6的85行使能芯片,前面已经说过,87行发送读命令,是0x0,你可以查看图(4),发送读之后,我们就要开始向地址寄存器中写一些地址,告诉nand flash我们要从什么地方开始读数据,下图:

图(7)

看图(7),我们知道我们要写5次地址来确认我们要从nand flash中读的内容,第一次是列地址,第二次也是,这是因为如果只有1次,那么8位最大表示256,而一列的大小是2K,所以需要有两个列地址来代表。那么我们一共有256K页,所以需要3次8位的行地址来代表我们要读的是哪行,其实具体的还要看你的nand flash datasheet来决定如何给定地址列表。

 

图6的102行,是在循环判断是否是否已经准备好了,如果准备好了,我们才能再105行把这一整页得内容读到buf里面,注意看是从那个寄存器读取的哦,NF_DATA8_REG(nand flash数据寄存器);

109行禁止芯片,然后回到图(5)的nandll_read_blocks继续读下一页,直到读了0x3c000/page_shift个页为止,正好是192K,uboot的大小,并放到了buf中(0x57e00000内存)。

 

好了,分析完了

<think> 我们正在讨论U-Boot中的`relocate_code`函数。根据用户的问题,我们需要介绍该函数的功能、使用方法和实现原理。结合之前的对话历史和提供的引用,我们可以总结如下: ### 功能 `relocate_code`的主要功能是将U-Boot自身代码从加载地址(如ROM中的地址)重定位到链接地址(如RAM中的运行地址)。这是因为在启动初期,U-Boot可能运行在ROM或SRAM中,但为了获得更快的执行速度和更大的运行空间,需要将自身复制到RAM中运行。重定位包括: 1. 复制代码段和数据段到新的地址。 2. 调整代码中的全局偏移表(GOT)和动态重定位表(.rel.dyn段)中的地址,使其指向新的位置。 ### 使用方法 在U-Boot启动流程中,`relocate_code`通常由`board_init_f`(重定位前的初始化)阶段的末尾调用。调用前需要准备好: - 目标地址(gd->relocaddr,即重定位后的新地址) - 重定位偏移量(目标地址 - 当前加载地址) 调用方式通常为: ```c relocate_code(gd->relocaddr, NULL, gd->start_addr_sp); ``` 其中,参数分别为目标地址、目标地址的旧值(通常为NULL)和新的堆栈指针。 ### 实现原理(结合引用内容) 1. **判断是否需要重定位**:首先检查目标地址和当前地址是否相同(引用[2]),若相同则跳过重定位。 2. **复制代码段**:将U-Boot的代码段(从`__image_copy_start`到`__image_copy_end`)复制到目标地址(引用[4])。 3. **重定位动态表(.rel.dyn段)**: - U-Boot使用位置无关代码(PIE)编译,生成.rel.dyn段(引用[3]),该段包含需要重定位的地址列表。 - 遍历.rel.dyn段中的每个条目,将原地址加上偏移量(目标地址 - 源地址)得到新地址(引用[1])。 4. **更新GOT表**:同样,全局偏移表(GOT)中的每个条目也需要加上偏移量(引用[1])。 ### 详细步骤(以ARM为例,参考引用[4]) 1. **计算偏移量**:`r4 = r0 - r1`,其中`r0`是目标地址,`r1`是源地址(`__image_copy_start`)。 2. **复制代码段**:循环将数据从源地址复制到目标地址,直到`__image_copy_end`。 3. **处理.rel.dyn段**: - 定位.rel.dyn段的起始(`__rel_dyn_start`)和结束(`__rel_dyn_end`)。 - 遍历每个条目: - 每个条目包含一个地址(需要重定位的位置)和一个标志(用于区分绝对地址和相对地址)。 - 对于需要重定位的条目,计算新地址:原地址 + 偏移量。 4. **清除缓存**:重定位后,刷新指令缓存以确保新代码被正确执行。 ### 示例代码片段(伪代码) ```c void relocate_code(addr_t relocaddr, ...) { addr_t source = (addr_t)__image_copy_start; addr_t dest = relocaddr; size_t size = (size_t)(__image_copy_end - source); // 复制代码段 memcpy(dest, source, size); // 计算偏移量 addr_t offset = dest - source; // 重定位.rel.dyn段 for (entry = __rel_dyn_start; entry < __rel_dyn_end; entry++) { // 获取需要重定位的地址 addr_t *addr_ptr = (addr_t *)(entry->offset + source); // 修改该地址:加上偏移量 *addr_ptr += offset; } // 刷新缓存 flush_cache(); } ``` ### 注意事项 - 重定位后,程序计数器(PC)需要跳转到重定位后的新地址继续执行(通常在`relocate_code`返回前完成)。 - 堆栈指针(SP)也需要更新为新的地址空间(通常由调用者设置)。 ### 总结 `relocate_code`是U-Boot启动过程中关键的一步,它通过复制代码和重定位地址,使U-Boot能够在RAM中高速运行。其核心是处理动态重定位表(.rel.dyn)和GOT表,确保所有地址引用正确指向新的位置。 §§相关问题§§ 1. U-Boot重定位后如何更新全局数据指针(gd)? 2. 为什么需要位置无关代码(PIE)来支持重定位? 3. U-Boot中.rel.dyn段的具体结构是什么? 4. 重定位后如何确保缓存一致性? 5. U-Boot重定位的目标地址是如何确定的? </think>### U-Boot `relocate_code` 功能、使用方法及实现原理 #### 一、核心功能 `relocate_code` 是 U-Boot 启动过程中实现**代码重定位(Code Relocation)** 的关键函数,主要功能包括: 1. **地址空间迁移** 将 U-Boot 自身代码从加载地址(如 ROM/FLASH 地址)复制到链接地址(如 RAM 的高端地址),解决链接地址与加载地址不一致的问题[^4]。 ``` 例:0x32000000(加载地址)→ 0x80000000(链接地址)[^1] ``` 2. **动态地址修正** 修改全局偏移表(GOT)和 `.rel.dyn` 段中的地址引用,使其指向新位置[^1][^3]。 3. **位置无关码支持** 通过 `-pie` 编译选项生成位置无关代码,确保重定位后程序能正确执行[^3]。 #### 二、使用方法 ##### 1. 调用时机 在 U-Boot 启动流程中,由 `board_init_f`(重定位前的初始化)调用: ```c // 伪代码示例 void board_init_f(...) { gd->relocaddr = 目标地址; // 如 0x9FF47000[^4] relocate_code(gd->relocaddr, NULL, gd->start_addr_sp); } ``` ##### 2. 关键参数 | 参数 | 作用 | 示例值 | |------------------|-----------------------------|-------------| | `目标地址` | 重定位后的新地址 | `0x9FF47000`[^4] | | `当前地址` | 一般为 NULL | - | | `新堆栈指针` | 重定位后的堆栈起始地址 | 由 `gd` 提供 | #### 三、实现原理(ARM 架构为例) ##### 1. 地址复制阶段 ```armasm relocate_code: ldr r1, =__image_copy_start // 源地址(如 0x87800000)[^4] ldr r0, [目标地址寄存器] // 目标地址(如 0x9FF47000) sub r4, r0, r1 // 计算偏移量(r4 = 0x18747000) ldr r2, =__image_copy_end // 复制结束地址 copy_loop: ldmia r1!, {r10-r11} // 从源地址加载 64 位数据 stmia r0!, {r10-r11} // 存储到目标地址 cmp r1, r2 // 检查是否完成 blo copy_loop // 未完成则循环 ``` > **作用**:将 `__image_copy_start` 到 `__image_copy_end` 的代码段完整复制到新地址[^4]。 ##### 2. 地址修正阶段 ```armasm ldr r2, =__rel_dyn_start // .rel.dyn 段起始地址 ldr r3, =__rel_dyn_end // .rel.dyn 段结束地址 fixloop: ldmia r2!, {r0-r1} // 读取重定位条目 cmp r1, #23 // 检查重定位类型(ARM_RELOC) bne skip add r0, r0, r4 // 原始地址 + 偏移量 → 新地址 ldr r1, [r0] // 读取原值 add r1, r1, r4 // 修正值 str r1, [r0] // 写回新地址 skip: cmp r2, r3 blo fixloop ``` > **作用**:遍历 `.rel.dyn` 段(动态重定位表),将每个条目中的地址加上偏移量 $ \Delta addr = \text{目标地址} - \text{源地址} $[^1][^3]。 ##### 3. 重定位检测 ```armasm cmp r4, #0 // 检查偏移量是否为 0 beq relocate_done // 偏移量为 0 则跳过重定位[^2] ``` > 若加载地址与链接地址相同($ \Delta addr = 0 $),直接跳过重定位流程[^2]。 #### 四、关键技术点 1. **位置无关码(PIC)** 通过编译选项 `-pie` 生成 `.rel.dyn` 段,存储所有需要重定位的地址引用[^3]。 2. **两级地址修正** - 一级修正:复制代码到新地址 - 二级修正:动态修改 `.rel.dyn` 中的地址指针 3. **缓存一致性** 重定位后需调用 `flush_cache()` 刷新指令缓存,确保 CPU 执行新地址的代码。 #### 五、典型应用场景 ```mermaid graph TD A[U-Boot 从 ROM 启动] --> B{加载地址 == 链接地址?} B -->|是| C[直接执行] B -->|否| D[调用 relocate_code] D --> E[复制代码到 RAM] D --> F[修正 GOT/.rel.dyn] D --> G[刷新缓存] E --> H[跳到新地址执行] ``` #### 六、注意事项 1. **重定位必须完整** 若 `.rel.dyn` 修正不彻底,会导致后续代码访问错误地址而崩溃。 2. **堆栈切换** 重定位后需立即更新堆栈指针到新地址空间。 3. **设备初始化顺序** 重定位需在 DDR 控制器初始化之后、全局数据结构 (`gd`) 更新之前完成。 > **总结**:`relocate_code` 是 U-Boot 从只读存储器迁移到可执行内存的核心机制,通过地址复制和动态重定位表修正,实现位置无关的灵活启动[^1][^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值