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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiangwei0512/article/details/82192735

前述

我们自定义的模块,有时候需要有一些用户可以调节的配置,这些配置可以放到Setup界面中,修改之后重启有效。

本文要介绍的就是如何在Setup界面中嵌入一个自定义的配置界面。

本文以MdeModulePkg\Universal\DriverSampleDxe\DriverSampleDxe.inf为例进行说明。

代码可以在https://gitee.com/jiangwei0512/vUDK2017下载到。

实际上该模块已经提供了比较完全的Setup示例。

图形界面说明

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

以及(Inventory.vfr):

前者对应的VFR代码是Vfr.vfr,后者对应的VFR代码是Inventory.vfr,两者还分别有一个UNI文件与它对应。

关于VFR和UNI的说明可以参考BIOS/UEFI基础——UEFI用户交互界面使用说明之VFR文件BIOS/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。

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
  )

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
  )

to be continued...

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/82192735