前述
我们自定义的模块,有时候需要有一些用户可以调节的配置,这些配置可以放到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...