DXE阶段运行的驱动有两种类型,这里要说的不是Boot Service Driver和Runtime Service Driver(还不是很清除如何自己实现一个Runtime Service Driver以及如何使用,以后有机会的话再写吧)。
而是根据是否满足UEFI Driver Model来区分,一种是普通的Driver,一种就是满足UEFI Driver Model的驱动。
简单的说,前者是再编写驱动的时候就主动去寻找设备并初始化它,而后者是系统服务自己根据设备来寻到到合适的驱动然后初始化。前者的实现一般在驱动运行的时候就直接完成了,而后者需要先注册驱动,然后再后续(通常是BDS阶段)通过调用系统服务来完成,这个系统复位就是EFI_BOOT_SERVICES.ConnectController()。
下图是两种类型执行的简单图示:
(示例代码可以在https://gitee.com/jiangwei0512/vUDK2017找到)
两种类型驱动的代码结构上类似,主要的区别是驱动的入口做了什么。
下面是一个驱动的inf文件的定义部分:
[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
最后一行是C代码的入口,对于普通的驱动,这个入口里面就是初始化设备的函数。
而对于符合UEFI Driver Model的驱动来说,它只是简单的安装了一个Protocol,以SnpDxe.inf模块为例:
EFI_STATUS
EFIAPI
InitializeSnpNiiDriver (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gSimpleNetworkDriverBinding,
ImageHandle,
&gSimpleNetworkComponentName,
&gSimpleNetworkComponentName2
);
}
这里的重点在于gSimpleNetworkDriverBinding这个Protocol,它的形式如下:
//
// Simple Network Protocol Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL gSimpleNetworkDriverBinding = {
SimpleNetworkDriverSupported,
SimpleNetworkDriverStart,
SimpleNetworkDriverStop,
0xa,
NULL,
NULL
};
所有的符合UEFI Driver Model的驱动都会安装一个如上结构的Protocol,在《UEFI Spec》里面有对该类型Protocol的详细介绍。
简单来说,就是DXE阶段安装了一大堆这种Protocol,然后gBS->ConnectController的时候,首先会执行xxxSupported()函数,如果返回的是EFI_SUCCESS,则会继续执行xxxStart()函数,而这个函数中就包含设备初始化所需要的代码。
还是以SnpDxe.inf模块为例,它的xxxSupported()函数如下:
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. 当扫描的这个设备的时候(设备用Controller表示),先判断它是否安装了DevicePathProtocol,没有就表示这个设备还没有准备好(或者说不是设备),后面的xxxStart()不用执行;
2. 然后判断NetworkInterfaceIdentifierProtocol是否安装,这个是网卡驱动一定会装的Protocol,Snp驱动底层的操作需要依赖于它,所以一定要安装,如果没有就不会执行后面的操作;
3. 判断NetworkInterfaceIdentifierProtocol是否满足要求,如果不满足则不会执行xxxStart()函数。
如果以上条件都满足,就可以认为该设备是一个网卡,然后这个驱动就会被执行,而之前获取到的DevicePathProtocol和NetworkInterfaceIdentifierProtocol就会成为操作正确设备的基础。
下面说明几个常会问到的问题。
驱动被调用的基本函数是LoadImage()和StartImage(),它们是Boot Service,所以可以在DXE和BDS阶段的大部分地方调用。
在DxeMain.c里面的DxeMain()函数,里面的CoreDispatcher()就是用来执行各个驱动的,除非自己写代码,否则DXE驱动都会在这个位置执行。
之前已经提到,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);
在BDS阶段,有不少位置可以调用,这里不一一说明。
另外,在UEFI Shell下有一个connect命令:
配合load命令可以手动到导入驱动和初始化设备。
之前一直以为普通的DXE驱动只能在DXE阶段运行,但是实际上普通的DXE驱动也可以在BDS阶段运行,只要写好依赖就行。
比如某个驱动依赖与gEfiPciIoProtocolGuid,而这个Protocol是在BDS阶段的PCIE扫描的时候安装的,只要在INF中加上这个依赖,那么它就会在BDS阶段运行,比如DxeDriverInBds.inf驱动:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = DxeDriverInBds
FILE_GUID = 04687443-0174-498F-A2F9-08F3A5363F84
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = DxeDriverInBdsEntry
#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64 IPF EBC
#
[Sources.common]
DxeDriverInBds.c
DxeDriverInBds.h
[Packages]
MdePkg/MdePkg.dec
OemPkg/OemPkg.dec
[LibraryClasses]
UefiLib
BaseLib
BaseMemoryLib
MemoryAllocationLib
UefiBootServicesTableLib
UefiRuntimeServicesTableLib
UefiDriverEntryPoint
DebugLib
[Guids]
[Protocols]
gEfiPciIoProtocolGuid ## CONSUMES
[Depex]
gEfiPciIoProtocolGuid
它增加了对PciIoProtocol的依赖,对应的代码如下:
EFI_STATUS
EFIAPI
DxeDriverInBdsEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
EFI_HANDLE *HandleArray;
UINTN HandleArrayCount;
UINTN Index;
UINT16 VendorId;
UINT16 DeviceId;
DEBUG ((EFI_D_ERROR, "[beni]DxeDriverInBdsEntry start.\n"));
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiPciIoProtocolGuid,
NULL,
&HandleArrayCount,
&HandleArray
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[beni]LocateHandleBuffer failed. - %r\n", Status));
return Status;
}
for (Index = 0; Index < HandleArrayCount; Index++) {
Status = gBS->HandleProtocol (
HandleArray[Index],
&gEfiPciIoProtocolGuid,
(VOID **)&PciIo
);
if (!EFI_ERROR (Status)) {
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint16,
0x00,
1,
&VendorId
);
if (EFI_ERROR (Status)) {
continue;
}
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint16,
0x02,
1,
&DeviceId
);
if (EFI_ERROR (Status)) {
continue;
}
DEBUG ((EFI_D_ERROR, "[beni]VendorId: 0x%04x, DeviceId: 0x%04x.\n", VendorId, DeviceId));
}
}
DEBUG ((EFI_D_ERROR, "[beni]DxeDriverInBdsEntry end.\n"));
if (NULL != HandleArray) {
FreePool (HandleArray);
HandleArray = NULL;
}
return EFI_SUCCESS;
}
QEMU下运行的串口打印如下:
[beni]DxeDriverInBdsEntry start.
[beni]VendorId: 0x8086, DeviceId: 0x1237.
[beni]VendorId: 0x8086, DeviceId: 0x7000.
[beni]VendorId: 0x8086, DeviceId: 0x7010.
[beni]VendorId: 0x8086, DeviceId: 0x7113.
[beni]VendorId: 0x1234, DeviceId: 0x1111.
[beni]VendorId: 0x8086, DeviceId: 0x100E.
[beni]DxeDriverInBdsEntry end.
可以看到它能够正常运行,能够在BDS阶段运行并找到PciIoProtocol。
接前一个问题。
因为BDS阶段调用到了普通的DXE驱动,那么到底是在什么地方调用的呢?
查看串口信息:
Terminal - Mode 0, Column = 80, Row = 25
Terminal - Mode 1, Column = 80, Row = 50
Terminal - Mode 2, Column = 100, Row = 31
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H
InstallProtocolInterface: 09576E91-6D3F-11D2-8E39-00A0C969723B 7101C18
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H
InstallProtocolInterface: 387477C1-69C7-11D2-8E39-00A0C969723B 71013C0
InstallProtocolInterface: DD9E7534-7762-4698-8C14-F58517A625AA 71014A0
InstallProtocolInterface: 387477C2-69C7-11D2-8E39-00A0C969723B 71013D8
InstallProtocolInterface: D3B36F2B-D551-11D4-9A46-0090273FC14D 0
InstallProtocolInterface: D3B36F2C-D551-11D4-9A46-0090273FC14D 0
InstallProtocolInterface: D3B36F2D-D551-11D4-9A46-0090273FC14D 0
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H[0m[35m[40m
Loading driver 04687443-0174-498F-A2F9-08F3A5363F84
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 70FE0C0
Loading driver at 0x000047CB000 EntryPoint=0x000047CB374 DxeDriverInBds.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 70FEC98
[beni]DxeDriverInBdsEntry start.
从上面的Loading driver可以看到,调用的函数是gDS->Dispatch()。
追代码可以看到,对于OVMF里面,调用的路径如下:
MdeModulePkg\Universal\BdsDxe\BdsDxe.inf --->
BdsEntry() --->
EfiBootManagerConnectAllDefaultConsoles() --->
EfiBootManagerConnectConsoleVariable() --->
EfiBootManagerConnectDevicePath()