UEFI开发-知识点汇总

UEFI开发-知识点汇总 阿帅 2024-01-25 15:22:06 1050

文章目录

概述

BIOS

作用

BIOS(basic I/O system)即基本输入/输出系统,它是存储在主板rom中的一段程序,作用包括:
1、加电硬件检测:开机时检测硬件设备
2、硬件设备初始化,创建中断向量
3、将操作系统拷贝到ram中,并执行

缺点

1、它是由汇编代码实现的:不便于开发
2、性能差
3、不支持硬盘2TB以上的地址寻址,bios是32位寻址最大到2的32次方=2TB

UEFI

UEFI(Unified extensible firmware interface)统一可扩展固件接口,它是一种标准,其实现是由其他公司或开源组织提供的。

作用

扫描硬件、安装驱动;提供给操作系统启动服务(boot services,BS)和运行时服务(runtime service,RT)、以及protocol
BS提供:
1、事件服务,可支持异步
2、内存管理
3、驱动管理
RT提供:
1、读写UEFI系统变量,例如bootOrder用于指定启动项顺序。
2、虚拟内存服务

参考资料

1、edk2用户手册
2、UEFI原理与编程-戴春华

编译

使用edk2进行开发,edk2是实现了uefi标准的开关框架。
在edk2开发,需要遵守以下规则:

Pkg目录

以pkg结尾的文件夹,作为一个模块。
在uefi执行中,将安装各个模块。

inf文件

相当于是一个模块pkg的子功能模块的makefile

[Sources]

注意:
1、要把uni文件、vfr文件放在前面,源文件放在后面。
因为uni文件会转换为h文件,vfr文件会转换为c文件。
同时vfr文件的名字不要和同目录下的文件名重名了。

dec文件

.dec文件记录的本模块的信息。

dsc文件

其中指定了编译规则,相当于Makefile

vfr文件

界面文件,可参考MdeModulePkg/Universal/DriverSampleDxe

UEFI启动各阶段说明

DXE阶段

执行系统大部分的初始化工作。由于此阶段内存已经可以被正常使用,因此该阶段可以执行大量复杂的工作。

BDS阶段

硬件检测和驱动匹配在该阶段进行。
具体功能如下:
1、执行启动策略(主要功能)。
2、初始化控制台设备。
3、加载必要的设备驱动。
4、根据系统设置加载和执行启动项。

网卡检测过程

在DXE阶段加载驱动
在BDS阶段扫描硬件设备,并匹配驱动(supported)
网卡设备的匹配,一些基础网络工具包均可与之相匹配,按照示例图所示:
先安装SNP,将device信息放到SNP句柄中,然后安装MNP,MNP获取SNP句柄中的device信息,获取设备mac地址等信息。

启动系统

启动项排序

获取启动项:

PhytiumPkg/Phytium2004Pkg/Library/PlatformBootManagerLib/
    PlatformBm.c
        PlatformBootManagerAfterConsole

启动项排序:

PhytiumPkg/Phytium2004Pkg/Library/PlatformBootManagerLib/
    PlatformBm.c
        PlatformBootManagerAfterConsole
            CompareBootOptionKL (0最高,100最低)

注意:一定要把想删掉的启动项优先级排到最低。

添加启动项
PlatformRegisterFvBootOption
通过上个接口添加启动项,其中:
参数1:模块的GUID,即inf文件里的FILE_GUID  
参数2:显示在启动列表中的名字 
参数3:启动类型
添加按键启动项
F8.ScanCode    = SCAN_F8; 
F8.UnicodeChar = CHAR_NULL;

// Register setup
OptionNumber = PlatformRegisterFvBootOption (
               &gUiAppFileGuid,
               L"Enter Setup", 
               LOAD_OPTION_ACTIVE | LOAD_OPTION_CATEGORY_APP /*| LOAD_OPTION_HIDDEN*/
               );
Status = EfiBootManagerAddKeyOptionVariable (
         NULL, (UINT16)OptionNumber, 0, &F8, NULL
         );

界面设计

界面设计可以通过vfr文件和Hii相关接口实现。

vfr文件

界面的主体文件,需要将其加到inf文件的sources下去编译它,可以得到vfr.c,其中包含vfr文件名+bin的一个数组。
例如:LaunchCheckPageVfr.vfr 编译得到 LaunchCheckPageVfr.c,其中包含数组:LaunchCheckPageVfrBin,它里面就是界面数据。
这个LaunchCheckPageVfrBin,通过如下加载到uefi中使用:

  HiiHandle[0] = HiiAddPackages (
                   &gLaunchCheckFormSetGuid,
                   DriverHandle[0],
                   LaunchCheckPageStrings,
                   LaunchCheckPageVfrBin,
                   NULL
                   );

其中LaunchCheckPageStrings,是inf文件中MODULE_UNI_FILE指定的文件名+Strings。
例:

  MODULE_UNI_FILE                = LaunchCheckPage.uni

那么对应的就是:LaunchCheckPageStrings

hii接口创建界面中的成员
复选框

在主界面上增加一个复选框,可选择X8X8或X16

创建界面
/**
  Create Select language menu in the front page with oneof opcode.

  @param[in]    HiiHandle           The hii handle for the Uiapp driver.
  @param[in]    StartOpCodeHandle   The opcode handle to save the new opcode.

**/
VOID
BmmCreatePcieMenu (
  IN EFI_HII_HANDLE              HiiHandle,
  IN VOID                        *StartOpCodeHandle
  )
{
    CHAR8                       *LangCode;
    CHAR8                       *Lang;
    UINTN                       LangSize;
    //CHAR8                       *CurrentLang;
    UINTN                       OptionCount;
    CHAR16                      *StringBuffer;
    VOID                        *OptionsOpCodeHandle;
    UINTN                       StringSize;
    EFI_STATUS                  Status;
    EFI_HII_STRING_PROTOCOL     *HiiString;
    UINT32    pcimode;
    UINT8   FuncConfig;
    UINT8   done_flag;

    Lang         = NULL;
    StringBuffer = NULL;

    //
    // Init OpCode Handle and Allocate space for creation of UpdateData Buffer
    //
    OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
    ASSERT (OptionsOpCodeHandle != NULL);

    OptionCount  = 0;
    pcimode = GetPcieMode();  //为了显示之前设置的模式
    //FuncConfig = (UINT8)(itemp >> 16);
    FuncConfig = (UINT8)(pcimode >> 0);//peu0

    if(FuncConfig == 1)//x16
    {
        HiiCreateOneOfOptionOpCode (
            OptionsOpCodeHandle,
            STRING_TOKEN(STR_PCIE_MODE_X16),//实现的内容,定义在uni文件中
            EFI_IFR_OPTION_DEFAULT,
            EFI_IFR_NUMERIC_SIZE_1,
            0   //0表示X16是第一个显示
            );
        HiiCreateOneOfOptionOpCode (
            OptionsOpCodeHandle,
            STRING_TOKEN(STR_PCIE_MODE_X8X8),
            0,
            EFI_IFR_NUMERIC_SIZE_1,
            1
            );    
            gCurrentPeu0Index = PEU_X16;//表示0位置是X16
    }
    else
    {
        HiiCreateOneOfOptionOpCode (
            OptionsOpCodeHandle,
            STRING_TOKEN(STR_PCIE_MODE_X8X8),
            0,
            EFI_IFR_NUMERIC_SIZE_1,
            0
            );
        HiiCreateOneOfOptionOpCode (
            OptionsOpCodeHandle,
            STRING_TOKEN(STR_PCIE_MODE_X16),
            EFI_IFR_OPTION_DEFAULT,
            EFI_IFR_NUMERIC_SIZE_1,
            1
            );
            gCurrentPeu0Index = PEU_X8X8;

    }   

    HiiCreateOneOfOpCode (
        StartOpCodeHandle,
        (EFI_QUESTION_ID)FORM_PEU0_MODE_ID,//自定义
        VARSTORE_ID_BOOT_MAINT,//要和vfr中的varsore对应的varid一致
        PEU0_MODE_VAR_OFFSET,
        STRING_TOKEN (STR_PCIE_PEU0_MODE_CFG),
        STRING_TOKEN (STR_PCIE_PEU0_MODE_CFG_HELP),
        EFI_IFR_FLAG_CALLBACK,
        EFI_IFR_NUMERIC_SIZE_1,
        OptionsOpCodeHandle,
        NULL
        );
}

注意:
1.

#define VAR_OFFSET(Field)              ((UINT16) ((UINTN) &(((BMM_FAKE_NV_DATA *) 0)->Field)))
#define PEU0_MODE_VAR_OFFSET            VAR_OFFSET (Peu0Mode)
其中Peu0Mode是结构体BMM_FAKE_NV_DATA的成员名
后端

当对上述复选框操作后,保存时将进入RouteConfig接口。
相关代码如下:

EFI_STATUS
EFIAPI
SetPcieMode(UINT8     PeuMode)
{
    EFI_STATUS Status;

#define    PAR_TABLE_LEN                0x00000580
#define    PCIEPAR_OFFSET                0x00000200
#define    PCIE_FUNC_OFFSET            0x00000014

    UINT8                *configBuff;
    ParTableSpiInit();
    //step 1:read par table from flash
    configBuff = AllocatePool(PAR_TABLE_LEN);
    ASSERT (configBuff != NULL);
    Status = ParTableRead(PAR_TABLE_LEN,configBuff);

    UINT32 pcie_mode = 0;
    if(PeuMode == 0)
    {
        if(gCurrentPeu0Index == PEU_X8X8)
            pcie_mode = 0x30003;
        else
            pcie_mode = 0x30001;
    }
    else if(PeuMode == 1)
    {
        if(gCurrentPeu0Index == PEU_X8X8)
            pcie_mode = 0x30001;
        else
            pcie_mode = 0x30003;
    }
    CopyMem(configBuff + PCIEPAR_OFFSET + PCIE_FUNC_OFFSET,&pcie_mode,4);
    Status = ParTableWrite(PAR_TABLE_LEN, configBuff);  
    FreePool (configBuff);

#undef PAR_TABLE_LEN
#undef PCIEPAR_OFFSET
#undef PCIE_FUNC_OFFSET

    return Status;
}

RouteConfig接口中相关代码:
  if (CompareMem (&NewBmmData->Peu0Mode, &OldBmmData->Peu0Mode, sizeof (NewBmmData->Peu0Mode)) != 0) {
    Status = SetPcieMode(NewBmmData->Peu0Mode);
    if (EFI_ERROR (Status)) {
      Offset = OFFSET_OF (BMM_FAKE_NV_DATA, Peu0Mode);
      goto Exit;
    }
  }

注意:

  1. 如何获取用户在界面上复选框的选择
    Status = SetPcieMode(NewBmmData->Peu0Mode);
    NewBmmData->Peu0Mode的值就是如下:
         HiiCreateOneOfOptionOpCode (
             OptionsOpCodeHandle,
             STRING_TOKEN(STR_PCIE_MODE_X8X8),
             0,
             EFI_IFR_NUMERIC_SIZE_1,
             0   //NewBmmData->Peu0Mode对应的值
             );
         HiiCreateOneOfOptionOpCode (
             OptionsOpCodeHandle,
             STRING_TOKEN(STR_PCIE_MODE_X16),
             EFI_IFR_OPTION_DEFAULT,
             EFI_IFR_NUMERIC_SIZE_1,
             1   
             );
             gCurrentPeu0Index = PEU_X8X8;
    即0对应的是X8X8,1对应的是用户选择的是X16
    

Hii.h文件

一般放到MdeModulePkg/include/Guid/下,内容:

#ifndef __LAUNCH_CHECK_HII_GUID_H__
#define __LAUNCH_CHECK_HII_GUID_H__

#define LAUNCH_CHECK_FORMSET_GUID \
  { \
    0x543E79AE, 0x82BA, 0xDFB6, {0x0D, 0x01, 0xD5, 0xDA, 0x73, 0xF3, 0x61, 0xCE} \
  }

#define LAUNCH_CHECK_INVENTORY_GUID \
  { \
    0x40E54D59, 0x6ADB, 0xF6FF, {0xA4, 0xCE, 0x8A, 0x70, 0xDB, 0x77, 0x4D, 0xFC} \
  }

#define EFI_IFR_REFRESH_ID_OP_GUID \
  { \
    0xA297B2CD, 0x1D86, 0x15B5, {0x77, 0x5F, 0x35, 0xE5, 0x1A, 0xB5, 0x08, 0x57} \
  }

extern EFI_GUID gLaunchCheckFormSetGuid;
extern EFI_GUID gLaunchCheckInventoryGuid;
extern EFI_GUID gLaunchCheckIfrRefreshIdOpGuid;

其中,gLaunchCheckFormSetGuid,可以理解为指针,你往里面放什么它就是什么。
它的值,在dec文件中定义,值和LAUNCH_CHECK_FORMSET_GUID相同。

使用示例:

  HiiHandle[0] = HiiAddPackages (
                   &gLaunchCheckFormSetGuid,
                   DriverHandle[0],
                   LaunchCheckPageStrings,
                   LaunchCheckPageVfrBin,
                   NULL
                   );

上述就是告诉UEFI,找到gLaunchCheckFormSetGuid,就能找到对应的界面数据了,即LaunchCheckPageVfrBin。

界面的后端处理

操作界面时,需要用到如下几个重要的回调函数

ExtractConfig

作用:用于获取之前用户在该页面上配置。
进入页面时调用。

RouteConfig

作用:保存用户修改后的配置数据。
当界面数据变化时进入该回调。

Callback

作用:处理用户对界面上的动作,包括保存、退出等等。

实例1:按F8进入配置界面流程

PlatformBm.c
    PlatformRegisterOptionsAndKeys
        // Register setup
        OptionNumber = PlatformRegisterFvBootOption (
                       &gUiAppFileGuid,
                       L"Enter Setup", 
                       LOAD_OPTION_ACTIVE | LOAD_OPTION_CATEGORY_APP /*| LOAD_OPTION_HIDDEN*/
                       );
其中PlatformRegisterFvBootOption获取gUiAppFileGuid对应的efi文件,并和按键绑定。  
gUiAppFileGuid:PhytiumPkg/Phytium2004Pkg/setup/UiApp/UiApp.inf的FILE_GUID
在PhytiumPkg/Phytium2004Pkg/Phytium2004Pkg.dec里面引用:  
gUiAppFileGuid = { 0x462CAA21, 0x7614, 0x4503, { 0x83, 0x6e, 0x8a, 0xb6, 0xf4, 0x66, 0x23, 0x31 }}  
注意这里是引用,不是定义,定义是在UiApp.inf的FILE_GUID

这样按下F8就进入PhytiumPkg/Phytium2004Pkg/setup/UiApp/的入口函数中了。

实例2:增加Shell启动项

目前代码将shell启动项删除了,增加shell需要修改如下:
1、MdeModulePkg/Application/BootManagerMenuApp/BootManagerMenu.c

