UEFI固件存储和Variable

一、什么是Variable?

``
     变量代表内存中具有特定属性的一个存储单元,它用来存放数据,也就是变量的值,在程序运行期间,这些值是可以改变的,在UEFI架构下,Variable是一个由表示信息、属性、和值够成的组合体,类似全局变量。 Variable 存储在memory中,或者根据 属性存储在flash当值

1、Variable属性

常用的3种:
在这里插入图片描述


二、什么是Variable Services?

   Variable Services是Runtime Services的一部分,提供关于Variable的一些服务,Variable被定义为键值对,由标识信息加上属性(键)和任意数据(值)组成。Variable用于存储在平台实现的EFI环境和EFI OS加载器以及在EFI环境中的其他应用程序之间传递数据。
注意
   1、一个Guid可以对应多个Variable,一个Variable中包含多种不同数据类型的数据(结构体)Setup界面中的一些值就是Variable,通过这种方式来存储

UEFI规范为Variable Services定义了四个接口,如下表所示:
在这里插入图片描述
   从上面的接口type可知,DXE阶段之后,Runtinme Service Table提供了完整的Variable server,利用这些service可以进行创建变量,修改变量,删除变量等操作,

2、接口

2.1 GetVariable这个接口的作用是

返回一个指定Variable的值。

其定义如下:

EFI_STATUS
GetVariable (
  IN CHAR16 *VariableName,
  IN EFI_GUID *VendorGuid,
  OUT UINT32 *Attributes OPTIONAL,
  IN OUT UINTN *DataSize,
  OUT VOID *Data
  );
  • VariableName:这是一个输入参数,表示要获取的变量的名称。

  • VendorGuid:这是一个输入参数,表示要获取的变量的Vendor GUID。

  • Attributes:这是一个可选的输出参数,如果不为NULL,则返回变量的属性。

  • DataSize:这是一个输入输出参数,输入时表示Data缓冲区的大小,输出时表示实际数据的大小。

  • Data:这是一个输出参数,用于返回变量的值。

函数的返回值是EFI_STATUS类型,表示函数的执行状态。

以下是一个使用GetVariable函数获取变量值的简单示例:

EFI_STATUS Status;
UINTN DataSize;
UINT32 Attributes;
EFI_GUID VendorGuid;
CHAR16 VariableName[] = L"MyVariable";
UINT8 Data[MAX_DATA_SIZE];

DataSize = sizeof(Data);

Status = GetVariable(VariableName, &VendorGuid, &Attributes, &DataSize, Data);
if (EFI_ERROR(Status)) {
  // Handle error
} else {
  // Process data
}

在这个例子中,GetVariable函数被用于获取名为"MyVariable"的变量的值。如果函数成功执行,那么Data将包含变量的值,DataSize将包含实际数据的大小,Attributes将包含变量的属性。如果函数执行失败,那么需要进行错误处理。

2.2 GetNextVariableName这个接口的作用

是枚举当前变量名
它的典型定义如下:

EFI_STATUS
GetNextVariableName (
  IN OUT UINTN *VariableNameSize,
  IN OUT CHAR16 *VariableName,
  IN OUT EFI_GUID *VendorGuid
  );
  • VariableNameSize:这是一个输入输出参数,输入时表示VariableName缓冲区的大小,输出时表示实际变量名的大小(包括结束字符)。

  • VariableName:这是一个输入输出参数,输入时表示上一个获取的变量名,输出时表示下一个变量名。

  • VendorGuid:这是一个输入输出参数,输入时表示上一个获取的变量的Vendor GUID,输出时表示下一个变量的Vendor GUID。

函数的返回值是EFI_STATUS类型,表示函数的执行状态。

在使用GetNextVariableName函数时,你需要先初始化VariableNameVendorGuid为第一个变量名和GUID,然后在循环中调用GetNextVariableName函数来获取所有的变量名和GUID。例如:

EFI_STATUS Status;
UINTN VariableNameSize;
CHAR16 VariableName[MAX_VARIABLE_NAME];
EFI_GUID VendorGuid;

VariableNameSize = sizeof(VariableName);
VariableName[0] = '\0';

while (TRUE) {
  Status = GetNextVariableName(&VariableNameSize, VariableName, &VendorGuid);
  if (EFI_ERROR(Status)) {
    // Handle error
    break;
  }

  // Process variable (VariableName, VendorGuid)
}

在这个例子中,GetNextVariableName函数被用于枚举所有的变量,并处理每个变量。当GetNextVariableName函数返回错误时,循环就会停止。

2.3 SetVariable这个接口的作用

SetVariable函数用于设置变量的值。变量是存储在UEFI系统中的数据,可以在不同的UEFI驱动程序和应用程序之间共享和访问。

SetVariable函数的使用如下所示:

EFI_STATUS SetVariable(
  IN CHAR16    *VariableName,
  IN EFI_GUID  *VendorGuid,
  IN UINT32    Attributes,
  IN UINTN     DataSize,
  IN VOID      *Data
);

参数说明如下:

  • VariableName:变量的名称,由16位Unicode字符组成。
  • VendorGuid:变量的供应商GUID,用于唯一标识变量。
  • Attributes:变量的属性,用于指定变量的访问权限和存储位置等信息。
  • DataSize:变量数据的大小,以字节为单位。
  • Data:要设置的变量数据。

SetVariable函数通过VariableName和VendorGuid确定要设置的变量,然后使用Attributes指定的属性将DataSize大小的Data数据存储到变量中。

SetVariable函数的使用如下所示:

