出處:http://www.cppblog.com/djxzh/
本篇为UEFI实战系列第一部分。
UEFI实战前10个部分计划如下:
UEFI 实战(1) 开发环境
UEFI 实战(2) HelloWorld
UEFI 实战(1)
按惯例,首先让我们用HelloWorld跟UEFI打个招呼吧
标准application
#include <Uefi.h>
EFI_STATUS
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
SystemTable -> ConOut-> OutputString(SystemTable -> ConOut, L”HelloWorldn”);
return EFI_SUCCESS;
}
有以下几点需要注意:
1。 头文件, 所有的UEFI程序都有include <Uefi.h>
2。 main函数, UEFI 基本Application的main函数是UefiMain
3。 main函数的返回值类型 EFI_STATUS。 在UEFI中基本上所有的返回值类型都是EFI_STATUS。
4。 main函数的参数。.efi 文件加载到内存后称为Image, ImageHandle 用来描述、访问、控制此Image。 第二个参数是SystemTable,它是我们的程序同UEFI内核打交道的桥梁,通过它我们可以使用UEFI提供的各种服务,如Boot Services和 Runtime Services。 SystemTable是UEFI内核中的一个全局结构体。
5。 输出是通过EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的OutputString服务完成的。 服务(函数)的第一个参数是This指针,指向Protocol本身。 OutputString()的第二个参数是Unicode字符串。
要想编译main.c,我们还需要.inf文件, 在main.c所在的目录下编辑main.inf文件
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = main #输出文件的名字为 main.efi
FILE_GUID = 6987936E-ED34-ffdb-AE97-1FA5E4ED2117
MODULE_TYPE = UEFI_APPLICATION #模块类型:
,
,
,
,BASE,等
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain #入口函数
#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64 IPF EBC
#
# 源文件
[Sources]
main.c
# .dec里面定义 include的路径
[Packages]
MdePkg/MdePkg.dec
#要链接的库
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
[Protocols]
[FeaturePcd]
[Pcd.common]
[Guids]
#编译选项, = 表示选项附加到默认选项后面。 == 表示仅使用所定义的选项,弃用默认选项。
[BuildOptions]
#MSFT:*_*_*_CC_FLAGS == /nologo /c /WX /GS- /W4 /Gs32768 /D UNICODE /O1ib2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /D EFI_SPECIFICATION_VERSION=0x0002000A /D TIANO_RELEASE_VERSION=0x00080006 /FAs /Oi-
#MSFT:*_*_*_CC_FLAGS = /wd4804
#MSFT:Debug_*_IA32_CC_FLAGS =
#MSFT:Debug_*_X64_CC_FLAGS =
#MSFT:Release_*_IA32_CC_FLAGS =
#MSFT:Release_*_IA32_CC_FLAGS =
#MSFT:Release_*_IA32_DLINK_FLAGS =
#GCC:Release_*_IA32_CC_FLAGS =
然后将 main.inf 添加到 Nt32Pkg.dsc 或UnixPkg.dsc 的[Components]部分, 例如添加下面一行(example目录在EDK2下)
然后就可以使用BaseTools下的build进行编译了。
edksetup.bat
build -p Nt32Pkgt32Pkg.dsc -a IA32
Linux 执行
source ./edksetup.sh BaseTools
build -p UnixPkg/UnixPkg.dsc -a IA32
其他类型的inf文件
(1) 可以看出标准的application处理命令行参数不方便,UEFI提供了帮我们处理命令行参数的入口函数ShellCEntryLib。 我们要实现INTN ShellAppMain(UINTN Argc, CHAR16** Argv) 作为(开发者视角的)入口函数。
#include <Uefi.h>
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
gST -> ConOut-> OutputString(gST -> ConOut, L”HelloWorldn”);
return 0;
}
inf文件。 我们需要连接ShellCEntryLib 库。
INF_VERSION = 0x00010006
BASE_NAME = Main
FILE_GUID = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = ShellCEntryLib
#
# VALID_ARCHITECTURES = IA32 X64 IPF
#
[Sources]
Main.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
[LibraryClasses]
ShellCEntryLib
UefiLib
[BuildOptions]
(2)使用main函数的application。如果你想像C一样使用main函数,那么你需要用到LibC。 LibC 中提供了ShellAppMain函数,我们要提供 int main(int Argc, char** Argv) 供其调用。
#include <Uefi.h>
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
return 0;
}
真正的入口函数是 ShellCEntryLib, 调用过程为 ShellCEntryLib -> ShellAppMain -> main.
inf 文件: 我们需要连接 ShellCEntryLib 和LibC库。
INF_VERSION = 0x00010006
BASE_NAME = Main
FILE_GUID = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = ShellCEntryLib
#
# VALID_ARCHITECTURES = IA32 X64 IPF
#
[Sources]
Main.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
[LibraryClasses]
LibC
ShellCEntryLib
[BuildOptions]
MSFT:*_*_IA32_CC_FLAGS = /Oi-
还要再说明一点,如果你的程序中用到了printf(…)等等标准C的库函数,那么一定要使用此种类型的application。 因为 ShellCEntryLib 函数中会调用ShellAppMain(…), StdLib的ShellAppMain(…) 会对stdlib 进行初始化。 然后才可以调用stdlib的函数。 (当然,如果你已经清楚地了解了入口函数的处理流程,你也可以手工调用StdLib的ShellAppMain进行出事后).
(3)Lib 模块的inf文件。开发大型工程的时候我们会用到lib,例如我们要开发视频解码程序,会用到zlib库,
INF_VERSION = 0x00010005
BASE_NAME = zlib
FILE_GUID = 348aaa62-BFBD-4882-9ECE-C80BBbbbb736
VERSION_STRING = 1.0
MODULE_TYPE = BASE #Base 表示此模块编译为library
LIBRARY_CLASS = zlib
#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64 IPF EBC
#
[Sources]
adler32.c
crc32.c
deflate.c
infback.c
inffast.c
inflate.c
inftrees.c
trees.c
zutil.c
compress.c
uncompr.c
gzclose.c
gzlib.c
gzread.c
gzwrite.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
StdLib/StdLib.dec
[LibraryClasses]
MemoryAllocationLib
BaseLib
UefiBootServicesTableLib
BaseMemoryLib
UefiLib
UefiRuntimeServicesTableLib
[Protocols]
[FeaturePcd]
[Pcd]
[Guids]
[BuildOptions]
GCC:*_*_IA32_CC_FLAGS = -D__UEFI__ -DLARGEFILE64_SOURCE=1 -w
然后将
放到.dsc 文件 [LibraryClasses]中。 需要链接zlib的时候,在.inf文件的
中添加 zlib即可。
(4)driver模块的inf文件。例如DiskIo的inf(
)
INF_VERSION = 0x00010005
BASE_NAME = DiskIoDxe
FILE_GUID = 6B38F7B4-AD98-40e9-9093-ACA2B5A253C4
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = InitializeDiskIo
#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64 IPF EBC
#
# DRIVER_BINDING = gDiskIoDriverBinding
# COMPONENT_NAME = gDiskIoComponentName
# COMPONENT_NAME2 = gDiskIoComponentName2
#
[Sources]
ComponentName.c
DiskIo.h
DiskIo.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiBootServicesTableLib
MemoryAllocationLib
BaseMemoryLib
BaseLib
UefiLib
UefiDriverEntryPoint
DebugLib
[Protocols]
gEfiDiskIoProtocolGuid ## BY_START
gEfiBlockIoProtocolGuid ## TO_START
现在我们已经扫除了编译UEFI应用的所有障碍。 在下一部分,我们将了解开发UEFI一定用到的系统服务。
UEFI 实战(3) 用C++开发UEFI应用
crt0.cpp
#pragma section(“.CRTMP$XCA”,long,read)
#pragma section(“.CRTMP$XCZ”,long,read)
#pragma section(“.CRTMP$XIA”,long,read)
#pragma section(“.CRTMP$XIZ”,long,read)
#pragma section(“.CRTMA$XCA”,long,read)
#pragma section(“.CRTMA$XCZ”,long,read)
#pragma section(“.CRTMA$XIA”,long,read)
#pragma section(“.CRTMA$XIZ”,long,read)
#pragma section(“.CRTVT$XCA”,long,read)
#pragma section(“.CRTVT$XCZ”,long,read)
#pragma section(“.CRT$XCA”,long,read)
#pragma section(“.CRT$XCAA”,long,read)
#pragma section(“.CRT$XCB”,long,read)
#pragma section(“.CRT$XCC”,long,read)
#pragma section(“.CRT$XCZ”,long,read)
#pragma section(“.CRT$XDA”,long,read)
#pragma section(“.CRT$XDC”,long,read)
#pragma section(“.CRT$XDZ”,long,read)
#pragma section(“.CRT$XIA”,long,read)
#pragma section(“.CRT$XIAA”,long,read)
#pragma section(“.CRT$XIC”,long,read)
#pragma section(“.CRT$XID”,long,read)
#pragma section(“.CRT$XIY”,long,read)
#pragma section(“.CRT$XIZ”,long,read)
#pragma section(“.CRT$XLA”,long,read)
#pragma section(“.CRT$XLC”,long,read)
#pragma section(“.CRT$XLD”,long,read)
#pragma section(“.CRT$XLZ”,long,read)
#pragma section(“.CRT$XPA”,long,read)
#pragma section(“.CRT$XPX”,long,read)
#pragma section(“.CRT$XPXA”,long,read)
#pragma section(“.CRT$XPZ”,long,read)
#pragma section(“.CRT$XTA”,long,read)
#pragma section(“.CRT$XTB”,long,read)
#pragma section(“.CRT$XTX”,long,read)
#pragma section(“.CRT$XTZ”,long,read)
#pragma section(“.rdata$T”,long,read)
#pragma section(“.rtc$IAA”,long,read)
#pragma section(“.rtc$IZZ”,long,read)
#pragma section(“.rtc$TAA”,long,read)
//#pragma section(“.rtc$TZZ”,long,read)
}
typedef void (__cdecl *_PVFV)(void);
typedef int (__cdecl *_PIFV)(void);
typedef void (__cdecl *_PVFI)(int);
#define _CRTALLOC(x) __declspec(allocate(x))
#undef NULL
#define NULL 0
_CRTALLOC(“.CRT$XIA”) _PIFV __xi_a[] = { NULL };
_CRTALLOC(“.CRT$XIZ”) _PIFV __xi_z[] = { NULL };
_CRTALLOC(“.CRT$XCA”) _PVFV __xc_a[] = { NULL };
_CRTALLOC(“.CRT$XCZ”) _PVFV __xc_z[] = { NULL };
_CRTALLOC(“.CRT$XPA”) _PVFV __xp_a[] = { NULL };
_CRTALLOC(“.CRT$XPZ”) _PVFV __xp_z[] = { NULL };
_CRTALLOC(“.CRT$XTA”) _PVFV __xt_a[] = { NULL };
_CRTALLOC(“.CRT$XTZ”) _PVFV __xt_z[] = { NULL };
#pragma comment(linker, “/merge:.CRT=.rdata”)
_PVFV *atexits = NULL;
int num_atexit = 0;
int max_atexit =-1;
/** The atexit function registers the function pointed to by func, to be
called without arguments at normal program termination.
@return The atexit function returns zero if the registration succeeds,
nonzero if it fails.
**/
int
atexit(void (*handler)(void))
{
if(handler == NULL)
return 0;
if(num_atexit >= max_atexit){
max_atexit += 32;
_PVFV* old_handler = atexits;
atexits = new _PVFV[max_atexit];
if(atexits == NULL) {
atexits = old_handler;
return -1;
}
for(int i=0;i<max_atexit-32;i++)
atexits[i] = old_handler[i];
delete old_handler;
}
atexits[num_atexit++] = handler;
return 0;
}
void static _g_finit()
{
for(int i =num_atexit-1; i>= 0; i–){
if ( atexits[i] != NULL )
(*atexits[i])();
}
}
#pragma section(“.CRT$XPYZ”, long ,read)
_CRTALLOC(“.CRT$XPYZ”) _PVFV __xp_finitz[] = { _g_finit };
template<typename _PVFV>
void _minitterm (
_PVFV * pfbegin,
_PVFV * pfend
)
{
/*
* walk the table of function pointers from the bottom up, until
* the end is encountered. Do not skip the first entry. The initial
* value of pfbegin points to the first valid entry. Do not try to
* execute what pfend points to. Only entries before pfend are valid.
*/
while ( pfbegin < pfend )
{
/*
* if current table entry is non-NULL, call thru it.
*/
if ( *pfbegin != NULL )
(**pfbegin)();
++pfbegin;
}
}
void _Uefi_cpp_init()
{
_minitterm(__xc_a,__xc_z);
_minitterm(__xi_a,__xi_z);
}
void _Uefi_cpp_finit()
{
_minitterm(__xp_a,__xp_z);
_minitterm(__xt_a,__xt_z);
}
stduefi.cpp
#ifdef __cplusplus
extern “C”{
#endif
/** The malloc function allocates space for an object whose size is specified
by size and whose value is indeterminate.
This implementation uses the UEFI memory allocation boot services to get a
region of memory that is 8-byte aligned and of the specified size. The
region is allocated with type EfiLoaderData.
@param size Size, in bytes, of the region to allocate.
@return NULL is returned if the space could not be allocated and errno
contains the cause. Otherwise, a pointer to an 8-byte aligned
region of the requested size is returned.<BR>
If NULL is returned, errno may contain:
– EINVAL: Requested Size is zero.
– ENOMEM: Memory could not be allocated.
**/
__inline__ void * malloc(size_t Size)
{
void *RetVal;
EFI_STATUS Status;
if( Size == 0) {
//errno = EINVAL; // Make errno diffenent, just in case of a lingering ENOMEM.
return NULL;
}
Status = gBS->AllocatePool( EfiLoaderData, (UINTN)Size, &RetVal);
if( Status != EFI_SUCCESS) {
RetVal = NULL;
//errno = ENOMEM;
}
return RetVal;
}
/** The free function causes the space pointed to by Ptr to be deallocated,
that is, made available for further allocation.
If Ptr is a null pointer, no action occurs. Otherwise, if the argument
does not match a pointer earlier returned by the calloc, malloc, or realloc
function, or if the space has been deallocated by a call to free or
realloc, the behavior is undefined.
@param Ptr Pointer to a previously allocated region of memory to be freed.
**/
__inline__ void free(void *Ptr)
{
(void) gBS->FreePool (Ptr);
}
#ifdef __cplusplus
}// end of extern “C”下载示例
#endif
__inline__ void * operator new( size_t cb )
{
void *res;
res = malloc(cb);
return res;
}
__inline__ void * operator new[]( size_t cb )
{
void *res = operator new(cb);
return res;
}
__inline__ void operator delete( void * p )
{
free( p );
}
__inline__ void operator delete[]( void * p )
{
operator delete(p);
}
main.cpp
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
_Uefi_cpp_init();
//! Do your work here
_Uefi_cpp_finit();
}
UEFI 实战(4) protocol
从字面意思上看,protocol是server和client之间的一种约定,双方根据这种约定互通信息。这里的server和client是一种广义的称呼,提供服务的称为server,使用服务的称为client。 TCP是一种protocol, client(应用程序)通过一组函数来压包和解包,压包和解包是server提供的服务。COM也是一种protocol,client通过CoCreateInstance(…)和GUID获得指向COM对象的指针,然后使用该指针获得COM对象提供的服务, GUID标示了这个COM对象。现在我们对protocol有了概念上的理解,那么具体到UEFI里,protocol是什么样子呢? 如何标示一个protocol?如何得到protocol对应的对象?…容我慢慢道来.
在讲protocol什么样子之前,还要插几句C与C++的区别。我们知道UEFI是用C来开发的,C是面向过程的一种语言。而管理和使用UEFI众多的protocol完全使用面向过程的思想会使程序变得复杂。protocol作为一种对象来设计管理会比较直观。因而UEFI中的Protocol引入了面向对象的思想,用struct来模拟class, Protocol用struct来实现,用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一参数必须是指向Protocol的指针,用来模拟this指针。
Protocol的摸样
以EFI_DISKIO_PROTOCOL 来看看Protocol的样子。
:220
/// This protocol provides control over block devices.///struct _EFI_BLOCK_IO_PROTOCOL {
///
/// The revision to which the block IO interface adheres. All future
/// revisions must be backwards compatible. If a future version is not
/// back wards compatible, it is not the same GUID.
/// UINT64 Revision;
///
/// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
/// EFI_BLOCK_IO_MEDIA *Media;
EFI_BLOCK_RESET Reset;
EFI_BLOCK_READ ReadBlocks;
EFI_BLOCK_WRITE WriteBlocks;
EFI_BLOCK_FLUSH FlushBlocks;
};
extern EFI_GUID gEfiBlockIoProtocolGuid;
:220
{
0x964e5b21, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b }
}
typedef struct _EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;
来看成员函数的声明
/**
Read BufferSize bytes from Lba into Buffer.
@param This Indicates a pointer to the calling context.
@param MediaId Id of the media, changes every time the media is replaced.
@param Lba The starting Logical Block Address to read from
@param BufferSize Size of Buffer, must be a multiple of device block size.
@param Buffer A pointer to the destination buffer for the data. The caller is
responsible for either having implicit or explicit ownership of the buffer.
@retval EFI_SUCCESS The data was read correctly from the device.
@retval EFI_DEVICE_ERROR The device reported an error while performing the read.
@retval EFI_NO_MEDIA There is no media in the device.
@retval EFI_MEDIA_CHANGED The MediaId does not matched the current device.
@retval EFI_BAD_BUFFER_SIZE The Buffer was not a multiple of the block size of the device.
@retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid,
or the buffer is not on proper alignment.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
IN EFI_BLOCK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN EFI_LBA Lba,
IN UINTN BufferSize,
OUT VOID *Buffer
);
EFI_BLOCK_READ具体用法我们先不看,我们来看它的第一个参数,指向EFI_BLOCK_IO_PROTOCOL 对象自己的this指针,这是成员函数区别于一般函数的重要特征。
如何使用Protocol
使用Protocol之前,我们要弄清楚Protocol位于什么地方。首先我们要来认识一下EFI_HANDLE,
/// A collection of related interfaces.///typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。 UEFI扫描总线后,会为每个设备建立一个Controller对象,用于控制设备,所有该设备的驱动以protocol的形式安装到这个controller中,这个Controller就是一个EFI_HANDLE对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象(此Image非图像的意识), 这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,EFI_HANDLE被理解为IHANDLE
/// IHANDLE – contains a list of protocol handles///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;
每个IHANDLE中都有一个Protocols链表,存放属于自己的protocol。所有的IHANDLE通过AllHandles链接起来。
要使用Protocol,首先要找到protocol对象,可以通过BootServices的OpenProtocol(…), HandleProtocl(…), LocateProtocol(…)获得。
EFI_STATUS
Handle是Protocol的提供者,如果Handle的Protocols链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED 。
如果在驱动中调用OpenProtocol(), AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle;ControllerHandle是拥有该驱动的Controller。
如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数。 ControllerHandle此时可以忽略。
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
HandleProtocol是OpenProtocol的简化版,因为大部分情况下我们都不需要关心AgentHandle,ControllerHandle和Attributes。
EFIAPI
CoreHandleProtocol (
IN EFI_HANDLE UserHandle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
)
{
return CoreOpenProtocol (
UserHandle,
Protocol,
Interface,
gDxeCoreImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
}
LocateProtocol(…)是从内核中找出指定Protocol的第一个实例。
EFI_STATUS
LocateProtocol (
IN EFI_GUID *Protocol,
IN VOID *Registration OPTIONAL,
OUT VOID **Interface
);
UEFI内核中某个Protocol的实例可能不止一个,例如每个硬盘及每个分区都有一个EFI_DISK_IO_PROTOCOL实例。LocateProtocol顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。
我们可以用BootServices提供的其它函数处理HANDLE和Protocol。
EFI_STATUS
LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID
*SearchKey OPTIONAL,
IN OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
);
可以获得所有支持指定Protocol的HANDLE,SearchType 有三种:AllHandles(查找所有HANDLE), ByRegisterNotify, ByProtocol(查找支持指定Protocol的HANDLE)。NoHandles是找到的HANDLE的数量, Buffer数组由UEFI复杂分配,由用户负责释放。
EFI_STATUS
LocateHandle (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
IN OUT UINTN *BufferSize,
OUT EFI_HANDLE *Buffer
);
与LocateHandleBuffer相似,只是用户负责分配和释放Buffer数组。
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID ***ProtocolBuffer,
OUT UINTN *ProtocolBufferCount
);
获得指定Handle所支持的所有Protocol, UEFI负责分配内存给ProtocolBuffer,用户负责释放该内存。
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
OUT UINTN *EntryCount
);
OpenProtocolInformation()获得指定Handle中指定Protocol的打开信息。
SPEC2.3.1第165页有很好的例子演示了怎么打开一个Protocol,
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL *This;
IN EFI_HANDLE ControllerHandle,
extern EFI_GUID gEfiXyzIoProtocol;
EFI_XYZ_IO_PROTOCOL *XyzIo;
EFI_STATUS Status;
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
打开Protocol之后就可以使用了,最后要通过CloseProtocol关闭打开的Protocol。
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);
通过HandleProtocol和LocateProtocol打开的Protocol因为没有指定AgentHandle,所以无法关闭。如果一定要去关闭它,要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后关闭它。
下面看一个完整的例子,用EFI_DISK_IO_PROTOCOL读取GPT硬盘的分区表
EFI_STATUS
EFIAPI
UefiMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN HandleIndex, HandleCount;
EFI_HANDLE *DiskControllerHandles = NULL;
EFI_DISK_IO_PROTOCOL *DiskIo;
/*找到所有提供 EFI_DISK_IO_PROTOCOL 的Controller */
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiDiskIoProtocolGuid,
NULL,
&HandleCount,
&DiskControllerHandles);
if (!EFI_ERROR(Status)) {
CHAR8 gptHeaderBuf[512]; EFI_PARTITION_TABLE_HEADER* gptHeader = (EFI_PARTITION_TABLE_HEADER*
)gpHeaderBuf;
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
/*打开EFI_DISK_IO_PROTOCOL */
Status = gBS->HandleProtocol(
DiskControllerHandles[HandleIndex],
&gEfiDiskIoProtocolGuid,
(VOID**)&DiskIo);
if (!EFI_ERROR(Status)){
{
EFI_DEVICE_PATH_PROTOCOL *DiskDevicePath;
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *Device2TextProtocol = 0;
CHAR16* TextDevicePath = 0;
/*1. 打开EFI_DEVICE_PATH_PROTOCOL */
Status = gBS->OpenProtocol(
DiskControllerHandles[HandleIndex],
&gEfiDevicePathProtocolGuid,
(VOID**)&DiskDevicePath,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if(!EFI_ERROR(Status)){
if(Device2TextProtocol == 0)
Status = gBS->LocateProtocol(
&gEfiDevicePathToTextProtocolGuid,
NULL,
(VOID**)&Device2TextProtocol
);
/*2. 使用 EFI_DEVICE_PATH_PROTOCOL 得到文本格式的Device Path */
TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, TRUE, TRUE);
Print(L”%sn”, TextDevicePath);
if(TextDevicePath)gBS->FreePool(TextDevicePath);
/*3. 关闭 EFI_DEVICE_PATH_PROTOCO */
Status = gBS->CloseProtocol(
DiskControllerHandles[HandleIndex],
&gEfiDevicePathProtocolGuid,
ImageHandle,
);
}
}
{
EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1);
EFI_BLOCK_IO_MEDIA* Media = BlockIo->Media;
/*读1号扇区。 */
Status = DiskIo->ReadDisk(DiskIo, Media->MediaId, 512, 512, gptHeader);
/*检查GPT标志。 */
if((!EFI_ERROR(Status)) &&( gptHeader -> Header.Signature == 0x5452415020494645)){
UINT32 CRCsum;
UINT32 GPTHeaderCRCsum = (gptHeader->Header.CRC32);
gptHeader->Header.CRC32 = 0;
gBS -> CalculateCrc32(gptHeader , (gptHeader->Header.HeaderSize), &CRCsum);
if(GPTHeaderCRCsum == CRCsum){
// Find out a GPT Header
}
}
}
}
}
gBS->FreePool(DiskControllerHandles);
}
}
========================================================================
上一节我们讲了服务型驱动,它有如下几个特点:
1. 在Image的入口函数中执行安装。
2. 服务型驱动不需要驱动特定硬件,可以安装到任意controller上。
3. 没有提供卸载函数。
一个真正的驱动程序,在安装时首先要找到对应的硬件设备(在UEFI中是要找到对应的Controller), 然后执行安装操作,将驱动程序安装到硬件设备的controller上。 有时候我们还需要卸载驱动,更新驱动(先卸载旧的驱动,然后安装新的驱动)。 有时候安装操作可能需要执行多次,例如:第一次安装时发现设备没有准备好,或者所依赖的某个Protocol没有安装,就需要退出安装,执行其他操作,然后进行第二次安装。
那么我们可以总结出一个完整的驱动程序框架需要三部分:
1. Findout() 找出对应的硬件设备
2. Install() 安装驱动到指定的硬件设备 或者说 Start() 启动硬件设备
3. Uninstall()从硬件设备中卸载驱动 或者说 Stop() 停止硬件设备。
另外很重要的一点是框架必须支持多次安装。 上一节我们实现的驱动是不能多次安装的(在入口函数中执行安装),如果首次安装失败例如InstallProtocolInterface(…)返回错误,我们只能unload 驱动文件(image),从新loadImage。
我们来看UEFI驱动框架是如何实现这几点的。
在UEFI驱动的入口函数中,安装EFI Driver Binding Protocol到某个Handle(大部分情况下是自身即ImageHandle, 有时也会安装到其它Handle上), 这个Driver Binding Protocol实例会常驻内存,用于驱动的安装和卸载。使用DriverBindingProtocol使得我们可以多次操作(查找设备,安装卸载)驱动.
在Driver Binding Protocol中实现了框架的三个部分的接口。下面是DriverBindingProtocol的声明:
EFI_DRIVER_BINDING_SUPPORTED Supported;
EFI_DRIVER_BINDING_START Start;
EFI_DRIVER_BINDING_STOP Stop;
///
/// The version number of the UEFI driver that produced the
/// EFI_DRIVER_BINDING_PROTOCOL. This field is used by
/// the EFI boot service ConnectController() to determine
/// the order that driver’s Supported() service will be used when
/// a controller needs to be started. EFI Driver Binding Protocol
/// instances with higher Version values will be used before ones
/// with lower Version values. The Version values of 0x0-
/// 0x0f and 0xfffffff0-0xffffffff are reserved for
/// platform/OEM specific drivers. The Version values of 0x10-
/// 0xffffffef are reserved for IHV-developed drivers.
/// UINT32 Version;
///
/// The image handle of the UEFI driver that produced this instance
/// of the EFI_DRIVER_BINDING_PROTOCOL.
/// EFI_HANDLE ImageHandle;
///
/// The handle on which this instance of the
/// EFI_DRIVER_BINDING_PROTOCOL is installed. In most
/// cases, this is the same handle as ImageHandle. However, for
/// UEFI drivers that produce more than one instance of the
/// EFI_DRIVER_BINDING_PROTOCOL, this value may not be
/// the same as ImageHandle.
/// EFI_HANDLE DriverBindingHandle;
};
核心是Support,Start,Stop三个成员函数,对应我们总结的三个部分。
首先看Support函数,简单来讲,如果ControllerHandle是我们要找的Controoler,该函数返回EFI_SUCCESS, 否则返回EFI_UNSUPPORTED、 EFI_ACCESS_DENIED或EFI_ALREADY_STARTED等等。
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL // Device driver 忽略该参数,
//Bus Driver(例如MdeModulePkg/Bus/Pci/PciBusDxe/PciBus.c)才会使用该参数。
);
再看Start函数,Start()用来启动硬件设备,在函数中最重要的事情是调用InstallProtocolInterface()或者InstallMultipleProtocolInterfaces()在ControllerHandle上安装驱动Protocol。
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_START) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
Stop函数,用于卸载驱动(调用UninstallProtocolInterface()或UninstallMultipleProtocolInterfaces()从ControllerHandle卸载驱动协议),并停止硬件设备。
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_STOP) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
);
对Device Driver来讲,NumberOfChildren 为0, ChildHandleBuffer 为NULL。 对Bus Driver来讲,如果NumberOfChildren不为零,那么ChildHandleBuffer中的子节点都要被释放。我们分别研究了驱动框架的三个部分,这三个部分是如何联系起来的呢?看下面的代码(MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:CoreConnectSingleController),可以大致了解UEFI驱动程序框架是如何工作的。
//
// 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);
看MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:876
ChildrenToStop = 0;
Status = EFI_SUCCESS;
if (ChildBufferCount > 0) {
if (ChildHandle != NULL) {
ChildrenToStop = 1;
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, &ChildHandle);
} else {
ChildrenToStop = ChildBufferCount;
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, ChildBuffer);
}
}
if (!EFI_ERROR (Status) && ((ChildHandle == NULL) || (ChildBufferCount == ChildrenToStop))) {
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, 0, NULL);
}
if (!EFI_ERROR (Status)) {
StopCount++;
}
}
了解了各个部分的细节后,我们在看一遍加载Driver的整个过程。
首先在Shell中使用命令Load 将Driver文件加载到内存, Load后UEFI会调用gBS->StartImage(…) 执行DriverImage的入口函数, 在入口函数里Driver Binding Protocol被加载到Handle上(Driver Image handle 或者其它的Controller handle),然后UEFI会遍历所有的Controller,调用Driver Binding Protocol的Supported 函数测试这个Driver是否支持该Controller,如果支持则调用Start()安装驱动。
EFI Component Name Protocol
编写Driver
驱动分为两部分,一部分是硬件相关的部分,这部分是驱动的内容,用于驱动硬件设备,为用户提供服务,以协议的形式出现,例如DiskIo,BlockIo。 另一部分驱动的框架部分,需要实现Driver Binding Protocol,主要是其三个接口(Supported, Start, Stop),这部分用于驱动的安装与卸载。硬件相关部分不是本节讲述的重点。
本节主要讲述Device Driver如何实现框架部分。
Spec中详细描述了如何编写Supported, Start, Stop 三个函数,下面的斜体内容翻译至Spec2.3
Supported函数要点(Spec2.3:339)
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 所需的所有Protocols成功打开,则测试这个Driver是否支持此Controller。有时使用这些Protocols足以完成测试,有时还需要此Controller的其它特征。如果任意一项测试失败,则用CloseProtocol()关闭所有打开的Protocol,返回EFI_UNSUPPORTED.
5. 测试成功,调用CloseProtocol()关闭所有已经打开的Protocols。
6. 返回 EFI_SUCCESS。
Start函数要点(spec2.3:345)
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 初始化ControllerHandle所指的设备。如果有错误,则关闭所有已打开的Protocols并返回 EFI_DEVICE_ERROR。
5. 分配并初始化要用到的数据结构,这些数据结构包括驱动Protocols及其它相关的私有数据结构。如果分配资源时发生错误,则关闭所有已打开的Protocols,释放已经得到的资源,返回EFI_OUT_OF_RESOURCES。
6. 用InstallMultipleProtocolInterfaces()安装驱动协议到ControllerHandle。如果有错误发生,则关闭所有已打开的Protocols,并返回错误代码。
7. 返回EFI_SUCESS。
Stop函数要点(spec2.3:)
2. 关闭所有已打开的Protocols。
3. 释放所有已申请的资源。
下面我们以AC97 控制器驱动为例,介绍如何编写Device driver。南桥芯片中集成的AC97控制器是一种PCI设备,在开始写驱动之前让我们补充一点PCI设备驱动及AC97声卡的背景知识。
首先让我们建立实验环境, 在QEMU中选择开启 Audio ,类型选择为Intel AC97.
PCI设备及PciIo
每个PCI设备都有三种地址空间:配置空间,IO空间和内存空间。
系统初始化时系统会初始化每个PCI设备的配置空间寄存器。配置地址空间大小为256字节,前64字节是标准的,后面的寄存器由设备自定义用途。
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf | |
0x00 | Vendor ID | Device ID | Command Reg. | Status Reg. | Revision ID | Class Code | Cache Line | Latency Timer | Header Type | BIST | ||||||
0x10 | Base Address 0 | Base Address 1 | Base Address 2 | Base Address 3 | ||||||||||||
0x20 | Base Address 4 | Base Address 5 | Card Bus CIS pointer | Subsytem Vendor ID | Subsystem Device ID | |||||||||||
0x30 | Expansion ROM Base Address | IRQ Line | IRQ Pin | Min_Gnt | Max_lat |
例如Qemu中AC97的配置空间内容如下
UINT16 VendorId :8086
UINT16 DeviceId :2415
UINT16 Command :7
UINT16 Status :280
UINT8 RevisionID : 1
UINT8 ClassCode[2] : 4
UINT8 ClassCode[1] : 1
UINT8 ClassCode[0] : 0
UINT8 CacheLineSize :0
UINT8 LatencyTimer : 0
UINT8 HeaderType : 0
UINT8 BIST : 0
Bar[0] : C001
Bar[1] : C401
Bar[2] : 0
Bar[3] : 0
Bar[4] : 0
Bar[5] : 0
PCI设备中的IO和内存空间被划分为1~6个互补重叠的子空间,每个子空间用于完成一组相对独立的子功能。Base Address 0 ~5 表示子空间的基地址(物理地址)。
对设备的操作主要是通过对子空间的读写来实现的。 UEFI提供了EFI_PCI_IO_PROTOCOL来操作PCI设备。
/// The EFI_PCI_IO_PROTOCOL provides the basic Memory, I/O, PCI configuration,/// and DMA interfaces used to abstract accesses to PCI controllers./// There is one EFI_PCI_IO_PROTOCOL instance for each PCI controller on a PCI bus./// A device driver that wishes to manage a PCI controller in a system will have to/// retrieve the EFI_PCI_IO_PROTOCOL instance that is associated with the PCI controller.///struct _EFI_PCI_IO_PROTOCOL {
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem;
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;
EFI_PCI_IO_PROTOCOL_ACCESS Mem;
EFI_PCI_IO_PROTOCOL_ACCESS Io;
EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;
EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem;
EFI_PCI_IO_PROTOCOL_MAP Map;
EFI_PCI_IO_PROTOCOL_UNMAP Unmap;
EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
EFI_PCI_IO_PROTOCOL_FLUSH Flush;
EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation;
EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes;
EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
///
/// The size, in bytes, of the ROM image.
/// UINT64 RomSize;
///
/// A pointer to the in memory copy of the ROM image. The PCI Bus Driver is responsible
/// for allocating memory for the ROM image, and copying the contents of the ROM to memory.
/// The contents of this buffer are either from the PCI option ROM that can be accessed
/// through the ROM BAR of the PCI controller, or it is from a platform-specific location.
/// The Attributes() function can be used to determine from which of these two sources
/// the RomImage buffer was initialized.
/// VOID *RomImage;
};
今天我们只用到 EFI_PCI_IO_PROTOCOL_ACCESS Io; 和 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; 这两个子功能, EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS 用于读写配置空间, EFI_PCI_IO_PROTOCOL_ACCESS用于读写Io空间。EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS :
Enable a PCI driver to access PCI controller registers in PCI configuration space.
@param This A pointer to the EFI_PCI_IO_PROTOCOL instance.
@param Width Signifies the width of the memory operations.
@param Offset The offset within the PCI configuration space for the PCI controller.
@param Count The number of PCI configuration operations to perform.
@param Buffer For read operations, the destination buffer to store the results. For write
operations, the source buffer to write data from.
@retval EFI_SUCCESS The data was read from or written to the PCI controller.
@retval EFI_UNSUPPORTED The address range specified by Offset, Width, and Count is not
valid for the PCI configuration header of the PCI controller.
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
@retval EFI_INVALID_PARAMETER Buffer is NULL or Width is invalid.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG)(
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
IN UINT32 Offset,
IN UINTN Count,
IN OUT VOID *Buffer
);
typedef struct {
///
/// Read PCI controller registers in PCI configuration space.
/// EFI_PCI_IO_PROTOCOL_CONFIG Read;
///
/// Write PCI controller registers in PCI configuration space.
/// EFI_PCI_IO_PROTOCOL_CONFIG Write;
} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS;
EFI_PCI_IO_PROTOCOL_ACCESS:
/**
Enable a PCI driver to access PCI controller registers in the PCI memory or I/O space.
@param This A pointer to the EFI_PCI_IO_PROTOCOL instance.
@param Width Signifies the width of the memory or I/O operations.
@param BarIndex The BAR index of the standard PCI Configuration header to use as the
base address for the memory or I/O operation to perform.
@param Offset The offset within the selected BAR to start the memory or I/O operation.
@param Count The number of memory or I/O operations to perform.
@param Buffer For read operations, the destination buffer to store the results. For write
operations, the source buffer to write data from.
@retval EFI_SUCCESS The data was read from or written to the PCI controller.
@retval EFI_UNSUPPORTED BarIndex not valid for this PCI controller.
@retval EFI_UNSUPPORTED The address range specified by Offset, Width, and Count is not
valid for the PCI BAR specified by BarIndex.
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_IO_MEM)(
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width, // 每个元素的大小
IN UINT8 BarIndex, // 0~5中的一个
IN UINT64 Offset, // 偏移
IN UINTN Count, // 元素个数
IN OUT VOID *Buffer
);
typedef struct {
///
/// Read PCI controller registers in the PCI memory or I/O space.
/// EFI_PCI_IO_PROTOCOL_IO_MEM Read;
///
/// Write PCI controller registers in the PCI memory or I/O space.
/// EFI_PCI_IO_PROTOCOL_IO_MEM Write;
} EFI_PCI_IO_PROTOCOL_ACCESS;
例如读第0个bar里偏移为4的寄存器,
Status = PciIo->Io.Read(PciIo,
EfiPciIoWidthUint8 , //读一个字节
0, // Bar 0
4, // 偏移4字节
1, // 一个元素
&Data);
AC97
Intel芯片组南桥中一般有AC97控制器芯片,AC97控制器通过AC-LINK与AC97 Codec进行通信,我们的音频驱动其实是AC97控制器驱动。
如何操作AC97控制器可以参考Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97, 这儿只做简单介绍。
AC97 IO空间有两个部分:Native Audio Bus Master Control Registers and Native Mixer Registers。
Bar0 表示 Native Mixer Registers(NAMBAR) , 访问以16-bits为一个单位(与AC-LINK单位数据大小一致)。 用于配置AC97参数,如音量,采样率等。
Bar1 表示Bus Master Control Registers 。 用于控制AC97
Native Mixer Registers 功能表
(Codec ID =00)
00h Reset
02h Master Volume
04h Aux Out Volume
06h Mono Volume
08h Master Tone (R & L)
0Ah PC_BEEP Volume
0Ch Phone Volume
0Eh Mic Volume
10h Line In Volume
12h CD Volume
14h Video Volume
16h Aux In Volume
18h PCM Out Volume
1Ah Record Select
1Ch Record Gain
1Eh Record Gain Mic
20h General Purpose
22h 3D Control
24h AC ’97 RESERVED
26h Powerdown Ctrl/Stat
28h Extended Audio
2Ah Extended Audio Ctrl/Stat
2Ch PCM Front DAC Rate
2Eh PCM Surround DAC Rate
30h PCM LFE DAC Rate
32h PCM LR ADC Rate
34h MIC ADC Rate
36h 6Ch Vol: C, LFE
38h 6Ch Vol: L, R Surround
3Ah S/PDIF Control
3C~56h Intel RESERVED
58h AC ’97 Reserved
5Ah Vendor Reserved
7Ch Vendor ID1
7Eh Vendor ID2
Bus Master Control Registers 功能表
Mnemonic | Name | Default | Access | |
00h | PI_BDBAR | PCM In Buffer Descriptor list Base Address | 00000000h | R/W |
04h | PI_CIV | PCM In Current Index Value | 00h | RO |
05h | PI_LVI | PCM In Last Valid Index | 00h | R/W |
06h | PI_SR | PCM In Status | 0001h | R/WC, RO |
08h | PI_PICB | PCM In Position in Current Buffer | 0000h | RO |
0Ah | PI_PIV | PCM In Prefetched Index Value | 00h | RO |
0Bh | PI_CR | PCM In Control | 00h | R/W, R/W (special) |
10h | PO_BDBAR | PCM Out Buffer Descriptor list Base Address | 00000000h | R/W |
14h | PO_CIV | PCM Out Current Index Value | 00h | RO |
15h | PO_LVI | PCM Out Last Valid Index | 00h | R/W |
16h | PO_SR | PCM Out Status | 0001h | R/WC, RO |
18h | PO_PICB | PCM In Position In Current Buffer | 0000h | RO |
1Ah | PO_PIV | PCM Out Prefetched Index Value | 00h | RO |
1Bh | PO_CR | PCM Out Control | 00h | R/W, R/W (special) |
20h | MC_BDBAR | Mic. In Buffer Descriptor List Base Address | 00000000h | R/W |
24h | MC_CIV | Mic. In Current Index Value | 00h | RO |
25h | MC_LVI | Mic. In Last Valid Index | 00h | R/W |
26h | MC_SR | Mic. In Status | 0001h | R/WC, RO |
28h | MC_PICB | Mic. In Position In Current Buffer | 0000h | RO |
2Ah | MC_PIV | Mic. In Prefetched Index Value | 00h | RO |
2Bh | MC_CR | Mic. In Control | 00h | R/W, R/W (special) |
2Ch | GLOB_CNT | Global Control | 00000000h | R/W, R/W (special) |
30h | GLOB_STA | Global Status | See register description | R/W, R/WC, RO |
34h | CAS | Codec Access Semaphore | 00h | R/W (special) |
40h | MC2_BDBAR | Mic. 2 Buffer Descriptor List Base Address | 00000000h | R/W |
44h | MC2_CIV | Mic. 2 Current Index Value | 00h | RO |
45h | MC2_LVI | Mic. 2 Last Valid Index | 00h | R/W |
46h | MC2_SR | Mic. 2 Status | 0001h | RO, R/WC |
48h | MC2_PICB | Mic 2 Position In Current Buffer | 0000h | RO |
4Ah | MC2_PIV | Mic. 2 Prefetched Index Value | 00h | RO |
4Bh | MC2_CR | Mic. 2 Control | 00h | R/W, R/W (special) |
50h | PI2_BDBAR | PCM In 2 Buffer Descriptor List Base Address | 00000000h | R/W |
54h | PI2_CIV | PCM In 2 Current Index Value | 00h | RO |
55h | PI2_LVI | PCM In 2 Last Valid Index | 00h | R/W |
56h | PI2_SR | PCM In 2 Status | 0001h | R/WC, RO |
58h | PI2_PICB | PCM In 2 Position in Current Buffer | 0000h | RO |
5Ah | PI2_PIV | PCM In 2 Prefetched Index Value | 00h | RO |
5Bh | PI2_CR | PCM In 2 Control | 00h | R/W, R/W (special) |
60h | SPBAR | S/PDIF Buffer Descriptor List Base Address | 00000000h | R/W |
64h | SPCIV | S/PDIF Current Index Value | 00h | RO |
65h | SPLVI | S/PDIF Last Valid Index | 00h | R/W |
66h | SPSR | S/PDIF Status | 0001h | R/WC, RO |
68h | SPPICB | S/PDIF Position In Current Buffer | 0000h | RO |
6Ah | SPPIV | S/PDIF Prefetched Index Value | 00h | RO |
6Bh | SPCR | S/PDIF Control | 00h | R/W, R/W (special) |
80h | SDM | SData_IN Map | 00h | R/W, RO |
表中有5个通道
每个通道有一个16-bit DMA引擎,用于传输音频数据(PCM格式)。 DMA引擎使用Buffer Descriptor List 数据结构获得数据缓冲区地址。Buffer Descriptor List 最多允许32项,Buffer Descriptor 声明如下:
UINT32 addr;
UINT16 len;
unsigned short reserved:14;
unsigned short BUP:1;
unsigned short IOC:1;
} BufferDescriptor;
addr的第0个bit必须是0。 List中最后一个BufferDescriptor 的BUP需为1。每个buffer最多有65536个采样。
启动DMA的过程如下(翻译自:Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97 Programmer’s Reference Manual (PRM) :30页):
1. 建立buffer descriptor list。
2. 将buffer descriptor list 的基地址写入Buffer Descriptor List Base Address register(对PCM Out而言是 MBBAR + 10h (POBAR))
3. 填充buffer descriptor list 中的buffer descriptor,并设置好数据缓存。
4. 设置 Last Valid Index(LVI)寄存器 (PCM OUT: MBBAR + 15h (POLVI))。从头开始播放则设为0。
5. 设置Control register中的run bit,启动DMA传输。
这时就可以听到声音了。
AC97驱动
现在我们开始设计AC97驱动。时候还记得我们讲过驱动分两个部分,硬件相关部分和框架部分。首先来设计硬件相关部分,也就是驱动硬件并提供给用户使用的协议。
我们把要提供的服务命名为EFI_AUDIO_PROTOCOL,在EFI_AUDIO_PROTOCOL, 我们提供播放音频的服务,首先我们要提供硬件初始化服务,还要提供PCM音频播放服务,音量调节服务,还要提供一个Event用来通知用户音频播放结束。
在audio.h中定义EFI_AUDIO_PROTOCOL相关数据结构
struct _EFI_AUDIO_PROTOCOL{
UINT64 Revision;
EFI_AC97_RESET Reset;
EFI_AC97_PLAY Play;
EFI_AC97_VOLUME Volume;
EFI_EVENT WaitForEndEvent;
}
设计EFI_AUDIO_PROTOCOL的GUID
{
0xce345171, 0xabcd, 0x11d2, {0x8e, 0x4f, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b }
}
在accdriver.c中还要定义用于标示音频播放上下文的数据结构,一般命名为X_PRIVATE_DATA。在上下文中我们要包含EFI_AUDIO_PROTOCOL实例,设备的EFI_PCI_IO_PROTOCOL实例,BufferDescriptor。
UINTN Signature;
EFI_AUDIO_PROTOCOL Audio;
EFI_PCI_IO_PROTOCOL *PciIo;
BufferDescriptor Bdes[32];
} AUDIO_PRIVATE_DATA;
定义EFI_AUDIO_PROTOCOL Template,用于在Start函数中初始化EFI_AUDIO_PROTOCOL实例
// Template for Audio private data structure.// The pointer to Audio protocol interface is assigned dynamically.//AUDIO_PRIVATE_DATA gDiskIoPrivateDataTemplate = {
AUDIO_PRIVATE_DATA_SIGNATURE,
{
EFI_AUDIO_PROTOCOL_REVISION,
AC97Reset,
AC97Play,
AC97Volume,
0
},
NULL,
{0}
};
下面要实现 EFI_AUDIO_PROTOCOL 中定义的三个服务,每个成员函数(服务)的第一个参数是This指针,在函数里首先要根据This指针获得上下文Private,然后根据上下文执行相应操作。
首先是Reset,主要是设置Mixer Register里德Powerdown Ctrl/Stat和Reset寄存器, 可以参考WinDDK里AC97驱动示例。
/**
@param This Indicates a pointer to the calling context.
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Reset(
IN EFI_AUDIO_PROTOCOL *This
)
{
NTSTATUS InitAC97 (void);
EFI_STATUS Status;
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
Status = InitAC97 ();
return Status;
}
然后是Play函数, 首先设置Buffer Descriptor List,然后设置POBAR和PO_LVI , 最后启动DMA。
@param This Indicates a pointer to the calling context.
@param PcmData Pointer to PCM Data
@param Format PCM Data Format
@param Size How many Samples in PCM Data
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Play(
IN EFI_AUDIO_PROTOCOL *This,
IN UINT8* PcmData,
IN UINT32 Format,
IN UINTN Size
)
{
EFI_STATUS Status;
UINTN i=0, LenLeft = Size;
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
for( i=0; i< 32 && LenLeft > 0; i++, LenLeft-=65536){
Private->Bdes[0].addr = (u32)(PcmData + 65536 * 4 * i);
Private->Bdes[0].len = (u16)(LenLeft <= 65536 ? LenLeft:LenLeft-65536);
}
Private->Bdes[i-1].BUP = 1;
Private->Bdes[i-1].IOC = 0;
WriteBMControlRegister32(PO_BDBAR , (u32)Private->Bdes);
WriteBMControlRegister(PO_LVI , 0);
(void) Status;
return EFI_SUCCESS;
}
设置音量的函数
@param This Indicates a pointer to the calling context.
@param Increase How much should the volume change,
+Number increase; -Number Decrease.
@param NewVolume if *NewVolume >=0 , It will set the volume as *NewVolume;
if *NewVolume <0, the Volume will be changed by Increase,
and *Newvolume returns the current Volume.
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Volume(
IN EFI_AUDIO_PROTOCOL *This,
IN INT32 Increase,
IN OUT INT32 * NewVolume
)
{
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
if(*NewVolume < 0){
WORD data= (WORD) (long ) NewVolume;
WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
}else{
WORD data= 0;
ReadCodecRegister(AC97REG_PCM_OUT_VOLUME, &data);
data += (INT16) Increase;
WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
*NewVolume = (INT32)data;
}
return EFI_SUCCESS;
}
驱动的框架部分
驱动的框架部分主要是实现EFI_DRIVER_BINDING_PROTOCOL及Image的初始化函数
// Driver binding protocol implementation for AC97 driver.//EFI_DRIVER_BINDING_PROTOCOL gAudioDriverBinding = {
AC97DriverBindingSupported,
AC97DriverBindingStart,
AC97DriverBindingStop,
0xa,
NULL,
NULL
};
(1)Supported(AC97DriverBindingSupported)函数用来检测设备是否AC97驱动器。分两步:1。 判断Controller时候有EFI_PCI_IO_PROTOCOL , 没有则返回错误。2。 有EFI_PCI_IO_PROTOCOL 则读取PCI配置空间,判断设备时候AC97驱动器。
Test to see if this driver supports ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to test
@param RemainingDevicePath Optional parameter use to pick a specific child
device to start.
@retval EFI_SUCCESS This driver supports this device
@retval EFI_ALREADY_STARTED This driver is already running on this device
@retval other This driver does not support this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
PCI_TYPE00 PciData;
EFI_PCI_IO_PROTOCOL *PciIo;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
(VOID**)&PciIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint32,
0,
sizeof (PciData) / sizeof (UINT32),
&PciData
);
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
if (EFI_ERROR (Status)) {
return Status;
}
if (!(PciData.Hdr.ClassCode[2] == PCI_CLASS_MEDIA && PciData.Hdr.ClassCode[1] == PCI_CLASS_MEDIA_AUDIO && PciData.Hdr.ClassCode[0] == 0x00) ) {
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
(2) Start(AC97DriverBindingStart) 启动设备,安装EFI_AUDIO_PROTOCOL。
Start this driver on ControllerHandle by opening a PCI IO protocol and
installing a Audio IO protocol on ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to bind driver to
@param RemainingDevicePath Optional parameter use to pick a specific child
device to start.
@retval EFI_SUCCESS This driver is added to ControllerHandle
@retval EFI_ALREADY_STARTED This driver is already running on ControllerHandle
@retval other This driver does not support this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
AUDIO_PRIVATE_DATA *Private;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
(VOID**)&PciIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Allocate a buffer to store the ATA_ATAPI_PASS_THRU_INSTANCE data structure
// Private = AllocateCopyPool (sizeof (AUDIO_PRIVATE_DATA), &gDiskIoPrivateDataTemplate );
if (Private == NULL) {
goto ErrorExit;
}
Private->PciIo = PciIo;
Status = gBS->CreateEvent(EVT_NOTIFY_WAIT, TPL_NOTIFY, (EFI_EVENT_NOTIFY)PlayEndEventNoify, (VOID*)Private, &Private->Audio.WaitForEndEvent);
Status = gBS->InstallProtocolInterface (
&ControllerHandle,
&gEfiAudioProtocolGUID,
EFI_NATIVE_INTERFACE,
&Private->Audio
);
ErrorExit:
if (EFI_ERROR (Status)) {
if (Private != NULL) {
FreePool (Private);
}
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}else{
// Init Ac97
AC97Reset(&Private->Audio);
}
return Status;
}
(3)Stop(AC97DriverBindingStop)函数
/**
Stop this driver on ControllerHandle by removing Audio IO protocol and closing
the PCI IO protocol on ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to stop driver on
@param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of
children is zero stop the entire bus driver.
@param ChildHandleBuffer List of Child Handles to Stop.
@retval EFI_SUCCESS This driver is removed ControllerHandle
@retval other This driver was not removed from this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
AUDIO_PRIVATE_DATA *Private;
EFI_AUDIO_PROTOCOL *Audio;
//
// Get our context back.
// Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiAudioProtocolGUID,
(VOID **) &Audio,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
Status = gBS->UninstallProtocolInterface (
ControllerHandle,
&gEfiAudioProtocolGUID,
&Private->Audio
);
if (!EFI_ERROR (Status)) {
Status = gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}
if (!EFI_ERROR (Status)) {
FreePool (Private);
}
return Status;
}
最后要在Image的入口函数安装EFI_DRIVER_BINDING_PROTOCOL
EFI_STATUS
EFIAPI
InitializeACC(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Install driver model protocol(s).
// Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gAudioDriverBinding,
ImageHandle,
&gAudioComponentName,
&gAudioComponentName2
);
//ASSERT_EFI_ERROR (Status);
return Status;
}
自此,驱动模型就介绍完了。
首先找出FAT分区的controller,然后打开EFI_SIMPLE_FILE_SYSTEM_PROTOCOL, 如果系统中只有一个FAT分区,用gBS->LocateProtocol(…)就可以了。EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 只提供了一个方法OpenVolume,用来打开设备,获得 EFI_FILE_PROTOCOL实例。
UINT64 Revision;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume;
} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
typedef
EFI_STATUS
(EFIAPI *EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME) (
IN EFI_SIMPLE_FILE_SYSTEM PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **Root
);
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
EFI_FILE_PROTOCOL *FileProtocol;
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID**)&SimpleFileSystem
);
if (EFI_ERROR(Status)) {
Print(L”Open FileSystemProtocol errn”);
return (EFI_NOT_FOUND);
}else {
Print(L”Open FileSystemProtocol OKn”);
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &FileProtocol);
得到该分区上EFI_FILE_PROTOCOL实例的指针后,就可以操作该分区上的文件了。
UINT64 Revision;
EFI_FILE_OPEN Open;
EFI_FILE_CLOSE Close;
EFI_FILE_DELETE Delete;
EFI_FILE_READ Read;
EFI_FILE_WRITE Write;
EFI_FILE_GET_POSITION GetPosition;
EFI_FILE_SET_POSITION SetPosition;
EFI_FILE_GET_INFO GetInfo;
EFI_FILE_SET_INFO SetInfo;
EFI_FILE_FLUSH Flush;
} EFI_FILE_PROTOCOL;
读写文件与我们常用的标准C读写文件大同小异。例如写文件
CHAR16 *Lbuf= (CHAR16*)L”This is test filen”;
Status = FileProtocol->Open(FileProtocol, &FileHandle, ( CHAR16*)L”testfileprotocol.txt”, EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE , 0);
CheckStatus(L” EFI_FILE_PROTOCOL Create file “);
if( FileHandle && !EFI_ERROR(Status)) {
LbufSize = StrLen(Lbuf) * 2;
Status = FileProtocol->Write (FileHandle, &LbufSize, Lbuf);
CheckStatus(L”EFI_FILE_PROTOCOL Write file “);
Status = FileProtocol->Close (FileHandle);
CheckStatus(L”EFI_FILE_PROTOCOL Close file “);
}
下面是Open/Read/Write/Close的函数原型,以供参考
EFI_STATUS
(EFIAPI *EFI_FILE_OPEN) (
IN EFI_FILE_PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **NewHandle,
IN CHAR16 *FileName,
IN UINT64 OpenMode,
IN UINT64 Attributes
);
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_CLOSE) (
IN EFI_FILE_PROTOCOL *This
);
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_READ) (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
);
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_WRITE) (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
);
值得注意的是:
1. Open函数的第一个参数是EFI_FILE_PROTOCOL实例的指针。其它函数的第一个参数是通过Open得到的文件Handle,其类型被强制转换成了EFI_FILE_PROTOCOL*.
2. Open函数的第三个参数是绝对路径,例如本例中的testfileprotocol.txt会产生在该分区的根目录。
typedef struct _EFI_SHELL_PROTOCOL {
EFI_SHELL_EXECUTE Execute;
EFI_SHELL_GET_ENV GetEnv;
EFI_SHELL_SET_ENV SetEnv;
EFI_SHELL_GET_ALIAS GetAlias;
EFI_SHELL_SET_ALIAS SetAlias;
EFI_SHELL_GET_HELP_TEXT GetHelpText;
EFI_SHELL_GET_DEVICE_PATH_FROM_MAP GetDevicePathFromMap;
EFI_SHELL_GET_MAP_FROM_DEVICE_PATH GetMapFromDevicePath;
EFI_SHELL_GET_DEVICE_PATH_FROM_FILE_PATH GetDevicePathFromFilePath;
EFI_SHELL_GET_FILE_PATH_FROM_DEVICE_PATH GetFilePathFromDevicePath;
EFI_SHELL_SET_MAP SetMap;
EFI_SHELL_GET_CUR_DIR GetCurDir;
EFI_SHELL_SET_CUR_DIR SetCurDir;
EFI_SHELL_OPEN_FILE_LIST OpenFileList;
EFI_SHELL_FREE_FILE_LIST FreeFileList;
EFI_SHELL_REMOVE_DUP_IN_FILE_LIST RemoveDupInFileList;
EFI_SHELL_BATCH_IS_ACTIVE BatchIsActive;
EFI_SHELL_IS_ROOT_SHELL IsRootShell;
EFI_SHELL_ENABLE_PAGE_BREAK EnablePageBreak;
EFI_SHELL_DISABLE_PAGE_BREAK DisablePageBreak;
EFI_SHELL_GET_PAGE_BREAK GetPageBreak;
EFI_SHELL_GET_DEVICE_NAME GetDeviceName;
EFI_SHELL_GET_FILE_INFO GetFileInfo;
EFI_SHELL_SET_FILE_INFO SetFileInfo;
EFI_SHELL_OPEN_FILE_BY_NAME OpenFileByName;
EFI_SHELL_CLOSE_FILE CloseFile;
EFI_SHELL_CREATE_FILE CreateFile;
EFI_SHELL_READ_FILE ReadFile;
EFI_SHELL_WRITE_FILE WriteFile;
EFI_SHELL_DELETE_FILE DeleteFile;
EFI_SHELL_DELETE_FILE_BY_NAME DeleteFileByName;
EFI_SHELL_GET_FILE_POSITION GetFilePosition;
EFI_SHELL_SET_FILE_POSITION SetFilePosition;
EFI_SHELL_FLUSH_FILE FlushFile;
EFI_SHELL_FIND_FILES FindFiles;
EFI_SHELL_FIND_FILES_IN_DIR FindFilesInDir;
EFI_SHELL_GET_FILE_SIZE GetFileSize;
EFI_SHELL_OPEN_ROOT OpenRoot;
EFI_SHELL_OPEN_ROOT_BY_HANDLE OpenRootByHandle;
EFI_EVENT ExecutionBreak;
UINT32 MajorVersion;
UINT32 MinorVersion;
} EFI_SHELL_PROTOCOL;
testFile(IN EFI_HANDLE ImageHandle)
{
EFI_STATUS Status;
SHELL_FILE_HANDLE FileHandle;
UINTN WbufSize;
UINTN RbufSize = 256;
CHAR16 *Wbuf= (CHAR16*)L”This is test filen”;
CHAR16 *Rbuf= new CHAR16[256] ;
//////////////////////////// Create&Write ///////////////////////////////////////////// Status = gEfiShellProtocol->CreateFile((CONST CHAR16*)L”testfile.txt”, EFI_FILE_MODE_WRITE, &FileHandle);
CheckStatus(L”Create file “);
LbufSize = StrLen(Lbuf) * 2;
Status = gEfiShellProtocol->WriteFile(FileHandle, &WbufSize, Wbuf);
CheckStatus(L”Write file “);
Status = gEfiShellProtocol->CloseFile(FileHandle);
CheckStatus(L”Close file “);
//////////////////////////// Read ///////////////////////////////////////////// for(UINTN i =0; i< 256;i++) Rbuf[i] = 0;
Status = gEfiShellProtocol->OpenFileByName((CONST CHAR16*)L”testfile.txt”, &FileHandle, EFI_FILE_MODE_READ);
CheckStatus(L”Open file “);
RbufSize = 256;
Status = gEfiShellProtocol->ReadFile(FileHandle, &RbufSize ,Rbuf );
AsciiPrint(“Reading %d: %sn”,RbufSize, (CHAR8*)Rbuf);
DebugStep (L”read”);
Status = gEfiShellProtocol->CloseFile(FileHandle);
CheckStatus(L”Close file “);
return EFI_SUCCESS;
}
testfile.txt会产生在当前目录下。