UEFI实战HII之代码示例

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI实战HII之代码示例相关的知识,希望对你有一定的参考价值。

代码示例

代码在https://gitee.com/jiangwei0512/edk2-beni,模块是BeniPkg\\DynamicCommand\\SetupDynamicCommand\\SetupDynamicCommand.inf。这里通过一个命令setup来打开图形界面。图形界面的form在Page.vfr中,还有若干的uni文件存放字符串,并通过如下的代码来初始化:

EFI_HII_HANDLE
InitializeHiiPackage (
  IN  EFI_HANDLE                    ImageHandle
  )

  EFI_STATUS                        Status;
  EFI_HII_PACKAGE_LIST_HEADER       *PackageList;
  EFI_HII_HANDLE                    HiiHandle;

  //
  // Retrieve HII package list from ImageHandle.
  //
  Status = gBS->OpenProtocol (
                  ImageHandle,
                  &gEfiHiiPackageListProtocolGuid,
                  (VOID **)&PackageList,
                  ImageHandle,
                  NULL,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) 
    return NULL;
  

  //
  // Publish HII package list to HII Database.
  //
  Status = gHiiDatabase->NewPackageList (
                           gHiiDatabase,
                           PackageList,
                           NULL,
                           &HiiHandle
                           );
  if (EFI_ERROR (Status)) 
    return NULL;
  

  return HiiHandle;

这里使用了将资源放到二进制中的方式。然后通过如下的代码来显示图形:

VOID
DisplayPage (
  VOID
  )

  EFI_STATUS                   Status;
  EFI_BROWSER_ACTION_REQUEST   ActionRequest;

  Status        = EFI_UNSUPPORTED;
  ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;

  Status = gFormBrowser2->SendForm (
                            gFormBrowser2,
                            &mSetupHiiHandle,
                            1,
                            &mFrontPageGuid,
                            0,
                            NULL,
                            &ActionRequest
                            );
  if (EFI_ERROR (Status)) 
    DEBUG ((EFI_D_ERROR, "[BENI]SendForm failed. - %r\\n", Status));
  

formset

一开始使用的Page.vfr文件内容:

// 76B732B8-B777-4ECF-A84E-7A8CA2484555
#define FORMSET_GUID         0x76b732b8, 0xb777, 0x4ecf, 0xa8, 0x4e, 0x7a, 0x8c, 0xa2, 0x48, 0x45, 0x55 

formset
  guid      = BENI_FORMSET_GUID,
  title     = STRING_TOKEN(STR_PAGE_TITLE),
  help      = STRING_TOKEN(STR_EMPTY_STRING),
  classguid = BENI_FORMSET_GUID,

endformset;

form

只有一个formset,此时什么也不会显示出来,还需要在里面加内容,首先是一个form:

// 76B732B8-B777-4ECF-A84E-7A8CA2484555
#define BENI_FORMSET_GUID    0x76b732b8, 0xb777, 0x4ecf, 0xa8, 0x4e, 0x7a, 0x8c, 0xa2, 0x48, 0x45, 0x55 
#define FRONT_PAGE_FORM_ID  0x1000

formset
  guid      = BENI_FORMSET_GUID,
  title     = STRING_TOKEN(STR_PAGE_TITLE_FORMSET),
  help      = STRING_TOKEN(STR_EMPTY_STRING),
  classguid = BENI_FORMSET_GUID,

  form
    formid  = FRONT_PAGE_FORM_ID,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM);

  endform;

endformset;

此时得到的结果:

标题来自STR_PAGE_TITLE_FORM,而Esc=ExitSendForm()自己生成的。

subtitle

之后就可以往form中添加内容。首先增加一个静态的字符串:

  form
    formid  = FRONT_PAGE_FORM_ID,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM);

    subtitle text = STRING_TOKEN(STR_PAGE_STATIC_TEXT);

  endform;

得到的结果:

可以看到SendForm()自己还生成了一个↑↓=Move Highlight

