【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】011 - arch\arm\cpu\armv8\start.S 汇编源码逐行详解
- 一、arch\arm\cpu\armv8\start.S 汇编源码 - 简单梳理及注释
- 二、arch\arm\cpu\armv8\start.S 汇编源码 - 逐行详解(结合ARMv8 及 Cortex_A55 芯片手册)
系列文章汇总:《【OpenHarmony4.1 之 U-Boot 源码深度解析】000 - 文章链接汇总》
本文链接:《【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】011 - arch\arm\cpu\armv8\start.S 汇编源码逐行详解》
前面主要 讲解了 Makefile、u-boot.lds 等脚本分析,以及对整个编译过程做了详细的分析。
本文开始, 我们正式进入源码开始分析
一、arch\arm\cpu\armv8\start.S 汇编源码 - 简单梳理及注释
我们先来看下 Start.S 源码,可以大致了解 Start.S 中做了什么
# u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
// 第一部分:头文件声明,主要是一些宏定义
#include <asm-offsets.h> //空文件
#include <config.h>
#include <linux/linkage.h> // 定义用于处理函数和数据的链接属性以及一些特殊指示符,如 ALIGN
#include <asm/macro.h> // 定义一些 用汇编实现的宏控函数
#include <asm/armv8/mmu.h> // MMU 相关配置定义
-----------------------------------------------------------------------------------------------
// 第二部分: b reset
// 定义在宏控 CONFIG_PLATFORM_ELFENTRY="_start", 指明代码为 _start
.globl _start
_start:
#if defined(CONFIG_LINUX_KERNEL_IMAGE_HEADER) // 未定义
#include <asm/boot0-linux-kernel-header.h>
#elif defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) // CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y 定义在 .config 中
#include <asm/arch/boot0.h>
#else
b reset
#endif
-----------------------------------------------------------------------------------------------
// 第三部分:全局变量定义
.align 3 // 配置为 8字节对齐,2^3=8
.globl _TEXT_BASE // 定义一个 8byte 全局变量存储 TEXT 段起始地址, _TEXT_BASE = 0x00a00000
_TEXT_BASE:
.quad CONFIG_TEXT_BASE // CONFIG_TEXT_BASE=0x00a00000
.globl _end_ofs // 定义一个 8byte 全局变量存储uboot的结束偏移地址
_end_ofs:
.quad _end - _start
.globl _bss_start_ofs // 定义一个 8byte 全局变量存储 bss 堆的起始偏移
_bss_start_ofs:
.quad __bss_start - _start
.globl _bss_end_ofs // 定义一个 8byte 全局变量存储 bss 堆的结束偏移
_bss_end_ofs:
.quad __bss_end - _start
reset:
b save_boot_params
-----------------------------------------------------------------------------------------------
// 第四部分:配置VBAR 异常向量表重定位,配置 小端模式、禁用MMU、禁用 i/d Cache
.globl save_boot_params_ret
save_boot_params_ret:
#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD)
.macro set_vbar, regname, reg // 定义宏控,用于配置 vbar 寄存器,用于重定向异常向量表
msr \regname, \reg
.endm
adr x0, vectors // 获取 vectors 异常向量表的超始地址,保存在 x0 寄存器中
#else
.macro set_vbar, regname, reg
.endm
#endif
switch_el x1, 3f, 2f, 1f // 读取并判断当前 Exception Level, 即当前处理器正在执行的特权级别
3: set_vbar vbar_el3, x0 // 配置 VBAR_EL3 向量表基址
mrs x0, scr_el3 // 获取 scr_el3 系统控制寄存器的值 ,保存在 x0 寄存器中
orr x0, x0, #0xf // 配置 CPU 运行于非安全模式,禁用所有的中断
msr scr_el3, x0 // 将x0 寄存器值写入 scr_el3 系统控制寄存器
msr cptr_el3, xzr // 清零 CPTR_EL3 寄存器,禁止在EL3模式下的所有协处理器操作陷入
b 0f // 跳转到往下第一个 0: 标签处执行
2: mrs x1, hcr_el2 // 读取 HCR_EL2 虚拟化配置寄存器的值 到 x1 寄存器中
tbnz x1, #HCR_EL2_E2H_BIT, 1f // 测试HCR_EL2 虚拟化配置寄存器的bit 34(保留位)是否为0,如果不为零,说明异常,跳到往下第一个 1标签
orr x1, x1, #HCR_EL2_AMO_EL2 // 将bit 5 AMO 位置1,启用异步错误掩码重写
msr hcr_el2, x1 // 将修改后的 x1 寄存器值 写入 HCR_EL2 虚拟化配置寄存器
set_vbar vbar_el2, x0 // 配置 EL2 的VBAR 异常向量表的地址的入口地址为 x0,前面 x0中保存了 vectors 异常向量表的超始地址
mov x0, #0x33ff // 配置 x0 = #0x33ff
msr cptr_el2, x0 // 使能 浮点指令异常 及 高级SIMD访问 陷入EL2进行执行
b 0f // 跳转到往下的第一个 0号标签
1: set_vbar vbar_el1, x0 // 配置 EL1 的 VBAR 异常向量表的地址的入口地址为 x0,前面 x0中保存了 vectors 异常向量表的超始地址
mov x0, #3 << 20
msr cpacr_el1, x0 // 配置所有异常级别下都允许浮点指令
0:
msr daifclr, #0x4 // 清除DAIF寄存器中的 A(SError)位,打开或使能系统错误(SError)中断
#if CONFIG_COUNTER_FREQUENCY // CONFIG_COUNTER_FREQUENCY=24000000
branch_if_not_highest_el x0, 4f // 判断当前CPU 是否支持比 EL0 更高级的特权级别(EL2,EL3), 如果支持更高级别的EL,则跳转到往下第一个 4 标签处
// 如果只支持 EL0 ,则配置 cntfrq_el0 寄存器值 为24000000
ldr x0, =CONFIG_COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
4: isb // 清空处理器的指令流水线,强制后续的指令重新从内存中读取, 一般用于修改了CPU特权处理器后使用,以确保在新的指令上立即生效
#ifdef CONFIG_ARMV8_SET_SMPEN // 在 RK3568中,未定义该宏控
switch_el x1, 3f, 1f, 1f // 判断当前处理EL 模式,如果EL3 则运行后面的3标签,如果是 EL2/EL1 则运行后面的1标签
3:
mrs x0, S3_1_c15_c2_1 // 读取 S3_1_c15_c2_1 寄存器的值到 x0 中, 将第6位使能,也就是 使能 SMPEN 功能
orr x0, x0, #0x40 // x0 = x0 | 0x40 (0100 0000)
msr S3_1_c15_c2_1, x0 // 写入 CPUECTLR_EL1 的寄存器中
isb // 清空处理器的指令流水线
1:
#endif
-----------------------------------------------------------------------------------------------
// 第五部分:选择运行 SP_EL1堆栈,跳转 _main 函数入口
// 对 Cortext A53 和 A57 做一些特殊的配置,我们此次分析的是八核A55 ,此处会直接返回
// 有兴趣的兄弟,可以参考 A53 或 A57 的手册进行分析下,此处我们就不深入分析了
bl apply_core_errata
// 禁用 指令缓存I-Cache,数据缓存D-Cache,分支预测BPB, 页面缓存TLB
bl lowlevel_init // 对于 RK3568 来说,相关宏控未定义,该函数为空
master_cpu:
msr SPSel, #1 // 往 SPSel 寄存器写入 1, 写入1 表示选择SP_EL1堆栈(即使与当前 EL模式的堆栈),写入0表示选择SP_EL0堆栈
bl _main // ENTRY(_main) 位于 # u-boot-2024.07-rc3\arch\arm\lib\crt0_64.S
二、arch\arm\cpu\armv8\start.S 汇编源码 - 逐行详解(结合ARMv8 及 Cortex_A55 芯片手册)
前面第一章中,我们针对 start.S 汇编源码进行了简单的注释,如果只是想大致了解开机过程中做了什么,看完前面第一章也就足够了。
本章开始,我们开始深入代码的每一行,结合ARMv8 及 Cortex_A55 芯片手册,深入到每个寄存器中,去了解具体原理,
清楚为什么要这样配,
熟悉这些后,后续当要给一颗新的芯片移植时,就知道 哪些地方需要改?这些定义在哪些手册中?为什么这个时候要配这个? 等等
总而言之,我们的目的,不单单是了解 U-Boot,而是努力理解 U-boot,进而能够修改、定制、维护 U-Boot
好,开始吧。
在前面第一章中,细心的兄弟,可以发现,我把 u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
里面的代码,
根据其功能,分成了五个部分:
- 第一部分:头文件声明,主要是一些宏定义
- 第二部分:
b reset
- 第三部分:全局变量定义
- 第四部分:配置VBAR 异常向量表重定位,配置 小端模式、禁用MMU、禁用 i/d Cache
- 第五部分:选择运行 SP_EL1堆栈,跳转 _main 函数入口
下面,我们就按这个划分,一一来详解下:
2.1 armv8\start.S 第一部分:头文件宏定义
为更好理解,下面,按照代码结构 层层递进来分析,详细见如下注释:
# u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
#include <asm-offsets.h> //空文件
#include <config.h>
----------------------------->
+ // u-boot-2024.07-rc3\include\config.h
+ // 定义 evb_rk3568 的 board 文件夹,其中定义了 board、vendor、config 的名字
+ #define CFG_BOARDDIR board/rockchip/evb_rk3568
+ ------------------->
+ + config SYS_BOARD
+ + default "evb_rk3568"
+ +
+ + config SYS_VENDOR
+ + default "rockchip"
+ +
+ + config SYS_CONFIG_NAME
+ + default "evb_rk3568"
+ <-------------------
+
+ #include <configs/evb_rk3568.h>
+ ------------------->
+ + #include <configs/rk3568_common.h>
+ + ---------------->
+ + + // u-boot-2024.07-rc3\include\configs\rk3568_common.h
+ + +
+ + + // 在 OTP 中的 cpu_id 地址偏移,RK3568 的Secure OTP大小为8K,其中7K允许为安全应用程序所使用
+ + + #define CFG_CPUID_OFFSET 0xa
+ + +
+ + + // RK 平台通用配置
+ + + #include "rockchip-common.h"
+ + + ----------------------------->
+ + + + // u-boot-2024.07-rc3\include\configs\rockchip-common.h
+ + + + #ifndef CFG_CPUID_OFFSET
+ + + + #define CFG_CPUID_OFFSET 0x7 // 如果没特殊定义,RK Sercure OTP中默认CPU ID偏移地址为 0x7
+ + + + #endif
+ + + +
+ + + + #ifndef CONFIG_SPL_BUILD
+ + + + #define BOOT_TARGETS "mmc1 mmc0 nvme scsi usb pxe dhcp spi" // 配置 boot 启动参数
+ + + + // mmc1 表示从第一块MMC设备启动
+ + + + // mmc0 表示从第0块MMC设备启动
+ + + + // nvme 表示从NVME设备启动
+ + + + // scsi 表示从SCSI设备启动
+ + + + // usb 表示从USB设备启动
+ + + + // pxe 表示从网络启动(网络唤醒)
+ + + + // dhcp 表示使用动态主机配置协议获取IP地址
+ + + + // spi 表示从SPI设备启动
+ + + +
+ + + + // 配置根文件系统的UUID(Universally Unique Identifier,通用唯一识别码)
+ + + + #ifdef CONFIG_ARM64
+ + + + #define ROOT_UUID "B921B045-1DF0-41C3-AF44-4C6F280D3FAE;\0"
+ + + + #else
+ + + + #define ROOT_UUID "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3;\0"
+ + + + #endif
+ + + +
+ + + + // 配置硬盘默认分区
+ + + + #define PARTS_DEFAULT \
+ + + + // disk硬盘的uuid 为 ${uuid_gpt_disk}
+ + + + "uuid_disk=${uuid_gpt_disk};" \
+ + + + // 第一块分区为 loader1, 起始地址为 0x8000 (32K) - 0x3EFFFF (4032K), 大小为 4000K,该分区UUID 为 uuid_gpt_loader1
+ + + + "name=loader1,start=32K,size=4000K,uuid=${uuid_gpt_loader1};" \
+ + + + // 第二块分区为 loader2, 起始地址为 0x800000 (8M) - 0xBFFFFF (12M), 大小为 4M, 该分区UUID 为 uuid_gpt_loader2
+ + + + "name=loader2,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};" \
+ + + + // 第三块分区为 trust, 起始地址为 0xC00000 (12M) - 0xFFFFFF (16M), 大小为 4M, 该分区UUID 为 uuid_gpt_atf
+ + + + "name=trust,size=4M,uuid=${uuid_gpt_atf};" \
+ + + + // 第四块分区为 boot, 起始地址为 0x1000000 (16M) - 0x7FFFFFF (128M), 大小为 112M, 该分区UUID 为 uuid_gpt_boot
+ + + + "name=boot,size=112M,bootable,uuid=${uuid_gpt_boot};" \
+ + + + // 第五块分区为 rootfs, 地始地址为 0x8000000 (128M), 该分区UUID 为 "B921B045-1DF0-41C3-AF44-4C6F280D3FAE"
+ + + + "name=rootfs,size=-,uuid="ROOT_UUID
+ + + +
+ + + + #endif
+ + + <-----------------------------
+ + +
+ + + #define CFG_IRAM_BASE 0xfdcc0000 // SYSTEM_SRAM 起始地址( 见Rockchip_RK3568_TRM_Part1_V1.3.PDF 第1.1章 Address Mapping)
+ + +
+ + + #define CFG_SYS_SDRAM_BASE 0
+ + + #define SDRAM_MAX_SIZE 0xf0000000 // SDRAM 最大地址,因为 0xf0000000 开始为外设映射地址
+ + +
+ + + #define ENV_MEM_LAYOUT_SETTINGS \ // 配置u-boot需要的内存布局参数,确定内核、设备树、初始化RAM磁盘等在内存中的位置和大小
+ + + "scriptaddr=0x00c00000\0" \ // u-boot脚本加载的内存地址为0x00c00000
+ + + "script_offset_f=0xffe000\0"\ // u-boot脚本文件的偏移量为0xffe000
+ + + "script_size_f=0x2000\0" \ // u-boot脚本文件的大小为0x2000
+ + + "pxefile_addr_r=0x00e00000\0"\ // PXE(Preboot eXecution Environment,网络启动环境)文件的读取地址为0x00e00000
+ + + "kernel_addr_r=0x02000000\0"\ // 内核镜像的读取地址为0x02000000
+ + + "kernel_comp_addr_r=0x0a000000\0"\ // 压缩内核的读取地址为0x0a000000
+ + + "fdt_addr_r=0x12000000\0"\ // 设备树的读取地址为0x12000000
+ + + "fdtoverlay_addr_r=0x12100000\0"\ // 设备树的覆盖读取地址为0x12100000
+ + + "ramdisk_addr_r=0x12180000\0"\ // RAM磁盘的读取地址为0x12180000
+ + + "kernel_comp_size=0x8000000\0" // 压缩内核的大小为0x8000000
+ + +
+ + + #define CFG_EXTRA_ENV_SETTINGS \
+ + + ENV_MEM_LAYOUT_SETTINGS \ // 配置u-boot需要的内存布局参数,确定内核、设备树、初始化RAM磁盘等在内存中的位置和大小
+ + + "fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \ // CONFIG_DEFAULT_FDT_FILE="rockchip/rk3568-evb1-v10.dtb"
+ + + "partitions=" PARTS_DEFAULT \ // CONFIG_MTDPARTS_DEFAULT=""
+ + + ROCKCHIP_DEVICE_SETTINGS \ // "stdout=serial,vidconsole\0 stderr=serial,vidconsole\0"
+ + + "boot_targets=" BOOT_TARGETS "\0" // boot 启动设备参数
+ + +
+ + <----------------
+ +
+ + #define ROCKCHIP_DEVICE_SETTINGS \
+ + "stdout=serial,vidconsole\0" \ // 将标准输出设备设置为serial(串口)和vidconsole(视频控制台)
+ + "stderr=serial,vidconsole\0" // 将标准错误输出设备也设置为serial(串口)和vidconsole(视频控制台)
+ <-------------------
+
+ #include <asm/config.h> // 为空
+ #include <linux/kconfig.h>
+ ------------------->
+ + // u-boot-2024.07-rc3\include\linux\kconfig.h
+ + // 配置一些宏控,如:判断宏控是否使能
+ + #define CONFIG_IS_ENABLED(option, ...) \
+ + __concat(__CONFIG_IS_ENABLED_, __count_args(option, ##__VA_ARGS__)) (option, ##__VA_ARGS__)
+ +
+ + // align 对齐宏定义
+ + #ifndef __ALIGN
+ + #define __ALIGN .align 4
+ + #endif
+ <-------------------
+
+ #include <config_fallbacks.h>
+ ------------------->
+ + // CONFIG_SPL_MAX_SIZE:定义了 Secondary Program Loader (SPL) 的最大大小, SPL 需要尽可能小,因为它通常被加载到物理 RAM 空间中,并从那里开始引导整个系统。
+ + // 用途是为了限制 SPL 的大小,防止它变得过大导致无法在有限的 RAM 空间中加载。
+ + //
+ + // CONFIG_SPL_PAD_TO: 定义了 U-Boot 在生成 SPL 时,需要将 SPL 填充到的大小位置
+ + // 这个配置可以确保 SPL 在内存中加载的位置是对齐的,这样可以提高内存访问的效率。
+ + // 同时,这个填充操作也可以确保 SPL 在内存中的位置是固定的,有助于在需要的时候找到 SPL。
+ + // u-boot-2024.07-rc3\include\config_fallbacks.h
+ + #ifdef CONFIG_SPL_PAD_TO // CONFIG_SPL_PAD_TO =0x7f8000
+ + #ifdef CONFIG_SPL_MAX_SIZE // CONFIG_SPL_MAX_SIZE=0x40000
+ + #if CONFIG_SPL_PAD_TO && CONFIG_SPL_PAD_TO < CONFIG_SPL_MAX_SIZE
+ + #error CONFIG_SPL_PAD_TO < CONFIG_SPL_MAX_SIZE
+ + #endif
+ + #endif
+ + #endif
+ +
+ + // 配置当前主板所支持的一些波特率,serial 串口来使用的
+ + #ifndef CFG_SYS_BAUDRATE_TABLE
+ + #define CFG_SYS_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 }
+ + #endif
+ <-------------------
<-----------------------------
// 定义用于处理函数和数据的链接属性以及一些特殊指示符,如 ALIGN
#include <linux/linkage.h>
// 定义一些 用汇编实现的宏控函数,如:write32、write16、write8、wait_timer、switch_el、branch_if_not_highest_el、branch_if_a57_core、armv8_switch_to_el2_m、armv8_switch_to_el1_m
#include <asm/macro.h>
--------> // u-boot-2024.07-rc3\arch\arm\include\asm\system.h 定义了一些寄存器bit位定义
// MMU 相关配置定义
#include <asm/armv8/mmu.h>
上面代码中, 在 #include <config_fallbacks.h> 有一些对 SPL 镜像校验大小的代码:
#ifdef CONFIG_SPL_PAD_TO
#ifdef CONFIG_SPL_MAX_SIZE
#if CONFIG_SPL_PAD_TO && CONFIG_SPL_PAD_TO < CONFIG_SPL_MAX_SIZE
#error CONFIG_SPL_PAD_TO < CONFIG_SPL_MAX_SIZE
#endif
#endif
#endif
结合我们编译后的 .config
文件知道 ,SPL
相关的宏控定义如下 :
CONFIG_SPL=y
CONFIG_SPL_IMAGE="spl/u-boot-spl.bin"
CONFIG_SUPPORT_SPL=y
CONFIG_SPL_MAX_SIZE=0x40000
CONFIG_SPL_PAD_TO=0x7f8000
2.1.1 SPL 有什么作用,为什么要定义 CONFIG_SPL_MAX_SIZE
简单的理解,uboot 分为 u-boot-spl 及 u-boot 两部分。
- 设计
SPL
(Secondary Program Loader
) 的初衷为:
SoC 的内部 SRAM 比较小,一般是几十KB,无法装下一个完整的u-boot 镜像,
因此,uboot 的计设者,就增加了一个 spl 镜像,它的大小要尽量小,
这样,在CPU上电启动后,
第一步,先把 SPL 镜像完整的装入物理SDRAM 中来运行,
在SPL 中主要负责初始化CPU、Board 相关配置,最后加载完整的 U-BOOT 到 RAM 中,然后跳转执行 U-Boot 镜像。
如上,我们 RK3568 芯片也是支持SPL 的,其镜像位于 spl/u-boot-spl.bin
,镜像大小限制为 0x40000
以内,即 32kb
CONFIG_SPL_PAD_TO
作用是指定SPL映像的大小,如果实际内容不够大,则填充到该大小。
具体来说,就是告诉编译器或打包工具,SPL映像应该被填充(padded)到指定的字节大小。
主要目的在于:让 SPL 镜像size 对齐,以优化读取和执行速度。
2.2 armv8\start.S 第二部分:b reset
前面的代码,主要下头文件的宏定义,
严格来说, 此处才是 u-boot
正式代码开始的地方
查找 .config
文件中的宏定义,
可知,在 RK3568
芯片中,如下这么一大段代码,其实就一句有效代码: b reset
# u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
// 定义在宏控 CONFIG_PLATFORM_ELFENTRY="_start", 指明代码为 _start
.globl _start
_start:
#if defined(CONFIG_LINUX_KERNEL_IMAGE_HEADER) // 未定义
#include <asm/boot0-linux-kernel-header.h>
#elif defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) // CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y 定义在 .config 中
// 一些 SoCs 在启动前需要做一些特殊的配置,定义在 boot0.h 中
#include <asm/arch/boot0.h>
----------------------->
+ #include <asm/arch-rockchip/boot0.h>
+ ------------------------->
+ + // 未定义该宏控
+ + // #ifdef CONFIG_SPL_BUILD
+ + // ...... 省略......
+ + // #endif
+ + // #if CONFIG_IS_ENABLED(ROCKCHIP_EARLYRETURN_TO_BROM)
+ + // ...... 省略......
+ + // #endif
+ +
+ + #if (defined(CONFIG_SPL_BUILD) || defined(CONFIG_ARM64))
+ + b reset
+ + // 对应反汇编为:
+ + // a00000: 1400000a b a00028 <reset>
+ + #endif
+ +
+ + // #if !defined(CONFIG_ARM64)
+ + // ...... 省略......
+ + // #endif
+ + // #if !defined(CONFIG_TPL_BUILD) && defined(CONFIG_SPL_BUILD) && (CONFIG_ROCKCHIP_SPL_RESERVE_IRAM > 0)
+ + // .space CONFIG_ROCKCHIP_SPL_RESERVE_IRAM /* space for the ATF data */
+ + // #endif
+ <-------------------------
+
<-----------------------
#else
b reset
#endif
我们可以通过如下命令对 u-boot
文件进行反汇编:
aarch64-linux-gnu-objdump -S u-boot > u-boot_src.txt
u-boot 文件 反汇编部分内容如下:
u-boot: file format elf64-littleaarch64
Disassembly of section .text:
0000000000a00000 <__image_copy_start>:
a00000: 1400000a b a00028 <reset>
a00004: d503201f nop
0000000000a00008 <_TEXT_BASE>:
a00008: 00a00000 .word 0x00a00000
a0000c: 00000000 .word 0x00000000
0000000000a00010 <_end_ofs>:
a00010: 000b01b8 .word 0x000b01b8
a00014: 00000000 .word 0x00000000
0000000000a00018 <_bss_start_ofs>:
a00018: 000b01b8 .word 0x000b01b8
a0001c: 00000000 .word 0x00000000
0000000000a00020 <_bss_end_ofs>:
a00020: 000ba430 .word 0x000ba430
a00024: 00000000 .word 0x00000000
0000000000a00028 <reset>:
a00028: 1400004b b a00154 <save_boot_params>
0000000000a0002c <save_boot_params_ret>:
a0002c: 10013ea0 adr x0, a02800 <vectors>
a00030: d5384241 mrs x1, currentel
可以看到,第一句执行的代码正为: b a00028 <reset>
其对应的机器码为: 1400000a
,这个就是 CPU 所能够执行的 机器码指令。
其在内存中存储的位置为:0x00a00000
,这个就是 U-Boot 的起始地址,定义在 CONFIG_TEXT_BASE=0x00a00000
中
a00028
对应的就是 reset 标号的地址,其指令为 b save_boot_params
b 跳转指令:见 《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第339页 C3.1 Branches, Exception generating, and System instructions
第340页 C3.1.2 Unconditional branch (immediate) 无条件分支(立即)
第1689页 C6.2.26 B
第1704页 C6.2.35 BL
// 待上图
31 26 0
+----------+-------+-------+-------+
| 0 01010 | imm26 |
+----------+-------+-------+-------+
1010 代表的是opcode,
imm26 代表的是跳转指令中的偏移量,而不是绝对地址, 实际的跳转地址通常是通过将这个偏移量左移2位(因为ARM指令是4字节对齐的)然后加上当前指令的地址(PC值)来计算的
-
1400000a -> 0001 0100 0000 0000 0000 0000 0000 1010
imm26 = 0xA
当前PC 地址为:a00000
a << 2 = 0x28, 偏移地址为 0x28 + 0xa00000 = 0xa00028
-
1400004b -> 0001 0100 0000 0000 0000 0000 0100 1011
imm26 = 0x4b
当前PC 地址为:a00028
4b << 2 = 0x12c, 偏移地址为 0x12c + 0xa00028 = 0xa00154
2.3 armv8\start.S 第三部分:全局变量定义
这部分内容,主要是定义了几个跟内存布局相关的全局变量:
.globl _TEXT_BASE
: 保存 .text 段起始地址.globl _end_ofs
:保存了 uboot 有效内容结束偏移地址(要加载到 RAM 中的内容).globl _bss_start_ofs
:保存了 bss 段的起始地址偏移.globl _bss_end_ofs
:保存了 bss 段的结束地址偏移
详细分析及注释如下:
# u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
// 配置为 8字节对齐,2^3=8
.align 3
// 对应反汇编为:
// a00004: d503201f nop
// 定义一个 8byte 全局变量存储 TEXT 段起始地址, _TEXT_BASE = 0x00a00000
.globl _TEXT_BASE
_TEXT_BASE:
.quad CONFIG_TEXT_BASE // CONFIG_TEXT_BASE=0x00a00000
// 对应反汇编为:
// a00008: 00a00000 .word 0x00a00000
// a0000c: 00000000 .word 0x00000000
/* These are defined in the linker script. */
// 定义一个 8byte 全局变量存储uboot的结束偏移地址,_end_ofs = _end - _start = 0000000000ab01b8 - 0000000000a00000 = 00000000000b01b8
// _start 等同于 起始地址, 在配置了 CONFIG_TEXT_BASE 的情况下,_start = 0000000000a00000
// _end 等同于 __bss_start 的起始地址, _end == __bss_start = 0000000000ab01b8
.globl _end_ofs
_end_ofs:
.quad _end - _start
// 对应反汇编为:
// a00010: 000b01b8 .word 0x000b01b8
// a00014: 00000000 .word 0x00000000
// 定义一个 8byte 全局变量存储 bss段的起始偏移,_bss_start_ofs = __bss_start - _start = 0000000000ab01b8 - 0000000000a00000 = 00000000000b01b8
// __bss_start段的起始地址, __bss_start = 0000000000ab01b8
.globl _bss_start_ofs
_bss_start_ofs:
.quad __bss_start - _start
// 对应反汇编为:
// a00018: 000b01b8 .word 0x000b01b8
// a0001c: 00000000 .word 0x00000000
// 定义一个 8byte 全局变量存储 bss 段的结束偏移,_bss_end_ofs = = __bss_start - _start = 0000000000aba430 - 0000000000a00000 = 00000000000ba430
// __bss_end 段的起始地址, __bss_start = 0000000000ab01b8 + 0000a278 = 0000000000aba430 // 在前面 头信息中有,这个大小只有链接完成后才知道
.globl _bss_end_ofs
_bss_end_ofs:
.quad __bss_end - _start
// 对应反汇编为:
// a00020: 000ba430 .word 0x000ba430
// a00024: 00000000 .word 0x00000000
reset:
/* Allow the board to save important registers */
b save_boot_params
u-boot 反汇编内容如下 :
u-boot: file format elf64-littleaarch64
Disassembly of section .text:
0000000000a00000 <__image_copy_start>:
__image_copy_start():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/./arch/arm/include/asm/arch-rockchip/boot0.h:44
a00000: 1400000a b a00028 <reset>
a00004: d503201f nop
0000000000a00008 <_TEXT_BASE>:
_TEXT_BASE():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/./arch/arm/include/asm/arch-rockchip/boot0.h:44
a00008: 00a00000 .word 0x00a00000
a0000c: 00000000 .word 0x00000000
0000000000a00010 <_end_ofs>:
_end_ofs():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/./arch/arm/include/asm/arch-rockchip/boot0.h:44
a00010: 000b01b8 .word 0x000b01b8
a00014: 00000000 .word 0x00000000
0000000000a00018 <_bss_start_ofs>:
_bss_start_ofs():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/./arch/arm/include/asm/arch-rockchip/boot0.h:44
a00018: 000b01b8 .word 0x000b01b8
a0001c: 00000000 .word 0x00000000
0000000000a00020 <_bss_end_ofs>:
_bss_end_ofs():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/./arch/arm/include/asm/arch-rockchip/boot0.h:44
a00020: 000ba430 .word 0x000ba430
a00024: 00000000 .word 0x00000000
0000000000a00028 <reset>:
reset():
/home/ciellee/work/u-boot-rk3568/u-boot-2024.07-rc3/arch/arm/cpu/armv8/start.S:57
a00028: 1400004b b a00154 <save_boot_params>
0000000000a0002c <save_boot_params_ret>:
2.4 armv8\start.S 第四部分:配置VBAR 异常向量表重定位,配置 小端模式、禁用MMU、禁用 i/d Cache
详细分析及注释如下:
# u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
reset:
/* Allow the board to save important registers */
b save_boot_params
------------------------->
+ WEAK(save_boot_params)
+ // 未定义宏控 CONFIG_BLOBLIST
+ // #if (IS_ENABLED(CONFIG_BLOBLIST))
+ // ...... 省略......
+ // #endif
+
+ b save_boot_params_ret /* back to my caller */
+ // 对应反汇编为:
+ // a00028: 1400004b b a00154 <save_boot_params>
+
+ ENDPROC(save_boot_params)
<-------------------------
.globl save_boot_params_ret
save_boot_params_ret:
// 未定义宏控 CONFIG_POSITION_INDEPENDENT
// #if CONFIG_POSITION_INDEPENDENT && !defined(CONFIG_SPL_BUILD)
// ...... 省略......
// #endif
// 配置异常向量表
#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD)
.macro set_vbar, regname, reg
msr \regname, \reg
.endm
// 获取 vectors 异常向量表的超始地址,保存在 x0 寄存器中
adr x0, vectors
// 对应反汇编为:
// a0002c: 10013ea0 adr x0, a02800 <vectors>
--------------------->
+ // u-boot-2024.07-rc3\arch\arm\cpu\armv8\exceptions.S
+ // 待深入分析异常处理过程
+ .align 11
+ .globl vectors
+ vectors:
+ .align 7 /* Current EL Synchronous Thread */
+ stp x29, x30, [sp, #-16]!
+ bl _exception_entry
+ bl do_bad_sync
+ b exception_exit
+
<---------------------
#else
.macro set_vbar, regname, reg
.endm
#endif
// 配置 小端模式、禁用MMU、禁用 i/d Cache
// 选择判断语句,根据当前的 EL 级别,运行 3\2\1 等标签代码
switch_el x1, 3f, 2f, 1f
// (1)如果当前处于 EL3 最高级别特权模式
// 将 x0中保存的地址(异常向量表的地址)保存在 vbar_el3 寄存器中,允许程序在运行时动态改变异常处理程序的位置
// VBAR_EL3 向量表基址(Vector Base Address Register)存放异常向量表的基地址,当发生异常时,处理器会查找向量表中的相应条目,转跳到该地址开始执行异常处理程序
3: set_vbar vbar_el3, x0
// 对应反汇编为:
// a00044: d51ec000 msr vbar_el3, x0
// scr_el3 是一个系统控制寄存器,它存储了一些与 Exception level 3(异常级别3)相关的配置和控制信息。
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第8205页 D23.2.155 SCR_EL3, Secure Configuration Register
// bit [0] NS Non-secure bit , 用于切换CPU运行在 Secure 或 Non-Secure 状态,当设置为1时,说明CPU 运行于Non-Secure 状态
// bit [1] IRQ 禁用物理中断, 当设置为1时,物理IRQ中断不能被接收
// bit [2] FIQ 禁用快速中断(FIQ), 当设置为1时,物理FIQ中断不能被接收
// bit [3] EA 禁用外部中断, 当设置为1时,物理异常中断不能被接收
// bit [4:5] RES1 保留为1的位,用于将来可能的功能扩展
// bit [6] RES0 保留为0的位,用于将来可能的功能扩展
// bit [7] SMD 控制安全监视器调用的处理方式, 当设置为1时,允许安全监视器调用被执行,否则被禁止
// bit [8] HCE Hypervisor Call instruction enable,控制HVC指令的使用, 当设置为1时,允许在EL0和EL1级别执行HVC指令
// bit [9] SIF Secure instruction fetch,这个位当设置时,阻止非安全指令从读取安全可访问的通用寄存器
// bit [10] RW 重置时选择EL2和EL3的异常级别
// bit [11] ST 控制Trap EL2的Secure EL1执行, 当设置为1时,安全状态下的EL1异常会切换到EL2级别。
// bit [12] TWI 控制软件step WFI指令的trap
// bit [13] TWE 控制软件step WFE指令的trap
// 获取 scr_el3 系统控制寄存器的值 ,保存在 x0 寄存器中
mrs x0, scr_el3
// 配置 CPU 运行于非安全模式,禁用所有的中断
// 将x0寄存器中的值的最低4位设置为1, 即 x0 = x0 & 0xF
// NS = 1, 配置 CPU 运行于Non-Secure 非安全模式下
// IRQ = 1, 配置 CPU 禁用物理中断
// FIQ = 1, 配置 CPU 禁用快速中断
// EA = 1, 配置 CPU 禁用外部中断
// 中断类型分为 FIQ, IRQ,
// 快速中断 FIQ (Fast Interrupt): 较高优先级的中断,处理速度最快,主要应用于对速度有较高要求的中断处理场合
// 物理中断 IRQ (Interrupt Request): 优先级较低, IRQ是标准的中断请求,优先级低于FIQ。当系统收到IRQ时,会暂停当前的任务,转去处理中断请求。处理完中断后再恢复原来的任务。
// 外部中断 EA (External Abort): 用于处理外部中断请求的机制,比如存储器或外设错误等。这种中断主要对应系统出错或者异常情况
// 优先级方面: FIQ > IRQ > EA
orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */
// 将 x0 寄存器的值 写入 scr_el3 系统控制寄存器
msr scr_el3, x0
// 清零 CPTR_EL3 寄存器,禁止在EL3模式下的所有协处理器操作陷入
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第7449页 D23.2.35 CPTR_EL3, Architectural Feature Trap Register (EL3)
// xzr 为 zero register , 等同于 0
// cptr_el3是系统寄存器,代表当前处理模式为EL3模式下的协处理器陷入(trap)寄存器
// CPTR_EL3 寄存器描述如下:
// TCPAC (bit 31): 禁止上下文的 PAC(Pointer Authentication Code,指针认证代码)处理
// 当此位为 0 时,软件可以在 EL3 使用 AArch64 的 PAC 指令
// 当此位为 1 时,所有尝试在 EL3 使用 AArch64 的 PAC 指令的行为都会导致 Undefined Instruction(未定义指令)异常
// TTA (bit 20): 禁止上下文的跟踪访问
// 当此位为 0 时,软件可以在 EL3 访问跟踪系统寄存器
// 当此位为 1 时,所有尝试在 EL3 访问跟踪系统寄存器的行为都会导致 Undefined Instruction(未定义指令)异常
// TFP (bit 10): 禁止浮点数和高级 SIMD 的使用
// 当此位为 0 时,软件可以在 EL3 使用浮点数和高级 SIMD 指令
// 当此位为 1 时,所有尝试在 EL3 使用浮点数和高级 SIMD 指令的行为都会导致 Undefined Instruction(未定义指令)异常
msr cptr_el3, xzr /* Enable FP/SIMD */
// 跳转到往下第一个 0: 标签处执行
b 0f
------------------->
+ 0:
+ // 清除DAIF寄存器中的 A(SError)位,打开或使能系统错误(SError)中断
+ msr daifclr, #0x4 /* Unmask SError interrupts */
+
+ // DAIFClr 系统寄存器: 用于清除(也就是使能或者打开)DAIF 字段中的各个标志位, 包含如下 四个标志位:
+ // bit[3] D(Debug exceptions): 调试异常 1-禁用调试中断, 0-启用调试中断
+ // bit[2] A(SError, system error 或者 asynchronous exceptions):系统错误或异步异常 1-禁用SError 中断, 0-启用SError 中断
+ // bit[1] I(IRQ, Interrupt request,中断请求): IRQ异常 1-禁用IRQ中断, 0-启用IRQ中断
+ // bit[0] F(FIQ, Fast interrupt request,快速中断请求): FIQ异常 1-禁用FIQ中断, 0-启用FIQ中断
+
+ // 配置 DAIF 系统寄存器为0x4 (0100), 清除DAIF寄存器中的 A(SError)位,打开或使能系统错误(SError)中断
+ // 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第890页, C5.2.3 DAIF, Interrupt Mask
<-------------------
// (2)如果当前处于 EL2 Hypervisor级别特权模式
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第7685页, D23.2.53 HCR_EL2, Hypervisor Configuration Register
// HCR_EL2 (64位) 寄存器各bit 功能如下:
// bit[0]: VM Virtualization MMU enable(虚拟化MMU使能) 设置为1时,启用虚拟化MMU; 设置为0时,禁用虚拟化MMU
// bit[1]: SWIO Set/Way Invalidation Override(设置/路失效重写) 覆盖数据缓存设置/清理命令,AArch32: DCISW is executed as DCCISW. AArch64: DC ISW is executed as DC CISW.
// 设置为1时,启用Set/Way失效重写。设置为0时,禁用Set/Way失效重写
// bit[2]: PTW Protected Table Walk(保护表遍历) 设置为1时,启用保护表遍历; 设置为0,禁用保护表遍历。
// bit[3]: FMO FIQ Mask Override(FIQ掩码重写) 设置为1时,启用FIQ掩码重写; 设置为0时,禁用FIQ掩码重写
// bit[4]: IMO IRQ Mask Override(IRQ掩码重写) 设置为1时,启用IRQ掩码重写; 设置为0时,禁用IRQ掩码重写。
// bit[5]: AMO Asynchronous Error Mask Override(异步错误掩码重写) 设置为1时,启用异步错误掩码重写; 设置为0时,禁用异步错误掩码重写。
// bit[6]: VF Virtual FIQ exception(虚拟FIQ异常) 设置为1时,启用虚拟FIQ异常; 设置为0时,禁用虚拟FIQ异常
// bit[7]: VI Virtual IRQ exception(虚拟IRQ异常) 设置为1时,启用虚拟IRQ异常; 设置为0时,禁用虚拟IRQ异常
// bit[8]: VSE Virtual SError exception(虚拟SError异常) 设置为1时,启用虚拟SError异常; 设置为0时,禁用虚拟SError异常
// bit[9]: FB Force Broadcast of (some) MMU maintenance operations(强制广播(一些)MMU维护操作) 设置为1时,强制广播MMU维护操作; 设置为0时,不强制广播
// bit[10:11]: BSU Barrier Shareability Upgrade(屏障可共享性升级) 设置为00,01,10,11分别对应不同的Barrier Shareability升级模式
// bit[12]: DC Default Cacheability(默认可缓存性) 设置为1时,设定默认的缓存策略; 设置为0时,禁用默认的缓存策略
// bit[13]: TWI Trap WFI(陷阱WFI) 设置为1时,陷阱WFI指令; 设置为0时,不陷阱WFI指令
// bit[14]: TWE Trap WFE(陷阱WFE) 设置为1时,陷阱WFE指令; 设置为0时,不陷阱WFE指令
// bit[15]: TID0 Trap ID register 0(陷阱ID寄存器0) 设置为1时,陷阱对应ID寄存器的访问; 设置为0时,不陷阱
// bit[16]: TID1 Trap ID register 1(陷阱ID寄存器1)
// bit[17]: TID2 Trap ID register 2(陷阱ID寄存器2)
// bit[18]: TID3 Trap ID register 3(陷阱ID寄存器3)
// bit[19]: TSC Trap SMC instruction(陷阱SMC指令) 设置为1时,陷阱SMC指令; 设置为0时,不陷阱SMC指令
// bit[20]: TIDCP Trap implementation defined functionality to EL2(实现定义功能到EL2的陷阱) 设置为1时,陷阱实现定义功能到EL2; 设置为0时,不陷阱
// bit[21]: TACR Trap ACTLR accesses(访问ACTLR的陷阱) 设置为1时,陷阱ACTLR访问; 设置为0时,不陷阱
// bit[22]: TSW Trap speculative Write accesses(猜测性写访问陷阱) 设置为1时,陷阱推测性写访问; 设置为0时,不陷阱
// bit[23]: TPC Trap Performance Monitors accesses(性能监视器访问陷阱)设置为1时,陷阱性能监视器访问; 设置为0时,不陷阱
// bit[24]: TPU Trap Pointer authentication accesses(指针认证访问陷阱)设置为1时,陷阱指针认证访问; 设置为0时,不陷阱
// bit[25]: TTLB Trap TLB maintenance operation(TLB维护操作陷阱) 设置为1时,陷阱TLB维护操作; 设置为0时,不陷阱
// bit[26]: TVM Trap Virtual Memory controls(虚拟内存控制陷阱) 设置为1时,陷阱虚拟内存控制; 设置为0时,不陷阱
// bit[27]: TGE Trap General Exceptions to EL2(将常规异常陷阱到EL2) 设置为1时,普通异常陷阱到EL2; 设置为0时,不陷阱
// bit[28]: TDZ Trap DC ZVA instructions(陷阱DC ZVA指令) 设置为1时,陷阱DC ZVA指令; 设置为0时,不陷阱
// bit[29]: HCD Hypervisor Call Disabled(禁用虚拟机调用) 设置为1时,禁用虚拟机调用; 设置为0时,启用虚拟机调用
// bit[30]: TRVM Trap Read of VMID(读取VMID的陷阱) 设置为1时,陷阱EL2对寄存器的访问; 设置为0时,不陷阱。
// bit[31]: RW Register Width 设置为1时,EL1和EL0在64位模式下运行; 设置为0时,EL1和EL0在32位模式下运行。
// bit[32]: CD Cache Disable(缓存禁用) 设置为1时,禁用缓存; 设置为0时,启用缓存。
// bit[33]: ID Implementation Defined(实现定义) 设置为1时,禁用指令同步化; 设置为0时,启用指令同步化
// bit[34:37]: RES0 Reserved(保留)
// bit[38]: MIOCNCE-Invalidate cache operation impact on subsequent instructions(使缓存失效操作影响后续指令) 设置为1时,禁用IO内存区域的非缓存性; 设置为0时,启用IO内存区域的非缓存性。
// bit[39:64]: RES0 Reserved(保留)
// 读取 HCR_EL2 虚拟化配置寄存器的值 到 x1 寄存器中
2: mrs x1, hcr_el2
// 测试HCR_EL2 虚拟化配置寄存器的bit 34(保留位)是否为0,如果不为零,说明异常,跳到往下第一个 1标签
tbnz x1, #HCR_EL2_E2H_BIT, 1f /* HCR_EL2.E2H */ // #define HCR_EL2_E2H_BIT 34
// 将bit 5 AMO 位置1,启用异步错误掩码重写
orr x1, x1, #HCR_EL2_AMO_EL2 /* Route SErrors to EL2 */ // #define HCR_EL2_AMO_EL2 (1 << 5) /* Route SErrors to EL2 */
// 将修改后的 x1 寄存器值 写入 HCR_EL2 虚拟化配置寄存器
msr hcr_el2, x1
// 配置 EL2 的VBAR 异常向量表的地址的入口地址为 x0,前面 x0中保存了 vectors 异常向量表的超始地址
set_vbar vbar_el2, x0
// 配置 x0 = #0x33ff
mov x0, #0x33ff
// 配置 cptr_el2 系统寄存器,,代表当前处理模式为EL2模式下的协处理器陷入(trap)寄存器
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第7439页, D23.2.34 CPTR_EL2, Architectural Feature Trap Register (EL2)
// CPTR_EL2 寄存器描述如下:
// bit[0:9] RES1 保留为1的位,用于将来可能的功能扩展
// bit[10] TFP TFP(Trap Floating Point), 如果设置为1,EL0和EL1对SIMD的访问和执行的浮点指令将会触发异常并跳转到EL2执行。如果设置为0,则执行浮点指令不会触发异常
// bit[11] RES0
// bit[12:13] RES1
// bit[14:19] RES0
// bit[20] TTA TTA(Trap trace access), 如果设置为1,对跟踪寄存器的访问将会触发异常并跳转到EL2执行。如果设置为0,则不会触发异常
// bit[21:30] RES0
// bit[31] TCPAC TCPAC(Trap general-purpose access to the Counter-timer Physical Secure and Non-secure in EL1),
// 如果设置为1,当EL1访问物理计数器/计时器(CNTPCT_EL0, CNTP_TVAL_EL0, CNTP_CTL_EL0, CNTVCT_EL0, CNTV_CVAL_EL0, CNTV_TVAL_EL0, or CNTV_CTL_EL0寄存器)时会触发异常并进入EL2。 如果设置为0,不会触发异常。
// 0x33ff = 0011 0011 1111 1111
// 使能 浮点指令异常 及 高级SIMD访问 陷入EL2进行执行
msr cptr_el2, x0 /* Enable FP/SIMD */
// 跳转到往下的第一个 0号标签
b 0f
// (3)如果当前处于操作系统内核代码(Linux内核)级别
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第8462页,D23.2.192 VBAR_EL1, Vector Base Address Register (EL1)
// bit [0-10] : RES0
// bit [11-63] : Vector Base Address
// 配置 EL1 的 VBAR 异常向量表的地址的入口地址为 x0,前面 x0中保存了 vectors 异常向量表的超始地址
1: set_vbar vbar_el1, x0
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第7433页, D23.2.33 CPACR_EL1, Architectural Feature Access Control Register
// CPACR_EL1 系统寄存器,用来控制EL0和EL1访问某些系统功能的权限
// CPACR_EL1 寄存器描述如下:
// bit [19,20,21] FPEN 这3个位控制浮点指令的执行权限
// 0b000:浮点指令在所有异常级别下都被禁止
// 0b001:在EL0下禁用浮点指令,但在EL1下允许
// 0b010:此值在ARMv8中是保留的,不应被使用
// 0b011:在所有异常级别下都允许浮点指令
// 0b100 to 0b111:这些值在ARMv8中是保留的,不应被使用
// bit [28] TTA TTA(Trap trace access),这个位在ARMv8-A中实际上没有定义,而是保留给未来使用。此位目前应设置为0,如果将其设置为1,将会导致未预期的行为
// 配置所有异常级别下都允许浮点指令
mov x0, #3 << 20
msr cpacr_el1, x0 /* Enable FP/SIMD */
0:
// 清除DAIF寄存器中的 A(SError)位,打开或使能系统错误(SError)中断
msr daifclr, #0x4 /* Unmask SError interrupts */
// DAIFClr 系统寄存器: 用于清除(也就是使能或者打开)DAIF 字段中的各个标志位, 包含如下 四个标志位:
// bit[3] D(Debug exceptions): 调试异常 1-禁用调试中断, 0-启用调试中断
// bit[2] A(SError, system error 或者 asynchronous exceptions):系统错误或异步异常 1-禁用SError 中断, 0-启用SError 中断
// bit[1] I(IRQ, Interrupt request,中断请求): IRQ异常 1-禁用IRQ中断, 0-启用IRQ中断
// bit[0] F(FIQ, Fast interrupt request,快速中断请求): FIQ异常 1-禁用FIQ中断, 0-启用FIQ中断
// 配置 DAIF 系统寄存器为0x4 (0100), 清除DAIF寄存器中的 A(SError)位,打开或使能系统错误(SError)中断
// 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第890页, C5.2.3 DAIF, Interrupt Mask
#if CONFIG_COUNTER_FREQUENCY // CONFIG_COUNTER_FREQUENCY=24000000
branch_if_not_highest_el x0, 4f
------------------>
+ // u-boot-2024.07-rc3\arch\arm\include\asm\macro.h
+ .macro branch_if_not_highest_el, xreg, label
+ // 判断当前处于什么模式,如果是 EL3 跳转到往下第一个3标号,如果是EL2 跳转到往下第一个2标号,如果是EL1 跳转到往下第一个1标号,
+ switch_el \xreg, 3f, 2f, 1f
+ ----------------------->
+ + // u-boot-2024.07-rc3\arch\arm\include\asm\macro.h
+ + .macro switch_el, xreg, el3_label, el2_label, el1_label
+ +
+ + mrs \xreg, CurrentEL
+ + cmp \xreg, #0x8
+ + b.gt \el3_label // 如果 CurrentEL 寄存器大于 8,说明处于 EL3 特权等级,跳转到 3f
+ + b.eq \el2_label // 如果 CurrentEL 寄存器等于 8,说明处于 EL2 特权等级,跳转到 2f
+ + b.lt \el1_label // 如果 CurrentEL 寄存器小于 8,说明处于 EL1 特权等级,跳转到 1f
+ + .endm
+ <-----------------------
+
+ // 配置 ID_AA64PFR0_EL1
+ // 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第7932页, D23.2.79 ID_AA64PFR0_EL1, AArch64 Processor Feature Register 0
+
+ // ID_AA64PFR0_EL1 寄存器描述如下:
+ // bit[0:3] EL0 EL0执行级别支持的异常级别,是否支持AArch32和AArch64状态, 0001:只支持AArch64, 0010:同时支持 AArch64 or AArch32
+ // bit[4:7] EL1 EL1执行级别支持的异常级别,是否支持AArch32和AArch64状态, 0001:只支持AArch64, 0010:同时支持 AArch64 or AArch32
+ // bit[8:11] EL2 EL1执行级别支持的异常级别,是否支持AArch32和AArch64状态, 0001:只支持AArch64, 0010:同时支持 AArch64 or AArch32 0000:EL2 不支持
+ // bit[12:15] EL3 EL1执行级别支持的异常级别,是否支持AArch32和AArch64状态, 0001:只支持AArch64, 0010:同时支持 AArch64 or AArch32 0000:EL3 不支持
+ // bit[16:19] FP 浮点硬件(Floating-point)的支持情况,0000 支持浮点运算,1111 不支持浮点运算
+ // bit[20:23] AdvSIMD 向量硬件(Advanced SIMD)的支持情况, 0000 支持Advanced SIMD,1111 不支持Advanced SIMD
+ // bit[24:27] GIC 通用中断控制器(GIC,Generic Interrupt Controller)的支持情况, 0001:支持GIC V3.0和V4.0, 0010:只支持GIC V3.0 0000:不支持GIC中断
+ // bit[28:63] RES0
+ 2: mrs \xreg, ID_AA64PFR0_EL1
+ // \xreg = \xreg & 0x0F000
+ // 获得 EL3 bit 12-15的值
+ and \xreg, \xreg, #(ID_AA64PFR0_EL1_EL3) // #define ID_AA64PFR0_EL1_EL3 (0xF << 12) /* EL3 implemented */
+ // 如果 EL3 的值不为零,说明支持执行 EL3 ,跳转到 \label 标签 4f 处
+ cbnz \xreg, \label
+ // 如果 EL3 为零,说明不支持EL3, 则跳转到往下的 3标签处
+ b 3f
+
+ 1: mrs \xreg, ID_AA64PFR0_EL1
+ // \xreg = \xreg & 0x0FF00
+ // 获得 EL2、EL3 的值,如果不为零,说明支持执行 EL3 或 EL2,跳转到 \label 标签 4f 处
+ and \xreg, \xreg, #(ID_AA64PFR0_EL1_EL3 | ID_AA64PFR0_EL1_EL2)
+ cbnz \xreg, \label
+
+ 3:
+ .endm // 函数结束
<------------------
ldr x0, =CONFIG_COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
// 清空处理器的指令流水线,强制后续的指令重新从内存中读取, 一般用于修改了CPU特权处理器后使用,以确保在新的指令上立即生效
4: isb
// 在ARMv8-A架构中,ISB(Instruction Synchronization Barrier)是一种特殊的Barrier指令,它的功能是清空处理器的指令流水线,并强制后续的指令重新从内存中获取。
// ISB指令通常用在修改了程序执行流的场合,例如修改了系统控制寄存器,或者修改了程序执行状态后。
// 因为处理器可能因为预取指令和流水线的原因,仍在执行修改前带来的指令,所以通过执行ISB指令,可以强制处理器停止执行后续预取的指令,而从新的位置重新获取和执行指令。
// 这样可以确保修改立即生效,不会因为指令预取和流水线导致延迟。
// 在 ARMV8-A 中,有三种上barrier 模式:
// ISB (Instruction Synchronization Barrier): 指令同步屏障, 强制处理器完成强ISB指令前所有指令的执行,然后才能执行ISB后的指令。这主要用于确保改变处理器状态的某些指令能在继续执行其他指令前生效
// DSB (Data Synchronization Barrier): 数据同步屏障, 确保DSB指令执行前所有的内存访问操作都完成了,这里的内存访问包括存储器的读/写操作、cache的清理、TLB的清理等, 主要是为了确保指令与数据的同步,防止因指令顺序的改变而导致数据错误
// DMB (Data Memory Barrier):数据内存屏障,用于多核处理器和多线程环境下,当多个处理器或线程对同一块内存进行操作时,DMB能够确保在执行DMB指令前的所有内存访问动作都完成,而在DMB指令后的内存访问将会等待,直到DMB执行完成。这样就能保证对同一块内存的操作不会互相影响,确保数据的一致性。
/* Enable SMPEN bit for coherency. This register is not architectural but at the moment this bit should be set for A53/A57/A72.*/
// 在 ARMv8-A 架构中,SMPEN 位于 ACTLR_EL1 寄存器中。
// SMPEN bit 是 SMP (Symmetric Multi-Processing) 的缩写,它被用来启动 SMP, 作用是确保在多核处理器系统中,所有的核心在操作共享数据时能看到一致的数据值。
// 当 SMPEN 位被设置为1时,它开启了数据一致性处理,使得每个核心在操作共享数据时,都能看到最新的、一致的变量值。这对于多核处理器中,多个核心同时访问和操作共享数据的场景非常关键。
// 如果 SMPEN 未被设置(值为0),则每个核心将会使用私有 L1 Cache,这可能会导致数据不一致的问题。
#ifdef CONFIG_ARMV8_SET_SMPEN
// 在 RK3568中,未定义该宏控
// 判断当前处理EL 模式,如果EL3 则运行后面的3标签,如果是 EL2/EL1 则运行后面的1标签
switch_el x1, 3f, 1f, 1f
// S3_1_c15_c2_1 并不是
// 在 A55 中没有这个寄存器,
// 我们以 A72 的芯片手册来分析下:
// 见:《cortex_a72_mpcore_trm_100095_0003_06_en.pdf》第208页,4.3.67 CPU Extended Control Register, EL1
// 内容如下:
// To access the CPUECTLR_EL1 in AArch64 state, read or write the register with:
// MRS <Xt>, S3_1_c15_c2_1 ; Read EL1 CPU Extended Control Register
// MSR S3_1_c15_c2_1, <Xt> ; Write EL1 CPU Extended Control Register
//
// To access the CPUECTLR in AArch32 state, read or write the CP15 register with:
// MRRC p15, 1, <Rt>, <Rt2>, c15 ; Read CPU Extended Control Register
// MCRR p15, 1, <Rt>, <Rt2>, c15 ; Write CPU Extended Control Register
// 等于说,在 AArch64 中,通过 S3_1_c15_c2_1 来读写CPUECTLR_EL1 寄存器; 在AArch32 需要通过P15协处理器指令来读写
// 我们来看下 CPUECTLR_EL1 的寄存器中,bit[6] 为 SMPEN,用于使能或禁用 SMPEN
// SMPEN : Enables the processor to receive instruction cache and TLB maintenance operations broadcast from other processors in the cluster.
3:
// 读取 S3_1_c15_c2_1 寄存器的值到 x0 中, 将第6位使能,也就是 使能 SMPEN 功能
mrs x0, S3_1_c15_c2_1 /* cpuectlr_el1 */
// x0 = x0 | 0x40 (0100 0000)
orr x0, x0, #0x40
// 写入 CPUECTLR_EL1 的寄存器中
msr S3_1_c15_c2_1, x0
// 清空处理器的指令流水线
isb
1:
#endif
2.4.1 switch_el x1, 3f, 2f, 1f 指令详解
switch_el x1, 3f, 2f, 1f
--------------------->
+ // u-boot-2024.07-rc3\arch\arm\include\asm\macro.h
+ .macro switch_el, xreg, el3_label, el2_label, el1_label
+
+ // 获取 CurrentEL(Current Exception Level)寄存器的值保存在 x1 寄存器中
+ // Exception Level定义了处理器的当前运行状态,即当前处理器正在执行的特权级别
+
+ // 问题来了,为什么通过判断 0x8 就能知道当前的EL模式?
+ // 见《Arm® Architecture Reference Manual for A-profile architecture.pdf》第888页 C5.2.2 CurrentEL, Current Exception Level
+ // bit[0:1] 保留
+ // bit[2:3] 00: EL0, 01:EL1, 10:EL2, 11:EL3
+ // bit[4:32] 保留
+ // 因此,EL1的值为 0100(4), EL2的值为 1000(8), EL2的值为 1100(C),这也是为什么通判断8 就能知道当前是EL模式
+ mrs \xreg, CurrentEL
+ cmp \xreg, #0x8
+ b.gt \el3_label // 如果 CurrentEL 寄存器大于 8,说明处于 EL3 特权等级,跳转到 3f
+ b.eq \el2_label // 如果 CurrentEL 寄存器等于 8,说明处于 EL2 特权等级,跳转到 2f
+ b.lt \el1_label // 如果 CurrentEL 寄存器小于 8,说明处于 EL1 特权等级,跳转到 1f
+ .endm
+
+ // 对应反汇编为:
+ // a00030: d5384241 mrs x1, currentel
+ // a00034: f100203f cmp x1, #0x8
+ // a00038: 5400006c b.gt a00044 <save_boot_params_ret+0x18>
+ // a0003c: 54000100 b.eq a0005c <save_boot_params_ret+0x30> // b.none
+ // a00040: 540001eb b.lt a0007c <save_boot_params_ret+0x50> // b.tstop
+
<---------------------
ARMv8-A架构中 异常状态级别如下:
- EL0: 用户模式,一般用户程序在此级别运行
- EL1: 操作系统内核代码(Linux内核)通常运行的级别,
它的权限为:可以配置内存管理单元(MMU)来管理虚拟内存,设置中断和异常向量等 - EL2: Hypervisor级别,主要用于虚拟化场景,
在此级别,Hypervisor可以对EL1和EL0级别的操作系统进行完全控制,监控和管理执 - EL3: Secure Monitor级别,最高等级,可以访问和控制所有硬件资源,包括处理器和内存。
这个级别主要用于运行安全关键代码,比如启动代码,固件,安全操作系统等。
在此级别,可以切换处理器的安全状态(Secure or Non-secure)
2.4.2 CurrentEL 寄存器介绍:为什么通过判断 0x8 就能知道当前处于的 ELx 模式?
ARM 官方芯片手册,下载地址为:
https://developer.arm.com/documentation/ddi0487/ka/?lang=en
https://documentation-service.arm.com/static/65fdad3c1bc22b03bca90781?token=
见 《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第888页 C5.2.2 CurrentEL, Current Exception Level
CurrentEL
是一个 64bit 寄存器,取值如下:
EL0 为 0x0 (0000)
EL1 为 0x4 (0100)
EL2 为 0x8 (1000)
EL3 为 0xC (1100)
从这就知道,EL2 对应的是 0x8,因此,比8大就是 EL3,相等就是 EL2, 比8小就是EL1,就很好理解了
2.4.3 SCR_EL3 寄存器:配置在EL3(最高异常级别)下的安全相关的特性
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第8205页 D23.2.155 SCR_EL3, Secure Configuration Register
SCR_EL3 各个 bit 介绍如下:
bit 位 | 简称 | 详细描述 |
---|---|---|
bit [ 1 ] | IRQ | 当设置为1时,它允许在EL3下的非安全世界中的IRQ中断 |
bit [ 2 ] | FIQ | 当设置为1时,它允许在EL3下的非安全世界中的FIQ中断 |
bit [ 3 ] | EA | 外部中止 (External Abort) 允许位, 当设置为1时,它允许在EL3处理外部中止异常 |
bit [ 7 ] | SMD | 安全监视器禁用位, 当设置为1时,它会禁用安全监视器模式,这通常用于调试 |
bit [ 8 ] | HCE | 硬件配置错误 (Hardware Configuration Error) 允许位, 当设置为1时,它允许在EL3处理硬件配置错误 |
bit [ 9 ] | SIF | 安全中断转发 (Secure Interrupt Forwarding) 位, 当设置为1时,安全中断可以转发到非安全世界 |
bit [ 10 ] | RW | 读/写 (Read/Write) 位, 当设置为1时,允许访问在安全状态下配置为读/写的寄存器 |
bit [ 11 ] | ST | 安全跟踪 (Secure Trace) 位, 当设置为1时,它允许在安全状态下进行跟踪 |
bit [ 12 ] | TWI | 陷阱写指令 (Trap Write Instruction) 位, 当设置为1时,它会将写指令陷阱到EL3 |
bit [ 13 ] | TWE | 陷阱写指令到EL3 (Trap Write to EL3) 位, 当设置为1时,对EL3寄存器的写操作将被陷阱 |
bit [ 14 ] | TLOR | 陷阱加载其他寄存器 (Trap Load Other Register) 位, 当设置为1时,加载其他寄存器的操作将被陷阱 |
bit [ 15 ] | TERR | 陷阱执行返回 (Trap Execution Return) 位, 当设置为1时,执行返回 (如从异常返回) 将被陷阱 |
bit [ 16 ] | APK | API保持 (API retention) 位, 当设置为1时,API (Application Processor ID) 值在休眠期间保持不变 |
bit [ 17 ] | API | API使能 (API Enable) 位, 当设置为1时,它允许访问API寄存器 |
bit [ 18 ] | EEL2 | EL2使能 (EL2 Enable) 位, 当设置为1时,它使能EL2 |
bit [ 19 ] | EASE | 早期异步中止选择 (Early Asynchronous Abort Select) 位, 它控制异步中止的处理 |
bit [ 20 ] | NMEA | 非安全内存管理使能 (Non-secure Memory Management Enable) 位, 当设置为1时,非安全世界可以使用内存管理单元 |
bit [ 21 ] | FIEN | 故障注入使能 (Fault Injection Enable) 位, 用于调试和测试 |
bit [ 25 ] | EnSCXT | 使能安全上下文 (Enable Secure Context) 位, 用于控制安全上下文的使能 |
bit [ 26 ] | ATA | 地址转换属性 (Address Translation Attribute) 位, 用于控制地址转换的属性 |
bit [ 27 ] | FGTEn | 功能门控使能 (Functional Gate Enable) 位, 用于控制功能门控 |
bit [ 28 ] | ECVEn | 错误检查和纠正使能 (Error Check and Correction Enable) 位 |
bit [ 29 ] | TWEDEn | 陷阱写指令到EL3的禁止使能 (Trap Write to EL3 Disable Enable) 位 |
bit [30:33] | TWEDEL | 陷阱写指令延迟 (Trap Write to EL3 Delay) , 用于配置在写入操作被陷阱到EL3之前,可以执行多少条非安全指令, 这个延迟可以帮助在处理陷阱时保持流水线的效率 |
bit [ 34 ] | TME | Translation Memory Enable, 这个位用于启用Translation Memory extension,这是ARMv8.2中的一个特性,它允许硬件支持更大的地址空间转换表 |
bit [ 35 ] | AMVOFFEN | AMV Offset Enable, 这个位与地址空间的隔离有关,用于启用地址空间隔离中的AMV偏移量 |
bit [ 36 ] | EnAS0 | Enable ASID 0. 当设置时,允许访问ASID (Address Space ID) 为0的翻译表条目 |
bit [ 37 ] | ADEn | Access Disable Enable. 这个位用于控制是否启用访问权限检查 |
bit [ 38 ] | HXEn | Hypervisor Extensions Enable. 这个位用于启用或禁用Hypervisor Extensions |
bit [ 39 ] | GCSEn | Guest Configuration Shadow Enable. 当启用时,这允许软件在EL1和EL0配置的影子寄存器中读取和写入 |
bit [ 40 ] | TRNDR | Transaction Disable. 这个位用于禁用事务性内存操作 |
bit [ 41 ] | EnTP2 | Enable Top Byte Ignore for Stage 2. 当启用时,这个位允许在第二阶段地址转换时忽略地址的最高字节 |
bit [ 42 ] | RCWMASKEn | Read-only Cacheability and Write Mask Enable. 这个位与缓存控制相关,用于启用只读缓存和写掩码功能 |
bit [ 43 ] | TCR2En | TCR2_EL3 Enable. 这个位用于启用TCR2_EL3寄存器,该寄存器在地址转换时使用 |
bit [ 44 ] | SCTLR2En | SCTLR2_EL3 Enable. 这个位用于启用SCTLR2_EL3寄存器,该寄存器控制系统控制寄存器的配置 |
bit [ 45 ] | PIEn | Performance Monitors Enable. 这个位用于启用性能监视器 |
bit [ 46 ] | AIEn | Activity Monitors Enable. 这个位用于启用活动监视器 |
bit [ 47 ] | D128En | Data Cache 128B Enable. 这个位用于启用128字节数据缓存 |
bit [ 48 ] | GPF | Guest Physical Flat. 这个位与虚拟化有关,用于控制虚拟机中的物理地址空间是否是平坦的 |
bit [ 49 ] | MECEn | Memory Encryption Enable. 这个位用于启用内存加密功能 |
bit [ 51 ] | TMEA | Transactional Memory At EL1/0. 这个位用于控制EL1/0级别的事务性内存 |
bit [ 52 ] | TWERR | Translation Walk Error. 这个位用于控制翻译走查错误的行为 |
bit [ 53 ] | PFAREn | Page Fault Attribute Register Enable. 这个位用于启用页错误属性寄存器 |
bit [ 55 ] | EnIDCP128 | Enable ID Caching for 128-bit accesses. 这个位用于控制128位访问的ID缓存的启用 |
bit [ 59 ] | FGTEn2 | Force GT Level 2. 这个位用于强制使用第二级的全局标签 |
bit [ 62 ] | NSE | Non-Secure EL1 Execution Enable. 这个位用于控制非安全EL1执行是否被启用 |
2.4.4 CPTR_EL3 寄存器:控制处理器特性
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第7449页 D23.2.35 CPTR_EL3, Architectural Feature Trap Register (EL3)
该寄存器主要 控制对CPACR、CPACR_EL1、HCPTR、CPTR_EL2、跟踪、活动监视器、SME、流式SVE、SVE以及高级SIMD和浮点功能访问的陷阱到EL3
各bit 功能详解如下:
bit 位 | 简称 | 详细描述 |
---|---|---|
bit[8] | EZ | 当设置为1时, 此位允许访问执行区域 (Execution Zone) , 执行区域是安全监控模式下的一个特性, 用于控制对某些资源的访问 |
bit[10] | TFP | 当设置为1时, 此位会使得所有的浮点操作都被陷阱 (trap) 到EL3, 这允许在执行浮点操作之前, 由EL3软件进行检查和控制 |
bit[12] | ESM | 当设置为1时, 此位启用统计性能监控, 这允许性能监控单元 (Performance Monitor Unit, PMU) 在EL0和EL1中收集统计信息 |
bit[20] | TTA | 当设置为1时, 此位会导致所有的Thumb-2指令执行被陷阱到EL3, 这允许EL3软件监控和控制对Thumb-2指令的使用 |
bit[30] | TAM | 当设置为1时, 此位会导致所有对监视器寄存器的访问被陷阱到EL3, 这允许EL3软件控制对性能监视器寄存器的访问 |
bit[31] | TCPAC | 当设置为1时, 此位会导致所有对CPACR (协处理器访问控制寄存器) 的访问被陷阱到EL3, 这允许EL3软件控制对协处理器的访问 |
2.4.5 DAIF 寄存器:控制中断屏蔽
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第890页, C5.2.3 DAIF, Interrupt Mask
-
D (Debug)
当D位被设置为1时,它屏蔽了所有的调试中断,例如调试异常。
这通常在调试时使用,以防止在执行特定的代码区域时被调试器中断。 -
A (Synchronous Abort)
当A位被设置为1时,它屏蔽了所有的同步异常,如数据访问中止和指令中止。
同步异常通常是由于非法的内存访问或指令执行时产生的。 -
I (IRQ):
当I位被设置为1时,它屏蔽了所有的外部中断请求(IRQ)。
这包括由外设生成的中断,如定时器、UART、I/O设备等。 -
F (FIQ):
当F位被设置为1时,它屏蔽了所有的快速中断请求(FIQ)。
FIQ是用于高速处理中断的一种特殊类型,通常用于需要快速响应的场景。
ID_AA64PFR0_EL1
2.4.6 HCR_EL2 寄存器:
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第7685页, D23.2.53 HCR_EL2, Hypervisor Configuration Register
2.4.7 CPTR_EL2 寄存器:
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第7439页, D23.2.34 CPTR_EL2, Architectural Feature Trap Register (EL2)
2.4.8 VBAR_EL1 寄存器:
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第8462页,D23.2.192 VBAR_EL1, Vector Base Address Register (EL1)
2.4.9 CPACR_EL1 寄存器:
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第7433页, D23.2.33 CPACR_EL1, Architectural Feature Access Control Regist
2.4.10 ID_AA64PFR0_EL1 寄存器:
见《Arm® Architecture Reference Manual for A-profile architecture.pdf》
第7932页, D23.2.79 ID_AA64PFR0_EL1, AArch64 Processor Feature Register 0
2.5 armv8\start.S 第五部分:选择运行 SP_EL1堆栈,跳转 _main 函数入口
考虑到文章篇幅,第五部分 代码详解,我们另起一篇文章来分析:
《【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】012 - arch\arm\cpu\armv8\start.S 汇编源码逐行详解 之 第五部分:跳转 _main》