2023-04-15
- Protocol是UEFI提供的接口函数。
- 每个Protocol都包含GUID、Protocol接口的结构体、和Protocol接口函数(Protocol服务)。
- 3个使用Protocol的函数OpenProtocol、HandleProtocol、LocateProtocol找到Protocol。
- .efi文件(如EFI_DRIVER)加载到内存后被称为Image,ImageHandle是Image的句柄。一般模块的入口函数会有一个ImageHandle入参,该参数指向了内存中的Image。
- UEFI扫描总线后,会为每个设备建立一个ControllerHandle句柄。即ControllerHandle指向了某个硬件。
- 每个Handle(对应的结构体为IHANDLE)都会有一个Protocols链表,存放自己的Protocol。所有的IHANDLE通过AllHandles链接起来。
typedef struct {
UINTN Signature;
/// All handles list of IHANDLE
LIST_ENTRY AllHandles;
/// List of PROTOCOL_INTERFACE's for this handle
LIST_ENTRY Protocols;
UINTN LocateRequest;
/// The Handle Database Key value when this handle was last created or modified
UINT64 Key;
} IHANDLE;
OpenProtocol()
#
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL)(
IN EFI_HANDLE Handle, //安装了此Protocol的Handle
IN EFI_GUID *Protocol, //要打开的Protocol的GUID
OUT VOID **Interface OPTIONAL, //返回打开的Protocol
IN EFI_HANDLE AgentHandle, //使用此Protocol的Image(存疑)
IN EFI_HANDLE ControllerHandle, //如果打开的是Protocol是符合UEFI驱动模型的驱动,则此参数为控制Protocol接口的控制器,否则为可选的,可能为NULL
IN UINT32 Attributes //打开Protocol的参数
);
- 对于符合UEFI驱动模型的UEFI驱动而言,ControllerHandle是拥有该驱动的控制器,AgentHandle是拥有该EFI_DRIVER_BINGDING_PROTOCOL实例的句柄;
- 对于UEFI应用而言,ControllerHandle可以忽略,AgentHandle是该程序的句柄,即UefiMain函数的第一个参数。
HandleProtocol()
#
- HandleProtocol不需要提供AgentHandle、ControllerHandle和Attributes。
- HandleProtocol的AgentHandle为gDxeCoreImageHandle,ControllerHandle为NULL,Attributes为EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL。
/**
@retval EFI_SUCCESS 成功返回指定的Protocol
@retval EFI_UNSUPPORTED 此Handle没有安装此Protocol
**/
typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL)(
IN EFI_HANDLE Handle, //安装了此Protocol的Handle
IN EFI_GUID *Protocol, //此Protocol的GUID
OUT VOID **Interface //返回此Protocol的实例
);
LocateProtocol()
#
- 当仅有一个Handle安装了某个Protocol时,可以使用LocateProtocol,该函数不需要提供安装了该Protocol的Handle。
- 如果多个Handle都安装了某个Protocol,就会顺序遍历HANDLE链表,找到第一个该Protocol实例。
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol, //此Protocol的GUID
IN VOID *Registration OPTIONAL, //不知道干啥用的,描述时从RegisterProtocolNotify()获得的key
OUT VOID **Interface //返回此Protocol的实例
);
EFI Driver Binding Protocol
#
- 从功能上划分,UEFI驱动分为以下类别:
- 符合UEFI驱动模型的驱动(UEFI Driver Model):包括总线驱动(Bus Drivers)、设备驱动(Device Drivers)和混合驱动(Hybrid Drivers),一般用来驱动对应的硬件设备。
- 服务型驱动(Service Drivers):这类驱动不管理任何设备,一般用来产生Protocol。
- 初始化驱动(Initializing Drivers):不会产生任何句柄,也不会增加任何Protocol到系统数据库,主要用来初始化一些操作,执行完后就从系统内存中卸载。
- 根桥型驱动(Root Bridge Drivers):用来初始化平台上的根桥控制器,并产生一个设备地址Protocol,以及访问总线设备的Protocol,一般用来通过总线驱动访问设备。
- 一个完整的符合UEFI驱动模型的驱动程序,大致可分为EFI Driver Binding Protocol和驱动本身提供的服务。前者用来管理驱动,后者才是用户需要使用提供的部分。
- 所提供的服务一般为多个Protocol。
- 为了方便用户使用,驱动程序一般还会包括EFI Component Name Protocol,这个Protocol用来显示驱动信息。
- EFI_DRIVER_BINDING_PROTOCOL的结构体如下:
struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_SUPPORTED Supported; //检查设备控制器是否支持驱动
EFI_DRIVER_BINDING_START Start; //安装驱动并启动设备
EFI_DRIVER_BINDING_STOP Stop; //停止设备并卸载驱动
UINT32 Version; //版本
EFI_HANDLE ImageHandle; //镜像句柄
EFI_HANDLE DriverBindingHandle;
};
- 接口变量ImageHandle是产生此Protocol实例的镜像句柄,DriverBindingHandle是安装了Protocol实例的句柄。
Supported()接口函数
#
- Supported()接口函数用来检测给定的设备控制器是否支持某驱动,函数原型如下:
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_SUPPORTED)(
//Protocol实例
IN EFI_DRIVER_BINDING_PROTOCOL *This,
//设备控制器句柄
IN EFI_HANDLE ControllerHandle,
//此参数对设备型驱动无效
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
Start()接口函数
#
- 用来将驱动安装到设备上,并启动硬件设备。一般在此函数中使用InstallProtocolInterface()或InstallMultipleProtocolInterface()函数进行安装。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_START)(
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
Stop()接口函数
#
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_STOP)(
IN EFI_DRIVER_BINDING_PROTOCOL *This, //Protocol实例
IN EFI_HANDLE ControllerHandle, //停止此控制器句柄上对应的驱动
IN UINTN NumberOfChildren, //子控制器数量
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL //子控制器数组
);
EFI Component Name Protocol
#
- 为了方便用户使用,UEFI驱动通常会提供名字,以便向用户显示驱动信息。
- 此功能由EFI_COMPONENT_NAME_PROTOCOL和EFI_COMPONENT_NAME2_PROTOCOL实现,这两种Protocol的功能相同,结构体相同,仅语言代码的格式不同,前者使用的是ISO 639-2语言代码,后者使用的是RFC4646语言代码。
2023-04-15
- 模块(module)是UEFI上最小的可单独编译的代码单元,它包含INF文件,源代码和二进制文件。INF文件用来描述文件的行为。
- 包(package)是由模块、平台描述文件(DSC)和包声明文件(DEC)组成,可以不包含模块,也可以包含多个模块。
- EDK2中定义了很多类型的模块:
类型 |
说明 |
BASE |
常用于库模块的开发 |
SEC |
|
PEI_CORE |
|
DXE_CORE |
|
DXE_DRIVER |
|
DXE_RUNTIME_DRIVER |
|
DXE_SAL_DRIVER |
|
DXE_SMM_DRIVER |
|
UEFI_DRIVER |
UEFI启动模块 |
UEFI_APPLICATION |
UEFI应用模块 |
搭建UEFI工程模块
#
- UEFI编译过程如下:
-

