需要分析的代码

/*
 *	__cpu_setup
 *
 *	Initialise the processor for turning the MMU on.
 *
 * Output:
 *	Return in x0 the value of the SCTLR_EL1 register.
 */
	.pushsection ".idmap.text", "awx"
SYM_FUNC_START(__cpu_setup)
	tlbi	vmalle1				// Invalidate local TLB
	dsb	nsh

	mov	x1, #3 << 20
	msr	cpacr_el1, x1			// Enable FP/ASIMD
	mov	x1, #1 << 12			// Reset mdscr_el1 and disable
	msr	mdscr_el1, x1			// access to the DCC from EL0
	isb					// Unmask debug exceptions now,
	enable_dbg				// since this is per-cpu
	reset_pmuserenr_el0 x1			// Disable PMU access from EL0
	reset_amuserenr_el0 x1			// Disable AMU access from EL0

打扫屋子后请客

第11行和第12行的含义

涉及到ARMv8架构下操作系统内核启动时CPU初始化的核心步骤。这两条指令的目的是为了建立一个干净、可预测的存储系统状态,为后续启用MMU(内存管理单元)和高级缓存管理做准备。
我们来逐句分析:

tlbi vmalle1

tlbi: 这条指令是 TLB Invalidate 的缩写,即“使TLB条目失效”。
vmalle1: 这是一个操作参数,具体含义是:
v: 表示当前异常级别(EL1或EL2)所配置的虚拟地址。
all: 表示所有 与当前VMID(虚拟机标识符)和ASID(地址空间标识符)相关的条目。
e1: 表示在异常级别1(EL1) 执行此操作。EL1通常是操作系统内核运行的级别。

合起来是什么意思?

这条指令的作用是清除当前CPU核心上,EL1级别的所有TLB缓存条目。

为什么需要这么做?

TLB是一个缓存,用于加速虚拟地址到物理地址的转换。
在CPU启动初期、MMU尚未启用或即将启用时,TLB中可能包含一些随机、无效或来自之前启动阶段(如Bootloader)的旧条目。
如果这些旧条目存在,当MMU启用后,CPU可能会根据这些错误的缓存进行地址转换,导致程序跑飞、内存访问错误等极其难以调试的问题。
因此,在建立新的页表并启用MMU之前,必须彻底清空TLB,确保CPU从全新的、正确的页表中开始加载地址映射关系。

dsb nsh

dsb: 这条指令是 Data Synchronization Barrier 的缩写,即“数据同步屏障”。
nsh: 这是一个限制范围的参数,具体含义是:
nsh: 表示 Non-Shared。这意味着屏障操作仅针对“非共享”的存储域。在ARMv8的多核系统中,它通常意味着屏障只在当前核心上执行,不需要与其他核心进行同步。

合起来是什么意思?

这条指令的作用是确保在当前CPU核心上,在它之前的所有内存访问指令(包括那个tlbi指令)都必须完成之后,才能执行它之后的指令。

为什么需要紧跟在tlbi之后?

tlbi指令本质上是对MMU系统的一个“控制寄存器”进行写入操作。这个操作可能会被放入一个写缓冲区中,并不会立即生效。
内存系统(包括MMU、Cache)是乱序执行的。如果不加屏障,CPU可能会在TLB失效操作真正完成之前,就去执行下一条指令。
下一条指令很可能就是操作页表或者直接启用MMU。如果MMU在旧的TLB条目被完全清除之前就启用了,那么同样会导致使用错误的地址映射,造成系统崩溃。
dsb nsh 就像一个“收银线”,它告诉CPU:“暂停一下,必须等前面所有的‘顾客’(内存访问操作,特别是TLB失效)都结账离开后,后面的人才能开始行动。”

总结:这两条指令的组合作用

tlbi vmalle1: 清场。把当前CPU核心上旧的、可能错误的地址映射缓存(TLB)全部清空。
dsb nsh: 立栅栏并等待。确保“清场”这个动作被当前CPU核心完全地、实实在在地执行完毕,不会因为乱序执行而被打折扣。

这是一个非常经典和必要的组合,在操作系统内核(如Linux)的启动代码、上下文切换、修改页表等关键场景中反复出现。它的目的是为了保证内存管理上下文切换的原子性和正确性,为接下来安全地启用MMU和配置缓存策略打下坚实的基础。
可以简单类比为:在画一幅新画(建立新页表)之前,先把旧画布彻底擦干净(tlbi),并且要等墨迹完全干透(dsb),然后再开始动笔。

