从u-boot理解armv7异常处理

本文详细介绍了ARMv7架构中的异常处理机制,包括7种异常类型及其触发条件,异常向量表的作用与实现方式,以及中断与异常处理的关系。此外,还探讨了异常栈的建立过程与异常向量表地址的设置。

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

    说到异常,就会想起不正常情况。异常确实是CPU处理非正常流程的一种手段。这里要说的是中断也是一种异常,而且ARMV7KH 定义了两种中断异常,irq与fiq。中断是我们熟知的,中断向量表跟异常向量表是什么关系呢。当外设发生中断时,通知中断控制器,中断控制器只是向CPU CORE发送了IRQ,也就是说在CORE看来所有中断只能称为IRQ异常。下面理一下armv7在异常处理上的设置。

1、armv7异常向量


从上面我们可以知道armv7支持7种异常。上面表要注意data abort与irq之间隔了一个向量,是一个没有使用的异常向量。

  (1)、复位(RESET)
    a、当处理器复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行,包括系统加电和系统复位。
    b、通过设置PC跳转到复位中断向量处执行称为软复位。
    (2)、未定义的指令
    当ARM处理器或者是系统中的协处理器认为当前指令未定义时,产生未定义的指令异常中断,可以通过改异常中断机制仿真浮点向量运算。
    (3)、软件中断
    这是一个由用户定义的中断指令(SWI)。该异常由执行SWI指令产生,可用于用户模式下的程序调用特权操作指令。在实时操作系统中可以通过该机制实现系统功能调用。
    (4)、指令与取终止(Prefech Abort)
    如果处理器预取的指令的地址不存在,或者该地址不允许当前指令访问,当被预取的指令执行时,处理器产生指令预取终止异常中断。
    (5)、数据访问终止(DATAABORT)
    如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问,处理器产生数据访问终止异常中断。
    (6)、外部中断请求(IRQ)
    当处理器的外部中断请求引脚有效,而且CPSR的寄存器的I控制位被清除时,处理器产生外部中断请求异常中断。系统中个外设通过该异常中断请求处理服务。
    (7)、快速中断请求(FIQ)
    当处理器的外部快速中断请求引脚有效,而且CPSR的F控制位被清除时,处理器产生外部中断请求异常中断。

2、异常向量表与异常响应

当相应的异常发生时,CPU跳到一个固定的地址去执行指定程序。这个通常由称为异常向量表来实现。异常向量来实现。向量表的定义如下图所示:


armv7实现向量表时用的是一个word,4个字节的空间,这里放指令的话也只能放下一条arm指令。所以这里要想实现更多处理只能放一条跳转指令。但是图中是一条ldr指令,给CPU直接赋值。那么如果这条跳转执行的话,我们如果在异常处理完成之后返回呢。这里要明白,在异常发生时,硬件做的事及软件要做的事。

硬件在异常发生时可能会伴随着模式的切换,关于ARMV7的几中模式看下面图。


在这里只要看到不同模式,采用了寄存组分组技术,也就说不同模式使用了不同的寄存器。那么要CPU要完成异常处理完成后再次回到异常发生的地方需要做那些事情呢。

1)硬件首先把发生异常模式的当前PC+4,也就是在发生模式切换时,把当前模式的下一条指令地址保存到该模式的lr寄存器。从上图可以看出,在各模式PC指令是一样的,但是lr寄存器使用不同寄存器。

2)将cpsr的值复制到异常模式的spsr.

3)将cpsr设置成对应的异常工作模式。

4)设置PC指针为为异常向量表相应地址去执行。

而软件要做的事是:

1)切换SP指针到对应的模式栈,也就是不同模式采用不同的栈。

2)在当前模式栈中保存共用寄存器。

看一下uboot代码中irq的处理:


3、中断与异常处理

中断是异常处理的子集。当我们给外设绑定一个中断号的处理程序时,其实是引发的IRQ异常,然后由do_irq中实现了二级向量表,实现各中断处理的。


4、异常栈的建立

关于各异常的建立是在运行时建立的,这个解释起来要使用gd这个全局结构与uboot内存布局。有兴趣可以参考一下board_init_f

这个函数,异常模式的栈就是在这里建立的。

5、异常向表的地址设置问题,这个表是可以放在高地址,也可以入在低地址空间的。放在高地址还是放在低地址是由CP15的SCTRL控制,如图所示:


v=0:低地址。

v=1:高地址。

那么具体放在高地什么位置是由c12的vbar寄存器指出。


这部分uboot代码如下所示:


<think>嗯,用户问的是U-Boot的入口函数在哪里,他现在有U-Boot的代码,应该从哪里开始看。首先,我需要回忆一下U-Boot的启动流程。U-Boot作为Bootloader,它的启动过程涉及多个阶段,不同架构可能有所不同,比如ARM和RISC-V的入口点可能不一样。用户可能是在开发某个具体的硬件平台,比如之前提到的BM1684X,但这次的问题比较通用,所以需要给出一般性的指导。 首先,U-Boot的入口函数通常是在链接脚本里指定的,也就是_start符号。对于ARM架构来说,入口点可能在arch/arm/lib/vectors.S中的_start,而对于RISC-V可能在arch/riscv/cpu/start.S。不过具体还要看平台。用户可能需要查看链接脚本来确定ENTRY(_start)的位置。 接下来,用户可能有不同的需求,比如调试启动问题,或者学习U-Boot的结构。这时候需要引导用户从入口开始,逐步分析代码流程。比如从_start开始,到board_init_f,再到relocate_code,最后到board_init_r,进入主循环。 另外,用户可能需要知道如何找到对应自己平台的代码。例如,如果用的是ARMv8,可能需要查看arch/arm/cpu/armv8/start.S。这时候需要建议用户检查他们的板级配置文件,比如configs目录下的defconfig文件,确定使用的架构和启动文件。 用户可能对如何开始阅读代码感到困惑,所以需要给出具体的步骤,比如使用grep查找ENTRY(_start)或查看链接脚本,或者从main函数开始跟踪。不过U-Boot的main函数可能是在board_init_r之后才执行的,所以需要解释清楚启动阶段的划分。 此外,用户可能关心不同阶段的代码位置,比如lowlevel_init在arch/arm/cpu/armvx/的低级初始化,或者板级特定的初始化在board/厂商/板名/目录下。需要指出这些关键目录,帮助用户定位代码。 最后,可能需要注意用户的实际应用场景,比如他是否在进行移植或调试,是否需要了解具体的硬件初始化流程。这时候需要建议他结合具体平台的文档和代码注释,以及使用调试工具进行跟踪。</think>了解U-Boot入口函数及其代码阅读路径是深入理解启动流程的关键。以下是针对U-Boot代码结构的详细解析: --- ### **一、入口函数定位** #### 1. **架构相关入口** - **ARM架构**(如Cortex-A系列): 入口文件:`arch/arm/lib/vectors.S` 入口符号:`_start` ```assembly .globl _start _start: b reset ; 复位向量 ldr pc, _undefined_instruction ... ; 其他异常向量 reset: bl save_boot_params ; 保存启动参数 ``` - **RISC-V架构**: 入口文件:`arch/riscv/cpu/start.S` 入口符号:`_start` ```assembly .globl _start _start: csrr a0, mhartid ; 读取硬件线程ID la sp, __stack_end ; 设置栈指针 ``` - **链接脚本确认**: 查看`u-boot.lds`(生成路径:`spl/u-boot-spl.lds`或`u-boot.lds`),定位`ENTRY(_start)`声明: ```ld OUTPUT_FORMAT("elf64-littleaarch64") OUTPUT_ARCH(aarch64) ENTRY(_start) ; 明确入口点 ``` --- ### **二、代码阅读路径** #### 1. **第一阶段(SPL/U-Boot Proper)** - **ARMv8启动流程**: ``` _start (arch/arm/cpu/armv8/start.S) → reset (跳转到低级初始化) → lowlevel_init (arch/arm/cpu/armv8/lowlevel_init.S) → _main (arch/arm/lib/crt0.S) → board_init_f (common/board_f.c) // 初始化DRAM、串口等 ``` #### 2. **第二阶段(重定位后)** ```c void board_init_f(ulong dummy) { ... // 初始化关键外设 gd->relocaddr = CONFIG_SYS_TEXT_BASE; // 确定重定位地址 relocate_code(addr_sp, id, addr); // 代码重定位 board_init_r(gd, addr); // 进入主逻辑 } void board_init_r(gd_t *id, ulong dest_addr) { ... main_loop(); // 进入命令行或自动启动流程 } ``` --- ### **三、关键代码文件** #### 1. **平台相关代码** | 目录 | 说明 | |-------------------------------|--------------------------| | `arch/arm/mach-<soc>/` | SoC特定初始化(如RK3399)| | `board/<vendor>/<board>/` | 板级硬件配置(如GPIO初始化)| | `drivers/<device_type>/` | 外设驱动(MMC、USB等) | #### 2. **核心逻辑** - **命令解析**:`common/main.c`中的`run_command()` - **环境变量**:`env/common.c`中的`env_get()/env_set()` - **设备树处理**:`cmd/fdt.c`中的fdt命令实现 --- ### **四、调试技巧** #### 1. **反汇编验证** ```bash aarch64-linux-gnu-objdump -D u-boot | grep "00000000 <_start>" # 输出示例: # 00000000 <_start>: # 0: 14000002 b 8 <reset> ``` #### 2. **添加调试日志** 在关键路径插入打印(需确保串口已初始化): ```c // 在lowlevel_init中添加: debug("Reached lowlevel_init, current stack: %p\n", sp); ``` #### 3. **QEMU仿真** 使用模拟器跟踪启动流程: ```bash qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic \ -bios u-boot.bin -s -S gdb-multiarch u-boot (gdb) target remote :1234 (gdb) b *0x0 // 在_start处断点 ``` --- ### **五、典型启动流程图** ``` Power-On → ROM Code → SPL(_start) ↓ 初始化时钟/DDR → 加载U-Boot Proper到内存 ↓ 跳转到U-Boot Proper的_start → 重定位代码 ↓ board_init_f() → 外设初始化 → board_init_r() ↓ main_loop() → 等待命令或启动内核 ``` --- **阅读建议**: 1. 从`_start`开始,配合`objdump`反汇编结果对照阅读 2. 重点关注`CONFIG_`开头的宏定义,这些决定了功能模块的开关 3. 使用`git blame`查看关键代码的修改历史,理解优化思路 4. 对ARMv8架构,特别注意`EL3→EL2→EL1`的异常级别切换代码(在`arch/arm/cpu/armv8/transition.S`中)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

如之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值