@@ -315,10 +315,11 @@ InitializeBootMenuData (
     if (IgnoreBootOption (&BootOption[Index])) {
       continue;
     }
-
+#if 0
    if ( StrStr (BootOption[Index].Description , L"UEFI Shell" ) != NULL) {
          continue;
     }
+#endif
 #if 0  
    if ( StrStr (BootOption[Index].Description , L"Update Bios" ) != NULL) {
          continue;

2、PhytiumPkg/Phytium2004Pkg/Library/PlatformBootManagerLib/PlatformBm.c

@@ -927,7 +927,7 @@ PlatformBootManagerAfterConsole (
   // Register UEFI Shell
   //
 //add by sunshuai : del shell in boot options
-#if 0
+#if 1
   PlatformRegisterFvBootOption (
     &gUefiShellFileGuid, L"UEFI Shell", LOAD_OPTION_ACTIVE
 //    &gUefiShellFileGuid, L"UEFI Shell", LOAD_OPTION_HIDDEN

3、UefiPayloadPkg/Library/PlatformBootManagerLib/PlatformBootManager.c

@@ -222,8 +222,9 @@ PlatformBootManagerAfterConsole (
   //
   // Register UEFI Shell
   //
-//  PlatformRegisterFvBootOption (PcdGetPtr (PcdShellFile), L"UEFI Shell", LOAD_OPTION_ACTIVE);                //ling
-
+  #if 1
+  PlatformRegisterFvBootOption (PcdGetPtr (PcdShellFile), L"UEFI Shell", LOAD_OPTION_ACTIVE);          //ling
+  #endif
   Print (
     L"\n"
     L"F2 or Down      to enter Boot Manager Menu.\n"

内存拷贝接口

mdepkg\library\baselib

1、CopyMem
2、ZeroMem
3、AllocateCopyPool
UnicodeSPrint (ipaddr, sizeof (ipaddr), L"192.168.%d.%d", Private[loop]->IfInfo->HwAddress.Addr[4],Private[loop]->IfInfo->HwAddress.Addr[5]);

打印接口

打印 8位 字符串: 
AsciiPrint("ver_data:%a\n", ver_data);  

打印 16位 字符串:  
CHAR16 *ver_data
Print(L"%s\n", ver_data)

CHAR8 *ver_data
Print(L"%a\n", ver_data)

字符串处理

mdepkg/library/baselib/String.c
mdepkg/library/baselib/SafeString.c

文件处理

gEfiSimpleFileSystemProtocolGuid

如果要遍历目录和文件,需要使用到gEfiSimpleFileSystemProtocolGuid协议。

    UINTN                            HandleCount;
    EFI_HANDLE                       *Handles = NULL;
    Status = gBS->LocateHandleBuffer (
                 ByProtocol,
                 &gEfiSimpleFileSystemProtocolGuid,
                 NULL,
                 &HandleCount,
                 &Handles
                 );
    if(EFI_ERROR(Status) || HandleCount == 0) {
        Status = EFI_NOT_FOUND;
        if(Handles != NULL){
            FreePool(Handles);
        }  
    }

获取各文件系统的EFI_SIMPLE_FILE_SYSTEM_PROTOCOL

一般BIOS会挂载多个文件系统,需要遍历,查找所有挂载的文件系统中的文件。

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *FS; 
    // RootFile相当于根目录
    EFI_FILE_PROTOCOL                *RootFile = NULL;

    for (Index = 0; Index < HandleCount; Index++) {
        Status = gBS->HandleProtocol (
                        Handles[Index],
                        &gEfiSimpleFileSystemProtocolGuid,
                        (VOID**)&FS
                        );
        ASSERT(!EFI_ERROR (Status));
        if(RootFile != NULL) {
            RootFile->Close(RootFile);
            RootFile = NULL;
        }  
        //得到根目录
        Status = FS->OpenVolume(FS, &RootFile);
        if(EFI_ERROR(Status)) {
            DEBUG((EFI_D_ERROR, "OpenRootFile Error:%r\n", Status));
            continue;
        }
        //在根目录中打开特定文件
        Status = ReadFileInFs(RootFile, BiosFileName, BiosData, BiosSize);
        if(!EFI_ERROR(Status)) {
            break;
        } 
    }
    if(Index >= HandleCount) {
        Status = EFI_NOT_FOUND;
        if(Handles != NULL) {
            FreePool(Handles);
        }  
        if(RootFile != NULL) {
            RootFile->Close(RootFile);
        } 
    }

文件读取

使用EFI_FILE_PROTOCOL协议进行操作

STATIC 
EFI_STATUS 
ReadFileInFs (
    EFI_FILE_PROTOCOL  *RootFile,
    CHAR16             *FilePathName,
    UINT8              **OutFileData,
    UINTN              *OutFileSize
)
{
    EFI_STATUS          Status;
    EFI_FILE_PROTOCOL   *File     = NULL;  
    EFI_FILE_INFO       *FileInfo = NULL;
    UINTN               FileInfoSize;
    UINT8               *FileData = NULL;
    UINTN               FileSize = 0;

    Status = RootFile->Open(
                       RootFile, 
                       &File, 
                       FilePathName, 
                       EFI_FILE_MODE_READ, 
                       0
                       );
    if(EFI_ERROR(Status)){
        goto ProcExit;
    }    

    FileInfo = NULL;
    FileInfoSize = 0;
    Status = File->GetInfo (
                   File,
                   &gEfiFileInfoGuid,
                   &FileInfoSize,
                   FileInfo
                   );
    if(Status == EFI_BUFFER_TOO_SMALL){
        FileInfo = AllocatePool(FileInfoSize);
        if(FileInfo == NULL){
            Status = EFI_OUT_OF_RESOURCES;
        } else {
            Status = File->GetInfo (
                           File,
                           &gEfiFileInfoGuid,
                           &FileInfoSize,
                           FileInfo
                           );
        }
    }
    if(EFI_ERROR(Status)){
        goto ProcExit;
    }  

    if(FileInfo->Attribute & EFI_FILE_DIRECTORY) {
        Status = EFI_INVALID_PARAMETER;
        goto ProcExit;
    }

    FileSize = (UINTN)FileInfo->FileSize;    
    FileData = AllocatePages(EFI_SIZE_TO_PAGES(FileSize));
    if(FileData == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto ProcExit;
    }  

    Status = File->Read(File, &FileSize, FileData);
    if(EFI_ERROR(Status)) {
        goto ProcExit;;
    }
    //OutFileData : 包含固件的所有数据
    *OutFileData = FileData;
    *OutFileSize = FileSize;

ProcExit:
    if(EFI_ERROR(Status) && FileData != NULL) {
        FreePages(FileData, EFI_SIZE_TO_PAGES(FileSize));
    }     
    if(FileInfo != NULL){
        FreePool(FileInfo);
    } 
    if(File != NULL){
        File->Close(File);
    }   
    return Status;
}

在UEFI代码中添加自己的库的过程

1、代码放到:MdeModulePkg\Library
2、对外提供的头文件放到:MdeModulePkg\Include\Library
3、在MdeModulePkg.dsc添加编译流程

[LibraryClasses]
  LaunchCheckPage|MdeModulePkg/Library/LaunchCheckPage/LaunchCheckPage.inf

[Components]
  MdeModulePkg/Library/LaunchCheckPage/LaunchCheckPage.inf

在MdeModulePkg.dec中添加:

[LibraryClasses]
  LaunchCheckPage|Include/Library/LaunchCheckPage.h

4、在Phytium2004Pkg.dsc添加编译流程
原因未知

[LibraryClasses.common]
  LaunchCheckPage|MdeModulePkg/Library/LaunchCheckPage/LaunchCheckPage.inf

注意:
1、库的inf文件内链接的库,不能包含UefiApplicationEntryPoint(app应用需要链接的库)
2、库中内部使用的接口要用STATIC修饰,否则可能会多重定义

外部使用的过程

1、在使用模块的inf中添加

[LibraryClasses]
  库名

2、源文件中包含头文件

#include <Library/库对外提供的头文件>
如果编译时报找不到对应接口定义

1、find -name 库名.lib
2、nm 库名.lib
查看库中是否有对应的接口,再继续查原因。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
阿帅
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
阿帅
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区