【UEFI实战】模块如何嵌入一个Setup配置界面

前述

我们自定义的模块,有时候需要有一些用户可以调节的配置,这些配置可以放到Setup界面中,修改之后重启有效。本文要介绍的就是如何在Setup界面中嵌入一个自定义的配置界面。

本文以MdeModulePkg\Universal\DriverSampleDxe\DriverSampleDxe.inf为例进行说明。代码可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.下载到。实际上该模块已经提供了比较完全的Setup示例。

图形界面说明

DriverSampleDxe.inf提供了两个配置界面(Vfr.vfr):

以及(Inventory.vfr):

前者对应的VFR代码是Vfr.vfr,后者对应的VFR代码是Inventory.vfr,两者还分别有一个UNI文件与它对应。关于VFR和UNI的说明可以参考【UEFI实战】UEFI用户交互界面使用说明之VFR文件【UEFI实战】UEFI用户交互界面使用说明之UNI文件。这里不再对显示与相应的VFR、UNI代码做说明。

初始化

DriverSampleDxe.inf的初始化位于MdeModulePkg\Universal\DriverSampleDxe\DriverSample.c文件中的DriverSampleInit()函数,该函数主要进行了如下的操作:

1. 初始化结构体DRIVER_SAMPLE_PRIVATE_DATA变量mPrivateData,其结构如下所示:

typedef struct {
  UINTN                            Signature;

  EFI_HANDLE                       DriverHandle[2];
  EFI_HII_HANDLE                   HiiHandle[2];
  DRIVER_SAMPLE_CONFIGURATION      Configuration;
  MY_EFI_VARSTORE_DATA             VarStoreConfig;

  //
  // Name/Value storage Name list
  //
  EFI_STRING_ID                    NameStringId[NAME_VALUE_NAME_NUMBER];
  EFI_STRING                       NameValueName[NAME_VALUE_NAME_NUMBER];

  //
  // Consumed protocol
  //
  EFI_HII_DATABASE_PROTOCOL        *HiiDatabase;
  EFI_HII_STRING_PROTOCOL          *HiiString;
  EFI_HII_CONFIG_ROUTING_PROTOCOL  *HiiConfigRouting;
  EFI_CONFIG_KEYWORD_HANDLER_PROTOCOL *HiiKeywordHandler;

  EFI_FORM_BROWSER2_PROTOCOL       *FormBrowser2;

  //
  // Produced protocol
  //
  EFI_HII_CONFIG_ACCESS_PROTOCOL   ConfigAccess;
} DRIVER_SAMPLE_PRIVATE_DATA;

该结构体最重要的是那些Protocol,其中前面5个(Consumed protocol)是UEFI中的UI框架所提供的,本模块依赖于这些Protocol。最后1个(Produced protocol)是本模块提供的,用来规定了本模块提供的Setup界面的基本操作形式。

另外,VarStoreConfig和Configuration用来存放与Setup界面有关的变量,因此本模块还需要依赖于变量相关的Protocol(这里使用的是gEfiVariableArchProtocolGuid和gEfiVariableWriteArchProtocolGuid)。NameStringId和NameValueName目前还不知道是做什么用的。

2. 安装两个界面的DevicePath和EFI_HII_CONFIG_ACCESS_PROTOCOL。

3. 设置界面上的某些需要动态更新的值:

  //
  // Update the device path string.
  //
  NewString = ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*)&mHiiVendorDevicePath0, FALSE, FALSE);
  if (HiiSetString (HiiHandle[0], STRING_TOKEN (STR_DEVICE_PATH), NewString, NULL) == 0) {
    DriverSampleUnload (ImageHandle);
    return EFI_OUT_OF_RESOURCES;
  }
  if (NewString != NULL) {
    FreePool (NewString);
  }

  //
  // Very simple example of how one would update a string that is already
  // in the HII database
  //
  NewString = L"700 Mhz";

  if (HiiSetString (HiiHandle[0], STRING_TOKEN (STR_CPU_STRING2), NewString, NULL) == 0) {
    DriverSampleUnload (ImageHandle);
    return EFI_OUT_OF_RESOURCES;
  }

  HiiSetString (HiiHandle[0], 0, NewString, NULL);

