本文的实现是在MIPS平台龙芯架构实现的,在其他平台上实现方式可能不一样,但是大体上都是相同的。

首先Sec阶段是UEFI最早的一个阶段,CPU上电后从固定地址取指开始执行。每种架构的CPU上电后第一条指令的地址是不同的,龙芯的CPU上电执行的第一条指令的32位地址是0x1fc00000,用cache的64位地址来访问就是0xffffffff9fc00000的地址。这里为什么上电后就能够使用cache来访问呢?是因为Cache是硬件初始化的,上电后cache就可以使用。这个地址我们在UEFI的fdf文件中写死了上电需要执行的第一条指令。

(1)上电执行的第一条指令

其实现如下:

DEFINE VARIABLE_OFFSET            = 0x00000000DEFINE VARIABLE_SIZE              = 0x6000$(VARIABLE_OFFSET)|$(VARIABLE_SIZE)
DATA = {
#jmp to 0xfffffff9fc10000                   0xc1, 0x9f, 0x1f, 0x3c, 0x08, 0x00, 0xe0, 0x03,    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}

上面的DATA数据经过CPU翻译之后的指令就是“lui ra 9fc1” "jr ra",lui指令就是将数据加载到32位地址的高16位,这条指令执行完之后ra的值就是0x9fc10000,然后下面的jr ra就是跳转到ra 寄存器中的地址处开始执行代码

(2)flash布局

这段数据被编译后放到了flash中的开头,也就是flash的头两天指令,flash的起始地址也就是上电的32位地址0x9fc00000,我们使用的是2M大小的flash。其他部分的代码依次从这个地址之后开始存放。这里我们的布局如下:

0x9fc00000-----0x9fc10000 (64k)存放的UEFI中的variable 和 event log和Fwt等信息

0x9fc10000-----0x9fc20000(64k)存放的是sec阶段的代码,这里面有可能是.bin也有可能是sec.fv,后面后介绍两种方式的区别。

0x9fc20000-----0x9fca0000(512k)存放的是pei阶段的代码,这里就是pei.fv

0x9fca0000-----0x9fce00000(2M-512k-128k)存放的是Dxe阶段的代码,也就是Dxe.fv

(3)sec阶段的流程

上电后跳转到0x9fc10000的位置刚好是SecMain.bin或者SecMain.fv的位置。刚好跳过了flash中前面预留的64k大小的空间。到这个地址后执行的代码是由前期的汇编代码编译后的指令,汇编代码主要是对CPU的初始化。如果这个位置是存放的是SecMain.bin,那么这里就直接就是代码段,就是汇编代码的起始代码,CPU可以直接拿来执行,如果是SecMain.fv那么这个地方存放的就是fv的头,代码段在其后偏移0x1094个字节开始的地址才是真正的代码段。

到这里我们首先说明下SecMain.bin和SecMain.fv的区别:

SecMain.bin:

首先sec.bin是bin文件,bin文件就是存放的代码段,就是CPU可以执行的代码,其没有任何的头,(可以使用hexdump sec.bin > sec.log查看二进制)。那么如何让sec阶段的代码编译成.bin文件呢?这里需要修改uefi的编译规则文件build_rule.template这个文件,在里面定义sec阶段的编译规则:

[Assembly-Code-File.SEC.MIPS64EL]<InputFile.GCC, InputFile.GCCLD>?.S, ?.s<ExtraDependency>$(MAKE_FILE)<OutputFile>$(OUTPUT_DIR)(+)${s_dir}(+)${s_base}.obj<Command.GCC, Command.GCCLD, Command.RVCT>"$(ASM)" $(ASM_FLAGS) -o ${dst} $(INC) ${src};[Binary.SEC.MIPS64EL]<InputFile>*.dll<OutputFile>$(DEBUG_DIR)(+)$(MODULE_NAME).bin<Command>"$(OBJCOPY)" -O binary $(DEBUG_DIR)(+)$(MODULE_NAME).dll $(DEBUG_DIR)(+)$(MODULE_NAME).bin

按照上面的编译规则修改后,就可以在编译后的目录中生成SecMain.bin文件,将这个文件放到flash的0x9fc10000的地址就可以了,跳转过来后直接开始运行这里的代码。到这里是不是很想知道SecMain.bin是如何放到flash中的0x9fc10000的地址呢?其实这都是uefi的fdf文件实现的,其实现如下:

[FD.LS3A30007A]
BaseAddress   = $(FD_BASE_ADDRESS)
Size          = $(FD_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FD_BLOCKS)!include Loongson.fdf.inc
$(SECFV_OFFSET)|$(SECFV_SIZE)
FILE=$(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/$(ARCH)/$(PLATFORM_DIRECTORY)Sec/SecMain/DEBUG/SecMain.bin

这里面SECFV_OFFSET=0x10000,SECFV_SIZE=0x10000,这个SECFV_OFFSET就是基于地址0x9fc00000这个地址开始偏移的。这样就把secMain.bin放到了0x9fc10000这个地址。

这里需要注意在sec阶段的前期是汇编代码实现的,在汇编代码中是不涉及到flash中的物理地址的,每条指令的地址都是与位置无关的。然后到后期cache锁定之后,使用cache as ram使用,这时开始建立C环境,然后跳转到C代码中去执行。这里面实现跳转的代码和对应的C代码如下:

汇编代码:

PRINTSTR("Copy Sec&PEI code to cache.\r\n")
__dli     a0, SEC_FLASH_CODE_START
__dli     a1, SEC_CACHE_CODE_START
__dli     a2, SEC_AND_PEI_CODE_SIZE1:
__ld      a3, 0(a0)
__sd      a3, 0(a1)
__ld      a3, 8(a0)
__sd      a3, 8(a1)
__ld      a3, 16(a0)
__sd      a3, 16(a1)
__ld      a3, 24(a0)
__sd      a3, 24(a1)
__ld      a3, 32(a0)
__sd      a3, 32(a1)
__ld      a3, 40(a0)
__sd      a3, 40(a1)
__ld      a3, 48(a0)
__sd      a3, 48(a1)
__ld      a3, 56(a0)
__sd      a3, 56(a1)
__dsubu   a2, a2, 64
__daddu   a0, 64
__daddu   a1, 64
__bnez    a2, 1b
__nop
__synccall_centry:
__dli___a0, 0x9800000110000000 # rambase
__lui___a1, 0x1                # size
__daddu_sp, a0, a1             # stackbase
__move__a1, sp                 # stackbase
__dsubu_sp, sp, 8
__dli___a0, 0x9800000110110000 # PeiFvBase
__dla___ra, SecCoreStartupWithStack
__jr____ra
__nop

C代码:

VOID
EFIAPI
SecCoreStartupWithStack(IN EFI_FIRMWARE_VOLUME_HEADER       *BootFv,IN VOID                             *TopOfCurrentStack)
{EFI_SEC_PEI_HAND_OFF             SecCoreData;EFI_FIRMWARE_VOLUME_HEADER       *BootPeiFv = ((EFI_FIRMWARE_VOLUME_HEADER *)BootFv);                                                                                                  DbgPrint(EFI_D_INFO, "Entering C environment\n");ProcessLibraryConstructorList(NULL, NULL);

上面的代码就是从flash中将SecMain.bin和PeiFv拷贝到cache中,然后跳转到cache中去运行。那么这里面有一点需要注意,cache当做ram来使用了那么锁定的2M的cache的地址是什么呢?我们的代码拷贝到cache中了,那么在cache里面执行的指令的地址就变成了cache的地址,每条指令编译的地址和拷贝后存放的地址是怎么对应的呢?

首先我们锁定的cache的地址是从 0x9800000110000000 - 0x9800000110200000这2M的地址作为ram来使用,后面内存初始化好之后解锁cache之后,这段地址的数据就会刷新到对应的内存(高端内存)上。建立堆栈的位置是0x9800000110000000至

0x9800000110010000的64k的大小。然而从flash中拷贝过来的代码是放在了0x9800000110100000即2M中的后1M开始存放,sec代码占0x10000(64K)pei占(0x80000)512K的大小,所以从0x9800000110100000到0x9800000110190000的位置都是存放的代码。现在假设函数SecCoreStartupWithStack是距离Sec阶段第一条汇编指令0x100的位置才是这个函数的实际地址,那么现在在cache中这个函数的虚拟地址就是0x9800000110100100,那么上面汇编代码中

dla___ra, SecCoreStartupWithStack

jr ra

为什么就能直接跳转到0x9800000110100100这个地址呢?那是因为我们在SecMain.inf中定义好了代码编译的起始地址,这个起始就是就是代码拷贝过来的起始地址0x9800000110100000。其实现如下:

[Defines]              INF_VERSION                    = 0x00010005BASE_NAME                      = SecMain                                                  FILE_GUID                      = 1f488fc5-adba-4e8d-8916-dddc2772b410MODULE_TYPE                    = SECVERSION_STRING                 = 1.0SECMAIN_CODE_BASE              =  0x9800000110100000[BuildOptions.MIPS64EL]*_GCC44_MIPS64EL_DLINK_FLAGS == --gc-sections -u $(IMAGE_ENTRY_POINT) -e $(IMAGE_ENTRY_POINT) -Map $(DEST_DIR_DEBUG)/$(BASE_NAME).map -melf64ltsmip --defsym=PECOFF_HEADER_SIZE=$(SECMAIN_CODE_BASE)

因此这里就完全吻合了。这样跳转过去后每一条指令的地址和编译链接的地址都是一样的。这样才能够稳定的运行。

SecMain.fv:

Fv就是firmvarm volume(简称固件卷)在编译生成的fd中,包含有PeiFv/DxeFv每个Fv中含有自己的Ffs,每个Ffs就是将每一个.efi封装一下,封装成ffs,他们有自己的格式。这里不做详细介绍。具体的可以看uefi build部分的spec里面有详细的介绍。

现在将Sec阶段编译成Fv的方式放到了flash中,那么就涉及到的问题就不只是代码的问题。在其他模块load一个.efi的时候,都是要先找到Fv然后去找Ffs然后去load每一个.efi,在load过程中还要做基于load基地址的relocation。但是在Sec阶段是没有这样的代码的,是找不大Fv地址,也找不到其中的SecMain.efi,还不能做relocation。因此要使用Fv就要解决这些问题。

下面看看如何编译出SecMain.fv,还是修改编译规则的文件:

[Assembly-Code-File.SEC.MIPS64EL]<InputFile.GCC, InputFile.GCCLD>?.S, ?.s<ExtraDependency>$(MAKE_FILE)<OutputFile>$(OUTPUT_DIR)(+)${s_dir}(+)${s_base}.obj<Command.GCC, Command.GCCLD, Command.RVCT>"$(ASM)" $(ASM_FLAGS) -o ${dst} $(INC) ${src};

然后修改fdf文件,如下所示:

[FD.LS3A30007A]
BaseAddress   = $(FD_BASE_ADDRESS)
Size          = $(FD_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FD_BLOCKS)!include Loongson.fdf.inc$(SECFV_OFFSET)|$(SECFV_SIZE)
FV = SECFV
[FV.SECFV]
FvNameGuid         = 763BED0D-DE9F-48F5-81F1-3E90E1B1A015
FvBaseAddress      = $(FLASH_CODE_SECFV_BASE_ADDRESS)
BlockSize          = 0x1000
FvAlignment        = 16
ERASE_POLARITY     = 1
MEMORY_MAPPED      = TRUE
STICKY_WRITE       = TRUE
LOCK_CAP           = TRUE
LOCK_STATUS        = TRUE
WRITE_DISABLED_CAP = TRUE
WRITE_ENABLED_CAP  = TRUE
WRITE_STATUS       = TRUE
WRITE_LOCK_CAP     = TRUE
WRITE_LOCK_STATUS  = TRUE
READ_DISABLED_CAP  = TRUE
READ_ENABLED_CAP   = TRUE
READ_STATUS        = TRUE
READ_LOCK_CAP      = TRUE
READ_LOCK_STATUS   = TRUEINF  LoongsonPlatFormPkg/Sec/SecMain.inf

这样flash中0x9fc10000这个位置就放的是SecFv,SecMain.bin就不需要了。

第一个问题:如何找到SecFv的entry point的,是如何跳转到代码段执行的?

这样我们上电后跳转到0x9fc10000这个位置后,就找到了Fv的头,我们在编译的时候,将头这部分信息做了处理,找到了Fv的entrypoint的之后,将相对这个地址的偏移找到到,然后将Fv头的前16个字节封装了一个跳转指令跳转到entrypoint地点去执行。这些工作都是在BaseTools下面的GenFv下面生成Fv的编译工具的源代码里面做的,也就是在编译的时候封装Fv的时候就做好了。这样才能找到SecMain.efi的entrypoint 去执行代码。

第二个问题:如何解决不需要reloaction就能运行的问题?
这个地方和上面的额SecMain.bin的实现是一样的,就是在编译的时候就已经指定好了链接的起始地址,也就是代码段的第一条指令的地址,下面的代码就是编译的实现:

[Defines]INF_VERSION                    = 0x00010005BASE_NAME                      = SecMainFILE_GUID                      = 1f488fc5-adba-4e8d-8916-dddc2772b410MODULE_TYPE                    = SECVERSION_STRING                 = 1.0SECMAIN_CODE_BASE              =  0x9800000110100000[BuildOptions.MIPS]*_GCC44_MIPS_DLINK_FLAGS == --gc-sections -u $(IMAGE_ENTRY_POINT) -e $(IMAGE_ENTRY_POINT) -Map $(DEST_DIR_DEBUG)/$(BASE_NAME).map -melf64ltsmip --defsym=PECOFF_HEADER_SIZE=$(SECMAIN_CODE_BASE)

这样编译后的第一条指令的地址就是0x9800000110100000,所以我们使用Fv的时候,锁定cache之后,会将Sec阶段的代码段和PEI整个的Fv都拷贝到cache中,然后跳转到cache中去执行。这里面需要注意,拷贝Sec阶段的时候,不能将整个Sec的Fv都拷贝出去,因为我们放的位置是从cache地址0x9800000110100000这个地址开始放代码,为什么这个位置一定要放代码段而不能放Fv的头呢,因为我们在Sec阶段的编译过程中指定了代码编译的其实地点就是这个地址。如果这里面放了Fv的头,那么实际的代码地址就比编译的代码的地址向上偏移了Fv头的大小的地址,当程序跳转到绝对的虚拟地址的手就会出现问题。

这段代码是写在SecMain.inf文件中的。这样编译出来的SecMain.dll文件反汇编后第一条指令的地址就是0x9800000110100000。

注意我们使用Fv后,拷贝Sec阶段的代码和是从偏移过Fv的头的位置开始放的,这个地址可能不是一个8字节对齐的地址。就不能使用64位操作的指令,所以这里拷贝的代码和上面使用SecMain.bin的时候有一些不一样。下面是代码:

#define SEC_FLASH_FV_BASE      0xffffffff9fc10000
#define PEI_FLASH_FV_BASE      0xffffffff9fc20000#define SEC_CACHE_CODE_START   0x9800000110100000
#define PEI_CACHE_FV_BASE      0x9800000110110000
#define SEC_CODE_SIZE          0x10000
#define PEI_FV_SIZE            0x80000PRINTSTR("Copy Sec code to SCache-As-Ram.\r\n")__dli     a0, SEC_FLASH_CODE_START
__dli     a1, SEC_CACHE_CODE_START
__dli     a2, SEC_CODE_SIZE
1:
__lw      a3, 0(a0)
__sw      a3, 0(a1)
__lw      a3, 0x4(a0)
__sw      a3, 0x4(a1)
__lw      a3, 0x8(a0)
__sw      a3, 0x8(a1)
__lw      a3, 0xc(a0)
__sw      a3, 0xc(a1)
__lw      a3, 0x10(a0)
__sw      a3, 0x10(a1)
__lw      a3, 0x14(a0)
__sw      a3, 0x14(a1)
__lw      a3, 0x18(a0)
__sw      a3, 0x18(a1)
__lw      a3, 0x1c(a0)
__sw      a3, 0x1c(a1)
__lw      a3, 0x20(a0)
__sw      a3, 0x20(a1)
__lw      a3, 0x24(a0)
__sw      a3, 0x24(a1)
__lw      a3, 0x28(a0)
__sw      a3, 0x28(a1)
__lw      a3, 0x2c(a0)
__sw      a3, 0x2c(a1)
__lw      a3, 0x30(a0)
__sw      a3, 0x30(a1)
__lw      a3, 0x34(a0)
__sw      a3, 0x34(a1)
__lw      a3, 0x38(a0)
__sw      a3, 0x38(a1)
__lw      a3, 0x3c(a0)
__sw      a3, 0x3c(a1)
__dsubu   a2, a2, 0x40
__daddu   a0, 0x40
__daddu   a1, 0x40
__bnez    a2, 1b
__nop
__syncPRINTSTR("Copy PEI code to SCache-As-Ram.\r\n")__dli     a0, PEI_FLASH_FV_BASE
__dli     a1, PEI_CACHE_FV_BASE
__dli     a2, PEI_FV_SIZE
1:
__ld      a3, 0(a0)
__sd      a3, 0(a1)
__ld      a3, 8(a0)
__sd      a3, 8(a1)
__ld      a3, 16(a0)
__sd      a3, 16(a1)
__ld      a3, 24(a0)
__sd      a3, 24(a1)
__ld      a3, 32(a0)
__sd      a3, 32(a1)
__ld      a3, 40(a0)
__sd      a3, 40(a1)
__ld      a3, 48(a0)
__sd      a3, 48(a1)
__ld      a3, 56(a0)
__sd      a3, 56(a1)
__dsubu   a2, a2, 0x40
__daddu   a0, 0x40
__daddu   a1, 0x40
__bnez    a2, 1b
__nop
__sync

这里注意有一个坑,SEC_FLASH_FV_BASE      0xffffffff9fc10000这个地址要使用高32位为ffffffff的地址,使用90或者98开始的地址会出问题,因为在这个时候地址窗口还没有配置,使用64地址窗口来访问flash会死机。这里面拷贝Sec阶段的代码使用的是lw sw指令,这个指令操作的都是32bit的地址。4字节拷贝的,然而拷贝pei的代码,采用的是ld sd操作的都是64bit的地址。这里为什么Sec要使用32位的呢?那就是Sec的代码段起始地址是基于Fv头的偏移0x1094个字节的地址,这样地址并不是8字节对齐,因此只能用32bit的地址来访问。

按照上面的配置之后,整个Sec阶段就都可以正常的运行了。

UEFI sec阶段的实现相关推荐

  1. UEFI启动阶段学习SEC阶段和PEI阶段_2020-05-12

       昨天学习了UEFI和BIOS的区别,以及UEFI系统的优点.今天学习UEFI系统驱动的七个不同阶段. UEFI系统的启动过程 UEFI系统从加电到关机可分为以下七个阶段: SEC(安全验证)-& ...

  2. Cstyle的UEFI导读之SEC第一篇 Reset Vector

    最近小看了一下SEC部分的code,现在来做个总结.所谓SEC就是CPU刚刚完成硬件初始化的是时候执行的和CPU体系架构息息相关的代码.主要是为后续CPU以及Chipset初始化代码所需的必备的环境做 ...

  3. UEFI Boot Flow 系列之 SEC Phase

    为什么要有SEC Phase? 1. 需要用汇编语言来完成C无法处理的工作,如C语言无法处理CPU的特殊寄存器(MSR,MTRR,CRX). 2. C语言需要Memory当成Stack来处理Local ...

  4. UEFI BIOS —— SEC阶段分析

    SEC(Security Phase)- 安全阶段 一.SEC阶段主要功能 SEC阶段是平台初始话的第一个阶段,计算机系统加电后首先进入这个阶段. SEC阶段的功能:UEFI系统开机或重启后首先进入S ...

  5. UEFI源码学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)

    文章目录 1. AARCH64编译环境搭建 2. ArmPlatformPriPeiCore 2.1 QEMU_EFI.fd包含了什么 2.2 QEMU virt aarch64相关 2.3 从第一条 ...

  6. EDK环境搭建UEFI工程模块文件介绍

    一.UEFI开发环境配置 UEFI开发环境目前支持Windows,Linux,支持的平台也有很多如Intel, AMD,ARM等. 下面主要是介绍如何在windows环境下进行EDK开发. 1.获取E ...

  7. 使用Rust开发操作系统(UEFI基本介绍)

    UEFI基本介绍 关于UEFI BIOS UEFI介绍 引导管理 UEFI Image UEFI 应用程序 OS Loader UEFI运行时服务 调用约定 调用约定的数据类型 IA-32架构调用约定 ...

  8. (转)UEFI系统的启动过程

    UEFI系统的启动过程(1) UEFI系统的启动遵循UEFI平台初始化(PlatformInitialization)标准.UEFI系统从加电到关机可分为7个阶段: SEC(安全验证)→PEI(EFI ...

  9. 高通UEFI研究[三]

    QTI针对UEFI规范使用TianocoreEDK2实现. 它是一种开放源代码实施,可从www.tianocore.org/edk2/获得. TianoCore EDK II提供了现代,适用于UEFI ...

最新文章

  1. 使用Stanford CoreNLP进行句法分析实战
  2. 在论坛中出现的各种疑难问题:性能优化
  3. [Java]图片压缩
  4. Linux卸载minikube命令整理
  5. java document select_javasript 操作option select
  6. mac安全与隐私只有两个选项,少了一个任何来源
  7. 键盘keydown值表
  8. 自定义DataAnnotations
  9. HBase 基本入门篇
  10. python输出一首诗_Python:如何打印我的简单诗
  11. Windows安装nginx服务
  12. Get请求参数中文乱码问题整理
  13. 硬件设计论坛_疫情让硬件教育迎百年巨变 EDA365 电子论坛成为主角
  14. 使用 RuPengGame游戏引擎包 建立游戏窗体 如鹏游戏引擎包下载地址 Thread Runnable 卖票实例...
  15. base64编码和解码算法
  16. linux驱动更新软件下载,NVIDIA英伟达显卡驱动程序更新下载(32/64位) v384.90 Linux版...
  17. 在网易有数上做数据加工和数据分析的实践
  18. 电脑中病毒了怎么修复?电脑中病毒了怎么办?
  19. matplotlib 点线动画
  20. 计算机网络_实验16_网络故障导致环路

热门文章

  1. 2020寒假训练第一周 思维+模拟
  2. 简易微信小程序签到功能
  3. ERROR_SXS_CANT_GEN_ACTCTX
  4. 如何衡量软件质量好坏?
  5. 最美不过年华,绚烂不过烟花
  6. qrcode 自定义二维码组件
  7. 二进制转十进制速记方法
  8. [ZJOI] 物流运输
  9. 用户和用户组管理-用户和用户配置文件-影子文件
  10. 开源版小程序开发一键生成平台源码 完整前后端+搭建教程