寄存器和名词解释

CPACR_EL1

第14, 15行涉及的寄存器 CPACR_EL1, Architectural Feature Access Control Register 的位域 bits[21:20]
该寄存器的含义如下 Controls access to trace, SVE, Advanced SIMD and floating-point functionality.
bits[21:20] 的含义如下:
FPEN, bits [21:20]
Traps EL0 and EL1 accesses to the SVE, Advanced SIMD, and floating-point registers to EL1,
or to EL2 when it is implemented and enabled for the current Security state and HCR_EL2.TGE is 1,
from both Execution states.
该位域的值有多种组合,分别表达的含义是
0b00
This control causes any instructions at EL0 or EL1 that use the registers associated with SVE,
Advanced SIMD and floating-point execution to be trapped,
unless they are trapped by CPACR_EL1.ZEN.
0b01
This control causes any instructions at EL0 that use the registers associated with SVE,
Advanced SIMD and floating-point execution to be trapped, unless they are trapped by CPACR_EL1.ZEN,
but does not cause any instruction at EL1 to be trapped.
0b10
This control causes any instructions at EL0 or EL1 that use the registers associated with SVE,
Advanced SIMD and floating-point execution to be trapped, unless they are trapped by CPACR_EL1.ZEN.
0b11
This control does not cause any instructions to be trapped.

MDSCR_EL1

第16行和第17行涉及到了寄存器 mdscr_el1 的位域 bit12
该寄存器的含义如下

Monitor Debug System Control Register,也就是 Main control register for the debug implementation
bit12的含义如下
TDCC, bit [12]
Traps EL0 accesses to the DCC registers to EL1, from both Execution states.
0b0 
This control does not cause any instructions to be trapped.
0b1 
EL0 using AArch64: EL0 accesses to the MDCCSR_EL0, DBGDTR_EL0, DBGDTRTX_EL0, and DBGDTRRX_EL0 registers are trapped to EL1.
EL0 using AArch32: EL0 accesses to the DBGDSCRint, DBGDTRRXint, DBGDTRTXint, DBGDIDR, DBGDSAR, and DBGDRAR registers are trapped to EL1.
也就是说,位域bit12设置为1时,在EL0异常等级下,访问 MDCCSR_EL0, DBGDTR_EL0, DBGDTRTX_EL0, and DBGDTRRX_EL0 将会切换到 EL1 异常等级

enable_dbg

第19行涉及到 enable_dbg,定义如下

	.macro	enable_dbg
	msr	daifclr, #8
	.endm

daifclr 涉及到 pstate 寄存器的 bits[9:6],分别是 DAIF,8对应 bit[9],表示清除调试掩码

PMU 和 AMU

第20行代码的注释是 Disable PMU access from EL0,入参为 x0
我们看一下宏定义的内容,x0 作为临时寄存器,入参的值并没有实际意义

/*
 * reset_pmuserenr_el0 - reset PMUSERENR_EL0 if PMUv3 present
 */
	.macro	reset_pmuserenr_el0, tmpreg
	mrs	\tmpreg, id_aa64dfr0_el1
	sbfx	\tmpreg, \tmpreg, #ID_AA64DFR0_PMUVER_SHIFT, #4
	cmp	\tmpreg, #1			// Skip if no PMU present
	b.lt	9000f
	msr	pmuserenr_el0, xzr		// Disable PMU access from EL0
9000:
	.endm

该宏定义首先获取寄存器 id_aa64dfr0_el1 的值存放在临时寄存器 tmpreg 里面,然后拷贝从 ID_AA64DFR0_PMUVER_SHIFT 开始的 4bit的数据到 tmpreg 里面,并且进行位扩展
ID_AA64DFR0_EL1, AArch64 Debug Feature Register 0,该寄存器的作用是 Provides top level information about the debug system in AArch64 state.
ID_AA64DFR0_PMUVER_SHIFT 的定义如下

#define ID_AA64DFR0_PMUVER_SHIFT	8

