专业的昆明网站建设,wordpress 网站,网站建设中的数据库规划,西安开发网站的公司一、EDK2简介 1.EDK2工作流
二、EDK2 Packages 1.Packages介绍 EDK2 Packages是一个容器#xff0c;其中包含一组模块及模块的相关定义。每个Package是一个EDK2单元。
整个Project的源代码可以被分割成不同的Pkg。这样的设计不仅可以降低耦合性#xff0c;还有利于分…一、EDK2简介 1.EDK2工作流
二、EDK2 Packages 1.Packages介绍 EDK2 Packages是一个容器其中包含一组模块及模块的相关定义。每个Package是一个EDK2单元。
整个Project的源代码可以被分割成不同的Pkg。这样的设计不仅可以降低耦合性还有利于分发和复用。
1.1.EDK2 Packages 每个Package中包含一个DEC文件该文件用来组织Package中的一系列Module。EDK2提供了一些兼容UEFI和PI的包如MdePkg、MdeModulePkg等。
MdeModulePkg包中有一组符合UEFI和PI规范的跨平台驱动程序。在开发新的UEFI和PI驱动时可以用来参考。
1.2.DEC文件 DEC文件用来定义每个Package中的公共接口公共头文件、GUID、PCDs。
DEC文件主要包括以下部分
1[Defines]
定义PKG的名字和PKG的GUID
2[Includes]
列出公共头文件的所处目录的根目录
3[LibraryClasses]
列出在Include\Library下的每个Library Class头文件
4[Guids]
给Include\Guid下的Guid赋值
5[Ppis]
给Include\Ppi下的Ppi赋Guid值
6[Protocols]
给Include\Protocol下的Protocol赋Guid值
7PCDs
相关类型的PCD声明FeatureFlag, FixedAtBuild, PatchableInModule, Dynamic, and DynamicEx。Dynamic类型的PCD在第四章附录章节还有补充说明。
若一个PCD支持多种类型则需要在全部类型中全部声明 DEC文件写法示例
1.3.DSC文件 编译时使用到的文件所有模块均可加入来进行Package编译和验证。
DSC文件主要包括以下部分
1[Defines]
设置Build的相关信息如build输出目录、build目的、Guid和build arch
2[LibraryClasses]
对每个LibraryClasses指定选择的Library实例
3PCD
用于配置[Components]部分中各模块使用的PCD类型和PCD值。
如果PCD的值与DEC中的默认值相同且PCD类型没有具体要求则将使用DEC中的默认值。 4[Components]
列出所有将被编译的Module如Drivers, Application, and Library Instances
Path是相对路径相对于Package根目录
DSC文件写法示例
2.Packages管理 2.1.创建Package 2.1.1.创建要求 1将有意义的包名作为包目录名并创建一个包目录例如PackageNamePkg
2在包根目录下创建包DEC和DSC文件来描述这个包
3创建子目录来包含不同的源文件
3.EDK II Module 一个EDKII Module包含源文件或者二进制文件和一个Module定义文件inf文件
3.1.Module组成 对于一个典型的EDK II Module而言Module是一个固件组件。Module在构建完成后首先被放入FFS文件然后放入FV Image。
Module可以以源代码或EFI二进制格式发布Module的内容可以是下列几种类型
1Raw data binary
例如$(WORKSPACE)\MdeModulePkg\Logo\Logo.inf 这就是包含了一个logo bitmap的image
2可选的设备ROM驱动
3独立的UEFI驱动或者UEFI应用
4一个 .lib 形式的library instance
3.2.Module分类 EDK II给Module定义了许多不同的Module类型。定义这些Module类型主要是为了
1区分不同类型模块的生命周期
例如PEIM在PEI阶段调度DXE_DRIVER或uefi_driver在DXE阶段
2表示不同类型模块的二进制image
3为不同类型的模块指定合适的library instance
主要的EDKII Module类型
3.3.Module创建 Driver Module和Library Module创建步骤相仿
1选择或者创建一个Package来存放Module
2为该Module创建一个目录并且放入一个inf文件
3在inf文件中添加Package dependencies
4在inf文件中添加PPI Protocol Guid等相关dependencies
5如果模块依赖PPI Protocol Guid则需要加入项[depex]
6创建源文件并且在inf中填入源文件的相对路径
3.4.增加Module目录 Module目录需要按照以下规则添加到合适的Package中
3.5.INF文件写法 INF文件是对于所在Module的说明文件放在根目录中,其中
1Module的基础信息有名字、GUID、模块类型等
2Module所依赖的所有Packages路径根目录的相对路径
所有模块都依赖MdePkg需要将其加入 如果使用来自Intel框架规范的定义则IntelFrameworkPkg也是必需的 如果使用到了其它Package中的内容则需要添加其它Packeage的dec文件
3源文件或者二进制文件的路径 4模块用到的系列接口如Protocol、Ppi、GUID
5模块所需的所有pcd和Library类的列表
6其它内容例如不同类型Module需要的不同的依赖部分
App Module的inf文件
Library Module的inf文件
3.6.添加Source文件 1.INF中的 [sources] 模块定义了Source文件的相关内容相关规则如下
1不同的体系结构Source文件放在不同的模块下例如
2使用的不同工具链需要标注例如
相关Tool Tag
3.7.添加Library Class References Library类Class将相关的宏定义、结构定义和函数声明进行了抽象而Library实例instance将这些内容进行了实现。
Library实例Instance根据不同Platform和相同Platform的不同阶段其具体实现会有所差异。
3.7.1.在模块中使用Library类的步骤 1在INF文件中给包含Library Class的package添加Dependency
2在INF文件中给Library Class添加dependency
3在代码头文件中包含LibraryClass
头文件的路径是相对路径相对于package DEC文件[include]中写的路径
实例
#include Library/OemHookStatusCodeLib.h 1
3.8.添加PCD References EDK II引入了PCD来实现宏定义的效果。例如对于“FeatureFlag”类型的PCD如果PCD的值为TRUE则会启用某些特性或功能。
EDK II提供了以下类型的PCD
3.8.1.使用PCD的步骤 1在Module的INF文件中为PcdLib添加Dependency
2在Module的INF文件中给MdePkg添加dependency
MdePkg是必需的因为MdePkg中的“PcdLib”Library Class提供了PCD访问函数和宏。 3在代码头文件中添加Library/PcdLib.h
4使用PcdLib提供的接口进行PCD Value的操作
PCD操作函数
PCD添加与使用实例
3.9.添加Prorocol、PPI、GUID Reference Protocol、PPI、Guid是UEFI中固件之间通信的接口。
3.9.1.在模块中使用Protocol、PPI、Guid 1在INF文件中对应类别[Protocol]、[Ppi]或[Guid]添加需要的Protocol
2相应头文件必须清楚包含在Source code的头中
头文件中都是相对路径相对于package DEC文件[include]中写的路径
3.10.为Module添加Dependency Module的Dependency限定了关于驱动程序的Entry Point的条件。
通过它可以确定PEIM和DXE模块的调度顺序。
一个表达式由一个或多个Protocol、PPI或GUID和操作符组成例如“AND”, “OR”, “TRUE”, “FALSE”, “NOT” 等。 表示gEfiSampleGuid 的值和gEfiSamplePpiGuid的值进行布尔和运算
具体运算法则描述参考Platform Initialization Specification的Dependency Expressions 章节和 Dependency Expression Grammar章节 3.11.Define Library Class Library Instance总是与Library Class相关Library Instance实现了Library Class中定义的所有接口。
因此Library Class名必须在Library Instance的INF文件的[definitions]中说明。
如下所示
UefiDriverEntryPoint 是Library Class的名字这个名字由Library Instance所来。
DXE_DRIVER和DXE_RUNTIME_DRIVER是这个Library Instance所支持的类型。
3.12 Driver Module的额外步骤 PEIM或者DXE Driver需要在INF文件中的[Defines]中标明函数Entry_Point。不同的Module类型具有不同的Entry_Point。
示例图
3.13 常见Library Class MdePkg中提供了许多Library Class用来基于UEFI和PI开发固件组件。
3.14 使用HII的Module DXE Module可以在BDS阶段中打印或更新browser将使用到下列资源
3.14.1 Forms 1VFR Resource File
VFR文件用来描述form即格式资源。VFR文件的用法和其它Source Code用法相似。
需要将其在Module的INF文件的[Sources]部分中列出。
用法示例
2打印VFR
在Module编译时vfr文件会由Vfr编译器编译为一个.vfr文件并且作为全局数组变量插入到Module image的IFR二进制区域中。这个全局数组变量的名字是Vfr文件名Bin。
示例如下
Inventory.vfr in the MdeModulePkg\Universal\DriverSampleDxe driver
is compiled into the global array variable InventoryBin.
3将VFR全局数组变量加入HII数据库
使用以下代码段
// // Create HII driver handle, paramter DriverHandle will hold the // returned new handle. // HiiLibCreateHiiDriverHandle defined in UefiHiiLib library class. // Status HiiLibCreateHiiDriverHandle(DriverHandle); // // Prepare HII package list, parameter InventoryBin is the VFR form data // HiiLibPreparePackageList defined in UefiHiiLib library class // PackageList HiiLibPreparePackageList (2, mInventoryGuid, InventoryBin, DriverSampleStrings); ASSERT (PackageList ! NULL); // // Create package into HII database via EFI_HII_PROTOCOL-NewPackageList // Status gHiiDatabase-NewPackageList ( gHiiDatabase, PackageList, DriverHandle, HiiHandle );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 3.14.2 Unicode Strings Unicode字符串被放到.uni文件中并在Module的INF文件的[Sources]部分列出和C文件一样。
示例如图
1打印Unicode String文件
uni文件中的内容会被编译工具解析和编译为一个二进制字符串包数组。这个二进制数组的名字为ModuleName “Strings”。
例如MdeModulePkg\Universal\DriverSampleDxe 中的inventorystring.uni最终的二进制数组名将会是
extern UINT8 DriverSampleStrings[]; 1 2将Strings array variable 添加进 HII Database
// // Create HII driver handle, paramter DriverHandle will hold the // returned new handle. // HiiLibCreateHiiDriverHandle defined in UefiHiiLib library class. // Status HiiLibCreateHiiDriverHandle(DriverHandle); // // Prepare HII package list, parameter DriverSampleStrings is the // strings binary data. // HiiLibPreparePackageList defined in UefiHiiLib library class // PackageList HiiLibPreparePackageList (2, mFormSetGuid, DriverSampleStrings, VfrBin); if (PackageList NULL) { return EFI_OUT_OF_RESOURCES; } // // Create package into HII database via EFI_HII_PROTOCOL-NewPackageList // Status HiiDatabase-NewPackageList ( HiiDatabase, PackageList, DriverHandle[0], HiiHandle[0]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 4. Module的编译 当Module Source Code完成以后Module的INF文件会被加入到Platform的DSC文件里Module将被编译到预期的二进制Image中。
EDK2编译系统支持编译Library Image、EFI Image、OptionRom Image。
4.1 将Module INF文件加入到Packeage DSC文件 为了编译一个Module会在DSC文件的[Components]部分指定Module的INF文件路径是Package目录开始的相对路径到INF文件名字为止
DSC的[Defines]部分会列出所有支持的体系。[Components]部分会详细列出Module的支持体系。
示例
4.2. 为Module选择Library Instancelibrary instance不需要此步 对于Driver和Application来说每个Library Class必须选中对应的Library Instance并且链接到二进制EFI Image处。
Module INF文件的[LibraryClasses] 部分列出了所有需要的Library Class这些Library Class都是Library Instance所产生的。
对于不同的目的例如对性能和大小的优化Library Instance可以有不同的实现方式。
在初始开发中通常使用没有经过优化的Library Instance来降低开发风险。完成模块的基本功能后可以进一步调整其大小和性能。
在MdePkg中为用户选择提供了许多可用的Library Instances详细信息可以在其INF文件或MdePkg规范中找到。
举例
在MdePkg中对于LibraryClassBaseMemory的Library InstanceBaseMemoryLibOptDxe的实现采用了寄存器操作内存的方式获得了更好的性能表现。
另一个例子是在MdePkg中LibraryInstancePeiIoLibCpuIo通过使用CpuIo PPI的服务来实现IO的LibraryClass减少了代码大小。
4.3 对于PCD的配置 对于使用到PCD的Module需要提前在Package DSC文件中配置。配置完成的PCD会被应用到Module和其链接的Library Instance两处。
PCD会在Package的DEC文件中声明。当PCD的Value和DEC文件中的默认值一致时这些PCD则不需要再DSC中再次赋值。
在DSC中PCD类型和值可以根据Platform要求进行配置。在DSC中一个PCD的类型只允许是唯一的类型。如果DSC中没有对其进行指定那么PCD的类型将和DEC中声明的类型保持一致。如果一个PCD支持多种PCD类型那么其默认PCD类型是固定PCD类型。
4.3.1 PCD的类型
1Feature Flag PCD
如果一个PCD被声明为PcdsFeatureFlag 那么它只能是FeatureFlag PCD type或者BOOLEAN data type。如果Module需要使用本类PCD则需要在INF的[FeaturePcd]中预先指定。
只有通过FeaturePcdGet接口才可以操作此种类型的PCD
示意
2Fixed PCD
如果一个PCD的Value在整个编译期间都是固定的那么它的类型应设置为PcdsFixedAtBuild 。
当Module使用此类PCD时它可以选择归属在Module INF的[FixedPcd]或者[PCD]中。
此外操作本类型的PCD需要使用FixedPcdGet或者PcdGet接口。
3Patchable PCD
如果一个PCD的Value在二进制Image中不固定会被修改它的类型应设置为PcdsPatchable 。在Module中使用此类PCD时它可以分在[FatchPcd]或者[PCD]。
4Dynamic PCD
如果PCD的Value在运行期间获得那么它的类型应设置为Dynamic。如果一个Dynamic PCD是从Driver共享的PCD数据库取得Value那么它的类型应该是PcdsDynamicDefault如果一个Dynamic PCD和UEFI Variable相关那么它的类型应该是PcdsDynamicHII。
当在Module中使用Dynamic PCD那么INF的 [PCD]部分需要注明。
此外操作Dynamic PCD只有通过PcdGet、PcdSet两个接口。
示例图
4.4 Build选项修改 Build Option为不同的工具链提供了不同的编译选项来编译出Image。它们在$(WORKSPACE)/Conf/tools_def.txt file被定义。在这个文件中列出了不同工具链常见的编译器选项。
编译器选项compiler option主要被分成两大类详细选项可见EDK II Build Specification 编译选项compile option
链接选项 link option
EDKII 编译系统提供了四种层次的override机制来实现Compiler Options的定制。
影响范围从大到小依次是Tool_def.txtDSC最为推荐只修改了DSC文件INF。
这些Options可以通过指定顺序实现互相覆盖。
4.4.1 修改Tool_def.txt 直接对tool_def.txt 进行修改将会影响workspace里的所有Modules和platforms
4.4.2 修改INF文件 在Module INF文件的**[BuildOptions]**中添加额外的编译器选项(compiler option)将会对该Module生效并且在任何DSC中都可被编译。
不同编译工具可选的编译器选项(compiler option)并不相同
Module INF示例
4.4.3 修改DSC文件 在DSC的 [BuildOptions]部分添加额外的编译器选项(compiler option)同一个DSC中描述的所有Module的编译器选项都会被影响。
DSC示例
在DSC的[Components]部分为某个Module添加编译器选项(compiler option)则只对该Module且只在该DSC生效。
DSC [Components]示例
4.5 Build Module Image 在DSC文件的 [Components] 中所添加的描述
4.5.1 Build Package-p 如果 [Components] 没有指明build module option那么package DSC的[Components]所列出来的Module都会被编译。如果build module option有多个只有最后一个会生效
示例
4.5.2 Build Module-m 如果一个指定Module需要build则其必须首先在DSC的 [Components] 中列出。如果没有列出DSC的所有Module都会被build。如果command line指定指令不止一次只有最后一个会生效。
示例
4.5.3 Build Arch 支持的ARCH Option有: IA32、X64、IPF、EBC。如果在命令行中指定了不止一次则每个ARCH会按顺序构建。
示例
4.5.4 Build Target 支持的Target有DEBUG和RELEASE。如果在命令行上指定多次则按顺序构建每个Target。
示例
4.5.5 Build Tool Tag Name Tool Tag Name定义在Conf\Tools_def.txt文件中表示编译器工具链。例如MYTOOLS是Microsoft VS2005工具链的默认Tool Tag Name。
如果在命令行上指定多次则会按顺序使用每个工具链。
4.5.6 示例Build Helloworld
在build DEBUG文件夹中会创建以下文件EFI Image、Intermediate Files、AutoGen.h、AutoGen.c、Module.map 文件
EDK II build工具根据所需的Module信息为每个Module生成AutoGen.h和AutoGen.c文件。其中包括了依赖的pcdGuid值和包含Module Entry Point相关的函数。
这些AutoGen函数在ModuleEntryPoint库实例中被引用。对于每个Module Entry Point函数首先调用AutoGen Code然后进入Module functions。
Module.Map是由compiler tool生成的用于列出该Module中所有函数及其相对地址。它们可用于在运行时定位Module函数地址。
AutoGen.c如图
PcdDxe.map如图
4.6 Build EFI Option Rom Image EFI Option Rom是一个标准的EFI image。EFI image可以通过上述build Module命令编译。与普通Module的不同在于: EFI Option Rom Image INF文件的 [Defines] 中会包含相关的PCI Option。当Module INF文件中设置了PCI Option。这个Module会被编译成EFI和Option Rom Images。
在build DEUBG文件下会出现ModuleName.efi 和ModuleName.rom
Option Rom INF示例
4.6.1 常见build Module报错
4.7 Debug Module 在开始Debug Module之前需要执行以下4个步骤
指令“Build –b DEBUG” EDK II支持生成DEBUG/RELEASE目标。target.txt中的BuildTarget字段与 ToolChain 字段共同生效以确定编译器tool-chain和build option的实际路径。开发人员可以直接打开
$(WORKSPACE)\Conf\ TARGET .txt并更改“TARGET DEBUG”作为调试提示。开发人员也可以使用命令行来覆盖该值例如在调试提示中使用“build -b DEBUG”。
选择合适的DebugLib Library Instance 对于DebugLib库类MdePkg和IntelFrameworkModulePkg核心包提供了几个库实例。其中包括BaseDebugLibNull、BaseDebugLibSerialPort UefiDebugLibConOut,
UefiDebugLibStdErr, PeiDxeDebugLibReportStatusCode。开发人员可以按实际要求在Package的DSC文件中选择合适的DebugLib库实例。
配置DebugLib使用的Pcds DebugLib library class header定义了两个用于调试库配置的pcd。与debug ability相关的pcd包括PcdDebugPropertyMask和PcdDebugPrintErrorLevel。前者用于控制print/assert并确定assert宏是通过CpuDeadLoop还是通过BreakPoint实现的。对于后者开发人员可以设置各种值来控制打印或过滤错误信息。
Change build option 开发人员可以修改或重写module build option。例如开发人员可以使用Microsoft 编译器的“/Od”选项来禁用编译器的优化并避免无序指令。还可以使用Microsoft编译器的“/FAsc”选项来生成一个源代码和汇编(.cod)文件来帮助调试。
4.7.1 基本调试方法 以下是三个基本的调试方法
使用DEBUG打印语句 在EDK II项目中有一组PCD用于开启/关闭DEBUG。开发人员可以在开始调试时打开该功能。
CpuDeadLoop() 开发人员可以使用API halt control flow有助于快速定位问题。
Module的 Map file EDK II为每个模块生成一个对应的FV Map文件
开发人员可以根据已加载Module的基址和Map File来计算函数的内存地址。
2通过Variable
Variable是一对 key/Value由标识信息加上属性(Key)和任意数据(Value)组成。Variables传递数据主要为存储平台实现的EFI环境、EFI OS Loader和在EFI环境中运行的其他应用之间的传递。
UEFI应用程序可以通过UEFI Runtime Services读写变量GetVariable()和SetVariable()。因为UEFI App必须在Dxe/UEFI驱动之后运行不可缺少Variable Arch protocol。
2.UEFI Driver 满足UEFI Driver Model的Driver被称为UEFI Driver。UEFI Driver初始化的过程中不允许接触任何硬件资源。相反它会在UEFI驱动的ImageHandle上安装一个EFI_DRIVER_BINDING_PROTOCOL的实例。
之后UEFI Driver可能会被EFI_DRIVER_BINDING_PROTOCOL 调用来对于一个指定的Hardware进行支持性测试。这项测试是为了确定一个driver是否支持一个给定的controller。
该测试在不对controller引起负面影响的前提下必须越快越好。Controller的大部分初始化都是在EFI_DRIVER_BINDING_PROTOCOL 服务的开始和结束时完成的。
UEFI Driver和DXE Driver的区别见第六节 2.1 UEFI Driver介绍 2.1.1 INF文件 UEFI Driver的INF file里[Defines] 部分的Module TYPE必须指定为UEFI Driver。
UEFI Driver没有 [Depex] 的部分。因为它一直依赖于DXE架构下的protocols。为了实现这一点UEFI Driver Entry Point库实例将所有DXE架构下的protocols的依赖关系附加到了Module Image的 [Depex] 部分。
示例
2.1.2 UEFI Driver Entry Point 下表列出了UEFI驱动程序入口点中最常用的Entry Point。
如图
1示例
2.1.3 Get Service Table UEFI Driver可以使用UEFI Boot Services、UEFI Runtime Services和UEFI System Tables。为此EDK II 提供了UefiBootServicesTableLib 和 UefiRuntimeServicesTableLib库以方便开发者访问。
Global Variables
2.1.4 UEFI Drivers之间的通信 1通过Protocol
UEFI驱动可以使用Protocol访问其他Module产生的Protocol接口
2通过Variable
UEFI驱动可以通过UEFI Runtime Services读取和写入变量GetVariable()和SetVariable()。
UEFI Driver和DXE Driver的区别 UEFI driver 和DXE 的driver的主要区别是是否遵循UEFI Driver Module
1.UEFI Driver不需要依赖Dependency来决定执行的顺序
2.UEFI Driver必须可以被重复执行
3.UEFI Driver不需要即时启动
4.UEFI Driver支持硬件的热插拔Hot-Plug
5.UEFI Driver支持软件的热插拔Unload
6.UEFI Driver所有的Function都是DeviceHandle结合DriverProtocol
3.SEC Module SEC模块是上电后执行的第一个模块。它负责配置PEI环境的内存调用堆栈。此外该模块发现并将控制权传递给PEI Core将信息传递给PEI Foundation。
3.1.1 INF File
1对于一个物理平台来说Module_Type必须是SEC对于一个模拟平台来说可以是SEC也可以是USER_DEFINED
2对于IA32来说入口一定是**_ModuleEntryPoint**
3对于安腾处理器家族平台入口点是可配置的例如SAMPLE_ENTRY。但是这个入口点应该添加到[BuildOptions]部分如下所示
4.Pre-EFI模块 Pre- EFI模块提供了一个基于标准的平台初始化。PEI阶段的责任是初始化足够的系统来为接下来的阶段提供一个稳定的基础。
强烈推荐PEI模块只做最小要求的工作来满足后续阶段的要求。PEI Foundation建立了所有PEI Module使用的PEI Service Table。
PEI阶段允许在Memory可用之前执行C语言代码实现的PEI Module。这是通过配置CPU的资源例如CPU data cache来实现内存栈。
4.1 PEIM INF 文件 PEI Module文件示例
4.2 PEI Module的EntryPoint 上述示例中Entry Point是PeimSampleInitialize 下面是Entry Point的原型。
FileHandle是正被调用文件的handlePeiServices是PEI Services Table的间接指针
示例图
4.3 获取PEI Services EDKII 在PEI Services Library Class中提供了所有的PEI Services的API。开发人员可以使用PEI Services Library来调用PEI Services。
EDKII 为PEI Modules提供了PEI Services Table Library来获取PEI Service Table。除了从PEI Module的Entry Point中的输入函数来获取PEI Services Table指针。EDKII 还允许使用定义在PEI Services Table Pointer Library中的GetPeiServicesTablePointer() 来获取PEI Services Table指针。
4.4 PEIM Modules之间的通信 PEIMs之间有三种通信方式:PPIs、hob和动态pcd。
4.4.1 PPI PEIM模块可以使用一种称为PEIM-toPEIM Interface (PPI) 的结构来互相通信。每个PPI有一个GUID。PEI Service Table 提供了一些PEI Services来使用PPI的数据库。
在EDKII 中一个PEIM Module可以通过GUID调用 PeiServicesInstallPpi() 来Publish自己的PPI Services到PPI database中。另一个PEIM Module可以根据GUID来调用PeiServicesLocatePpi() 在PPI database中定位PPI Services。
1Installing a PPI
如果一个Module A想要publish一个PPI Services的Template假设其中包括三个APIinterface1、2、3. 他可以通过使用PeiserciecesInstallPpi来Install 这个PPI Template。
示例
2Locating a PPI
如果Module B需要调用PPI Template提供的Interface2他可以通过使用下列代码来Locate。
示例
4.4.2 HOB PEIM Modules可以build一个Hand Off BlockHOB来传递一些信息给DXE Module和DXE Foundation。此外其他的PEIMS可以通过使用PEI Service Table中的HOB Services来从HOB中获得相似的信息。
在EDKII 中Hob Library为PEIMs和DXE Driver提供了通用的接口来使用HOBs。
4.4.3 PCD PEIM可以通过Dynamic PCD来和其他PEIMs通信。和HOBs一样只有PEIMs可以获取
动态PCD的值这些值之前由DXE Driver设定。Get PCD的用法在附录A Dynamic PCD有介绍。
4.5 与DXE Module的通信 4.5.1 HOB 通过使用Hand-Off Block, PEIMs可以将一些信息传递给DXE Foundation和DXE模块例如在PEI阶段发现的内存信息。
在EDK II中Hob Library提供了一组接口来帮助构建Hob例如BuildGuidHob ()。Hob库还为PEIMs和DXE Drivers提供了一组api来定位Hob。
例如
4.5.2 Variable PEIMs可以读取以前由DXE驱动程序分配的Variable。PEIMs不能写入Variable。
PEIMs可以使用ReadOnlyVariable2 来获取Variables。
具体步骤如下 1定位ReadOnlyVariable2 PPI
2当size为0时调用GetVariable()来获取Variable的实际大小。
3为Variable分配内存空间
4再一次以实际大小调用GetVariable()
代码示例
4.5.3 PCD PEIMs可以通过动态pcd与DXE驱动程序进行通信。PEIMs可以获得以前由DXE驱动程序设置的动态pcd值。获取PCD的用法见附录A。
4.6 Boot Mode 有时候PEIMs需要确认boot modeS3、S5等等并且根据boot mode采取合适的举动。例如VariablePei Module在recovery boot path下时将不会Install EFI ReadOnlyVariable2Ppi。
PEI Service Table提供了一系列的services来设置或者获取boot mode。PEI Service Library中相应的API为SetBootMode() and GetBootMode() 。
get boot mode的示例
4.7 PEIMs Excution in Place(XIP) 大多数PEIMs都是XIPExcution in Place就地执行并且不被压缩。因为他们在permanent memory之前运行。
在代码的空间复杂性和Module的时间复杂性之间有这样的权衡保持Modules的小还是保持代码路径的短。
PEIM 代码的数量和复杂性需要简化。例如对于在Flash上运行的代码来说需要避免很大的循环。
当PEIM尝试将自己load到system memory并且再次运行时它可以使用RegisterForShadow () 来实现。RegisterForShadow () 在Pei Service Table中。
4.8 PEIMs的Dependency PEIM必须有Dependency的部分。PEIM在Dependency的条件全部满足以后被dispatch。
如果一个PEIM的Dependency是True那么其可以立刻被dispatch。在扩展INF文件中Dependency部分包含在[Depex]部分中。PPI dependency被PPI GUID定义。
示例 该模块只在Read Only Variable2 Ppi, CachePpi and CapsulePpi全部Install完成后dispatch。
5.DXE Drivers非UEFI Drivers DXE驱动指的是满足PI Spec的驱动。PI Spec将DXE驱动分成两类UEFI驱动模型的驱动和非UEFI驱动普通DXE Driver模型的驱动。本节重点是普通DXE Driver。
非UEFI驱动模型的驱动在DXE阶段早期执行。这些驱动是DXE Foundation产生所有要求服务的先决条件。
DXE驱动程序必须设计成不需要不可用的服务。考虑到这一限制所有可能的工作都应该交由UEFI驱动程序完成。
UEFI Driver和DXE Driver的区别见第六节 5.1 INF 文件 DXE驱动程序需要扩展INF文件。INF文件的基本介绍请参见第二章。
DXE驱动的 [Defines] 部分应该按照如下修改
5.2 DXE Driver Entry Point UEFI驱动程序入口点只允许将protocol实例安装到自己的Image Handle上不能接触任何硬件。与UEFI驱动程序入口点不同DXE驱动程序入口点没有这样的限制。它可以将任何protocol安装到system中并且操控必要的硬件进行软件初始化。
在下面的例子中(来自MdeModulePkg中的WatchDogTimerDxe驱动程序)如果protocol尚未安装DXE驱动程序入口点将安装它的Architectural Protocol。
函数示例 DXE驱动程序入口点的两个参数是ImageHandle和SystemTable。
5.3获取Services Table DXE Drivers的Services Table可能会涉及UEFI Boot Services, UEFI Runtime Services, and DXE Services。此外DXE Driver还可以参考UEFI System Table。
UEFI Boot Services, UEFI Runtime Services, and UEFI System Table在UEFI Spec中都有定义。DXE Services在PI中有定义。
DXE Driver可以通过下列Library Class提供的全局变量检索这些tables。
5.4 DXE Drivers之间的通信 DXE Drivers之间的通信方式主要包括protocol、variable和PCD
5.4.1 Protocol UEFI Spec定义了一系列的boot services来handle protocols包括**install protocol的services **和 检索protocols的services。
如图
首先要使用这些protocolsModule开发人员必须在INF文件中声明Module使用的protocols然后写代码来使用这些protocols。
1下面的例子演示了DXE Driver如何产生一个protocol
2下面演示DXE Driver如何retrieve一个protocol并且调用这个API
5.4.2 Variables Variables被定义为 一对key/ Value 这对键值对由key确认信息加上属性和 value任意数据组成。Variables是为了存放数据而使用这些数据是在平台安装的EFI环境和EFI环境运行的EFI OS Loader和其他App之间传递的数据。
DXE Driver可以通过UEFI Runtime Services提供的GetVariable() and SetVariable() 来读写Variable。这两个Services在DXE刚开始的时候并不可用。
需要对易失环境Variables进行只读或者读写的DXE Drivers必须在INF的dependency中中加入EFI_VARIABLE_ARCH_PROTOCOL 。
需要对非易失环境Variable进行写操作的DXE Driver必须在INF的dependency中中加入EFI_VARIABLE_WRITE_ARCH_PROTOCOL 。
环境Variable 服务的完整实现在EFI_VARIABLE_ARCH_PROTOCOL and EFI_VARIABLE_WRITE_ARCH_PROTOCOL 安装之前不可用。
对Variable读写的Sample Code示例
5.4.3 动态PCD EDK II提供动态pcd作为模块间通信的高级机制。详见附录A。
5.5 DXE Driver和PEIMs的通信 DXE驱动程序与PEIM之间的通信通道包括HOB、variable、PCD。
5.5.1 HOB HOB是将数据从PEI传递到DXE的单向通道。HOB列表是在PEI阶段提供的在DXE阶段必须将其视为只读数据结构。它传递DXE Foundation启动时系统的状态。DXE驱动程序不能修改HOB列表的内容。
HobLib提供了一组api来构建和解析HOB列表。由于DXE驱动程序只读取HOB列表所以DXE驱动程序的模块编写者可以专注于解析HOB列表的api。
下面的例子展示了几种典型的使用类型 1遍历HOB列表中的所有HOB
2仅检索HOB列表中特定类型的第一个HOB以CPU HOB为例
3遍历HOB列表中的特定类型HOB以CPU HOB为例
4仅检索HOB列表中具有特定GUID的第一个GUIDed HOB
5在HOB列表中使用特定的GUID遍历GUIDed HOB
5.5.2 Variable 非易失性变量可以作为从DXE向PEI传递数据的通道。因为只有DXE驱动程序可以写入变量而PEIM只能读取变量所以这个从DXE到PEI的通道也是一个单向通道。
5.5.3 动态PCD 非易失性动态PCD也是DXE驱动程序和PEIM之间通信的高级机制。请参考附录A。
5.6 Dependency 表达式 Dependency Expression指定DXE驱动程序需要执行的protocol。EDK II中在INF文件的[Depex]部分指定。
示例只有在安装了列出的所有四种协议之后才能执行此驱动程序
5.7 EVT_SIGNAL_EXIT_BOOT_SERVICES 的handle 当操作系统即将完全控制平台时一些DXE驱动程序需要将它们的控制器置于静止状态或执行其他控制器特定的操作。
在这种情况下DXE驱动程序应该创建一个信号类型事件当EFI OS Loader调用gBS-ExitBootServices()时通知该事件。
此Event的通知功能不允许使用内存分配services或者调用任何使用内存分配services的函数并且应该只调用已知没有使用内存分配的函数services因为这些services修改当前内存映射。
通知功能和事件注册模板代码如下:
5.8 DXE Runtime Driver DXE Runtime Driver可以运行在boot services和runtime services环境下。这意味着这些Modules产生的services在ExitBootServices() 调用前和调用后皆可用包括OS运行的时候。如果SetVirtualAddressMap() 被调用那么根据OS提供的虚拟地址映射这种类型的Modules会被重新定位。
DXE Foundation被认为是一个引导服务组件所以当ExitBootServices() 被调用时DXE Foundation也可以被发布。因此runtime时的驱动程序可能不会使用任何UEFI Boot Services, DXE Services 或者调用ExitBootServices()后引导服务驱动程序产生的服务。
DXE runtime driver在INF文件中将MODULE_TYPE定义DXE_RUNTIME_DRIVER。此外因为DXE Runtime Driver在其生存周期中导致了
SetVirtualAddressMap() 。它可能需要为event EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE注册一个event handle。
5.8.1 INF文件 对于DXE Runtime DriverModule type应该是DXE Runtime Driver。
示例
5.8.2 Virtual Address Event的Handle 当OS调用SetVirtualAddressMap() 可能需要通知DXE Runtime Driver。在此情况下DXE Runtime Driver必须创建一个signal类型的Event当SetVirtualAddressMap() 被OS调用的时候该Event将被通知。调用SetVirtualAddressMap() 允许DXE Runtime Driver讲指针从物理地址翻转为虚拟地址。
DXE Runtime Driver创建的signal类型的Event所用到的通知函数不允许直接或者间接使用UEFI Boot Services、**UEFI Console Services **或者 UEFI Protocol Services 。因为当调用SetVirtualAddressMap()之时这些Services都将不可用。
通常DXE Runtime Driver创建的signal类型的Event所用到的通知函数使用
ConvertPointer() 来将指针从物理转换为虚拟。
通知函数和Event注册模板代码如下
5.9 DXE SMM Driver DXE SMM Driver这种类型由load到SMRAM中的SMM Driver使用。因此这种类型只有IA32和X64 CPU可用。这些Module由SMM Foundation分配并且永远不会损坏。这意味着这些Module产生的services在ExitBootServices(). 之后依然可用。 SMM Driver的生命周期可以被分为两个阶段SMM初始化和SMM Runtime两个阶段有不同的限制条件。
SMM初始化是SMM驱动程序初始化的阶段,它从调用直到驱动程序的入口点开始,并从驱动器的入口点返回。
SMM Runtime是SMM Driver初始化的阶段。这个阶段在驱动入口点返回以后。
5.9.1 INF 文件 对于SMM驱动程序MODULE_TYPE为DXE_SMM_DRIVER。
示意图
5.9.2 限制条件 SMM驱动程序模型有类似于DXE运行时驱动程序的约束。
在SMM Runtime运行期间drivers可能不可以使用内核protocol services。有相关的SMST based services供Drivers使用。但是UEFI System Table和其他在boot services阶段之间安装的protocols可能并不一定可用。
在SMM 初始化期间UEFI Boot Services、UEFI Runtime Services、SMST-based services都是可用的。
5.9.3 SMM Driver初始化 当Driver加载到SMRAM并且Driver的Entry Point被调用时SMM Driver的初始化阶段就开始了。SMM Driver的初始化阶段终止于其Entry Point的return。简单来说整个SMM Driver初始化的过程就是SMM Driver Entry Point运行的整个过程。
在SMM Driver初始化的过程中SMM Driver可以使用两类protocolUEFI protocol和SMM protocol。
UEFI Protocol指的是Install和discover时使用UEFI Boot Services的protocols。SMM Driver只有在初始化的过程中才可以locate和使用UEFI Protocols。
SMM Protocols指的是使用System Management Services TableSMST来install和discover的protocols。
在SMM Drivers初始化期间SMM Drivers不允许使用UEFI Boot Services Exit() and ExitBootServices()。
5.9.4 SMM Driver Runtime SMM Driver Runtime期间SMM Driver只允许使用SMST-based Services。此外对于不同的平台体系而言SMM Driver可能没有权限使用SMRAM以外的内存区域。同样UEFI Drivers可能不允许使用SMRAM里的内存区域。
这些SMM Driver Runtime的特性导致了关于UEFI Services用法的一些限制。
1在SMM Driver初始化期间locate的Interface和services在SMM Driver Runtime期间不可以被调用或者引用。
2SMM Driver 初始化期间创建的 Events必须在 Driver Entry Point退出之前关闭。
6.UEFI Driver和DXE Driver的区别和联系 UEFI Driver和DXE Driver的主要区别在于是否满足UEFI Driver Model。
DXE Driver是在编写驱动的时候主动寻找设备并且对其进行初始化UEFI Driver则是系统服务自己根据设备寻找合适的驱动然后对其进行初始化。前者一般在驱动运行的时候就直接完成。后者需要先对驱动进行注册然后通过调用系统服务来完成初始化。
两种类型的示意图
6.1 两种Driver的Entry Point DXE Driver和UEFI Driver的代码结构类似主要区别在于Driver的Entry Point做了什么。
6.1.1 DXE Driver 在一个DXE Driver的INF文件中Entry Point是设备初始化的函数。
[Defines] INF_VERSION 0x00010005 BASE_NAME DxeDriverInBds FILE_GUID 04687443-0174-498F-A2F9-08F3A5363F84 MODULE_TYPE UEFI_DRIVER VERSION_STRING 1.0 ENTRY_POINT DxeDriverEntry 1 2 3 4 5 6 7 6.1.2 UEFI Driver UEFI Driver只是安装了一个Protocol。以SnpDxe这个Module为例gSimpleNetworkDriverBinding这个Protocol就是这个设备在DXE阶段安装的Protocol。所有符合UEFI Driver Model的驱动都会安装一个类似的Protocol。
简而言之UEFI Driver就是在DXE阶段安装了这样的Protocol然后在 gBS - Connect Controller的时候。首先将会执行 xxSupported函数根据 EFI SUCCESS返回值继续执行 xxStart函数该 xxStart函数中就实现了设备初始化的代码。
SnpDXE.inf的 xxSupported函数和主要流程如下
当扫描的这个设备的时候设备用Controller表示先判断它是否已经Install了DevicePathProtocol没有就表示这个设备还没有准备好或者说不是设备后面的xxxStart()不用执行
然后判断NetworkInterfaceIdentifierProtocol是否安装这个是网卡驱动一定会装的ProtocolSnp驱动底层的操作需要依赖于它所以一定要安装如果没有就不会执行后面的操作
判断NetworkInterfaceIdentifierProtocol是否满足要求如果不满足则不会执行xxxStart()函数。
如果以上条件都满足就可以认为该设备是一个网卡然后这个驱动就会被执行执行xxxStart()函数而之前获取到的DevicePathProtocol和NetworkInterfaceIdentifierProtocol就会成为操作正确设备的基础。
EFI_STATUS EFIAPI SimpleNetworkDriverSupported ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *NiiProtocol; PXE_UNDI *Pxe;
Status gBS-OpenProtocol ( Controller, gEfiDevicePathProtocolGuid, NULL, This-DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_TEST_PROTOCOL ); if (EFI_ERROR (Status)) { return Status; }
Status gBS-OpenProtocol ( Controller, gEfiNetworkInterfaceIdentifierProtocolGuid_31, (VOID **) NiiProtocol, This-DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER );
if (EFI_ERROR (Status)) { if (Status EFI_ALREADY_STARTED) { DEBUG ((EFI_D_INFO, “Support(): Already Started. on handle %p\n”, Controller)); } return Status; }
DEBUG ((EFI_D_INFO, “Support(): UNDI3.1 found on handle %p\n”, Controller));
// // check the version, we don’t want to connect to the undi16 // if (NiiProtocol-Type ! EfiNetworkInterfaceUndi) { Status EFI_UNSUPPORTED; goto Done; } // // Check to see if !PXE structure is valid. Paragraph alignment of !PXE structure is required. // if ((NiiProtocol-Id 0x0F) ! 0) { DEBUG ((EFI_D_NET, “\n!PXE structure is not paragraph aligned.\n”)); Status EFI_UNSUPPORTED; goto Done; }
Pxe (PXE_UNDI *) (UINTN) (NiiProtocol-Id);
// // Verify !PXE revisions. // if (Pxe-hw.Signature ! PXE_ROMID_SIGNATURE) { DEBUG ((EFI_D_NET, “\n!PXE signature is not valid.\n”)); Status EFI_UNSUPPORTED; goto Done; }
if (Pxe-hw.Rev PXE_ROMID_REV) { DEBUG ((EFI_D_NET, “\n!PXE.Rev is not supported.\n”)); Status EFI_UNSUPPORTED; goto Done; }
if (Pxe-hw.MajorVer PXE_ROMID_MAJORVER) {
DEBUG ((EFI_D_NET, \n!PXE.MajorVer is not supported.\n));
Status EFI_UNSUPPORTED;
goto Done;} else if (Pxe-hw.MajorVer PXE_ROMID_MAJORVER Pxe-hw.MinorVer PXE_ROMID_MINORVER) { DEBUG ((EFI_D_NET, “\n!PXE.MinorVer is not supported.”)); Status EFI_UNSUPPORTED; goto Done; } // // Do S/W UNDI specific checks. // if ((Pxe-hw.Implementation PXE_ROMID_IMP_HW_UNDI) 0) { if (Pxe-sw.EntryPoint Pxe-sw.Len) { DEBUG ((EFI_D_NET, “\n!PXE S/W entry point is not valid.”)); Status EFI_UNSUPPORTED; goto Done; }
if (Pxe-sw.BusCnt 0) {DEBUG ((EFI_D_NET, \n!PXE.BusCnt is zero.));Status EFI_UNSUPPORTED;goto Done;
}}
Status EFI_SUCCESS; DEBUG ((EFI_D_INFO, “Support(): supported on %p\n”, Controller));
Done: gBS-CloseProtocol ( Controller, gEfiNetworkInterfaceIdentifierProtocolGuid_31, This-DriverBindingHandle, Controller );
return Status; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 6.2 两种驱动的开始处 6.2.1 DXE Driver 驱动被调用的基本函数是LoadImage()和StartImage()这两个函数都是Boot Service所以可以在DXE和BDS阶段的大部分地方调用。
DxeMain.c里面的DxeMain()函数其中的CoreDispatcher()就是用来执行各个驱动的除非自己写代码否则DXE驱动都会在这个位置执行。
6.2.2 UEFI Driver 之前已经提到ConnectController()函数里面会执行驱动的xxxSupported()函数对应调用位置如下
do {
//
// Loop through the sorted Driver Binding Protocol Instances in order, and see if
// any of the Driver Binding Protocols support the controller specified by
// ControllerHandle.
//
DriverBinding NULL;
DriverFound FALSE;
for (Index 0; (Index NumberOfSortedDriverBindingProtocols) !DriverFound; Index) {if (SortedDriverBindingProtocols[Index] ! NULL) {DriverBinding SortedDriverBindingProtocols[Index];PERF_START (DriverBinding-DriverBindingHandle, DB:Support:, NULL, 0);Status DriverBinding-Supported(DriverBinding,ControllerHandle,RemainingDevicePath);PERF_END (DriverBinding-DriverBindingHandle, DB:Support:, NULL, 0);if (!EFI_ERROR (Status)) {SortedDriverBindingProtocols[Index] NULL;DriverFound TRUE;//// A driver was found that supports ControllerHandle, so attempt to start the driver// on ControllerHandle.//PERF_START (DriverBinding-DriverBindingHandle, DB:Start:, NULL, 0);Status DriverBinding-Start (DriverBinding,ControllerHandle,RemainingDevicePath);PERF_END (DriverBinding-DriverBindingHandle, DB:Start:, NULL, 0);if (!EFI_ERROR (Status)) {//// The driver was successfully started on ControllerHandle, so set a flag//OneStarted TRUE;}}}
}} while (DriverFound);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 四、附录 1.Dynamic PCD Dynamic类型的PCD用来动态配置或者设置值。相比之下Static类型的PCD如FeatureFlag, FixedPcd, PatchablePcd等等在build时已经在最后生成的FD image中被固定了。
Dynamic 决定意味着下列三种情况中的一种。
1PCD 设定值由Driver在执行过程中产生和使用。
2PCD 设定值可由用户在setup中配置
3PCD 设定值由平台OEM供应商在指定区域中产生。
2.什么时候使用Dynamic PCD Module开发人员在写Source Code或者INF时并不关心PCD是Dynamic还是Static。Dynamic PCD和Dynamic类型由平台整合商在平台DSC文件中指明。
3.Dynamic Type的类型 根据Module分发的方式Dynamic PCD可以被分为以下几种类型
3.1 Dynamic 如果Module在Source Code中发布并且将以平台DSC编译那么这个Module使用的Dynamic PCD可以用这样的方式访问PcdGetxx(PcdSampleDynamicPcd)
在编译平台编译工具将PcdSampleDynamicPcd翻译为参数 Token Space GuidToken Number。
3.2 Dynamic Ex 如果一个Module以binary形式release并且没有包含在平台编译中那么这个Module使用的Dynamic PCD必须以这样的方式访问PcdGetxxEx(gEfiMyTokenspaceGuid, PcdSampleDynamicPcd)
3.3 Default Storage PCD值保存在PCD数据库中PCD数据库由PCD Driver在boot time memory中维护。Default Storage类型用来在PEIM和DXE Drivers或者DXE和DXE Drivers中通信。所有的Set或者Get的值在boot time memory关闭后将会丢失。
[PcdsDynamicDefault]在平台DSC文件中被作为此类型的PCD的名字。[PcdsDynamicExDefault]用于dynamicEx类型的pcd。
3.4 Variable Storage 此种PCD值保存在Variable区域。作为默认的存储类型这种类型的PCD可以被PEI DXE的驱动通信使用。除此以外这种类型的PCD也可以被用来保存通过Variable Interface的HII所设置的相关的值。
在PEI阶段这个PCD值可以被获得但是不可以被设置因为这个Variable区域是只读的。
[PcdsDynamicHii]被用作平台DSC文件中这种类型的PCD的节名。
[pcdsdynamicexhibit]表示PCD的dynamicEx类型。 文章转载自: http://www.morning.gqryh.cn.gov.cn.gqryh.cn http://www.morning.lpgw.cn.gov.cn.lpgw.cn http://www.morning.pqwhk.cn.gov.cn.pqwhk.cn http://www.morning.ljzss.cn.gov.cn.ljzss.cn http://www.morning.sqlh.cn.gov.cn.sqlh.cn http://www.morning.gpcy.cn.gov.cn.gpcy.cn http://www.morning.csjps.cn.gov.cn.csjps.cn http://www.morning.rfxyk.cn.gov.cn.rfxyk.cn http://www.morning.xrpwk.cn.gov.cn.xrpwk.cn http://www.morning.cwqln.cn.gov.cn.cwqln.cn http://www.morning.rmryl.cn.gov.cn.rmryl.cn http://www.morning.mswkd.cn.gov.cn.mswkd.cn http://www.morning.zfwjh.cn.gov.cn.zfwjh.cn http://www.morning.blfgh.cn.gov.cn.blfgh.cn http://www.morning.yqzyp.cn.gov.cn.yqzyp.cn http://www.morning.vvbsxm.cn.gov.cn.vvbsxm.cn http://www.morning.muzishu.com.gov.cn.muzishu.com http://www.morning.tkztx.cn.gov.cn.tkztx.cn http://www.morning.crdtx.cn.gov.cn.crdtx.cn http://www.morning.yfzld.cn.gov.cn.yfzld.cn http://www.morning.kwqt.cn.gov.cn.kwqt.cn http://www.morning.mlyq.cn.gov.cn.mlyq.cn http://www.morning.qnbgh.cn.gov.cn.qnbgh.cn http://www.morning.qrcsb.cn.gov.cn.qrcsb.cn http://www.morning.nrqnj.cn.gov.cn.nrqnj.cn http://www.morning.c7623.cn.gov.cn.c7623.cn http://www.morning.enjoinfo.cn.gov.cn.enjoinfo.cn http://www.morning.pdkht.cn.gov.cn.pdkht.cn http://www.morning.jwsrp.cn.gov.cn.jwsrp.cn http://www.morning.mfnjk.cn.gov.cn.mfnjk.cn http://www.morning.bkppb.cn.gov.cn.bkppb.cn http://www.morning.hbhnh.cn.gov.cn.hbhnh.cn http://www.morning.mtsgx.cn.gov.cn.mtsgx.cn http://www.morning.kjlia.com.gov.cn.kjlia.com http://www.morning.qnywy.cn.gov.cn.qnywy.cn http://www.morning.nwqyq.cn.gov.cn.nwqyq.cn http://www.morning.jxcwn.cn.gov.cn.jxcwn.cn http://www.morning.xbmwm.cn.gov.cn.xbmwm.cn http://www.morning.lfbzg.cn.gov.cn.lfbzg.cn http://www.morning.hbfqm.cn.gov.cn.hbfqm.cn http://www.morning.xrwtk.cn.gov.cn.xrwtk.cn http://www.morning.nwnbq.cn.gov.cn.nwnbq.cn http://www.morning.ljmbd.cn.gov.cn.ljmbd.cn http://www.morning.ywpwg.cn.gov.cn.ywpwg.cn http://www.morning.rmxgk.cn.gov.cn.rmxgk.cn http://www.morning.cfpq.cn.gov.cn.cfpq.cn http://www.morning.bhdyr.cn.gov.cn.bhdyr.cn http://www.morning.gmnmh.cn.gov.cn.gmnmh.cn http://www.morning.rpzqk.cn.gov.cn.rpzqk.cn http://www.morning.zpqlf.cn.gov.cn.zpqlf.cn http://www.morning.mkhwx.cn.gov.cn.mkhwx.cn http://www.morning.fmtfj.cn.gov.cn.fmtfj.cn http://www.morning.bpmdr.cn.gov.cn.bpmdr.cn http://www.morning.ltrms.cn.gov.cn.ltrms.cn http://www.morning.cknsx.cn.gov.cn.cknsx.cn http://www.morning.fgxr.cn.gov.cn.fgxr.cn http://www.morning.kjlhb.cn.gov.cn.kjlhb.cn http://www.morning.pxrfm.cn.gov.cn.pxrfm.cn http://www.morning.tgfjm.cn.gov.cn.tgfjm.cn http://www.morning.zdwjg.cn.gov.cn.zdwjg.cn http://www.morning.wmdbn.cn.gov.cn.wmdbn.cn http://www.morning.jcrlx.cn.gov.cn.jcrlx.cn http://www.morning.mzskr.cn.gov.cn.mzskr.cn http://www.morning.qcztm.cn.gov.cn.qcztm.cn http://www.morning.jkbqs.cn.gov.cn.jkbqs.cn http://www.morning.stlgg.cn.gov.cn.stlgg.cn http://www.morning.hnpkr.cn.gov.cn.hnpkr.cn http://www.morning.jnhhc.cn.gov.cn.jnhhc.cn http://www.morning.ssjee.cn.gov.cn.ssjee.cn http://www.morning.lekbiao.com.gov.cn.lekbiao.com http://www.morning.slpcl.cn.gov.cn.slpcl.cn http://www.morning.sqfrg.cn.gov.cn.sqfrg.cn http://www.morning.kdnrc.cn.gov.cn.kdnrc.cn http://www.morning.nqrfd.cn.gov.cn.nqrfd.cn http://www.morning.ckfqt.cn.gov.cn.ckfqt.cn http://www.morning.fpczq.cn.gov.cn.fpczq.cn http://www.morning.khxwp.cn.gov.cn.khxwp.cn http://www.morning.pngdc.cn.gov.cn.pngdc.cn http://www.morning.fdrch.cn.gov.cn.fdrch.cn http://www.morning.psgbk.cn.gov.cn.psgbk.cn