4. 处理变量,这里是mPrivateData->Configuration和mPrivateData->VarStoreConfig这两个变量以及NameStringId和NameValueName,对应到Vfr.vfr中的变量:

  //
  // Define a Buffer Storage (EFI_IFR_VARSTORE)
  //
  varstore DRIVER_SAMPLE_CONFIGURATION,     // This is the data structure type
    varid = CONFIGURATION_VARSTORE_ID,      // Optional VarStore ID
    name  = MyIfrNVData,                    // Define referenced name in vfr
    guid  = DRIVER_SAMPLE_FORMSET_GUID;     // GUID of this buffer storage

  //
  // Define a EFI variable Storage (EFI_IFR_VARSTORE_EFI)
  //
  efivarstore MY_EFI_VARSTORE_DATA,
    attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,  // EFI variable attribures  
    name  = MyEfiVar,
    guid  = DRIVER_SAMPLE_FORMSET_GUID;

  //
  // Define a Name/Value Storage (EFI_IFR_VARSTORE_NAME_VALUE)
  //
  namevaluevarstore MyNameValueVar,                // Define storage reference name in vfr
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME0), // Define Name list of this storage, refer it by MyNameValueVar[0]
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME1), // Define Name list of this storage, refer it by MyNameValueVar[1]
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME2), // Define Name list of this storage, refer it by MyNameValueVar[2]
    guid = DRIVER_SAMPLE_FORMSET_GUID;             // GUID of this Name/Value storage

5. 之后创建了一个事件:

  Status = gBS->CreateEventEx (
        EVT_NOTIFY_SIGNAL, 
        TPL_NOTIFY,
        EfiEventEmptyFunction,
        NULL,
        &gEfiIfrRefreshIdOpGuid,
        &mEvent
        );
  ASSERT_EFI_ERROR (Status);

原因不明。

6. 之后就是注册按键:

  //
  // Example of how to use BrowserEx protocol to register HotKey.
  // 
  Status = gBS->LocateProtocol (&gEdkiiFormBrowserExProtocolGuid, NULL, (VOID **) &FormBrowserEx);
  if (!EFI_ERROR (Status)) {
    //
    // First unregister the default hot key F9 and F10.
    //
    HotKey.UnicodeChar = CHAR_NULL;
    HotKey.ScanCode    = SCAN_F9;
    FormBrowserEx->RegisterHotKey (&HotKey, 0, 0, NULL);
    HotKey.ScanCode    = SCAN_F10;
    FormBrowserEx->RegisterHotKey (&HotKey, 0, 0, NULL);
    
    //
    // Register the default HotKey F9 and F10 again.
    //
    HotKey.ScanCode   = SCAN_F10;
    NewString         = HiiGetString (mPrivateData->HiiHandle[0], STRING_TOKEN (FUNCTION_TEN_STRING), NULL);
    ASSERT (NewString != NULL);
    FormBrowserEx->RegisterHotKey (&HotKey, BROWSER_ACTION_SUBMIT, 0, NewString);
    HotKey.ScanCode   = SCAN_F9;
    NewString         = HiiGetString (mPrivateData->HiiHandle[0], STRING_TOKEN (FUNCTION_NINE_STRING), NULL);
    ASSERT (NewString != NULL);
    FormBrowserEx->RegisterHotKey (&HotKey, BROWSER_ACTION_DEFAULT, EFI_HII_DEFAULT_CLASS_STANDARD, NewString);
  }

7. 函数的最后会调用显示函数,不过这个其实没有运行,实际上运行的时候也可能因为Protocol不齐而无法运行,这个界面后续会在Setup界面里面显示,所以这个代码不管也不要紧:

  //
  // Example of how to display only the item we sent to HII
  // When this driver is not built into Flash device image,
  // it need to call SendForm to show front page by itself.
  //
  if (DISPLAY_ONLY_MY_ITEM <= 1) {
    //
    // Have the browser pull out our copy of the data, and only display our data
    //
    Status = FormBrowser2->SendForm (
                             FormBrowser2,
                             &(HiiHandle[DISPLAY_ONLY_MY_ITEM]),
                             1,
                             NULL,
                             0,
                             NULL,
                             NULL
                             );

    HiiRemovePackages (HiiHandle[0]);

    HiiRemovePackages (HiiHandle[1]);
  }

以上就是初始化的过程。

界面操作

前面已经说过DriverSampleDxe.inf有两个界面,其中一个Inventory.vfr比较简单,它就是一个用于显示的界面,几乎没有交互操作,所以本节主要以Vfr.vfr为主进行介绍。

关于UEFI的界面操作,实际上最重要的即使实现EFI_HII_CONFIG_ACCESS_PROTOCOL这个Protocol,它包含三个接口:

///
/// This protocol provides a callable interface between the HII and
/// drivers. Only drivers which provide IFR data to HII are required
/// to publish this protocol.
///
struct _EFI_HII_CONFIG_ACCESS_PROTOCOL {
  EFI_HII_ACCESS_EXTRACT_CONFIG     ExtractConfig;
  EFI_HII_ACCESS_ROUTE_CONFIG       RouteConfig;
  EFI_HII_ACCESS_FORM_CALLBACK      Callback;
} ;

第一个接口是用来获取当前的配置的,第二个接口用来处理配置的修改,而第三个函数用来处理forms browser调用来响应用户的操作。下面分别来介绍一些这三个接口,已经它们在DriverSampleDxe.inf中的实现。

ExtractConfig

该函数原型如下:

/**
  This function allows a caller to extract the current configuration for one
  or more named elements from the target driver.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Request                A null-terminated Unicode string in
                                 <ConfigRequest> format.
  @param  Progress               On return, points to a character in the Request
                                 string. Points to the string's null terminator if
                                 request was successful. Points to the most recent
                                 '&' before the first failing name/value pair (or
                                 the beginning of the string if the failure is in
                                 the first name/value pair) if the request was not
                                 successful.
  @param  Results                A null-terminated Unicode string in
                                 <ConfigAltResp> format which has all values filled
                                 in for the names in the Request string. String to
                                 be allocated by the called function.

  @retval EFI_SUCCESS            The Results is filled with the requested values.
  @retval EFI_OUT_OF_RESOURCES   Not enough memory to store the results.
  @retval EFI_INVALID_PARAMETER  Request is illegal syntax, or unknown name.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
ExtractConfig (
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL   *This,
  IN  CONST EFI_STRING                       Request,
  OUT EFI_STRING                             *Progress,
  OUT EFI_STRING                             *Results
  )

第一个参数是Protocol本身,没有什么好说的。下面的三个参数都是String类型的,而且是遵循特定格式的字符串。在《UEFI Spec》中Request的说明如下:

可以看到它满足一种称为ConfigRequest的结构,它是怎么样的结构呢?下面是基本的元素表示:

上述有一层层的嵌套关系,到最终可以看到的大致上是有如下的形式:

GUID=…&PATH=…&Fred&George&Ron&Neville

这里比较重要的就是这个GUID=...&NAME=...&PATH=...,它标识了真正当前模块,这也是为什么在初始化的时候要安装Device Path Protocol和GUID(如下图的mHiiVendorDevicePath0和gDriverSampleFormSetGuid):

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &DriverHandle[0],
                  &gEfiDevicePathProtocolGuid,
                  &mHiiVendorDevicePath0,
                  &gEfiHiiConfigAccessProtocolGuid,
                  &mPrivateData->ConfigAccess,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  mPrivateData->DriverHandle[0] = DriverHandle[0];

  //
  // Publish our HII data
  //
  HiiHandle[0] = HiiAddPackages (
                   &gDriverSampleFormSetGuid,
                   DriverHandle[0],
                   DriverSampleStrings,
                   VfrBin,
                   NULL
                   );

关于Results的说明如下:

关于MultiConfigAltResp的形式在前面的途中也已经有表示了,它是相对于Request的一个返回,还是以上面的字符串为例,则这里会返回的可能值如下:

GUID=…&PATH=…&Fred=16&George=16&Ron=12&Neville=11&GUID=…&PATH=…&ALTCFG=0037&Fred=12&Neville=7

Process是指向Request的指针,它指向的是还没有对应Results返回的那一个Request,是用来作为进度的指示。当它指向NULL的时候就表示所有的Request都有了对应的Result。下面简单说明DriverSampleDxe.inf模块中该函数的基本操作:

1. 首先是获取Setup变量:

  //
  // Get Buffer Storage data from EFI variable.
  // Try to get the current setting from variable.
  //
  BufferSize = sizeof (DRIVER_SAMPLE_CONFIGURATION);
  Status = gRT->GetVariable (
            VariableName,
            &gDriverSampleFormSetGuid,
            NULL,
            &BufferSize,
            &PrivateData->Configuration
            );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }

因为这个函数的作用就是获取配置,而这个配置通常就是存放在变量中的。

2. 构造Request字符串,这里有两种情况。

2.1 第一种是传入的参数Request==NULL的情况,此时表示需要所有的配置,下面是这种情况下穿件的Request字符串:

    //
    // Request is set to NULL, construct full request string.
    //

    //
    // Allocate and fill a buffer large enough to hold the <ConfigHdr> template
    // followed by "&OFFSET=0&WIDTH=WWWWWWWWWWWWWWWW" followed by a Null-terminator
    //
    ConfigRequestHdr = HiiConstructConfigHdr (&gDriverSampleFormSetGuid, VariableName, PrivateData->DriverHandle[0]);
    Size = (StrLen (ConfigRequestHdr) + 32 + 1) * sizeof (CHAR16);
    ConfigRequest = AllocateZeroPool (Size);
    ASSERT (ConfigRequest != NULL);
    AllocatedRequest = TRUE;
    UnicodeSPrint (ConfigRequest, Size, L"%s&OFFSET=0&WIDTH=%016LX", ConfigRequestHdr, (UINT64)BufferSize);
    FreePool (ConfigRequestHdr);
    ConfigRequestHdr = NULL;

这个Request字符串分为两个部分,一部分通过函数HiiConstructConfigHdr来创建,它创建的是Request的头部,格式如下:

GUID=<HexCh>32&NAME=<Char>NameLength&PATH=<HexChar>DevicePathSize<Null>

再稍微细看一下HiiConstructConfigHdr()这个函数,它接受三个参数,第一个是FormSet对应的GUID,第二个是变量的名称,第三个是Device Path所在的Handle,它们相应的就对应到了Request字符串的三个部分GUID=、NAME=和PATH=。如下是该函数得到的结果:

GUID=f4274aa000df424db55239511302113d&NAME=004d0079004900660072004e00560044006100740061&PATH=01041400f4274aa000df424db55239511302113d7fff0400

上述显示是通过DEBUG打印出来的,看上去与格式有一些出入,不过应该也能说明问题。

另外一部分是格式如下:

&OFFSET=0&WIDTH=WWWWWWWWWWWWWWWW

这一部分就不需要多讲了,实际上就是一个UnicodeSPrint的调用。最终得到的Request字符串如下:

GUID=f4274aa000df424db55239511302113d&NAME=004d0079004900660072004e00560044006100740061&PATH=01041400f4274aa000df424db55239511302113d7fff0400&OFFSET=0&WIDTH=00000000000000CF

2.2 第二种情况是Request参数!=NULL的情况,这表示调用者需要某个特定的配置。首先会对Request参数进行有效性判断:

    //
    // Check routing data in <ConfigHdr>.
    // Note: if only one Storage is used, then this checking could be skipped.
    //
    if (!HiiIsConfigHdrMatch (Request, &gDriverSampleFormSetGuid, NULL)) {
      return EFI_NOT_FOUND;
    }
    //
    // Check whether request for EFI Varstore. EFI varstore get data
    // through hii database, not support in this path.
    //
    if (HiiIsConfigHdrMatch(Request, &gDriverSampleFormSetGuid, MyEfiVar)) {
      return EFI_UNSUPPORTED;
    }

注意第二个判断条件,有一种变量不会在本函数处理。在Vfr.vfr中可以看到存在三种变量:

  //
  // Define a Buffer Storage (EFI_IFR_VARSTORE)
  //
  varstore DRIVER_SAMPLE_CONFIGURATION,     // This is the data structure type
    varid = CONFIGURATION_VARSTORE_ID,      // Optional VarStore ID
    name  = MyIfrNVData,                    // Define referenced name in vfr
    guid  = DRIVER_SAMPLE_FORMSET_GUID;     // GUID of this buffer storage

  //
  // Define a EFI variable Storage (EFI_IFR_VARSTORE_EFI)
  //
  efivarstore MY_EFI_VARSTORE_DATA,
    attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,  // EFI variable attribures  
    name  = MyEfiVar,
    guid  = DRIVER_SAMPLE_FORMSET_GUID;

  //
  // Define a Name/Value Storage (EFI_IFR_VARSTORE_NAME_VALUE)
  //
  namevaluevarstore MyNameValueVar,                // Define storage reference name in vfr
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME0), // Define Name list of this storage, refer it by MyNameValueVar[0]
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME1), // Define Name list of this storage, refer it by MyNameValueVar[1]
    name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME2), // Define Name list of this storage, refer it by MyNameValueVar[2]
    guid = DRIVER_SAMPLE_FORMSET_GUID;             // GUID of this Name/Value storage

在这里能够处理的是第一种和最后一种,中间的efivarstore不是在这里处理的。之后会判断这个Request参数是否包含OFFSET=,如果没有的话就会添加默认的:

    if (StrStr (Request, L"OFFSET") == NULL) {
      //
      // Check Request Element does exist in Reques String
      //
      StrPointer = StrStr (Request, L"PATH");
      if (StrPointer == NULL) {
        return EFI_INVALID_PARAMETER;
      }
      if (StrStr (StrPointer, L"&") == NULL) {
        Size = (StrLen (Request) + 32 + 1) * sizeof (CHAR16);
        ConfigRequest    = AllocateZeroPool (Size);
        ASSERT (ConfigRequest != NULL);
        AllocatedRequest = TRUE;
        UnicodeSPrint (ConfigRequest, Size, L"%s&OFFSET=0&WIDTH=%016LX", Request, (UINT64)BufferSize);
      }
    }

第2步的最后将得到一个完整的Request字符串ConfigRequest。

3. ConfigRequest也有两种情况,一种是有包含OFFSET=的,另一种是没有包含OFFSET=的字符串,前者表示要处理的是Name/Value类型的变量,后者表示要处理的普通的变量,具体可以参看前面2.2中的说明。

3.1 对于处理Name/Value类型的变量的变量代码如下:

    //
    // Allocate memory for <ConfigResp>, e.g. Name0=0x11, Name1=0x1234, Name2="ABCD"
    // <Request>   ::=<ConfigHdr>&Name0&Name1&Name2
    // <ConfigResp>::=<ConfigHdr>&Name0=11&Name1=1234&Name2=0041004200430044
    //
    BufferSize = (StrLen (ConfigRequest) +
      1 + sizeof (PrivateData->Configuration.NameValueVar0) * 2 +
      1 + sizeof (PrivateData->Configuration.NameValueVar1) * 2 +
      1 + sizeof (PrivateData->Configuration.NameValueVar2) * 2 + 1) * sizeof (CHAR16);
    *Results = AllocateZeroPool (BufferSize);
    ASSERT (*Results != NULL);
    StrCpyS (*Results, BufferSize / sizeof (CHAR16), ConfigRequest);
    Value = *Results;

    //
    // Append value of NameValueVar0, type is UINT8
    //
    if ((Value = StrStr (*Results, PrivateData->NameValueName[0])) != NULL) {
      Value += StrLen (PrivateData->NameValueName[0]);
      ValueStrLen = ((sizeof (PrivateData->Configuration.NameValueVar0) * 2) + 1);
      CopyMem (Value + ValueStrLen, Value, StrSize (Value));

      BackupChar = Value[ValueStrLen];
      *Value++   = L'=';
      UnicodeValueToStringS (
        Value,
        BufferSize - ((UINTN)Value - (UINTN)*Results),
        PREFIX_ZERO | RADIX_HEX,
        PrivateData->Configuration.NameValueVar0,
        sizeof (PrivateData->Configuration.NameValueVar0) * 2
        );
      Value += StrnLenS (Value, (BufferSize - ((UINTN)Value - (UINTN)*Results)) / sizeof (CHAR16));
      *Value = BackupChar;
    }

其实也没有特别需要说明的。它们处理的是mPrivateData的如下部分:

  //
  // Name/Value storage Name list
  //
  EFI_STRING_ID                    NameStringId[NAME_VALUE_NAME_NUMBER];
  EFI_STRING                       NameValueName[NAME_VALUE_NAME_NUMBER];

3.2 普通变脸处理的是mPrivateData的如下内容:

  DRIVER_SAMPLE_CONFIGURATION      Configuration;

对应的函数如下:

    //
    // Convert buffer data to <ConfigResp> by helper function BlockToConfig()
    //
    Status = HiiConfigRouting->BlockToConfig (
                                  HiiConfigRouting,
                                  ConfigRequest,
                                  (UINT8 *) &PrivateData->Configuration,
                                  BufferSize,
                                  Results,
                                  Progress
                                  );

这个UEFI Setup框架提供的基本函数了,这里不关注它的具体实现。以上就是具体的处理函数了,之后会返回结果到Results入参中。

4. 最后就是一些收尾工作:

  //
  // Set Progress string to the original request string.
  //
  if (Request == NULL) {
    *Progress = NULL;
  } else if (StrStr (Request, L"OFFSET") == NULL) {
    *Progress = Request + StrLen (Request);
  }

以上是ExtractConfig()函数的基本流程。

RouteConfig

该函数原型如下:

/**
  This function processes the results of changes in configuration.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Configuration          A null-terminated Unicode string in <ConfigResp>
                                 format.
  @param  Progress               A pointer to a string filled in with the offset of
                                 the most recent '&' before the first failing
                                 name/value pair (or the beginning of the string if
                                 the failure is in the first name/value pair) or
                                 the terminating NULL if all was successful.

  @retval EFI_SUCCESS            The Results is processed successfully.
  @retval EFI_INVALID_PARAMETER  Configuration is NULL.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
RouteConfig (
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL   *This,
  IN  CONST EFI_STRING                       Configuration,
  OUT EFI_STRING                             *Progress
  )

该函数主要做的是存储的动作,下面是基本的流程:

1. 一些初始化和入参判断:

  if (Configuration == NULL || Progress == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  PrivateData = DRIVER_SAMPLE_PRIVATE_FROM_THIS (This);
  HiiConfigRouting = PrivateData->HiiConfigRouting;
  *Progress = Configuration;

  //
  // Check routing data in <ConfigHdr>.
  // Note: if only one Storage is used, then this checking could be skipped.
  //
  if (!HiiIsConfigHdrMatch (Configuration, &gDriverSampleFormSetGuid, NULL)) {
    return EFI_NOT_FOUND;
  }

  //
  // Check whether request for EFI Varstore. EFI varstore get data
  // through hii database, not support in this path.
  //
  if (HiiIsConfigHdrMatch(Configuration, &gDriverSampleFormSetGuid, MyEfiVar)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Get Buffer Storage data from EFI variable
  //
  BufferSize = sizeof (DRIVER_SAMPLE_CONFIGURATION);
  Status = gRT->GetVariable (
            VariableName,
            &gDriverSampleFormSetGuid,
            NULL,
            &BufferSize,
            &PrivateData->Configuration
            );
  if (EFI_ERROR (Status)) {
    return Status;
  }

2. 下面又分为两个部分,用来处理两种类似的变量,前面已经介绍过,efivarstore不在这里处理,这里要处理的是namevaluevarstore和varstore。

2.1 先介绍varstore,它的存储很简单,直接调用现有接口:

  //
  // Convert <ConfigResp> to buffer data by helper function ConfigToBlock()
  //
  BufferSize = sizeof (DRIVER_SAMPLE_CONFIGURATION);
  Status = HiiConfigRouting->ConfigToBlock (
                               HiiConfigRouting,
                               Configuration,
                               (UINT8 *) &PrivateData->Configuration,
                               &BufferSize,
                               Progress
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Store Buffer Storage back to EFI variable
  //
  Status = gRT->SetVariable(
                  VariableName,
                  &gDriverSampleFormSetGuid,
                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  sizeof (DRIVER_SAMPLE_CONFIGURATION),
                  &PrivateData->Configuration
                  );

2.2 再回到前一种namevaluevarstore,这种配置中,入参Configuration字符串中不带OFFSET=:

  if (StrStr (Configuration, L"OFFSET") == NULL) {
    //
    // Update Name/Value storage Names
    //
    Status = LoadNameValueNames (PrivateData);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Convert value for NameValueVar0
    //
    if ((Value = StrStr (Configuration, PrivateData->NameValueName[0])) != NULL) {
      //
      // Skip "Name="
      //
      Value += StrLen (PrivateData->NameValueName[0]);
      Value++;
      //
      // Get Value String
      //
      StrPtr = StrStr (Value, L"&");
      if (StrPtr == NULL) {
        StrPtr = Value + StrLen (Value);
      }
      //
      // Convert Value to Buffer data
      //
      DataBuffer = (UINT8 *) &PrivateData->Configuration.NameValueVar0;
      ZeroMem (TemStr, sizeof (TemStr));
      for (Index = 0, StrPtr --; StrPtr >= Value; StrPtr --, Index ++) {
        TemStr[0] = *StrPtr;
        DigitUint8 = (UINT8) StrHexToUint64 (TemStr);
        if ((Index & 1) == 0) {
          DataBuffer [Index/2] = DigitUint8;
        } else {
          DataBuffer [Index/2] = (UINT8) ((UINT8) (DigitUint8 << 4) + DataBuffer [Index/2]);
        }
      }
    }

后面两个省略。最后还是要保存变量:

    //
    // Store Buffer Storage back to EFI variable
    //
    Status = gRT->SetVariable(
      VariableName,
      &gDriverSampleFormSetGuid,
      EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
      sizeof (DRIVER_SAMPLE_CONFIGURATION),
      &PrivateData->Configuration
      );

Callback

该函数原型如下:

/**
  This function processes the results of changes in configuration.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Action                 Specifies the type of action taken by the browser.
  @param  QuestionId             A unique value which is sent to the original
                                 exporting driver so that it can identify the type
                                 of data to expect.
  @param  Type                   The type of value for the question.
  @param  Value                  A pointer to the data being sent to the original
                                 exporting driver.
  @param  ActionRequest          On return, points to the action requested by the
                                 callback function.

  @retval EFI_SUCCESS            The callback successfully handled the action.
  @retval EFI_OUT_OF_RESOURCES   Not enough storage is available to hold the
                                 variable and its data.
  @retval EFI_DEVICE_ERROR       The variable could not be saved.
  @retval EFI_UNSUPPORTED        The specified Action is not supported by the
                                 callback.

**/
EFI_STATUS
EFIAPI
DriverCallback (
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL   *This,
  IN  EFI_BROWSER_ACTION                     Action,
  IN  EFI_QUESTION_ID                        QuestionId,
  IN  UINT8                                  Type,
  IN  EFI_IFR_TYPE_VALUE                     *Value,
  OUT EFI_BROWSER_ACTION_REQUEST             *ActionRequest
  )

这个函数首先要说明下它的各个参数:

This:这个不多说。

Action:它是一个整型,表示的是一个动作,有如下的可能:

#define EFI_BROWSER_ACTION_CHANGING   0
#define EFI_BROWSER_ACTION_CHANGED    1
#define EFI_BROWSER_ACTION_RETRIEVE   2
#define EFI_BROWSER_ACTION_FORM_OPEN  3
#define EFI_BROWSER_ACTION_FORM_CLOSE 4
#define EFI_BROWSER_ACTION_SUBMITTED  5
#define EFI_BROWSER_ACTION_DEFAULT_STANDARD      0x1000
#define EFI_BROWSER_ACTION_DEFAULT_MANUFACTURING 0x1001
#define EFI_BROWSER_ACTION_DEFAULT_SAFE          0x1002
#define EFI_BROWSER_ACTION_DEFAULT_PLATFORM      0x2000
#define EFI_BROWSER_ACTION_DEFAULT_HARDWARE      0x3000
#define EFI_BROWSER_ACTION_DEFAULT_FIRMWARE      0x4000

QuestionId:它是一个16位的整型,对应到Vfr.vfr中的key这个元素,下面是一个例子:

      help   = STRING_TOKEN(STR_EXIT_TEXT),
      text   = STRING_TOKEN(STR_EXIT_TEXT),
      flags  = INTERACTIVE,   // VfrCompiler will generate opcode EFI_IFR_ACTION for Text marked as INTERACTIVE
      key    = 0x1245;

    text
      help   = STRING_TOKEN(STR_SAVE_TEXT),
      text   = STRING_TOKEN(STR_SAVE_TEXT),
      flags  = INTERACTIVE,   // VfrCompiler will generate opcode EFI_IFR_ACTION for Text marked as INTERACTIVE
      key    = 0x1246;