根据寄存器的手册描述,位域 bits[11:8]的含义如下
PMUVer, bits [11:8]
Performance Monitors Extension version.
0b0000 Performance Monitors Extension not implemented.
0b0001 Performance Monitors Extension implemented, PMUv3.
0b0100 PMUv3 for Armv8.1. As 0b0001, and also includes support for:
• Extended 16-bit PMEVTYPER<n>_EL0.evtCount field.
• If EL2 is implemented, the MDCR_EL2.HPMD control bit.
0b0101 PMUv3 for Armv8.4. As 0b0100 and also includes support for the PMMIR_EL1 register.
0b0110 PMUv3 for Armv8.5. As 0b0101 and also includes support for:
• 64-bit event counters.
• If EL2 is implemented, the MDCR_EL2.HCCD control bit.
• If EL3 is implemented, the MDCR_EL3.SCCD control bit.
0b1111 IMPLEMENTATION DEFINED form of performance monitors supported, PMUv3 not supported. Arm does not recommend this value in new implementations.
从上述描述我们可以得知,只有当位域的值为0和-1的时候,才表示 PMUv3 没有效
所以宏定义里面判断,该位域的值小于1的时候,直接跳转到9000处,否则将寄存器 pmuserenr_el0 清零
PMUSERENR_EL0, Performance Monitors User Enable Register 的含义如下 Enables or disables EL0 access to the Performance Monitors.
用于控制用户模式(EL0)对性能监视单元(PMU)的访问权限,在这里将其清零,表示禁止在 EL0 异常等级下访问。

第22行与第21行很类似,只不过是操作的 AMU 相关的 id_aa64pfr0_el1 和 AMUSERENR_EL0 寄存器,其宏定义如下

/*
 * reset_amuserenr_el0 - reset AMUSERENR_EL0 if AMUv1 present
 */
	.macro	reset_amuserenr_el0, tmpreg
	mrs	\tmpreg, id_aa64pfr0_el1	// Check ID_AA64PFR0_EL1
	ubfx	\tmpreg, \tmpreg, #ID_AA64PFR0_AMU_SHIFT, #4
	cbz	\tmpreg, .Lskip_\@		// Skip if no AMU present
	msr_s	SYS_AMUSERENR_EL0, xzr		// Disable AMU access from EL0
.Lskip_\@:
	.endm

代码走读

这段ARM汇编代码是__cpu_setup函数的实现,主要用于在ARMv8架构的处理器上进行CPU核心的初始化设置。

tlbi vmalle1
作用:无效化当前核心的所有EL1(异常级别1,通常是内核态)的TLB(Translation Lookaside Buffer)条目。
目的:确保TLB中没有旧的或无效的地址转换缓存,避免后续操作出现不一致的地址转换。

dsb nsh
作用:数据同步屏障(Data Synchronization Barrier),nsh表示非共享域(Non-shareable domain)。
目的:确保tlbi操作在所有流水线中完成,保证内存访问顺序。

mov x1, #3 << 20 和 msr cpacr_el1, x1
作用:将cpacr_el1(架构特性访问控制寄存器)的FPEN(浮点和SIMD使能位)字段设置为0b11。
目的:允许EL1和EL0(用户态)访问浮点(FP)和SIMD(ASIMD)指令,启用浮点和向量运算单元。

mov x1, #1 << 12 和 msr mdscr_el1, x1
作用:设置mdscr_el1(调试系统控制寄存器)的MDSCR_EL1.SS(单步调试)位。
目的:重置调试控制状态,并禁止EL0通过DCC(Debug Communications Channel)访问调试功能。

isb
作用:指令同步屏障(Instruction Synchronization Barrier)。
目的:确保之前的寄存器设置(如cpacr_el1和mdscr_el1)生效,清空流水线。

enable_dbg
作用:用于启用调试异常(如设置DAIF寄存器中的调试掩码位)。
目的:允许调试异常触发,便于内核调试。

reset_pmuserenr_el0 x1
作用:将pmuserenr_el0寄存器清零。
目的:禁止EL0(用户态)访问性能监控单元(PMU),保护性能计数器不被用户程序滥用。

reset_amuserenr_el0 x1
作用:将amuserenr_el0寄存器清零。
目的:禁止EL0访问活动监控单元(AMU),保护硬件性能计数器。

总结

这段代码是CPU核心初始化的一部分,主要完成以下工作:
清除TLB缓存,确保内存一致性。
启用浮点和SIMD指令支持。
配置调试寄存器,限制用户态对调试和性能监控硬件的访问。
设置屏障指令保证操作的原子性和顺序性。
这段代码确保每个核心处于一致的初始状态。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