3. 实现SEC阶段的临时RAM配置
任务目标
在UEFI的SEC(Security)阶段,正式内存(DRAM)尚未初始化,但C语言执行环境需要栈(Stack)支持。本案例通过配置处理器Cache作为临时RAM(Cache As RAM,CAR),在SEC阶段建立可用栈,并验证栈指针(RSP)已正确指向CAR区域。
实现原理
CAR的核心思路:利用CPU Cache的SRAM特性,通过设置MTRR(Memory Type Range Registers)将指定内存区域配置为Write-Back(WB)类型,然后对该区域执行读写操作触发Cache填充,使Cache行锁定数据,从而在没有DRAM的情况下获得可读写的临时存储。本案例基于x64架构,使用NEM(No-Eviction Mode)模式——通过设置IA32_MISC_ENABLE的Bit 11(CAR位)禁止Cache行被逐出,确保数据驻留。
关键代码实现
// SecCar.c - SEC阶段临时RAM配置实现(x64,NEM模式)
#include <Base.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
// MSR定义
#define MSR_IA32_MISC_ENABLE 0x1A0
#define MSR_IA32_MTRR_CAP 0x0FE
#define MSR_IA32_MTRR_PHYSBASE0 0x200
#define MSR_IA32_MTRR_PHYSMASK0 0x201
#define MSR_IA32_CR_PAT 0x277
#define MSR_IA32_MISC_ENABLE_NEM (1ULL << 11)
// PAT内存类型(与MTRR配合)
#define PAT_WB 0x06
#define PAT_UC 0x00
// CAR区域配置(通过PCD可调整)
#define CAR_BASE_ADDRESS FixedPcdGet32 (PcdCarBaseAddress) // 默认0x800000
#define CAR_REGION_SIZE FixedPcdGet32 (PcdCarRegionSize) // 默认0x40000 (256KB)
#define CAR_MASK (~(CAR_REGION_SIZE - 1) & 0xFFFFFFFFF000ULL)
// 栈顶位置(栈向下增长,栈顶在CAR区域最高地址)
#define CAR_STACK_TOP (CAR_BASE_ADDRESS + CAR_REGION_SIZE)
/**
配置MTRR将CAR区域设为WB类型
**/
VOID
ConfigureCarMtrr (
VOID
)
{
UINT64 MtrrCap;
UINT64 PhysBase;
UINT64 PhysMask;
UINT64 PatValue;
// 读取MTRR能力,确认可变范围MTRR数量
MtrrCap = AsmReadMsr64 (MSR_IA32_MTRR_CAP);
ASSERT ((MtrrCap & 0xFF) > 0);
// 设置MTRR PhysBase0:基地址 + 类型WB(0x06)
PhysBase = (CAR_BASE_ADDRESS & 0xFFFFFFFFF000ULL) | 0x006;
AsmWriteMsr64 (MSR_IA32_MTRR_PHYSBASE0, PhysBase);
// 设置MTRR PhysMask0:有效位(Bit 11)+ 掩码
PhysMask = CAR_MASK | 0x800; // Bit 11 = Valid
AsmWriteMsr64 (MSR_IA32_MTRR_PHYSMASK0, PhysMask);
// 设置PAT,确保WB类型映射正确(保持默认PAT即可,此处显式确认)
PatValue = AsmReadMsr64 (MSR_IA32_CR_PAT);
PatValue = (PatValue & ~(0x07ULL << 0)) | (PAT_WB << 0); // PA0 = WB
AsmWriteMsr64 (MSR_IA32_CR_PAT, PatValue);
}
/**
启用NEM模式(禁止Cache逐出)
**/
VOID
EnableNem (
VOID
)
{
UINT64 MiscEnable;
MiscEnable = AsmReadMsr64 (MSR_IA32_MISC_ENABLE);
MiscEnable |= MSR_IA32_MISC_ENABLE_NEM;
AsmWriteMsr64 (MSR_IA32_MISC_ENABLE, MiscEnable);
}
/**
填充Cache:对CAR区域执行写操作触发Cache Line填充
**/
VOID
FillCarRegion (
VOID
)
{
volatile UINT64 *Buffer;
UINTN Index;
UINTN Count;
Buffer = (volatile UINT64 *) (UINTN) CAR_BASE_ADDRESS;
Count = CAR_REGION_SIZE / sizeof (UINT64);
// 写入整个区域,确保所有Cache Line被填充并锁定
for (Index = 0; Index < Count; Index++) {
Buffer[Index] = 0x5A5A5A5A5A5A5A5AULL;
}
// 读回验证(确保数据驻留Cache)
for (Index = 0; Index < Count; Index++) {
if (Buffer[Index] != 0x5A5A5A5A5A5A5A5AULL) {
CpuDeadLoop (); // 填充失败,挂起
}
}
}
/**
设置栈指针并验证
**/
VOID
SetupCarStack (
VOID
)
{
UINT64 RspValue;
// 将RSP设置为CAR区域顶部(栈向下增长)
RspValue = CAR_STACK_TOP;
// 内联汇编设置RSP
__asm {
mov rsp, qword ptr [RspValue]
}
// 验证RSP是否在CAR有效范围内
RspValue = (UINT64) &RspValue; // 取当前栈上变量地址验证
if ((RspValue < CAR_BASE_ADDRESS) || (RspValue >= CAR_STACK_TOP)) {
CpuDeadLoop (); // 栈指针异常
}
}
/**
SEC阶段入口 - CAR配置主流程
**/
VOID
EFIAPI
SecCarMain (
VOID
)
{
// 1. 配置MTRR使CAR区域为WB
ConfigureCarMtrr ();
// 2. 启用NEM模式
EnableNem ();
// 3. 填充Cache,锁定CAR数据
FillCarRegion ();
// 4. 设置栈指针
SetupCarStack ();
// 5. 栈验证:调用一个函数测试栈可用性
// 此时RSP已指向CAR区域,函数调用将使用CAR栈
volatile UINT64 StackMarker = 0xDEADBEEF;
if (StackMarker != 0xDEADBEEF) {
CpuDeadLoop ();
}
// 到达此处说明CAR配置成功,栈可正常使用
// 后续可调用C函数继续SEC阶段流程
}
编译与链接说明
本代码需在EDK2环境中编译,依赖以下库:
BaseLib:提供AsmReadMsr64/AsmWriteMsr64等汇编封装PcdLib:读取PCD配置的CAR基地址与大小DebugLib:提供CpuDeadLoop(断言失败时挂起)
在.inf文件中声明依赖,并在.dsc中定义PCD默认值:
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdCarBaseAddress|0x800000
gEfiMdePkgTokenSpaceGuid.PcdCarRegionSize|0x40000
验证方法
在真实硬件或QEMU(-cpu qemu64)中运行,通过以下方式确认成功:
- 断点观察:在
SetupCarStack执行后设置断点,检查RSP寄存器值是否等于0x840000(基址0x800000 + 大小0x40000)。 - 函数调用测试:在CAR配置完成后调用任意C函数(如
DebugPrint),若函数正常执行且不崩溃,说明栈可用。 - 数据完整性:在
FillCarRegion中读回验证已确保数据未被逐出;若CpuDeadLoop未触发,说明Cache填充成功。
通过条件:RSP值位于[0x800000, 0x840000)区间内,且后续C函数调用正常返回,无#PF异常。
常见问题
- Cache未命中导致死循环:确认MTRR的PhysMask有效位(Bit 11)已置1,且掩码计算正确(基地址按4KB对齐)。
- NEM位被硬件忽略:部分早期CPU不支持NEM,需改用传统CAR方式(填充后锁定Cache行),此时需额外配置Cache控制寄存器。
- 栈溢出:CAR区域通常仅256KB,避免在SEC阶段分配大数组或深层递归。