EDKII Build System篇之—工程配置文件(元数据)

EDKII Build System

文章目录

  • EDKII Build System
  • 前言
  • 一、EDKII Build 框架简介
  • 二、元数据(meta-data)文件
    • 前言
    • 1.元数据(meta-data)文件类型
      • 1.1 平台描述文件(DSC)
      • 1.2 包声明文件(DEC)
      • 1.3 模块声明文件(INF)
      • 1.4 Flash声明文件(FDF)
    • 2.EDKII 工程配置文件语法
      • 2.1 DSC文件定义
      • 2.2 DEC文件定义
      • 2.3 INF文件定义
        • 2.3.1 EDKII中模块类型
        • 2.3.2 模块INF文件语法
      • 2.4 FDF文件定义
        • 2.4.1 二进制文件FD
        • 2.4.2 FDF文件语法
        • 2.4.3 FDF文件各个字块的定义
          • 2.4.3.1 [Defines]
          • 2.4.3.2 [FD]
          • 2.4.3.3 [FV]
          • 2.4.3.4 [Rule]
  • 总结


前言

EDKII的Build系统基于Python和C代码,支持跨平台编译。其底层逻辑是:将DEC、DSC、INF、FDF等用户元数据文件通过Build工具转换成Makefile和AutoGen.c、Autogen.h等中间文件。然后基于Makefile进行构建,生成最终FD镜像。本文主要介绍DEC、DSC、INF、FDF等用户元数据文件的写法,以及Build系统的主要执行流程。实现细节请阅读《edk2-BuildSpecification-release》。


一、EDKII Build 框架简介

下图给出了EDKII build系统的工作流程。描述了在EDKII中工程的配置文件(元数据)在构建过程中的作用和Build工程的处理流程。

EDKII Build System篇之—工程配置文件(元数据)_第1张图片

总的来说,工作流程分为3个阶段。

  1. 元数据(meta-data)解析阶段

    Build工具将分析元数据文件(DSC, DEC和FDF、INF等),生成一个顶层的Makefile; 并且针对各个模块也生成单独的Makefile文件Makefile和AutoGen.c/Autogen.h。
    另外,在此过程中,Build工具还会将Moudule所需要的Guids、Protocols、Ppis、PCDs等数据经过处理输出到Autogen文件中。该文件最后会被编译进指定的二进制文件,如*.efi、.acpi、.aml等。

  2. 编译阶段

    该阶段主要工作是编译源码。基于上个阶段生成的Makefile文件和AutoGen.c/Autogen.h,从顶层Makefile开始执行构建规则,调用编译工具将源码编译成符合UEFI 规范的PE32/PE32+/COFF 几种镜像文件。即各种*.efi、.acpi、.aml等。

  3. 固件生成阶段

    该阶段主要是为了生成最终的FD 镜像。根据FDF 文件和源文件定义的相关规则,通过GenFw工具、GenFV工具,将上个阶段生成的EFI 格式的文件(.efi .acpi .aml .bin缀名文件), 处理成二进制镜像文件(.fd 文件)。

上面提到的大部分Build工具都位于BaseTools目录下,它是我们在搭建环境时生成的。当然,也可以直接使用编译好的工具。

二、元数据(meta-data)文件

上一节介绍了基本的Build流程,让我们大致知道,镜像文件FD是怎么编译来的。下面是本篇的重点,将对各个元数据文件进行介绍。

前言

在介绍元数据文件之间,先对UEFI规范中两个重要的概念——模块(module)和包(package)进行下简单介绍。以便理解后文。

模块(module),是UEFI中最小的可单独编译的代码单元,或是预编译的二进制文件,比如*.efi可执行文件。一个模块,包括INF文件、源代码和二进制文件,其中INF文件用来描述模块的编译行为,比如产生库(Library)、产生协议(Protocol)、该模块所依赖的库、该模块的唯一标识符(GUID)等。

包(package),由平台描述文件(DSC文件)和包声明文件(DEC)组成。包主要是为了体现工程的独立性,比如在edk2源码中一个公司的代码可以作为一个独立的包存在。它可以不包含模块,也可以包含多个模块。可以看到edk2目录下有很多现成的包,比如MdeModulePkg、ArmPlatformPkg等。

1.元数据(meta-data)文件类型

对于大型工程而言,开发者都需要基于系统的构建系统创建自己的工程文件。针对不同的平台架构、芯片、目标设备,进行不同的配置,以指导构建系统选择编译适配的源码和模块。对于工程的配置,通常以配置文件的形式存在,对于构建系统来说就是元数据。
EDKII 采用模块化、层次化的概念对源代码和各种文件按照依赖关系和规则进行封装。配置文件声明了:有效包、模块之间的依赖关系;中间文件的生成规则和变量定义。

EDKII 内的包和模块描述文件称为元数据文件,主要有四种类型:

  • 平台描述文件(DSC)
  • 包声明文件(DEC)
  • 模块信息文件(INF)
  • 闪存声明文件(FDF)

下面逐个介绍这四种文件的作用和语法。

1.1 平台描述文件(DSC)

该文件主要描述,包中的哪些库(Library)、组件(Componet)和模块(Module)参与编译。每个包内会有一个DSC 文件,Build工具可以根据DSC文件的内容将用到的模块编译进FD镜像。

DSC 文件中有如下几种字块:

  • [Header] DSC 的头信息
  • [Defines]平台的配置信息
  • [SkuIds] 处理PCD 的不同的方法,以数值形式表示
  • [Libraries]库信息定义
  • [LibraryClasses]库实例定义
  • [Pcds]PCD 设置
  • [Components]声明EDK 组件和模块
  • [UserExtensions]扩展功能;用户自定义信息

1.2 包声明文件(DEC)

该文件主要声明,当前包中提供了哪些有效变量和描述信息。主要作用是声明包内定义的数据和接口,以对外公开,供其他模块使用。

对外提供的信息有:

  1. 当前包中(库、模块)对外公开的头文件,include
  2. 当前包中定义且对外公开的GUIDs
  3. 当前包中定义且对外公开的Protocols,PPIs 和PCDs。

每个包必须有一个DEC 文件。包内的所有模块可以获得DEC 内定义的内容,但是没有修改权限。DEC 文件中的字块和字块定义与DSC 文件内相同。

1.3 模块声明文件(INF)

INF 文件用来定义一个模块,类似于Android中的Android.mk或LK中的rule.mk。该文件定义:模块的名称,模块需要的源文件,模块依赖的其他包,模块依赖的其他库、Protocols、和PCDs等。查看源码可以知道,edk2中每个模块目录下都有一个*.inf文件。

1.4 Flash声明文件(FDF)

FDF的全称是Flash Description File。用于生成image,描述(定义)Image(FD)的内容和布局信息,地位相当于Bootloader中的*.ld链接脚本,但是要比ld文件复杂很多。

它由[Defines],[FD],[FV],[Rule]等几个部分组成。

2.EDKII 工程配置文件语法

这里所谓的工程配置文件,就是我们上面所说的几种元数据文件。本节主要介绍这些文件的语法,以及在具体工程中的使用。

2.1 DSC文件定义

DSC文件描述了包中的模块、库和组件整体构建规则,其中包括很多字块,用来定义编译的内容和规则。其中包括很多字块,必要的字块有[Defines]和[Components]块。可选字块有[Libraries],[LibraryClasses],[Pcds],[SkuIds] ,[UserExtensions],[BuildOptions]等。

注意事项:

  • 所有字块的名称,都置于中括号中,而且大小写敏感。

  • 同一个中括号中,可以包含多个字符串,以逗号隔开。比如,

       [LibraryClassess.X64,LibraryClassess.IPF]
    
  • 这种LibraryClassess.(ARCH)指定CPU架构的字块要比不指定ARCH的LibraryClassess通用字块优先级高。

  • 在DSC文件中,可以使用!include来包含其他文件,!include可以在DSC文件的任意字块出现,例如,

    [Components]
    !include StdLib/StdLib.inc
    !include sharkl3/Sharkl3.dsc.inc
    

使用!include需要注意
(1)!include包含的文件必须符合所在字块的要求
(2)!include包含的文件必需有完整的一个或多个字块

  1. [Defines]

    [Defines]用于设置Build相关的全局变量,这些变量可以被!include 包含的其他文件和FDF文件引用,通过$(MACRO)的方式引用。

    [Defines]必须是DSC文件的第一个部分,可以按任意顺序排布,其语法格式如下,

    [Defines]
    宏变量名 = 值
    DEFINE 宏变量名 = 值
    EDK_GLOBAL 宏变量名 = 值
    

    DSC中定义的变量有:
    EDKII Build System篇之—工程配置文件(元数据)_第2张图片
    EDKII Build System篇之—工程配置文件(元数据)_第3张图片

    EDKII Build System篇之—工程配置文件(元数据)_第4张图片

    示例:

    // /EDK2/ShellPkg/ShellPkg.dsc  示例
    [Defines]
      PLATFORM_NAME                  = Shell
      PLATFORM_GUID                  = E1DC9BF8-7013-4c99-9437-795DAA45F3BD
      PLATFORM_VERSION               = 1.0
      DSC_SPECIFICATION              = 0x00010006
      OUTPUT_DIRECTORY               = Build/Shell
      SUPPORTED_ARCHITECTURES        = IA32|IPF|X64|EBC|ARM|AARCH64
      BUILD_TARGETS                  = DEBUG|RELEASE
      SKUID_IDENTIFIER               = DEFAULT
    
  2. [LibraryClasses]

    [LibraryClasses]用来列出当前包包含哪些模块。主要是为了声明,将模块名和其INF文件一一对应。

    在[LibraryClasses]块定义了库的名字以及库INF的路径。这些库可以被[Components]块内的模块引用。

    如果DSC文件中的模块不需要使用库,可以不定义该字块。

    语法:

    [LibararyClasses.$(Arch).$(MODULE_TYPE),LibararyClasses.$(Arch1).$(MODULE_TYPE1)]
    LibraryName | path/LibraryName.inf
           
    

    其中, ( A r c h ) 和 (Arch)和 (Arch)(MODULE_TYPE)是可选项,字块内的库对指定的架构和模块都有效。其实, [LibraryClasses]有6种表示方法,依据模块优先搜索的顺序,从前到后(优先级从高到底)分别如下:
    (1) LibraryClasses, 在DSC文件的[Components]字块定义,和INF文件联系在一起。

           MdeModulePkg/Universal/PCD/Pei/Pcd.inf {
      <LibraryClasses>
               PcdLib|MdePkg/Lirbary/BasePcdLibNull/BasePcdLibNull.inf
    

    (2) [LibararyClasses. ( A r c h ) . (Arch). (Arch).(MODULE_TYPE),LibararyClasses. ( A r c h 1 ) . (Arch1). (Arch1).(MODULE_TYPE1)]
    (3) [LibararyClasses. ( A r c h ) . (Arch). (Arch).(MODULE_TYPE)]
    (4) [LibararyClasses.common. ( M O D U L E T Y P E ) ] ( 5 ) [ L i b a r a r y C l a s s e s . (MODULE_TYPE)] (5) [LibararyClasses. (MODULETYPE)](5)[LibararyClasses.(Arch)]
    (6) [LibararyClasses.common]

    • A r c h 和 Arch和 ArchMODULE_TYPE是可选项。不使用表示通用。
    • $Arch表示体系结构,可以是下列值之一:IA32,X64,IPF,EBC,ARM,common。common表示对所有体系结构有效。
    • M O D U L E T Y P E 表 示 模 块 的 类 别 , 块 内 列 出 的 库 只 能 提 供 MODULE_TYPE表示模块的类别,块内列出的库只能提供 MODULETYPE(MODULE_TYPE)类别的模块连接。它可以是下列值:SEC、PEI_CORE、PEIM、DEX_CORE、DEX_SAL_DRIVER、BASE、DXE_SMM_DRIVER、DXE_DRIVER、\ DXE_RUNTIME_DRIVER、UEFI_DRIVER、UEFI_APPLICATION、USER_DEFINED。

Note: [Components]中的模块在寻找所需要的库时,按上面6种方法以优先级递减,以此寻找。如果找到,继续编译;如果没找到,则库会报无法找到的错误。

另外:

该块中支持!include 包含其他inc文件。但是,所包含的inc文件必须有完整的[LibararyClasses]

实例:

[LibraryClasses.common]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
···
[LibraryClasses.ARM]
NULL|ArmPkg/Library/CompilerIntrinsicsLib/CompilerIntrinsicsLib.inf
···
[LibraryClasses.AARCH64]
NULL|ArmPkg/Library/CompilerIntrinsicsLib/CompilerIntrinsicsLib.inf
···

3.[Components]

配置该包中哪些模块参与编译。该区块内定义的模块都会被build工具编译并生成.efi文件。

语法:

[Components]
 path/Exectuables.inf

或者

[Components]
 path/Exectuables.inf{
   < LibraryClasses> # 做嵌套
     LibraryName | Path/LibraryName.inf
   < BuildOptions> # 嵌套块
     #字块中还可以包含< Pcds*>
}

注意,path使用相对于EDK2根目录的相对路径。另外,也可以通过DEFINE来定义定INF文件的路径,比如:

[Components]
DEFINE XX_PATH=/home/xx/edk2/...../
$(XX_PATH)/xx.inf

4.[Pcds]

PCD的全称是Platform Configuration Database,用于定义平台配置数据。它的目的是在不改动.inf文件的情况下完成对平台的配置。也就是说,我们在自己的平台上,可以对某个模块的inf定制化配置。

关于PCD我们在其他的专题再详细介绍。

在DSC文件的Pcds]块中,PCD变量的格式如下:

[Pcds]
	TokenSpaceGuidCName.PcdCName|DefaultValue|DatumType|Token
  • 其中,[Pcds]块有下述类型(主要区别体现在访问方式):

    1. FeatureFlag PCD:它最终返回的是TRUE或者FALSE,用于判断条件中。
    2. PatchableInModule PCD:这种变量的值可以在编译的时候确定,这个不算特别,特别的是它可以在编译完成的二进制文件上通过工具来修改值。
    3. FixedAtBuild PCD:静态值,在编译的时候确定,整个UEFI阶段不可变。
    4. Dynamic PCD:前面的三种类型可以认为是静态的PCD,而这里以及之后的是动态的PCD;它的特点是可以在UEFI运行的过程中通过Set宏来修改值;在《edk-ii-build-specification.pdf》中有说明该种类型的PCD必须在DSC中在列一遍,但是实际使用似乎并不是必须的。
    5. DynamicEx PCD:跟Dynamic PCD类似,算是加强版,使用宏PcdGetEx/PcdSetEx来访问变量。
  • TokenSpaceGuidCName是一个GUID,PcdCName是一个变量名,两者合起来构成了唯一的PCD变量。TokenSpaceGuidCName在DEC文件中声明并赋值。

    [Guids]
      gUefiOemPkgTokenSpaceGuid      = { 0x6bb4bec8, 0x23d8, 0x40af, { 0x8f, 0x24, 0x99, 0xe7, 0xac, 
    
  • DefaultValue是PCD的默认值,DatumType是PCD的类型,Token是一个32位的整型,在DEC中每个PCD都有一个独一无二的Token。

  • 在DSC中设置PCD的值。这个设置并不是必须的,如果没有整个设置,就使用在DEC中默认的值。

    # // OEM defined dynamic PCD.
      gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0x1234567
    
  • 该Pcd如果在模块中用到,需要在模块INF文件的[Pcds]中声明。

DSC中,各类[Pcds]的声明示例:

 
[PcdsDynamic]
  gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0xFFFFFFFF|UINT32|0x40000001
  
[PcdsFixedAtBuild]
  gUefiOemPkgTokenSpaceGuid.PcdTestVar1|0xA5|UINT8|0x20000001
  gUefiOemPkgTokenSpaceGuid.PcdTestVar2|0xA5A5|UINT16|0x20000002
 
[PcdsFeatureFlag]
  gUefiOemPkgTokenSpaceGuid.PcdTestFeatureVar|FALSE|BOOLEAN|0x30000001

5.[BuildOptions]

**[BuildOptions]**指定编译器相关的参数,它会覆盖为模块准备的默认参数。如果是为了替换编译参数,则可以使用“==”;如果为了增加编译参数,可以使用“=”。其典型语法格式如下:

[BuildOptions]
$(FAMILY):$(TARGET)_$(TAGNAME)_$(ARCH)_$(TOOLCODE)_FLAGS[=|==] 编译参数

例如,

[BuildOptions]
	*_*_*_PLATFORM_FLAGS = -march=armv8-a -DCONFIG_DEBUG=true

[BuildOptions.common.EDKII.DXE_SMM_DRIVER, BuildOptions.common.EDKII.SMM_CORE]
	GCC:*_*_*_DLINK_FLAGS = -z common-page-size=0x1000
	XCODE:*_*_*_DLINK_FLAGS = -seg1addr 0x1000 -segalign 0x1000
	XCODE:*_*_*_MTOC_FLAGS = -align 0x1000
  	CLANGPDB:*_*_*_DLINK_FLAGS = /ALIGN:4096

FAMILY,TARGET,TAGNAME,ARCH,TOOLCODE,这5个变量是由Conf/tools_def.txt中定义的。

  • FAMILY,指编译器类型,可以取值:GCC、MDFT、INTEL、RVCT。
  • TARGET,可以取两个值:DEBUG和RELEASE,通配符*。
  • TAGNAME,工具链的名字。通配符*。
  • ARCH, 处理器架构,可以取值:ARM、AARCH64,IA32,X64,IPF等。通配符*。
  • TOOLCODE,比如CC(编译选项)和DLNK(连接选项)。

2.2 DEC文件定义

DEC文件定义了公开的数据和接口,供其他模块使用。每个包只要一个DEC文件,一般和DSC文件成对出现。文件中的语法,和DSC文件中对应的基本相同。它包含了必需区块[Defines]以及可选区块[Includes]、[LibraryClasses]、[Guids]、[Protocol]、[Ppis]和[PCD]几个部分。

1.[Defines]块

[Defines]块用于提供Package的名称、GUID、版本号等信息。语法和DSC文件相同。

## /EDK2/MdePkg/MdePkg.dec
[Defines]
  DEC_SPECIFICATION              = 0x00010005  #规范的版本号
  PACKAGE_NAME                   = MdePkg      #包名称
  PACKAGE_UNI_FILE               = MdePkg.uni  #包使用的字符串资源文件
  PACKAGE_GUID                   = 1E73767F-8F52-4603-AEB4-F29B510B6766 #包GUID
  PACKAGE_VERSION                = 1.06        #包版本号

2.[Includes]块

列出本Package提供的头文件所在的目录。DSC文件中无此块。

格式:

 [Includes.$(Arch)]
  path

头文件的路劲为相对路径,其根目录为DEC文件所在目录。

指定架构的时候,可以使用“private”限定符,限定包含的头文件只能在本包中的模块使用。例如,

 [Includes.common]
 	include
 [Includes.common.Private]
 	xx/include
 [Includes.AARCH64.Private]
 	xx/arm64/include

Note:

  1. 不带private标志的项不能和带Private标志的项混合。比如,下列情况出错

    [Includes.common, Includes.AARCH64.Private]
    	include
    
  2. 同一文件目录,不能同时指定带private和不带private标志的项。例如,

     [Includes.common.Private]
     	xx/include
     [Includes.AARCH64.Private]
     	xx/include
    

3.[LibraryClasses]

对外提供的库都会提供头文件,这些头文件位于包下的Include/Library目录下。这个块用来规定库和头文件的对应关系。语法格式如下:

[LibraryClasses.$(Arch)]
	LibraryName | Path/LibraryHeader.h

例如,

## /EDK2/MdePkg/MdePkg.dec
[LibraryClasses]
  UefiUsbLib|Include/Library/UefiUsbLib.h
  UefiRuntimeServicesTableLib|Include/Library/UefiRuntimeServicesTableLib.h
···

4.[Guids]块

在Package中源文件,我们会定义一个或几个GUID

## /EDK2/MdePkg/Include/Guid/Gpt.h
extern EFI_GUID gEfiPartTypeUnusedGuid;
extern EFI_GUID gEfiPartTypeSystemPartGuid;
extern EFI_GUID gEfiPartTypeLegacyMbrGuid;

这些定义只是声明,常量真正定义在AutoGen.c中,它的值定义在DEC文件的[Guids]区块。

## /EDK2/MdePkg/MdePkg.dec
[Guids]
## Include/Guid/Gpt.h
  gEfiPartTypeLegacyMbrGuid      = { 0x024DEE41, 0x33E7, 0x11D3, { 0x9D, 0x69, 0x00, 0x08, 0xC7, 0x81, 0xF3, 0x9F }}
 
  ## Include/Guid/Gpt.h
  gEfiPartTypeSystemPartGuid     = { 0xC12A7328, 0xF81F, 0x11D2, { 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B }}
 
  ## Include/Guid/Gpt.h
  gEfiPartTypeUnusedGuid         = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}

当在模块工程文件INF的[Guids]中引用这些Guid时,这些值就会复制到AutoGen.c中。

5.[Protocols]块

与GUIDS类似,在包中我们会自定义一些协议。例如,在MdePkg/Include/Protocol 目录下的BlockIo.h定义了BlockIo Protocol。

extern EFI_GUID gEfiBlockIoProtocolGuid;

gEfiBlockIoProtocolGuid 的值就定义在MdePkg.dec 的[Protocols]块内

## Include/Protocol/BlockIo.h
gEfiBlockIoProtocolGuid        = { 0x964E5B21, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}

6.其它块

  • [Ppis]用于定义源文件中用到的PPI(PPI是PEI阶段PEI模块之间通信的接口),语法类似于Protocol。
  • [PCD]块是对DSC文件中[PCD]块的补充。

2.3 INF文件定义

INF文件是模块的工程文件,其后缀名为.inf,描述了模块的属性。其内容包括模块名称、模块类型、GUID,以及模块的build规则,包括模块由哪些源文件组成,提供了什么、依赖什么库等信息。

对ODM厂商(第三方开发者)而言,可以针对自家设备发布二进制形式的模块,不必提供源代码。

INF文件的内容如下:
EDKII Build System篇之—工程配置文件(元数据)_第5张图片

2.3.1 EDKII中模块类型

EDKII Build System篇之—工程配置文件(元数据)_第6张图片
EDKII Build System篇之—工程配置文件(元数据)_第7张图片

2.3.2 模块INF文件语法

1.[Defines]

与DSC文件的[Defines]类似,定义各种变量,供后续编译中使用。

如下是必须定义的变量,其余是可选的。

  • INF_VERSION:INF版本号,用于编译时解释INF文件,通常设置为0x00010005,INF_VERSION版本号便于EDKII系统升级。
  • BASE_NAME:用于设置编译输出的文件名称,根据文件功能设置,不能包含空格。它通常也是输出文件名字。
  • FILE_GUID:文件的全局唯一标识符,格式:8-4-4-4-12,用于生成固件。例如: 4ea97c46-7491-4dfd-b442-747010f3ce5f.可以通过 http://www.guidgen.com/获得GUID。
  • VERSION_STRING:文件版本字符串,用于标注文件的版本号
  • MODUL_TYPE:用于标识模块的类型、应用工程文件设置为:UEFI_APPLICATION。定义模块的模块类型,可以是SEC/PEI_CORE/PEIM/DXE_CORE/DXE_SAL_DRIVER/DXE_SMM_DRIVER/UEFI_DRIVER/DXE_DRIVER/DXE_RUNTIME_DRIVER/UEFI_APPLICATION/BASE中的一个。对于标准应用程序工程模块来说,为UEFI_APPLICATION类。
  • ENTRY POINT:模块的入口函数

例如: helloword 应用程序模块的INF文件Defines

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = main                    #输出文件的名字为 main.efi
  FILE_GUID                      = 6987936E-ED34-ffdb-AE97-1FA5E4ED2117
  MODULE_TYPE                    = UEFI_APPLICATION #模块类型
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain     #入口函数

2.Sources
列出文件内所有的源文件和资源文件。

[Sources]  
	a.c
[Sources.ARM]  
	b.c

另外,还可以对源文件指定编译工具链。即只要使用此工具链时,此源文件才会被编译。

[Sources.ARM]  
	Gicv3/Arm/ArmGicV3.S        |Gcc
	Gicv3/Arm/ArmGicV3.asm      |RVCT

3.Packages

列出本模块所引用的包的DEC文件。如果source块内包含了源文件,必须将MdePkg/MdePkg.dec放在首行。

[Packages]
	MdePkg/MdePkg.dec
	MdeModulePkg/MdeModulePkg.dec

4.LibraryClasses

列出本模块需要链接的库。应用工程文件必须包含UefiApplicationEntryPoint库,驱动文件必须包含UEFIDriverEntryPoint库。

其语法格式如下:

[LibraryClasses.$(Arch)]
LibraryName [|FeatureFlagExpression] 

如果FeatureFlagExpression为TRUE,必须保证库已经存在。库的编译在DSC文件指定;如果FeatureFlagExpression为FALSE,编译工具会忽略添加的该库。

#要链接的库
[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

5.Protocols

[Protocols]列出了本模块使用的协议。在INF中列出的时协议的GUID,通过edk2工具,GUID会被输出到AutoGen.c中。

语法格式如下:

[Protocols]
   gEfiPciRootBridgeIoProtocolGuid               ## CONSUMES

6.Depex

本模块要运行入口函数,需要哪些协议被安装。主要是列出协议的GUID。例如,

[Depex]
	gEfiPciRootBridgeIoProtocolGuid
[Depex.common.DXE_DRIVER, Depex.common.DXE_RUNTIME_DRIVER, Depex.common.DXE_SAL_DRIVER, Depex.common.DXE_SMM_DRIVER]
	gEfiPciRootBridgeIoProtocolGuid

需要注意的是,多个GUID需要使用AND连接。

7.BuildOptions和Pcd。

与DSC文件完全一样。

2.4 FDF文件定义

关于FDF文件,更细节的地方请查看手册《edk2-FdfSpecification-release-1.28.01.pdf》

FDF的全称是Flash Description File。用于描述(定义)FD镜像文件的内容和布局信息。

2.4.1 二进制文件FD

前面介绍过,在UEFI Build流程的最后一个阶段,是生成一个可以固化在Flash的镜像文件FD。FD文件是Build工具根据FDF文件的描述生成的。下面详细介绍一下FD文件中的内容都是怎么存放的。

固件文件FD 的内部组织结构如图所示。

EDKII Build System篇之—工程配置文件(元数据)_第8张图片

为方便理解,先梳理下FD、FV、FFS三者之间的关系:

1.一个FD(Flash Device binary image)文件,就是最终的二进制镜像,由多个固件卷文件(FV)和代码数据段,日志段等内容组成。

2.一个FV(Firmware Volumes),可以看作FD的一个段。每一个FV段都有一个特定的功能,比如FVMAIN通常包含的是DXE、BDS阶段的代码,它通常是被压缩过的;FV Recovery包含SEC和PEI阶段的代码,未经压缩,以XIP方式执行。

3.每一个FV 包含多个FFS(Firmware File System)文件。FFS是FV内部的一个结构,用来表示各个Firmware的组成。一个FFS文件 由一个或者多个.efi 文件封装组成。

下面是FD创建的一个示意图:

EDKII Build System篇之—工程配置文件(元数据)_第9张图片

每一个模块的源码经过编译生成一个.efi文件。Build工具按照配置文件指定的规则将一个或多个.efi文件封装成FFS文件,FFS文件又被封装到FV文件中,多个FV 最后组成了FD文件。

FD文件烧写到ROM后,首先知道secFv的位置,然后通过SecFv找到代码段的起始入口开始执行。执行到SEC后期,会将PeiFv的位置传递过去,从PeiFv的位置找到PeiCore的File,也就是PeiCoreImage,然后再从Image中找到PE32 的Section,并找到Entry point 开始执行PeiCore的代码。剩下的段就是经过压缩的了。

2.4.2 FDF文件语法

  1. 注释使用#;

  2. 赋值使用SET,而使用=表示的是TOKEN,是一种特殊的类似与常量的值;

  3. 可以使用整型,布尔值,EFI_GUID等值

  4. $()获取DEFINE语句定义的宏的值;

  5. SET语句:对PCD进行赋值,如下所示

    SET gPlatformModuleTokenSpaceGuid.PcdFlashAreaBaseAddress = $(FLASH_AREA_BASE_ADDRESS)
    SET gPlatformModuleTokenSpaceGuid.PcdFlashAreaSize      = $(FLASH_AREA_SIZE)
    
  6. DEFINE语句:定义一个宏,如下所示
    定义的宏可以通过$()来访问。注意DSC文件中也会使用DEFINE语句,且定义的宏可以在FDF文件中访问。

    DEFINE DEBUG_ENABLE_OUTPUT      = FALSE       # Set to TRUE to enable debug output
    DEFINE DEBUG_PRINT_ERROR_LEVEL  = 0x80000040  # Flags to control amount of debug output
    DEFINE DEBUG_PROPERTY_MASK      = 0
    
  1. !if语句。判断语句,为TRUE时才包含其内部的组件,其语法如下:

      !if $(MACRO)
      或者
      !if $(MACRO) == "Literal String"
      或者
      !if $(MACROALPHA) == $(MACROBETA)
      或者
      !if $(MACRONUM) == 数字
      或者
      !if $(MACROBOOL) == 布尔值
    

    注意需要与!endif语句一起使用,中间也可以有!else语句。下面是例子:

    !if $(SECURE_BOOT_ENABLE)
    # Signature: gEfiAuthenticatedVariableGuid = { 0xaaf32c78, 0x947b, 0x439a, { 0xa1, 0x80, 0x2e, 0x14, 0x4e, 0xc3, 0x77, 0x92 } }
    0x78, 0x2c, 0xf3, 0xaa, 0x7b, 0x94, 0x9a, 0x43,
    0xa1, 0x80, 0x2e, 0x14, 0x4e, 0xc3, 0x77, 0x92,
    !else
    #  Signature: gEfiVariableGuid = { 0xddcf3616, 0x3275, 0x4164, { 0x98, 0xb6, 0xfe, 0x85, 0x70, 0x7f, 0xfe, 0x7d }}
    0x16, 0x36, 0xcf, 0xdd, 0x75, 0x32, 0x64, 0x41,
    0x98, 0xb6, 0xfe, 0x85, 0x70, 0x7f, 0xfe, 0x7d,
    !endif 
    

    其它类似的还有!ifdef语句和!ifndef语句。
    以上是比较通用的语句,还有一些需要在特定的Section中使用的语句将在之后介绍。

  2. 常用的Section。Section的大致格式如下:

     [oo.xx.zz]
    

    上述的代码描述中,oo是必选的,而xx、zz等需要根据oo的值来确定是否存在以及具体是什么。

    Section是FDF文件中最重要的组件,因为它们将FDF文件分成了若干个部分,各个部分由不同的内容构成,最终组成整个FD。

    下面就介绍这些常用的Section关键字。

2.4.3 FDF文件各个字块的定义

2.4.3.1 [Defines]

可选的块。用来跟踪FDF文件的版本、DEFINE全局宏以及设定PCD的值,其语法格式如下:

[Defines]
	Name=value
	DEFINE MACRO=Value
2.4.3.2 [FD]

关于FD这种Section关键字在之前也已经提到,它表示了一个完整的image布局。一个FDF文件里面可以有多个FD。

[FD]关键字中的FD之后还可以接一个后缀,格式如下:

[FD.FdUiName]

这里的FdUiName表示FD的名称,可以是随意的值。

当FDF文件中只存在一个[FD] Section的时候,这个FdUiName是可选的,如果不选,则使用定义在[Defines]中的PLATFORM_NAME作为名称。

例如,S9863a1h10.fdf中:

[FD.EDK2]
BaseAddress   = 0x9F000000|gArmTokenSpaceGuid.PcdFdBaseAddress  # The base address of the Firmware in NOR Flash.
Size          = 0x000F0000|gArmTokenSpaceGuid.PcdFdSize         # The size in bytes of the FLASH Device
ErasePolarity = 1

只有一个FD段,[FD.EDK2],因此只编译一个image—EDK2.bin。

[FD]中的声明包括如下内容

1) 令牌申明(TOKEN Statements)。 其语法格式如下:

Token = Value [| PcdName]

其中,Token可以为如下变量:

  • BaseAddress:表示FD的基址,它image被加载到系统中的位置
  • Size:表示FD的大小,单位是字节
  • ErasePolarity:表示的是用1还是0擦Flash,目前基本上都是1
  • BlockSize:表示Flash中一个Block的大小,一般就是4K,64K等
  • NumBlocks:表示Flash中Block的个数,通常就是Size除以BlockSize

2) 定义声明(DEFINE Statements)。 前面已经介绍,其语法格式如下:

DEFINE MACRO=PATH

这里用来定义宏的声明,所定义的宏在整个FDF文件都有效。使用的时候可以通过$(MACRO)来引用。

前面我们在介绍DSC文件的时候提到过,在DSC文件的[Defines]中定义的变量和宏,可以在FDF文件中引用。

示例如下:

DEFINE FV_DIR = $(OUT_DIR)/$(TARGET)_$(TOOL_CHAIN_TAG)/$(ARCH)
DEFINE MDE_MOD_TSPG = gEfiMdeModulePkgTokenSpaceGuid
DEFINE FV_HDR_SIZE = 0x66

3)设置声明(SET Statements)。前面已经介绍过,其语法格式如下:

SET PcdName = Value

设置声明一般用来设置PCD变量的值,如

SET gPlatformModuleTokenSpaceGuid.PcdFlashAreaBaseAddress = $(FLASH_AREA_BASE_ADDRESS)
SET gPlatformModuleTokenSpaceGuid.PcdFlashAreaSize      = $(FLASH_AREA_SIZE)

4)包含INF文件。[FD]中还可以直接包含INF文件,格式如下:“INF [Options] PathAndInfFileName”。目前没有发现哪个Pkg这么使用。

5)区域布局(Region Layout)。 用来指明各种区域类型的数据在FD中的布局,其语法格式如下。

