UEFI实战DXE驱动相关
Posted jiangwei0512
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI实战DXE驱动相关相关的知识,希望对你有一定的参考价值。
简介
DXE阶段运行的驱动有两种类型,这里要说的不是Boot Service Driver和Runtime Service Driver(还不是很清除如何自己实现一个Runtime Service Driver以及如何使用,以后有机会的话再写吧)。
而是根据是否满足UEFI Driver Model来区分,一种是普通的Driver,一种就是满足UEFI Driver Model的驱动。
简单的说,前者是再编写驱动的时候就主动去寻找设备并初始化它,而后者是系统服务自己根据设备来寻到到合适的驱动然后初始化。前者的实现一般在驱动运行的时候就直接完成了,而后者需要先注册驱动,然后再后续(通常是BDS阶段)通过调用系统服务来完成,这个系统复位就是EFI_BOOT_SERVICES.ConnectController()。
下图是两种类型执行的简单图示:
(示例代码可以在vUDK2017: https://github.com/tianocore/edk2.git Tag 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就会成为操作正确设备的基础。
几个问题
下面说明几个常会问到的问题。
DXE驱动是在哪里开始的?
驱动被调用的基本函数是LoadImage()和StartImage(),它们是Boot Service,所以可以在DXE和BDS阶段的大部分地方调用。
在DxeMain.c里面的DxeMain()函数,里面的CoreDispatcher()就是用来执行各个驱动的,除非自己写代码,否则DXE驱动都会在这个位置执行。
UEFI Driver Model类型的驱动是在哪里执行的?
之前已经提到,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驱动以来与一个UEFI Driver Model类型的驱动要怎么处理?
之前一直以为普通的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驱动?
接前一个问题。
因为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()
以上是关于UEFI实战DXE驱动相关的主要内容,如果未能解决你的问题,请参考以下文章
UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动
UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动
UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级