oneof

然后增加选择框(checkbox)并伴有变量,如下所示:

  efivarstore BENI_SETUP_DATA,
    attribute = 0x2, // EFI_VARIABLE_BOOTSERVICE_ACCESS
    name  = BeniSetupData,
    guid  = BENI_FORMSET_GUID;

  form
    formid  = FRONT_PAGE_FORM_ID,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM);

    subtitle text = STRING_TOKEN(STR_PAGE_STATIC_TEXT);

    oneof varid = BeniSetupData.Data1,
      prompt      = STRING_TOKEN(STR_SELECT_DATA_1_PROMPT),
      help        = STRING_TOKEN(STR_SELECT_DATA_1_HELP),
      flags       = NUMERIC_SIZE_1 | INTERACTIVE | RESET_REQUIRED,
      option text = STRING_TOKEN(STR_SELECT_DATA_0), value = 0, flags = DEFAULT;
      option text = STRING_TOKEN(STR_SELECT_DATA_1), value = 1, flags = 0;
    endoneof;
    oneof varid = BeniSetupData.Data2,
      prompt      = STRING_TOKEN(STR_SELECT_DATA_2_PROMPT),
      help        = STRING_TOKEN(STR_SELECT_DATA_2_HELP),
      flags       = NUMERIC_SIZE_1 | INTERACTIVE | RESET_REQUIRED,
      option text = STRING_TOKEN(STR_SELECT_DATA_0), value = 0, flags = DEFAULT;
      option text = STRING_TOKEN(STR_SELECT_DATA_1), value = 1, flags = 0;
    endoneof;

  endform;

对应的变量结构体:

//
// This is used in name of efivarstore.
//
#define BENI_SETUP_DATA_VAR_NAME    L"BeniSetupData"

typedef struct 
  UINT8    Data1;
  UINT8    Data2;
  UINT8    Rsvd1[2];
 BENI_SETUP_DATA;

显示结果如下:

这里可以修改值,并且保存,但是因为后端没有代码实现,所以会报错:

因此还需要增加后端的代码,这主要包含几个部分:变量的初始化,EFI_HII_CONFIG_ACCESS_PROTOCOL的实现和安装。

这里首先初始化vfr中对应的变量:

EFI_STATUS
PrepareData (
  VOID
  )

  EFI_STATUS         Status;
  BENI_SETUP_DATA    *Data;
  UINTN              DataSize;

  Status    = EFI_UNSUPPORTED;
  Data      = NULL;
  DataSize  = sizeof (BENI_SETUP_DATA);

  Data = AllocateZeroPool (DataSize);
  if (NULL == Data) 
    DEBUG ((EFI_D_ERROR, "[BENI]%a %d Out of memory\\n", __FUNCTION__, __LINE__));
    return EFI_OUT_OF_RESOURCES;
  

  Status = gRT->GetVariable (
                  BENI_SETUP_DATA_VAR_NAME,
                  &gBeniSetupFormSetGuid,
                  NULL,
                  &DataSize,
                  Data
                  );
  if (EFI_ERROR (Status)) 
    if (EFI_NOT_FOUND == Status) 
      DEBUG ((EFI_D_ERROR, "[BENI]Initialize Setup data\\n"));
      Data->Data1 = 1;
      Data->Data2 = 1;
      DataSize    = sizeof (BENI_SETUP_DATA);
      Status = gRT->SetVariable (
                    BENI_SETUP_DATA_VAR_NAME,
                    &gBeniSetupFormSetGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS,
                    DataSize,
                    Data
                    );
      DEBUG ((EFI_D_ERROR, "[BENI]Status: - %r\\n", Status));
    
  

  return Status;

这个本身意义不大,就是初始化和设置了一个变量而已,变量的值是1(所以显示的不再是Zero,而是One),这在界面中也会体现出来。然后就是安装EFI_HII_CONFIG_ACCESS_PROTOCOL:

  mPrivateData->ConfigAccess.ExtractConfig  = ExtractConfig;
  mPrivateData->ConfigAccess.RouteConfig    = RouteConfig;
  mPrivateData->ConfigAccess.Callback       = DriverCallback;
  
  //
  // Publish sample formset.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrivateData->DriverHandle,
                  &gEfiDevicePathProtocolGuid,
                  &mHiiVendorDevicePath,
                  &gEfiHiiConfigAccessProtocolGuid,
                  &mPrivateData->ConfigAccess,
                  NULL
                  );

这里的DriverCallback()等函数可以根据实际情况来实现,目前只是增加了打印信息而已,在操作上述的选择框时会被调用并输出信息。

string

string是一个可编辑的字符串,编辑之后可以保存到变量,下面是一个示例:

    string varid = BeniSetupData.DriverDescriptionData,
      questionid = PAGE_DESCRIPTION_ID,
      prompt     = STRING_TOKEN(STR_STRING_DESC_PROMPT),
      help       = STRING_TOKEN(STR_STRING_HELPER),
      flags      = INTERACTIVE,
      minsize    = 6,
      maxsize    = 30,
    endstring;

DriverDescriptionData是变量BeniSetupData的成员,它也可以预先初始化(本例中初始化成“Hello World”),PAGE_DESCRIPTION_ID可以在EFI_HII_CONFIG_ACCESS_PROTOCOLCallback()中定位,此外还有一些帮助信息、大小和操作限制等等配置。

下面是显示结果:

numeric

没什么好说的,就是数字:

    numeric varid   = BeniSetupData.Id,
            prompt  = STRING_TOKEN(STR_NUMERIC_ID_PROMPT),
            help    = STRING_TOKEN(STR_NUMERIC_ID_HELPER),
            minimum = 0,
            maximum = 1024,
    endnumeric;

下面是显示的结果:

跟string类似,只不过只能输入数字,通过flag的配置,可以选择使用十进制还是十六进制。

text

跟subtitle不同的是,text可以被选中,下面是一个例子:

    text
      help   = STRING_TOKEN(STR_TEXT_PROMPT),
      text   = STRING_TOKEN(STR_TEXT_HELPER),
      flags  = INTERACTIVE,
      key    = PAGE_TEXT_ID;

PAGE_TEXT_IDEFI_HII_CONFIG_ACCESS_PROTOCOLCallback()中使用:

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
  )

  BENI_MODULE_START

  if (Action == EFI_BROWSER_ACTION_CHANGING) 
    switch (QuestionId) 
      case PAGE_TEXT_ID:
        DEBUG ((DEBUG_ERROR, "%a %d PAGE_TEXT_ID\\n", __FUNCTION__, __LINE__));
        break;
      default:
        break;
    
   else if (Action == EFI_BROWSER_ACTION_CHANGED) 
    switch (QuestionId) 
      case PAGE_TEXT_ID:
        DEBUG ((DEBUG_ERROR, "%a %d PAGE_TEXT_ID\\n", __FUNCTION__, __LINE__));
        break;
      default:
        break;
    
  

  BENI_MODULE_END
  return EFI_SUCCESS;

显示如下:

可以看到text那一行可以被选中,点击之后可以看到打印信息:

[BENI]DriverCallback start...
DriverCallback 138 PAGE_TEXT_ID
[BENI]DriverCallback end...
[BENI]DriverCallback start...
DriverCallback 146 PAGE_TEXT_ID
[BENI]DriverCallback end...

之所以能够操作这一行,原始主要在于flags = INTERACTIVE,,这样就会创建一个EFI_IFR_ACTION的操作码,相当于植入了一个可操作的动作。

checkbox

勾选框,只有TRUE和FALSE,或者0和1两个值。下面是一个示例:

    grayoutif ideqval BeniSetupData.Disabled == 1;
      text
        help   = STRING_TOKEN(STR_TEXT_PROMPT),
        text   = STRING_TOKEN(STR_TEXT_HELPER),
        flags  = INTERACTIVE,
        key    = PAGE_TEXT_ID;
    endif;

    checkbox varid   = BeniSetupData.Disabled,
             prompt   = STRING_TOKEN(STR_CHECKBOXK_PROMPT),
             help     = STRING_TOKEN(STR_CHECKBOXK_HELPER),
             flags    = CHECKBOX_DEFAULT,
    endcheckbox;

这里还使用了grayoutif,选中之后之前测试用的text会变灰,如下所示:

goto

用于跳转到另外的界面:

    goto PAGE_FORM_ID_2,
      prompt  = STRING_TOKEN(STR_GOTO_PROMPT),
      help    = STRING_TOKEN(STR_GOTO_HELPER);

  endform;

  form
    formid  = PAGE_FORM_ID_2,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM_2);

    subtitle text = STRING_TOKEN(STR_PAGE_NETX_PAGE);

  endform;

显示的结果:

label

label相当于一个VFR中的一个占位符,本身不会产生可显示的内容,而是需要通过代码动态的增加显示内容,具体如何增加,就是使用之前介绍的HiiCreateXXX()函数在增加form组件。下面是label的示例:

#define LABEL_START                 0x1004
#define LABEL_END                   0x1005

  form
    formid  = PAGE_FORM_ID_2,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM_2);

    subtitle text = STRING_TOKEN(STR_PAGE_NETX_PAGE);
    subtitle text = STRING_TOKEN(STR_EMPTY_STRING);

    label LABEL_START;
    label LABEL_END;

  endform;

可以看到这里只是增加了两个label而已,真正的操作还是在代码中:

/**
  Customize menus in the page.

  @param[in]  HiiHandle             The HII Handle of the form to update.
  @param[in]  StartOpCodeHandle     The context used to insert opcode.

  @retval  NA

**/
VOID
CustomizePage (
  IN  EFI_HII_HANDLE                HiiHandle,
  IN  VOID                          *StartOpCodeHandle
  )

  //
  // Add OpCode here.
  //
  HiiCreateSubTitleOpCode (StartOpCodeHandle, STRING_TOKEN (STR_TEXT_IN_CODE), 0, 0, 0);


/**
  Update components.

  @param  NA

  @retval  NA

**/
VOID
UpdatePageForm (
  VOID
  )

  VOID                        *StartOpCodeHandle;
  VOID                        *EndOpCodeHandle;
  EFI_IFR_GUID_LABEL          *StartGuidLabel;
  EFI_IFR_GUID_LABEL          *EndGuidLabel;

  //
  // Allocate space for creation of UpdateData Buffer
  //
  StartOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (StartOpCodeHandle != NULL);

  EndOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (EndOpCodeHandle != NULL);

  //
  // Create Hii Extend Label OpCode as the start opcode
  //
  StartGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (StartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
  StartGuidLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
  StartGuidLabel->Number       = LABEL_START;
  //
  // Create Hii Extend Label OpCode as the end opcode
  //
  EndGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (EndOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
  EndGuidLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
  EndGuidLabel->Number       = LABEL_END;

  CustomizePage (
    mPrivateData->SetupHiiHandle,
    StartOpCodeHandle
    );

  HiiUpdateForm (
    mPrivateData->SetupHiiHandle,
    &gBeniSetupFormSetGuid,
    PAGE_FORM_ID_2,
    StartOpCodeHandle,
    EndOpCodeHandle
    );

  return;

得到的结果如下,红色部分就是通过代码生成的:

以上是关于UEFI实战HII之代码示例的主要内容,如果未能解决你的问题,请参考以下文章

UEFI实战HII之FrontPage

UEFI实战HII之FrontPage

UEFI实战HII之vfr文件

UEFI实战HII之vfr文件

UEFI实战HII之配置

UEFI实战HII之常用函数