至强服务器BIOS/UEFI驱动开发笔记
- 驱动开发基础
-
- Hello UEFI Driver 项目
-
- 选择项目位置
- 初始化驱动代码文件结构
- 驱动程序入口和基本功能
- 导入AMI工程
- AMI平台Hello UEFI Driver 编译问题
- 测试结果
- 打印设备列表
-
- 继续开发`HelloWorldSupported`函数
- 依赖配置
- 使用脚本编译
- 编译测试此DXE驱动模块
- 改进`HelloWorldSupported`函数
- 问题
- 继续实验
- AMI实战
-
- SDL和CIF
-
- 以界面方式增加工程
- 以代码方式增加工程
- RoboVeb
- 踩过的坑
- AMI VEB构建技巧
-
- AMI VEB命令行构建
- AMI构建单个工程
- AMI App开发
-
- 关键函数和协议
- 主要步骤
- HellWorld.c
- 测试结果
- PCI Driver开发
-
- 基于EDKII工程的HelloWorldDxe
- 测试结果
- 继续开发`Supported`函数
- 测试结果
- AMI PCI 驱动开发
-
- 新增加的DXE驱动放到和BIOS固件的哪里?
- U盘和键盘全消失等问题
- 矛盾的根源
- UDK2015
-
- 编译环境
-
- Windows编译环境
- OvmfPkg
- VS2008安装问题
- UDK2017
- OVMF 2015
- 经典的DXE驱动案例
-
- VGA驱动
- 仿照VGA驱动修改MyPciDxe
- 以上代码迁移到服务器
- 驱动的其它属性
-
- 工具类函数
- 简化的Supported函数与初步的Start函数
- 驱动的名字
- 实验使用的CPU架构Broardwell。
- EFI App的构建过程实际上先构建可以在OS上运行的动态库/可执行文件,然后利用PE32+工具改为UEFI运行。
- UEFI基于GObject(https://docs.gtk.org/gobject/)用C模拟OOP或C++。
- JRE 1.7安装目录整个拷贝到
VisualeBios.exe
所在目录,并改名为jre。则Visual Bios的启动不需要安装JRE。卸载JRE 1.7验证。 - UEFI编译系统强制要求函数的局部变量统一声明在函数体的头部,否则报错。
- GRUB运行在BDS阶段,因为GRUB运行期间未调用ExitBootServices方法,实际调用此方法的是OS Loader。
- UEFI环境特点
- 支持X86、X64、ARM等平台
- 单核CPU,没有线程,没有进程
- 没有抢断/优先级
- 没有中断,唯一的路径是定时器
- 模块内部通讯通过Protocols(协议)和Events(事件)
- C语言编程(原文:Programming is done through C language,实际上有汇编)
- 包的声明用dsc,模块的声明用inf,模块的依赖用dec
驱动开发基础
Hello UEFI Driver 项目
用UEFI Shell装载驱动进行测试。受载板平台限制,测试工程放在AMI项目里。
选择项目位置
与UEFI App开发不同,驱动代码所处项目架构应当与硬件构成映射关系。如果驱动代码与驱动硬件不在相同架构,则开发者需要手动处理固件布局才成封装成为正确的固件。我亲自踩坑证明这个说法:
关键错误消息:Build\GetPpiName.c(1) : fatal error C1083: Cannot open include file: '/RELEASE_MYTOOLS/PpiTableIA32.c': No such file or directory
。不知道驱动项目存放须按规定的开发者会认为这个问题是玄学问题,怎么生成的代码找不到生成的代码呢?建议参照下图和项目结构选择合适的UEFI驱动存储放置:
根据驱动目标选择合适的项目位置。本示例的目标为USB键盘驱动,因此选择MdeModulePkg/Bus。当然,如果你已对BIOS固件布局已非常清楚,你可以随意。
初始化驱动代码文件结构
新建目录HelloWorldDxe
,新建HelloWorldDxe.inf
,代码如下:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME=HelloWorldDxe
FILE_GUID = de296c9d-8bac-08bc-ac6d-db2998aff781
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 0.1
ENTRY_POINT = DriverMain
[Sources]
HelloWorldDxe.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
BaseLib
BaseMemoryLib
DebugLib
MemoryAllocationLib
PrintLib
UefiDriverEntryPoint
UefiLib
驱动程序入口和基本功能
新建HelloWorldDxe.c
,代码如下:
#include <Uefi.h>
#include <Protocol/DriverBinding.h>
#include <Protocol/ComponentName2.h>
#include <Protocol/ComponentName.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#define HELLOWORLD_VERSION 0x10
EFI_STATUS EFIAPI HelloWorldStart(
IN EFI_DRIVER_BINDING_PROTOCOL* This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL* RemainingDevicePath
)
{
EFI_STATUS status = EFI_SUCCESS;
Print(L"[HelloWorldStart] HelloWorld driver started.\n");
return status;
}
EFI_STATUS EFIAPI HelloWorldSupported(
IN EFI_DRIVER_BINDING_PROTOCOL* This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL* RemainingDevicePath
)
{
EFI_STATUS status = EFI_SUCCESS;
Print(L"[HelloWorldSupported] HelloWorld driver supported.\n");
return status;
}
EFI_STATUS EFIAPI HelloWorldStop(
IN EFI_DRIVER_BINDING_PROTOCOL* This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE* ChildHandleBuffer
)
{
EFI_STATUS status = EFI_SUCCESS;
Print(L"[HelloWorldStop] HelloWorld driver stopped.\n");
return status;
}
EFI_DRIVER_BINDING_PROTOCOL g_helloworld_driver_binding = {
HelloWorldSupported,
HelloWorldStart,
HelloWorldStop,
HELLOWORLD_VERSION,
NULL,
NULL
};
EFI_STATUS EFIAPI DriverMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE* SystemTable
)
{
EFI_STATUS status = EFI_SUCCESS;
status = EfiLibInstallDriverBindingComponentName2(
ImageHandle,
SystemTable,
&g_helloworld_driver_binding,
ImageHandle,
NULL,
NULL
);
ASSERT_EFI_ERROR(status);
return status;
}
导入AMI工程
- 在ModuleExplorer视图中依次展开:
Components
、Core
、MdeModulePkg
、LibraryInstances
,右键单击,选择弹出菜单Add INF Module
。关于Select EDK Project Root
选项,选择结果无论是否正确,导入结果都存在错误。下文第2步和第3步就是纠错。
- 找到导入的工程,移动生成的
sdl
文件到inf
文件所在目录,并更名。
- 按下图所示修改
MdeModulePkg\Library\LibraryInstances.cif
- 关掉VeB软件,重新打开。编译报错:
<work root>\Build\GrangevillePkg\RELEASE_MYTOOLS\X64\MdeModulePkg\Bus\HelloWorldDxe\HelloWorldDxe\DEBUG\AutoGen.h(16) : fatal error C1083: Cannot open include file: 'Uefi.h': No such file or directory
。原因是HelloWorldDxe.inf
有错。具体错误是Package
依赖错误地写成dsc
,正确的做法是写成dec
AMI平台Hello UEFI Driver 编译问题
-
AMI的工程不能运行EDKII提供的命令
-
VeB不允许编译单个驱动
这是假象。实际原因是VeB没有把导入INF生成的
sdl
文件放到inf
所在目录。解决办法是移动sdl
文件到inf
文件所在目录并修正上级cif
文件中的错误。
测试结果
- UEFI Shell运行运行
load
指令可见大量的HelloWorld
输出,说明UEFI驱动管理支持一个设备绑定多个驱动,不会因为某个设备已存在绑定的驱动而停止匹配新驱动。UEFI如何选择调用哪个驱动呢?驱动的版本的如何在驱动选择中发挥作用的? - 服务器启动慢问题存在新证据,证据指向问题发生在SEC或者PEI阶段。下图右边神秘的数字,在
HelloWorldDxe
集成进BIOS固件之前不确定它显示时CPU执行阶段。现在可以证明处于DXE阶段。那么,从通电到HelloWorldDxe
产生输出的大约10秒钟时间,很可能都处于SEC和PEI阶段。现在没有确定串口设备未初始化造成的HelloWorldDxe
无输出的时间。
-
EFI Shell启动时会再执行一次设备驱动管理过程。
-
UEFI驱动管理在得到
Supported
函数的返回结果为EFI_SUCCESS后立即调用Start
函数。上图Supported输出与Start输出成对出现无间断说明这一点。 -
任何驱动应在EFI Shell中先
load
试运行。否则驱动出错造成很大的麻烦。DXE出错的结果是载板变砖头,救砖的办法可能只有把FLASH从电路板上焊下来,烧好程序后再焊上去。 -
固件烧录与固件运行时不同。以下截图的实验:
- A版本包含HelloWorldDxe驱动,B版本与2023XXXX版本相同,唯一的区别是重新编译。编译环境、工具完全相同。
- 固件升级到A版本,重启后驱动绑定过程出现大量的HelloWorld打印
- 固件升级到B版本,重启时控制台出现大量的HelloWorld打印
打印设备列表
继续开发HelloWorldSupported
函数
目标:以字符串形式输出所有Device关键字,尝试寻找设备特征,在特征中搜寻键盘设备。代码如下:
EFI_STATUS EFIAPI HelloWorldSupported(
IN EFI_DRIVER_BINDING_PROTOCOL* This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL* RemainingDevicePath
)
{
EFI_STATUS status = EFI_SUCCESS;
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL* device2txt = NULL;
CHAR16* device_path = NULL;
status = gBS->LocateProtocol(&gEfiDevicePathToTextProtocolGuid, NULL, (void**)&device2txt);
if (EFI_ERROR(status))
{
Print(L"[HelloWorld Driver] LocateProtocol result: %d\n", status);
return status;
}
device_path = device2txt->ConvertDeviceNodeToText(RemainingDevicePath, TRUE, TRUE);
Print(L"[HelloWorld Driver] device: %s\n", device_path);
return EFI_UNSUPPORTED;
}
依赖配置
这里采用VeB配置。
- 新增外部依赖
DevicePathLib
- 新增Protocols依赖
gEfiDevicePathProtocolGuid
和gEfiDevicePathToTextProtocolGuid
使用脚本编译
脚本编译的目的是为CI做准备。
@echo off
chcp 65001
title AMI UEFI Build Tool
echo Any question can be sent to zhtqs8@163.com
set CCX86DIR=<work root>\software\Aptio_5.x\x86\x86
set CCX64DIR=<work root>\software\Aptio_5.x\x86\amd64
set TOOLS_DIR=<work root>\software\Aptio_5.x\BuildTools
set PATH=%PATH%;<work root>\software\Aptio_5.x\x86\x86
set PATH=%PATH%;<work root>\software\Aptio_5.x\amd64
set PATH=%PATH%;<work root>\software\Aptio_5.x\x86
set PATH=%PATH%;<work root>\software\Aptio_5.x\BuildTools
set PATH=%PATH%;<work root>\software\Aptio_5.x\BuildTools\Bin\Win32
set PATH=%PATH%;<work root>\software\Aptio_5.x\VisualeBios\jre\bin\
cd <work root>
cmd /k
运行指令make rebuild
,结果如下:
编译测试此DXE驱动模块
-
编译,确认编译输出
- Done - Build end time: 17:07:12, Sep.15 2023 Build total time: 00:00:07
-
运行
load HelloWorldDxe.efi
,确认结果RemainingDevicePath
的值始终为:F3 EE 00 F0
。期望的结果为不同的设备不同的值。UEFI Driver Writer’s Guide大部分示例显示,Supported
适用的流程是开发者用期望的Protocol尝试打开。结果成功就是支持,结果失败就是不支持。很多示例㫫示Start
还会把这个逻辑再运行一次。RemainingDevicePath
不一定指设备,它还兼顾方便开发者在当前设备下挂载子设备。另外: