至强服务器BIOS/UEFI驱动开发笔记

至强服务器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函数
      • 驱动的名字

  1. 实验使用的CPU架构Broardwell。
  2. EFI App的构建过程实际上先构建可以在OS上运行的动态库/可执行文件,然后利用PE32+工具改为UEFI运行。
  3. UEFI基于GObject(https://docs.gtk.org/gobject/)用C模拟OOP或C++。
  4. JRE 1.7安装目录整个拷贝到VisualeBios.exe所在目录,并改名为jre。则Visual Bios的启动不需要安装JRE。卸载JRE 1.7验证。
  5. UEFI编译系统强制要求函数的局部变量统一声明在函数体的头部,否则报错。
  6. GRUB运行在BDS阶段,因为GRUB运行期间未调用ExitBootServices方法,实际调用此方法的是OS Loader。
  7. UEFI环境特点
    1. 支持X86、X64、ARM等平台
    2. 单核CPU,没有线程,没有进程
    3. 没有抢断/优先级
    4. 没有中断,唯一的路径是定时器
    5. 模块内部通讯通过Protocols(协议)和Events(事件)
    6. C语言编程(原文:Programming is done through C language,实际上有汇编)
  8. 包的声明用dsc,模块的声明用inf,模块的依赖用dec

驱动开发基础

Hello UEFI Driver 项目

用UEFI Shell装载驱动进行测试。受载板平台限制,测试工程放在AMI项目里。

选择项目位置

与UEFI App开发不同,驱动代码所处项目架构应当与硬件构成映射关系。如果驱动代码与驱动硬件不在相同架构,则开发者需要手动处理固件布局才成封装成为正确的固件。我亲自踩坑证明这个说法:
至强服务器BIOS/UEFI驱动开发笔记_第1张图片
关键错误消息:Build\GetPpiName.c(1) : fatal error C1083: Cannot open include file: '/RELEASE_MYTOOLS/PpiTableIA32.c': No such file or directory。不知道驱动项目存放须按规定的开发者会认为这个问题是玄学问题,怎么生成的代码找不到生成的代码呢?建议参照下图和项目结构选择合适的UEFI驱动存储放置:

至强服务器BIOS/UEFI驱动开发笔记_第2张图片
根据驱动目标选择合适的项目位置。本示例的目标为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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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工程

  1. 在ModuleExplorer视图中依次展开:ComponentsCoreMdeModulePkgLibraryInstances,右键单击,选择弹出菜单Add INF Module。关于Select EDK Project Root选项,选择结果无论是否正确,导入结果都存在错误。下文第2步和第3步就是纠错。
    至强服务器BIOS/UEFI驱动开发笔记_第3张图片
  2. 找到导入的工程,移动生成的sdl文件到inf文件所在目录,并更名。
    至强服务器BIOS/UEFI驱动开发笔记_第4张图片
  3. 按下图所示修改MdeModulePkg\Library\LibraryInstances.cif
    至强服务器BIOS/UEFI驱动开发笔记_第5张图片
    至强服务器BIOS/UEFI驱动开发笔记_第6张图片
  4. 关掉VeB软件,重新打开。编译报错:\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 编译问题

  1. AMI的工程不能运行EDKII提供的命令
    至强服务器BIOS/UEFI驱动开发笔记_第7张图片

  2. VeB不允许编译单个驱动

    这是假象。实际原因是VeB没有把导入INF生成的sdl文件放到inf所在目录。解决办法是移动sdl文件到inf文件所在目录并修正上级cif文件中的错误。

至强服务器BIOS/UEFI驱动开发笔记_第8张图片

测试结果

  1. UEFI Shell运行运行load指令可见大量的HelloWorld输出,说明UEFI驱动管理支持一个设备绑定多个驱动,不会因为某个设备已存在绑定的驱动而停止匹配新驱动。UEFI如何选择调用哪个驱动呢?驱动的版本的如何在驱动选择中发挥作用的?
  2. 服务器启动慢问题存在新证据,证据指向问题发生在SEC或者PEI阶段。下图右边神秘的数字,在HelloWorldDxe集成进BIOS固件之前不确定它显示时CPU执行阶段。现在可以证明处于DXE阶段。那么,从通电到HelloWorldDxe产生输出的大约10秒钟时间,很可能都处于SEC和PEI阶段。现在没有确定串口设备未初始化造成的HelloWorldDxe无输出的时间。

至强服务器BIOS/UEFI驱动开发笔记_第9张图片

  1. EFI Shell启动时会再执行一次设备驱动管理过程。
    至强服务器BIOS/UEFI驱动开发笔记_第10张图片

  2. UEFI驱动管理在得到Supported函数的返回结果为EFI_SUCCESS后立即调用Start函数。上图Supported输出与Start输出成对出现无间断说明这一点。

  3. 任何驱动应在EFI Shell中先load试运行。否则驱动出错造成很大的麻烦。DXE出错的结果是载板变砖头,救砖的办法可能只有把FLASH从电路板上焊下来,烧好程序后再焊上去。

  4. 固件烧录与固件运行时不同。以下截图的实验:

    1. A版本包含HelloWorldDxe驱动,B版本与2023XXXX版本相同,唯一的区别是重新编译。编译环境、工具完全相同。
    2. 固件升级到A版本,重启后驱动绑定过程出现大量的HelloWorld打印
    3. 固件升级到B版本,重启时控制台出现大量的HelloWorld打印
      至强服务器BIOS/UEFI驱动开发笔记_第11张图片

打印设备列表

继续开发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配置。

  1. 新增外部依赖DevicePathLib
    至强服务器BIOS/UEFI驱动开发笔记_第12张图片
  2. 新增Protocols依赖gEfiDevicePathProtocolGuidgEfiDevicePathToTextProtocolGuid
    至强服务器BIOS/UEFI驱动开发笔记_第13张图片

使用脚本编译

脚本编译的目的是为CI做准备。

@echo off
chcp 65001
title AMI UEFI Build Tool
echo Any question can be sent to [email protected]
set CCX86DIR=\software\Aptio_5.x\x86\x86
set CCX64DIR=\software\Aptio_5.x\x86\amd64
set TOOLS_DIR=\software\Aptio_5.x\BuildTools
set PATH=%PATH%;\software\Aptio_5.x\x86\x86
set PATH=%PATH%;\software\Aptio_5.x\amd64
set PATH=%PATH%;\software\Aptio_5.x\x86
set PATH=%PATH%;\software\Aptio_5.x\BuildTools
set PATH=%PATH%;\software\Aptio_5.x\BuildTools\Bin\Win32
set PATH=%PATH%;\software\Aptio_5.x\VisualeBios\jre\bin\
cd 
cmd /k

运行指令make rebuild,结果如下:
至强服务器BIOS/UEFI驱动开发笔记_第14张图片

编译测试此DXE驱动模块

  1. 编译,确认编译输出

    - Done -
    Build end time: 17:07:12, Sep.15 2023
    Build total time: 00:00:07
    
  2. 运行load HelloWorldDxe.efi,确认结果

    RemainingDevicePath的值始终为:F3 EE 00 F0。期望的结果为不同的设备不同的值。UEFI Driver Writer’s Guide大部分示例显示,Supported适用的流程是开发者用期望的Protocol尝试打开。结果成功就是支持,结果失败就是不支持。很多示例㫫示Start还会把这个逻辑再运行一次。

    RemainingDevicePath不一定指设备,它还兼顾方便开发者在当前设备下挂载子设备。另外:

你可能感兴趣的:(C/C++,单片机,Linux,服务器,驱动开发,笔记)