Offset|Size
[TokenSpaceGuidCName.PcdOffsetCName | TokenSpaceGuidCName.PcdSizeCName] ?
[RegionType] ?

上述代码的含义是:在FD中开辟一块空间,用来放置[RegionType] 所指明的内容。

(1) Offset和Size, 是这段空间的对于整个FD的偏移和大小。

(2) Pcd, 是可选的,其实就是初始化了PCD来表示这段空间偏移和大小供后续使用,并不是必须的,它更像是C语言中的宏,你只需要设置一次,就可以在FDF文件的各处使用。

(3) RegionType, 表示这段空间的类型,可以是FV(Firmware Volume)、DATA(裸数据)、FILE(文件)、INF(INF文件)和CAPSULE(更新固件的image)等内容。RegionType不是必须的,如果没用定义则表示这段空间不能去动它,它是有其它用处的,会有其它的机制(比如非易失日志)用到。

I. 开辟FV (Firmware Volume)类型的空间

例子中使用了FV,采用“FV = UiFvName”的形式。这里的UiFvName是在[FV]这个Section中定义的,用于控制将哪个[FV]段编译进[FD]定义的image。

[FD.EDK2]
0x00000000|0x000F0000
gArmTokenSpaceGuid.PcdFvBaseAddress|gArmTokenSpaceGuid.PcdFvSize
FV = FVMAIN_COMPACT

$(FVMAIN_SIZE)|$(SECFV_SIZE)
FV = SECFV

表示从0地址开始开辟0x000F0000个字节的空间,使用gArmTokenSpaceGuid.PcdFvBaseAddress和gArmTokenSpaceGuid.PcdFvSize标识这块空间的而地址和size,供其他模块使用。定义这块空间用来放FV数据。

II. 开辟DATA(裸数据)类型的空间

使用格式如“DATA = {xx}”,xx可以是16进制的字节数组,下面是一个例子:

0x0058e000|0x00002000
gEmulatorPkgTokenSpaceGuid.PcdEmuFlashNvStorageFtwWorkingBase|gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingSize
#NV_FTW_WORKING
DATA = {
  # EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER->Signature = gEdkiiWorkingBlockSignatureGuid         =
  #  { 0x9e58292b, 0x7c68, 0x497d, { 0xa0, 0xce, 0x65,  0x0, 0xfd, 0x9f, 0x1b, 0x95 }}
  0x2b, 0x29, 0x58, 0x9e, 0x68, 0x7c, 0x7d, 0x49,
  0xa0, 0xce, 0x65,  0x0, 0xfd, 0x9f, 0x1b, 0x95,
  # WriteQueueSize: UINT64
  0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}

xx还可以是一个!include语句,用来包含外部的数据,下面是一个例子:

0x0CA000 | 0x002000
gEfiMyTokenSpaceGuid.PcdFlashNvStorageBase | gEfiMyTokenSpaceGuid.PcdFlashNvStorageSiz
e
DATA = {
  !include NvStoreInit.txt
}

III. 开辟FILE(文件)类型的空间

FILE指向一个二进制文件,生成FD的之后这个二进制文件就会被包含进来,使用格式如“FILE = $(FILE_DIR)/Filename.bin”

例如树莓派的FDF文件中,将BL1、BL2 、BL31 3个启动阶段的bootloader image连同UEFI本image都打包到一个FD文件。

[Defines]
	...
DEFINE TFA_BUILD_BL1 = Platform/RaspberryPi/$(PLATFORM_NAME)/TrustedFirmware/bl1.bin
DEFINE TFA_BUILD_FIP = Platform/RaspberryPi/$(PLATFORM_NAME)/TrustedFirmware/fip.bin

[FD.RPI_EFI]
# ATF primary boot image #
0x00000000|0x00010000
FILE = $(TFA_BUILD_BL1)

# ATF secondary boot image in FIP format (BL2 + BL31)
0x00020000|0x00010000
FILE = $(TFA_BUILD_FIP)

# UEFI image
0x00030000|0x001a0000
gArmTokenSpaceGuid.PcdFvBaseAddress|gArmTokenSpaceGuid.PcdFvSize
FV = FVMAIN_COMPACT

IV. 使用CAPSULE(更新固件的image)类型空间

使用格式如“CAPSULE = UiCapsuleName”。其中的UiCapsuleName是[Capsule] Section的名称。目前在代码中没有看到具体的使用。

2.4.3.3 [FV]

FV是FD固件的逻辑区块,相当于FD的一个分区。FV的主要作用就是列出要将哪些文件或信息放在这个Fv。

它的格式如下:

 [FV.UiFvName]

UiFvName是自定义的名称,在FD中会用到,它在文件中必须是唯一的。

FV是可以嵌套的,FV中也可以通过UiFvName来包含另外一个FV。

下面介绍[FV] Section中包含的内容。

1)TOKEN

[FV]的最开始是几个TOKEN。如下是一个例子:

BlockSize          = $(FLASH_BLOCK_SIZE)
  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   = TRUE

2)DEFINE声明

可选。和[FD]中一样。

3)SET声明

可选。和[FD]中一样。

4)Block声明

Block声明用来定义FV所需要的Block size和Block数量。这类FV不放在Flash物理分区,而是放在其他地方。

例如:

BLOCK_SIZE = VALUE
NUM_BLOCKS = VALUE

5)文件声明

可选。[FV]中可以直接包含文件,使用的是FILE语句。主要目的是:直接将FFS文件包含进FV。

其格式如下:

   FILE Type $(NAMED_GUID) [Options] FileName
   或者
   FILE Type $(NAMED_GUID) [Options] {
   SECTION SECTION_TYPE = FileName
   SECTION SECTION_TYPE = FileName
}

(1)Type指的是FV 文件 Types。

常用的FV文件类型有:

  • RAW:普通的二进制文件。
  • FREEFORM:分段的二进制文件。
  • SEC: 分段的文件,包含pad section,一个terse section 和一个可选的 rawsection.
  • FV_IMAGE:这就这里介绍的[FV],指的是包含其他FV
  • DRIVER:包含一个DXE阶段的驱动
  • PEI_CORE 、DXE_CORE 、PEIM 、COMBO_PEIM_DRIVER、SMM_CORE 、DXE_SMM_DRIVER、APPLICATION 、FV_IMAGE 、DISPOSABL。具体请看官方文档《EDK II Flash Description (FDF) File Specification》

(2)NAMED_GUID 为所要包含的文件设定一个ID,我们可以在代码中通过这个GUID获取文件。

​ $(NAMED_GUID)通常是由INF文件的[define]节FILE_GUID元素决定的。

(3)Options有如下可用的值:Fixed、Alignment和Checksum等,这里不一一介绍。

例如:

FILE FREEFORM    = PCD(gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdLogoFile) {
  SECTION RAW = MdeModulePkg/Logo/Logo.bmp
}

例如:

#Encapsulation - Compress
FILE FOO = 12345678-0000-AAAA-FFFF-0123ABCD12BD {
SECTION COMPRESS {
SECTION PE32 = $(WORKSPACE)/EdkModulePkg/Core/Dxe/DxeMain.inf
SECTION VERSION = "1.2.3"
}
}
# Encapsulation - GUIDED
FILE FV_IMAGE = 87654321-FFFF-BBBB-2222-9874561230AB {
SECTION GUIDED gEfiTianoCompressionScheme {
SECTION PE32 = $(WORKSPACE)/EdkModulePkg/Core/Dxe/DxeMain.inf
}
}
# LEAF Section
FILE DXE_CORE = B5596C75-37A2-4b69-B40B-72ABD6DD8708 {
SECTION VERSION $(BUILD_DIR)/$(ARCH)/D6A2CB7F-6A18-4E2F-B43B-9920A733700A-DxeMain.ver
}

6) INF声明

INF 块主要是在FV中声明一个模块或组件,通过其INF文件。

解析工具将扫描INF文件以确定组件或模块的类型。组件或模块类型用于引用FDF文件中其他地方定义的标准规则。因此,在此声明INF是为了引用rule块中定义的东西。

格式如下:

INF [Options] PathAndInfFileName

其中PathAndInfFileName就是一个普通的inf文件,Options是可选项。常见内容如下:

  • RuleOverride = RuleName. 表示当前INF的RULE,关于RULE,之后会在[Rule] Section中说明,RuleName是该Seciton的名称。每一个[FV]中的inf都有一个默认的RULE,这里就是覆盖默认的RULE,而使用这里提供的RULE。
  • USE = ARCH. 表示该INF对应的架构,这个用的不多。
  • VERSION = “String”.该选项会在FFS中创建一个EFI_SECTION_VERSION Section。
  • UI = “String”. 与VERSION类似,会创建一个EFI_SECTION_USER_INTERFACE Section。
  • INF相关的一个元素是APRIORI语句,它指定了INF的执行顺序。

根据文档:

使用INF语句,使得构建工具基于INF 模块的MODULE_TYPE和内容,隐式的构建带有EFI_FV_FILETYPE 的FFS文件。

例如,在FV中指定以下sections将生成带有EFI_FV_FILETYPE_DRIVER的FFS文件,该文件包含三个section: EFI_SECTION_PE32和一个EFI_SECTION_VERSION和一个EFI_SECTION_DXE_DEPEX。虽然在INF中没有定义version文件,但它由VERSION选项指定;并且在INF文件的源文件中指定了一个依赖文件列表。

DEFINE MMP_U_MT = MdeModulePkg/Universal/MemoryTest
INF VERSION = "1.1" $(MMP_U_MT)/NullMemoryTestDxe/NullMemoryTestDxe.inf

INF文件使用示例:

DEFINE IFMP = IntelFrameworkModulePkg
INF USE = IA32 $(EDK_SOURCE)/Sample/Universal/Network/Ip4/Dxe/Ip4.inf
INF $(EDK_SOURCE)/Sample/Universal/Network/Ip4Config/Dxe/Ip4Config.inf
INF RULE_OVERRIDE = MyCompress $(IFMP)/Bus/Pci/IdeBusDxe/IdeBusDxe.inf

7) 关联规则设定(APRIORI Scoping)

在一个FV中,可以创建一个APRIORI文件,它由多个模块的GUID组成。编译后,这些模块的.efi文件会按照顺序打包进该APRIORI文件。一个FV,只允许出现一个PEI 和一个 DXE 。总的来说,主要用来指定INF的执行顺序。

FV嵌套是允许的,Apriori文件仅限于指定文件,而不包括位于FV映像范围内的FVs。作用域在大括号{}范围内有效。

APRIORI DXE {
       INF  MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
       INF  MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
   !if $(SMM_REQUIRE) == FALSE
       INF  OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
    !endif
   }

如上例子中3个INF按照顺序执行。

2.4.3.4 [Rule]

这个Section用来描述FFS文件的构成,FFS文件就是通过INF文件编译得到,然后组成二进制。规则与[FV]节的模块INF类型一起使用,定义如何通过一个给定的INF 文件来生成FFS文件。

如果不通过INF显示指定rule, 构建系统会使用默认的规则隐式的创建FFS。隐式规则遵循 PI Specification and UEFI Specification.

规则可以有多个变种,如:

[Rule.ARCH.MODULE_TYPE.TEMPLATE_NAME]

下面是一些具体例子:

[Rule.Common.SEC]
  FILE SEC = $(NAMED_GUID) {
    PE32     PE32           $(INF_OUTPUT)/$(MODULE_NAME).efi
    UI       STRING ="$(MODULE_NAME)" Optional
    VERSION  STRING ="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)
  }
 
[Rule.Common.PEI_CORE]
  FILE PEI_CORE = $(NAMED_GUID) {
    PE32     PE32   Align=Auto    $(INF_OUTPUT)/$(MODULE_NAME).efi
    UI       STRING ="$(MODULE_NAME)" Optional
    VERSION  STRING ="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)
  }
 
[Rule.Common.PEIM]
  FILE PEIM = $(NAMED_GUID) {
     PEI_DEPEX PEI_DEPEX Optional           $(INF_OUTPUT)/$(MODULE_NAME).depex
     PE32      PE32   Align=Auto         $(INF_OUTPUT)/$(MODULE_NAME).efi
     UI       STRING="$(MODULE_NAME)" Optional
     VERSION  STRING="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)
 c
 }

其内部是一个FILE语句,它跟之前在FV中使用的FILE语句结构是一样的。

此外还有[VTF]、[OptionRom]、[Capsule]等Section,因为用的不多,这里不再介绍。


总结

此文档只是一个使用文档,主要介绍FDF、DSC、INF、DEC几种配置文件的写法。读者可能对这些文件的具体作用,以及它们之间的关系,如何影响整个编译过程一头雾水。下一篇将会对中提到的FD、FV、FFS、efi等文件的关系,以及如何作用于build,做一个梳理。

你可能感兴趣的:(Build系统,linux,android,物联网,arm)