#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PcdLib.h>


//ShellCEntryLib call user interface ShellAppMain
EFI_STATUS
EFIAPI
MyHVariableAppEntry(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  EFI_STATUS                         Status = EFI_SUCCESS;
  UINTN                              DataSize;
  UINT64                             CSDNEnable = 1;
  EFI_GUID  gEfiCsdnEnableGuid = { 0xb71604d2, 0xb8ba, 0x4d9c, { 0x78, 0x88, 0xac, 0xcf, 0x1d, 0xa2, 0x26, 0xc3 }};

  DataSize = sizeof (CSDNEnable);
  Status = gRT->SetVariable (
                  L"CSDNEnable",
                  &gEfiCsdnEnableGuid,
                  EFI_VARIABLE_NON_VOLATILE |
                  EFI_VARIABLE_BOOTSERVICE_ACCESS |
                  EFI_VARIABLE_RUNTIME_ACCESS,
                  DataSize,
                  &CSDNEnable
                  );

  ASSERT(Status);
  DEBUG ((EFI_D_ERROR," [CSDN]: Setvariable CSDNEnable 0x%x.\n", CSDNEnable));

  CSDNEnable = 0;
  DataSize = sizeof (CSDNEnable);
  Status = gRT->GetVariable (
                L"CSDNEnable",
                &gEfiCsdnEnableGuid,
                NULL,
                &DataSize,
                &CSDNEnable
                );
  ASSERT(Status);

  if (CSDNEnable) {
    DEBUG ((EFI_D_ERROR, "[CSDN] CSDNEnable\n"));
  }

  return EFI_SUCCESS;
}
2.3 QueryVariableInfo这个接口的作用

这个函数的作用是返回关于EFI变量的信息。
      QueryVariableInfo()函数允许调用者获得关于可用于存储EFI变量的最大存储空间,可用于存储EFI变量的最大剩余存储空间的大小和每个EFI变量的最大空间,与指定的属性相关联。
函数原型为:

typedef 
EFI_STATUS 
QueryVariableInfo ( 
	IN UINT32 Attributes, 
	OUT UINT64 *MaximumVariableStorageSize, 
	OUT UINT64 *RemainingVariableStorageSize, 
	OUT UINT64 *MaximumVariableSize 
);

三、PEI阶段访问Variable

      Variable在PEI阶段只能读取,不能写,在PEI阶段,可以使用EFI_PEI_READ_ONLY_VARIABLE2_PPI接口访问UEFI变量。这是一个PEI服务,可以在PEI阶段读取UEFI变量。

以下是一个基本的例子:

#include <Ppi/ReadOnlyVariable2.h>

EFI_STATUS
EFIAPI
PeiModuleEntryPoint (
  IN     EFI_PEI_FILE_HANDLE       FileHandle,
  IN     const EFI_PEI_SERVICES    **PeiServices
  )
{
  EFI_STATUS                        Status;
  EFI_GUID                          gEfiPeiReadOnlyVariable2PpiGuid = EFI_PEI_READ_ONLY_VARIABLE2_PPI_GUID;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI   *VariableServices;
  UINTN                             VariableSize;
  MY_VARIABLE_STRUCTURE              MyVariable;

  Status = (*PeiServices)->LocatePpi (
                             PeiServices,
                             &gEfiPeiReadOnlyVariable2PpiGuid,
                             0,
                             NULL,
                             (VOID**)&VariableServices
                             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  VariableSize = sizeof(MyVariable);
  Status = VariableServices->GetVariable (
                               VariableServices,
                               L"MyVariable",
                               &gMyVariableGuid, // This is the GUID of your variable
                               NULL,
                               &VariableSize,
                               &MyVariable
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // Now MyVariable contains the data of the UEFI variable

  return EFI_SUCCESS;
}

   在上述例子中,首先定位到EFI_PEI_READ_ONLY_VARIABLE2_PPI接口,然后使用GetVariable函数读取变量。这样可以在PEI阶段访问UEFI变量。

请注意,这个例子假设有一个名为MY_VARIABLE_STRUCTURE的结构体,以及一个想要读取的UEFI变量的GUID。需要根据的具体情况来修改这个例子。

这个例子只是在PEI阶段读取UEFI变量,不包括写入变量。在PEI阶段,无法写入UEFI变量,因为在这个阶段,固件还没有初始化变量存储设备。

四、接口初始化

      上述接口是在gRT中的,它们并非一开始就可以使用,必须要到UEFI的DXE阶段才可以使用。并且,也不是DXE阶段一开始就可以使用。在DXE的运行过程中,会加载一个个的模块,来填满整个表。
上述接口的初始化有不同的分类,分别在不同的模块中实现,并且只需要其中的一个模块运行就可以了。

例如EmuVariableRuntimeDxe.inf模块:
      EmuVariableRuntimeDxe.inf模块是一个UEFI驱动程序,它提供了一个虚拟的NVRAM(非易失性随机访问存储器)环境,允许操作系统和应用程序使用NVRAM存储和检索变量值。这对于测试和开发UEFI固件和驱动程序非常有用,因为它可以避免在实际硬件上进行测试时对NVRAM的更改。该模块的实现基于UEFI规范中的Variable Services Protocol和Runtime Services Protocol。
简单来说,初始化分为三个部分:

  1. 初始化mVariableModuleGlobal;
  2. 为全局变量gRT赋值;
  3. 安装相关的Protocol和事件(该事件用于物理地址到虚拟地址的转变)。

参考

UEFI变量基础

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值