DSC文件
#
- dsc文件(description)是平台描述文件,描述了模块、库和组件如何编译。文件中还包含很多的节标志如
[Defines]
。
- 在dsc文件中,经常用
!include
来包含其他文件,用#
来表示注释。
[Defines]
中定义了各种变量,必须是dsc文件中的第一个。语法格式如下:
[Defines]
PLATFORM_NAME = MdeModule
PLATFORM_GUID = 587CE499-6CBE-43cd-94E2-186218569478
PLATFORM_VERSION = 0.98
DSC_SPECIFICATION = 0x00010005
#可选
OUTPUT_DIRECTORY = Build/MdeModule
SUPPORTED_ARCHITECTURES = IA32|X64|EBC|ARM|AARCH64|RISCV64|LOONGARCH64
BUILD_TARGETS = DEBUG|RELEASE|NOOPT
SKUID_IDENTIFIER = DEFAULT
[LibraryClasses]
PeiCoreEntryPoint|MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.inf
PeimEntryPoint|MdePkg/Library/PeimEntryPoint/PeimEntryPoint.inf
[Components]
用来定义模块编译,指定模块的INF文件所在的位置。格式如下:
[Components]
MdeModulePkg/Application/HelloWorld/HelloWorld.inf
MdeModulePkg/Application/DumpDynPcd/DumpDynPcd.inf
#这种格式下,{}内的内容只对当前的INF文件有效
AppPkg/Applications/LibSample/MyLibApp/MyLibApp.inf{
<LibraryClasses>
MyLibraryLib|AppPkg/Applications/LibSample/MyLibrary/MyLibrary.inf
}
INF文件
#
-
inf文件是模块的工程文件,描述了模块的属性,如模块由哪些代码组成、依赖了哪些库等。
-
EDK2的模块一般位于包的子目录下,如果是提供库的模块一般位于Library子目录下,如果是UEFI Application,一般位于Application下。
-
INF文件包含很多节标志,包括[Defines]
、[Sources]
等。
-
[Defines]
中定义了各种变量,后续编译步骤中需要使用这些变量。
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = Uefi_Main
FILE_GUID = 6937936E-ED34-44ab-AE97-1FA5E7ED2116
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
用于列出模块中所有的源文件和资源文件,这些文件位于inf所在的目录或者子目录中。
[Sources]
Uefi_Main.c
[BuildOptions]
和dsc文件的[BuildOptions]
语法格式基本相同,INF文件只对本模块有效,DSC对包下所有模块有效。
[Protocols]
列出了模块时用到的协议,在INF文件中列出的是协议的GUID。
[Protocols]
gEfiSimpleTextInputExProtocolGuid
gEfiGraphicsOutputProtocolGuid
LibraryClasses
列出本模块需要链接的库。模块如果需要添加库,一般需要:
- 在INF文件下的
[LibraryClasses]
中添加库名
- 在DES文件的
[LibraryClasses]
中寻找这个库,如果没有,则需要添加编译此库的INF文件。
[LibraryClasses]
UefiLib
ShellCEntryLib
[Packages]
列出本模块引用的所有包的DEC文件。
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
3种入口函数的UEFI应用
#
-
<Uefi.h>
定义了UEFI中的基本数据类型和核心数据结构。
-
<Library/UefiLib.h>
提供通用的库函数。
-
<Library/BaseLib.h>
提供字符串处理、数学、文件路径处理等相关库函数。
-
<Library/BaseMemoryLib.h>
处理内存的库函数。
-
<Library/DebugLib.h>
功能调试输出的库函数。
-
ImageHandle指向了模块自身加载到内存的Image对象。SystemTable是UEFI应用和UEFI内核交互的桥梁,通过它可以获得UEFI提供的各种服务,包括BS服务和CS服务。
-
在EFI_SYSTEM_TABLE的结构体中,提供了访问BS服务和RT服务的指针。针对一些常用的Protocol的接口,如ConsoleIn(键盘)和ConsoleOut(屏幕)也提供了访问指针。
FDF文件
#
- Flash Description File FDF文件用来描述固件在Flash中的布局和位置。
- 一般来说,生成固件的源码中只有一个FDF文件,其作用是规定把哪些包编入Flash中,并确定编译的位置。
- FDF文件由
[Defines] [FD] [FV] [Capsule] [VTF] [Rule] [OptionRom]
等几个节组成。
- [Defines]
- [FD]
- Firmware Device 即固件设备,常用的BIOS ROM就是一个FD。
- [FV]
- Fireware Volume FV是固件的逻辑区域,相当于FD上的分区。这个节定义了镜像包含的组件和模块。
2023-02-18
GCC
#
GCC编译
#
#预处理
gcc -E -o hello.i hello.c
#编译(包含预处理)
gcc -S -o hello.s hello.i
#汇编(包含预处理和编译)
gcc -c -o hello.o hello.s
#链接(包含上面的步骤)
gcc -o hello hello.o
预处理
#
- 源文件中以"#“开头的命令被称为预处理命令,如”#include"、"#define"、"#ifdef"等,预处理是将要包含的文件插入到原文件中,将宏定义展开、根据条件选择要使用的代码,最后将结果输出到一个文件中等待后续处理。
编译
#
- 编译是将预处理后的文件翻译成汇编代码,用到的工具为cc1。
汇编
#
- 汇编试讲汇编代码翻译成机器码,在linux上一般表现为ELF目标文件(OBJ文件),用到的工具为as。
链接
#
- 链接是将生成的OBJ代码和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为ld或collect2.
常用选项
#
参数 |
描述 |
-E |
预处理 |
-c |
把预处理、编译、汇编都做的,但是不链接 |
-o |
指定输出文件 |
-I |
指定头文件目录 |
-L |
指定链接时使用库文件的目录 |
-l(小L) |
指定链接哪一个库文件,如-labc就是链接libabc.so |
-v |
显示编译器驱动程序、预处理器、编译器办吧等信息。 |
-Wall |
打开所有需要注意的警告信息 |
#include <xxx> //在标准库的目录开始搜索(包括使用-Idir定义的目录)
#include "xxx" //先从用户的工作目录开始搜索,再搜索标准库目录
#创建动态链接库
gcc -shared -o libsub.so sub.o sub1.o
#使用当前目录下得sub库
gcc -o test main.o -lsub -L ./
#运行时需要指定动态库的位置,可以将libsub.so移动到/lib目录下,或者配置一个环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/xiaox
#生成静态库
ar crs libsub.a sub.o sub2.o
#将静态库打包到程序里
gcc -o tet main.o libsub.a
命令
#
#列出头文件目录、库目录(LIBRARY_PATH)等
echo 'main(){}'| gcc -E -v -
Makefile
#
target : prerequires1 prerequires2 ...
command1
command2
...
变量
#
A := XXX //立即变量
B = XXX //延时变量,在使用时才生效
C += yyy //拼接字符串
D ?= xxx //如果D之前定义过,则此语句无效;如果之前定义值,此语句定义。
A := $(C)
B = $(C)
C = abc
D ?= weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
#结果
#A = 立即变量定义式就确定,所以A的值为空
#B = abc 123 延时变量只有在用到时才确定,当执行make时,会解析Makefile里面的所有变量。
#D = weidongshan 因为D在前面没有定义,所以是此值。
通配符
#
*.c
表示所有的.c结尾的文件。
$@
表示target。
$<
表示第一个依赖文件。
$^
表示所有的依赖文件。
假想目标.PHONY
#
- target并非只要求是文件,也可以是个标记(假想目标),声明要采取的动作。
clean :
rm -rf *.o
.PHONY : clean
函数
#
#对于list中的每一个var,执行text的动作
$(foreach var,list,text)
#例
A := a b c
B := $(foreach f, $(A), $(f).c)
all :
@echo B = $(B)
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
#例
A := a b c dxx
B := $(filter %xx, $(A))
all :
@echo $(B)
#结果为dxx
#如果var中的值匹配pattern,就将它替换为replacement
$(patsubst pattern, replacement, $(var))
#例
files := a.c b.c c.c d.c e.c abc
dep_files := $(patsubst %.c,%.d,$(files))
all :
@echo dep_files = $(dep_files)
#结果为 dep_files = a.d b.d c.d d.d e.d abc
头文件依赖
#
- 通过命令,自动生成依赖的的头文件,当头文件发生变化时,重新生成对应的文件。
#-M获取c.c依赖的头文件
#-MF将依赖输出的c.d的文件
gcc -M -MF c.d c.c
objs = a.o b.o c.o
dep_files := $(patsubst %,.%.d, $(objs))
dep_files := $(wildcard $(dep_files))
test : $(objs)
gcc -o test $^
%.o : %.c
gcc -c -o $@ $< -M -MF .$@.d
ifneq ($(dep_files),)
include $(dep_files)
endif
clean:
rm *.o test
rm $(dep_files)
.PHONY: clean