UEFI基础EFI_HANDLE
Posted jiangwei0512
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI基础EFI_HANDLE相关的知识,希望对你有一定的参考价值。
EFI_HANDLE
EFI_HANDLE其实就是一个指针:
///
/// A collection of related interfaces.
///
typedef VOID *EFI_HANDLE;
下面简单介绍在EDK代码中可能的用法。
ImageHandle
一个普通的DXE模块的入口(以Snp.c举例):
EFI_STATUS
EFIAPI
InitializeSnpNiiDriver (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
这里的SystemTable不解释,但是ImageHandle又是什么呢?
这需要了解DXE Dispatcher里面的操作。
代码要从DxeMain.c中说起:
DxeMain()函数中有如下的代码:
//
// Initialize the DXE Dispatcher
//
PERF_START (NULL,"CoreInitializeDispatcher", "DxeMain", 0) ;
CoreInitializeDispatcher ();
PERF_END (NULL,"CoreInitializeDispatcher", "DxeMain", 0) ;
//
// Invoke the DXE Dispatcher
//
PERF_START (NULL, "CoreDispatcher", "DxeMain", 0);
CoreDispatcher ();
PERF_END (NULL, "CoreDispatcher", "DxeMain", 0);
在CoreDispatcher()函数里面,就会运行各个DXE模块。
最终模块运行起来的代码是:
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
这里的ImageHandle是下面的函数传进来的:
EFI_STATUS
EFIAPI
CoreStartImage (
IN EFI_HANDLE ImageHandle,
OUT UINTN *ExitDataSize,
OUT CHAR16 **ExitData OPTIONAL
)
而这个函数就在CoreDispatcher()中:
Status = CoreStartImage (DriverEntry->ImageHandle, NULL, NULL);
所以我们实际要找的ImageHandle是DriverEntry的一部分。
原本它的值是NULL:
//
// Load the DXE Driver image into memory. If the Driver was transitioned from
// Untrused to Scheduled it would have already been loaded so we may need to
// skip the LoadImage
//
if (DriverEntry->ImageHandle == NULL && !DriverEntry->IsFvImage)
DEBUG ((DEBUG_INFO, "Loading driver %g\\n", &DriverEntry->FileName));
Status = CoreLoadImage (
FALSE,
gDxeCoreImageHandle,
DriverEntry->FvFileDevicePath,
NULL,
0,
&DriverEntry->ImageHandle
);
在CoreLoadImage()中会有赋值的操作,再跟进去这个函数:
Status = CoreLoadImageCommon (
BootPolicy,
ParentImageHandle,
FilePath,
SourceBuffer,
SourceSize,
(EFI_PHYSICAL_ADDRESS) (UINTN) NULL,
NULL,
ImageHandle,
NULL,
EFI_LOAD_PE_IMAGE_ATTRIBUTE_RUNTIME_REGISTRATION | EFI_LOAD_PE_IMAGE_ATTRIBUTE_DEBUG_IMAGE_INFO_TABLE_REGISTRATION
);
还需要继续跟进CoreLoadImageCommon()函数:
//
// Success. Return the image handle
//
*ImageHandle = Image->Handle;
这里的Image->Handle又是什么?
//
// Allocate a new image structure
//
Image = AllocateZeroPool (sizeof(LOADED_IMAGE_PRIVATE_DATA));
Image其实就是各个被加载的模块的一个数据表现。
//
// Install the protocol interfaces for this image
// don't fire notifications yet
//
Status = CoreInstallProtocolInterfaceNotify (
&Image->Handle,
&gEfiLoadedImageProtocolGuid,
EFI_NATIVE_INTERFACE,
&Image->Info,
FALSE
);
如果一个模块被顺利加载了,就会安装Protocol。上面就是安装一个LoadedImageProtocol的代码。
在这个函数中:
//
// If caller didn't supply a handle, allocate a new one
//
Handle = (IHANDLE *)*UserHandle;
if (Handle == NULL)
Handle = AllocateZeroPool (sizeof(IHANDLE));
……
if (!EFI_ERROR (Status))
//
// Return the new handle back to the caller
//
*UserHandle = Handle;
可以看到ImageHandle的最终赋值。
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;
而ImageHandle的类型其实是VOID*:
///
/// A collection of related interfaces.
///
typedef VOID *EFI_HANDLE;
由于空指针能够只想任何结构体,所以这里也没有问题。
从IHANDLE的结构体可以看出,一个ImageHandle连接着所有的Handle,以及它下挂的所有Protocol。
总结:
ImageHandle是DXE模块对应的数据结构中的一个参数,在该模块运行的过程中,会在这个ImageHandle上安装一系列的接口(就是Protocol)。而这个ImageHandle本身会在整个UEFI运行过程中被放在一个EFI_HANDLE数据库中。
因此在某个DXE模块中实现的接口(Protocol),可以通过一系列的函数,先找到ImageHandle,再通过它找到对应的Protocol,然后在其它的地方调用这些Protocol。
这里举一个例子,下面是代码,可以在Shell下运行(可以在https://code.csdn.net/jiangwei0512/edk2-udk2017.git的BeniTest3.c中看到具体的代码):
// Get a handle, just find one, use it free.
//
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiSimpleTextOutProtocolGuid,
NULL,
&Handles,
&Buffer
);
if (EFI_ERROR (Status))
Print (L"LocateHandleBuffer failed. - %r\\r\\n", Status);
goto Error;
if (0 < Handles)
Status = gBS->HandleProtocol (
Buffer[0],
&gEfiSimpleTextOutProtocolGuid,
(VOID **)&Protocol
);
if (!EFI_ERROR (Status))
Print (L"Hanlde: 0x%08x.\\r\\n", Buffer[0]);
这里就是获取到一个Handle,然后打印它的值,结果如下:
查看这个位置的值:
前面4个字节就是IHANDLE的Signature。
所以实际上的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;
可以看到AllHandles这个链表,它其实是将所有的Handle连接在了一起,而Protocols也是个链表,它表示了所有安装在该Handle上的Protocols,最终就形成了如下的样子:
Controller
同样以Snp.c举例,其中包含着叫做UEFI Driver Model的东西,它的入口也有一个类型为EFI_HANDLE的参数:
EFI_STATUS
EFIAPI
SimpleNetworkDriverStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
);
这个函数是通过gBS->ConnectController()来调用的:
//
//Perform Connect
//
HandleCount = 0;
while (1)
OldHandleCount = HandleCount;
Status = gBS->LocateHandleBuffer (
AllHandles,
NULL,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status))
break;
if (HandleCount == OldHandleCount)
break;
for (Index = 0; Index < HandleCount; Index++)
gBS->ConnectController (HandleBuffer[Index], NULL, NULL, TRUE);
以上代码在BDS阶段执行,这里只是截取了一种形式,即获取DXE阶段安装的所有ImageHandle并作为参数传入。
实际上还有其它的方式,不过上面这种应该是最全面的了。
下面就需要看一下ConnectController()函数的实现,这个函数也是在DxeMain.c中初始化的:
(EFI_CONNECT_CONTROLLER) CoreConnectController, // ConnectController
//
// Connect all drivers to ControllerHandle
// If CoreConnectSingleController returns EFI_NOT_READY, then the number of
// Driver Binding Protocols in the handle database has increased during the call
// so the connect operation must be restarted
//
do
ReturnStatus = CoreConnectSingleController (
ControllerHandle,
DriverImageHandle,
AlignedRemainingDevicePath
);
while (ReturnStatus == EFI_NOT_READY);
而在CoreConnectSingleController()函数中:
Status = DriverBinding->Start (
DriverBinding,
ControllerHandle,
RemainingDevicePath
);
这里的ControllerHandle就是gBS->ConnectController()函数中传入的第一个参数。
也就是说ControllerHandle可以是ImageHandle。
当然由于并不是所有的DXE模块都有安装UEFIDriver Model所有并不是所有ImageHandle都是ControllerHandle。
另外还需要说明,UEFI Driver Model对应一个EFI_DRIVER_BINDING_PROTOCOL:
//
// Simple Network Protocol Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL gSimpleNetworkDriverBinding =
SimpleNetworkDriverSupported,
SimpleNetworkDriverStart,
SimpleNetworkDriverStop,
0xa,
NULL,
NULL
;
也就是SimpleNetworkDriverStart()函数的第一个参数。
这里还需要说一下SimpleNetworkDriverSupported()函数。它是一个条件判断函数,只有其中的函数满足了要求,才会运行对应的Start()函数,比如:
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
);
这里的Controller需要安装了DevicePathProtocol和NetworkInterfaceIdentifierProtocol这两个Protocol。
因此就需要在之前的某个模块中,比如某个网卡模块,它需要安装了这两个协议,然后才能使网卡模块对应的EFI_HANDLE之上安装SNP这样的网络传输协议。
有个问题?
网卡运行起来后安装NetworkInterfaceIdentifierProtocol是没有问题的,但是DevicePathProtocol需要在什么时候安装呢?PCI扫描的时候?
可能也是在网卡驱动运行的时候吧……
需要有源代码才好。
据我所知,网卡驱动也是UEFI Driver Model。
说到DevicePathProtocol,就需要提供了另一种gBS->ConnectController()的调用方式了:
Status = gBS->LocateDevicePath (
&gEfiDevicePathProtocolGuid,
&TempPciDevicePath,
&PciDeviceHandle
);
if (EFI_ERROR (Status))
return Status;
gBS->ConnectController (PciDeviceHandle, NULL, MyDevicePath, FALSE);
这里就是根据安装有DevicePathProtocol的EFI_HANDLE来ConnectController。
追踪代码可以看到在PCI扫描的时候有:
RegisterPciDevice (
IN EFI_HANDLE Controller,
IN PCI_IO_DEVICE *PciIoDevice,
OUT EFI_HANDLE *Handle OPTIONAL
)
EFI_STATUS Status;
VOID *PlatformOpRomBuffer;
UINTN PlatformOpRomSize;
UINT8 PciExpressCapRegOffset;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT8 Data8;
BOOLEAN HasEfiImage;
//
// Install the pciio protocol, device path protocol
//
Status = gBS->InstallMultipleProtocolInterfaces (
&PciIoDevice->Handle,
&gEfiDevicePathProtocolGuid,
PciIoDevice->DevicePath,
&gEfiPciIoProtocolGuid,
&PciIoDevice->PciIo,
NULL
);
if (EFI_ERROR (Status))
return Status;
所以在PCI扫描的时候,也会为设备创建EFI_HANDLE并安装对应的DevicePathProtocol。
总结:
ControllerHandle有两个主要来源,一个是ImageHandle,另外一个就是PCI扫描的时候创建的EFI_HANDLE。
其它还有没有不是很确定了。比如ChildHandle,不知道是不是也有一部分可以做ControllerHandle?
以上是关于UEFI基础EFI_HANDLE的主要内容,如果未能解决你的问题,请参考以下文章