Type:表示的是对应VFR中的元素的类型,下面是具体的可能值:

//
// Types of the option's value.
//
#define EFI_IFR_TYPE_NUM_SIZE_8        0x00
#define EFI_IFR_TYPE_NUM_SIZE_16       0x01
#define EFI_IFR_TYPE_NUM_SIZE_32       0x02
#define EFI_IFR_TYPE_NUM_SIZE_64       0x03
#define EFI_IFR_TYPE_BOOLEAN           0x04
#define EFI_IFR_TYPE_TIME              0x05
#define EFI_IFR_TYPE_DATE              0x06
#define EFI_IFR_TYPE_STRING            0x07
#define EFI_IFR_TYPE_OTHER             0x08
#define EFI_IFR_TYPE_UNDEFINED         0x09
#define EFI_IFR_TYPE_ACTION            0x0A
#define EFI_IFR_TYPE_BUFFER            0x0B
#define EFI_IFR_TYPE_REF               0x0C

Value:它是一个结构图如下所示,要根据具体的情况使用。

typedef union {
  UINT8           u8;
  UINT16          u16;
  UINT32          u32;
  UINT64          u64;
  BOOLEAN         b;
  EFI_HII_TIME    time;
  EFI_HII_DATE    date;
  EFI_STRING_ID   string; ///< EFI_IFR_TYPE_STRING, EFI_IFR_TYPE_ACTION
  EFI_HII_REF     ref;    ///< EFI_IFR_TYPE_REF
  // UINT8 buffer[];      ///< EFI_IFR_TYPE_BUFFER
} EFI_IFR_TYPE_VALUE;

ActionRequest:它是一个整型,有如下的取值:

#define EFI_BROWSER_ACTION_REQUEST_NONE   0
#define EFI_BROWSER_ACTION_REQUEST_RESET  1
#define EFI_BROWSER_ACTION_REQUEST_SUBMIT 2
#define EFI_BROWSER_ACTION_REQUEST_EXIT   3
#define EFI_BROWSER_ACTION_REQUEST_FORM_SUBMIT_EXIT  4
#define EFI_BROWSER_ACTION_REQUEST_FORM_DISCARD_EXIT 5
#define EFI_BROWSER_ACTION_REQUEST_FORM_APPLY        6
#define EFI_BROWSER_ACTION_REQUEST_FORM_DISCARD      7
#define EFI_BROWSER_ACTION_REQUEST_RECONNECT         8

表示操作结束之后要对Setup做什么动作。本函数根据入参Action的值有很多不同的操作,这里就不再具体说明。

  • 19
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值