青蚨 發表於 2022-6-6 10:58:16

UEFI开发基础HII代码示例

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>代码示例</li><ul class="second_class_ul"><li>模块</li><li>formset</li><li>form</li><li>subtitle</li><li>oneof</li><li>string</li><li>numeric</li><li>text</li><li>checkbox</li><li>goto</li><li>label</li></ul></ul></div><p class="maodian"></p><h2>代码示例</h2>
<p>代码&nbsp;https://gitee.com/jiangwei0512/edk2-beni</p>
<p class="maodian"></p><h3>模块</h3>
<blockquote><p>BeniPkg\DynamicCommand\SetupDynamicCommand\SetupDynamicCommand.inf。</p></blockquote>
<p>这里通过一个命令setup来打开图形界面。图形界面的form在Page.vfr中,还有若干的uni文件存放字符串,并通过如下的代码来初始化:</p>
<div class="jb51code"><pre class="brush:cpp;">EFI_HII_HANDLE
InitializeHiiPackage (
INEFI_HANDLE                  ImageHandle
)
{
EFI_STATUS                        Status;
EFI_HII_PACKAGE_LIST_HEADER       *PackageList;
EFI_HII_HANDLE                  HiiHandle;
//
// Retrieve HII package list from ImageHandle.
//
Status = gBS-&gt;OpenProtocol (
                  ImageHandle,
                  &amp;gEfiHiiPackageListProtocolGuid,
                  (VOID **)&amp;PackageList,
                  ImageHandle,
                  NULL,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
if (EFI_ERROR (Status)) {
    return NULL;
}
//
// Publish HII package list to HII Database.
//
Status = gHiiDatabase-&gt;NewPackageList (
                           gHiiDatabase,
                           PackageList,
                           NULL,
                           &amp;HiiHandle
                           );
if (EFI_ERROR (Status)) {
    return NULL;
}
return HiiHandle;
}
</pre></div>
<p>这里使用了将资源放到二进制中的方式。然后通过如下的代码来显示图形:</p>
<div class="jb51code"><pre class="brush:cpp;">VOID
DisplayPage (
VOID
)
{
EFI_STATUS                   Status;
EFI_BROWSER_ACTION_REQUEST   ActionRequest;
Status      = EFI_UNSUPPORTED;
ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
Status = gFormBrowser2-&gt;SendForm (
                            gFormBrowser2,
                            &amp;mSetupHiiHandle,
                            1,
                            &amp;mFrontPageGuid,
                            0,
                            NULL,
                            &amp;ActionRequest
                            );
if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "SendForm failed. - %r\n", Status));
}
}
</pre></div>
<p class="maodian"></p><h3>formset</h3>
<p>一开始使用的Page.vfr文件内容:</p>
<div class="jb51code"><pre class="brush:cpp;">// {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;
</pre></div>
<p class="maodian"></p><h3>form</h3>
<p>只有一个formset,此时什么也不会显示出来,还需要在里面加内容,首先是一个form:</p>
<div class="jb51code"><pre class="brush:cpp;">// {76B732B8-B777-4ECF-A84E-7A8CA2484555}
#define BENI_FORMSET_GUID   { 0x76b732b8, 0xb777, 0x4ecf, 0xa8, 0x4e, 0x7a, 0x8c, 0xa2, 0x48, 0x45, 0x55 }
#define FRONT_PAGE_FORM_ID0x1000
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;
</pre></div>
<p>此时得到的结果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433618.png" /></p>
<p>标题来自<code>STR_PAGE_TITLE_FORM</code>,而<code>Esc=Exit</code>是<code>SendForm()</code>自己生成的。</p>
<p class="maodian"></p><h3>subtitle</h3>
<p>之后就可以往form中添加内容。首先增加一个静态的字符串:</p>
<div class="jb51code"><pre class="brush:cpp;">form
    formid= FRONT_PAGE_FORM_ID,
    title   = STRING_TOKEN(STR_PAGE_TITLE_FORM);
    subtitle text = STRING_TOKEN(STR_PAGE_STATIC_TEXT);
endform;
</pre></div>
<p>得到的结果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433619.png" /></p>
<p>可以看到<code>SendForm()</code>自己还生成了一个<code>&uarr;&darr;=Move Highlight</code>。</p>
<p class="maodian"></p><h3>oneof</h3>
<p>然后增加选择框(checkbox)并伴有变量,如下所示:</p>
<div class="jb51code"><pre class="brush:cpp;">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;
</pre></div>
<p>对应的变量结构体:</p>
<div class="jb51code"><pre class="brush:cpp;">//
// This is used in name of efivarstore.
//
#define BENI_SETUP_DATA_VAR_NAME    L"BeniSetupData"
typedef struct {
UINT8    Data1;
UINT8    Data2;
UINT8    Rsvd1;
} BENI_SETUP_DATA;
</pre></div>
<p>显示结果如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433620.png" /></p>
<p>这里可以修改值,并且保存,但是因为后端没有代码实现,所以会报错:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433621.png" /></p>
<p>因此还需要增加后端的代码,这主要包含几个部分:变量的初始化,EFI_HII_CONFIG_ACCESS_PROTOCOL的实现和安装。</p>
<p>这里首先初始化vfr中对应的变量:</p>
<div class="jb51code"><pre class="brush:cpp;">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, "%a %d Out of memory\n", __FUNCTION__, __LINE__));
    return EFI_OUT_OF_RESOURCES;
}
Status = gRT-&gt;GetVariable (
                  BENI_SETUP_DATA_VAR_NAME,
                  &amp;gBeniSetupFormSetGuid,
                  NULL,
                  &amp;DataSize,
                  Data
                  );
if (EFI_ERROR (Status)) {
    if (EFI_NOT_FOUND == Status) {
      DEBUG ((EFI_D_ERROR, "Initialize Setup data\n"));
      Data-&gt;Data1 = 1;
      Data-&gt;Data2 = 1;
      DataSize    = sizeof (BENI_SETUP_DATA);
      Status = gRT-&gt;SetVariable (
                  BENI_SETUP_DATA_VAR_NAME,
                  &amp;gBeniSetupFormSetGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  DataSize,
                  Data
                  );
      DEBUG ((EFI_D_ERROR, "Status: - %r\n", Status));
    }
}
return Status;
}
</pre></div>
<p>这个本身意义不大,就是初始化和设置了一个变量而已,变量的值是1(所以显示的不再是Zero,而是One),这在界面中也会体现出来。然后就是安装EFI_HII_CONFIG_ACCESS_PROTOCOL:</p>
<div class="jb51code"><pre class="brush:cpp;">mPrivateData-&gt;ConfigAccess.ExtractConfig= ExtractConfig;
mPrivateData-&gt;ConfigAccess.RouteConfig    = RouteConfig;
mPrivateData-&gt;ConfigAccess.Callback       = DriverCallback;
//
// Publish sample formset.
//
Status = gBS-&gt;InstallMultipleProtocolInterfaces (
                  &amp;mPrivateData-&gt;DriverHandle,
                  &amp;gEfiDevicePathProtocolGuid,
                  &amp;mHiiVendorDevicePath,
                  &amp;gEfiHiiConfigAccessProtocolGuid,
                  &amp;mPrivateData-&gt;ConfigAccess,
                  NULL
                  );
</pre></div>
<p>这里的<code>DriverCallback()</code>等函数可以根据实际情况来实现,目前只是增加了打印信息而已,在操作上述的选择框时会被调用并输出信息。</p>
<p class="maodian"></p><h3>string</h3>
<p>string是一个可编辑的字符串,编辑之后可以保存到变量,下面是一个示例:</p>
<div class="jb51code"><pre class="brush:cpp;">    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;
</pre></div>
<p><code>DriverDescriptionData</code>是变量<code>BeniSetupData</code>的成员,它也可以预先初始化(本例中初始化成&ldquo;Hello World&rdquo;),<code>PAGE_DESCRIPTION_ID</code>可以在<code>EFI_HII_CONFIG_ACCESS_PROTOCOL</code>的<code>Callback()</code>中定位,此外还有一些帮助信息、大小和操作限制等等配置。</p>
<p>下面是显示结果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433622.png" /></p>
<p class="maodian"></p><h3>numeric</h3>
<p>没什么好说的,就是数字:</p>
<div class="jb51code"><pre class="brush:cpp;">    numeric varid   = BeniSetupData.Id,
            prompt= STRING_TOKEN(STR_NUMERIC_ID_PROMPT),
            help    = STRING_TOKEN(STR_NUMERIC_ID_HELPER),
            minimum = 0,
            maximum = 1024,
    endnumeric;
</pre></div>
<p>下面是显示的结果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433623.png" /></p>
<p>跟string类似,只不过只能输入数字,通过<code>flag</code>的配置,可以选择使用十进制还是十六进制。</p>
<p class="maodian"></p><h3>text</h3>
<p>跟subtitle不同的是,text可以被选中,下面是一个例子:</p>
<div class="jb51code"><pre class="brush:cpp;">    text
      help   = STRING_TOKEN(STR_TEXT_PROMPT),
      text   = STRING_TOKEN(STR_TEXT_HELPER),
      flags= INTERACTIVE,
      key    = PAGE_TEXT_ID;
</pre></div>
<p><code>PAGE_TEXT_ID</code>在<code>EFI_HII_CONFIG_ACCESS_PROTOCOL</code>的<code>Callback()</code>中使用:</p>
<div class="jb51code"><pre class="brush:cpp;">EFI_STATUS
EFIAPI
DriverCallback (
INCONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
INEFI_BROWSER_ACTION                      Action,
INEFI_QUESTION_ID                         QuestionId,
INUINT8                                 Type,
INEFI_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;
}
</pre></div>
<p>显示如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433624.png" /></p>
<p>可以看到<code>text</code>那一行可以被选中,点击之后可以看到打印信息:</p>
<div class="jb51code"><pre class="brush:plain;">DriverCallback start...
DriverCallback 138 PAGE_TEXT_ID
DriverCallback end...
DriverCallback start...
DriverCallback 146 PAGE_TEXT_ID
DriverCallback end...
</pre></div>
<p>之所以能够操作这一行,原始主要在于<code>flags = INTERACTIVE,</code>,这样就会创建一个<code>EFI_IFR_ACTION</code>的操作码,相当于植入了一个可操作的动作。</p>
<p class="maodian"></p><h3>checkbox</h3>
<p>勾选框,只有TRUE和FALSE,或者0和1两个值。下面是一个示例:</p>
<div class="jb51code"><pre class="brush:cpp;">    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;
</pre></div>
<p>这里还使用了<code>grayoutif</code>,选中之后之前测试用的<code>text</code>会变灰,如下所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433625.png" /></p>
<p class="maodian"></p><h3>goto</h3>
<p>用于跳转到另外的界面:</p>
<div class="jb51code"><pre class="brush:cpp;">    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;
</pre></div>
<p>显示的结果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433626.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433727.png" /></p>
<p class="maodian"></p><h3>label</h3>
<p>label相当于一个VFR中的一个占位符,本身不会产生可显示的内容,而是需要通过代码动态的增加显示内容,具体如何增加,就是使用之前介绍的<code>HiiCreateXXX()</code>函数在增加form组件。下面是label的示例:</p>
<div class="jb51code"><pre class="brush:cpp;">#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;
</pre></div>
<p>可以看到这里只是增加了两个<code>label</code>而已,真正的操作还是在代码中:</p>
<div class="jb51code"><pre class="brush:cpp;">/**
Customize menus in the page.
@paramHiiHandle             The HII Handle of the form to update.
@paramStartOpCodeHandle   The context used to insert opcode.
@retvalNA
**/
VOID
CustomizePage (
INEFI_HII_HANDLE                HiiHandle,
INVOID                        *StartOpCodeHandle
)
{
//
// Add OpCode here.
//
HiiCreateSubTitleOpCode (StartOpCodeHandle, STRING_TOKEN (STR_TEXT_IN_CODE), 0, 0, 0);
}
/**
Update components.
@paramNA
@retvalNA
**/
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, &amp;gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
StartGuidLabel-&gt;ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
StartGuidLabel-&gt;Number       = LABEL_START;
//
// Create Hii Extend Label OpCode as the end opcode
//
EndGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (EndOpCodeHandle, &amp;gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
EndGuidLabel-&gt;ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
EndGuidLabel-&gt;Number       = LABEL_END;
CustomizePage (
    mPrivateData-&gt;SetupHiiHandle,
    StartOpCodeHandle
    );
HiiUpdateForm (
    mPrivateData-&gt;SetupHiiHandle,
    &amp;gBeniSetupFormSetGuid,
    PAGE_FORM_ID_2,
    StartOpCodeHandle,
    EndOpCodeHandle
    );
return;
}
</pre></div>
<p>得到的结果如下,红色部分就是通过代码生成的:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060610433728.png" /></p>
<p>以上就是UEFI开发基础HII代码示例的详细内容,更多关于UEFI开发HII代码的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>UEFI开发实战用户交互界面基础说明</li><li>UEFI开发实战用户交互界面使用说明UNI文件</li><li>UEFI开发实战用户交互界面使用说明VFR文件</li><li>UEFI开发基础汇编代码的使用</li><li>UEFI开发实战SlimBootloader中调用FSP</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: UEFI开发基础HII代码示例