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)中运行,通过以下方式确认成功:

  1. 断点观察:在SetupCarStack执行后设置断点,检查RSP寄存器值是否等于0x840000(基址0x800000 + 大小0x40000)。
  2. 函数调用测试:在CAR配置完成后调用任意C函数(如DebugPrint),若函数正常执行且不崩溃,说明栈可用。
  3. 数据完整性:在FillCarRegion中读回验证已确保数据未被逐出;若CpuDeadLoop未触发,说明Cache填充成功。

通过条件:RSP值位于[0x800000, 0x840000)区间内,且后续C函数调用正常返回,无#PF异常。

常见问题

  • Cache未命中导致死循环:确认MTRR的PhysMask有效位(Bit 11)已置1,且掩码计算正确(基地址按4KB对齐)。
  • NEM位被硬件忽略:部分早期CPU不支持NEM,需改用传统CAR方式(填充后锁定Cache行),此时需额外配置Cache控制寄存器。
  • 栈溢出:CAR区域通常仅256KB,避免在SEC阶段分配大数组或深层递归。