/** @file
Implementation of HII utility library.
Copyright (c) 2019, Intel Corporation. All rights reserved.
(C) Copyright 2021 Hewlett Packard Enterprise Development LP
Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "HiiInternal.h"
/**
Initialize the internal data structure of a FormSet.
@param Handle PackageList Handle
@param FormSetGuid On input, GUID or class GUID of a formset. If not
specified (NULL or zero GUID), take the first
FormSet with class GUID EFI_HII_PLATFORM_SETUP_FORMSET_GUID
found in package list.
On output, GUID of the formset found(if not NULL).
@param FormSet FormSet data structure.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_NOT_FOUND The specified FormSet could not be found.
**/
EFI_STATUS
CreateFormSetFromHiiHandle (
IN EFI_HII_HANDLE Handle,
IN OUT EFI_GUID *FormSetGuid,
OUT HII_FORMSET *FormSet
)
{
EFI_STATUS Status;
EFI_HANDLE DriverHandle;
EFI_HII_DATABASE_PROTOCOL *HiiDatabase;
if ((FormSetGuid == NULL) || (FormSet == NULL)) {
return EFI_INVALID_PARAMETER;
}
//
// Locate required Hii Database protocol
//
Status = gBS->LocateProtocol (
&gEfiHiiDatabaseProtocolGuid,
NULL,
(VOID **)&HiiDatabase
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetIfrBinaryData (Handle, FormSetGuid, &FormSet->IfrBinaryLength, &FormSet->IfrBinaryData);
if (EFI_ERROR (Status)) {
return Status;
}
FormSet->Signature = HII_FORMSET_SIGNATURE;
FormSet->HiiHandle = Handle;
CopyMem (&FormSet->Guid, FormSetGuid, sizeof (EFI_GUID));
//
// Retrieve ConfigAccess Protocol associated with this HiiPackageList
//
Status = HiiDatabase->GetPackageListHandle (HiiDatabase, Handle, &DriverHandle);
if (EFI_ERROR (Status)) {
return Status;
}
FormSet->DriverHandle = DriverHandle;
Status = gBS->HandleProtocol (
DriverHandle,
&gEfiHiiConfigAccessProtocolGuid,
(VOID **)&FormSet->ConfigAccess
);
if (EFI_ERROR (Status)) {
//
// Configuration Driver don't attach ConfigAccess protocol to its HII package
// list, then there will be no configuration action required
//
FormSet->ConfigAccess = NULL;
}
Status = gBS->HandleProtocol (
DriverHandle,
&gEfiDevicePathProtocolGuid,
(VOID **)&FormSet->DevicePath
);
if (EFI_ERROR (Status)) {
//
// Configuration Driver don't attach ConfigAccess protocol to its HII package
// list, then there will be no configuration action required
//
FormSet->DevicePath = NULL;
}
//
// Parse the IFR binary OpCodes
//
Status = ParseOpCodes (FormSet);
return Status;
}
/**
Initialize a Formset and get current setting for Questions.
@param FormSet FormSet data structure.
**/
VOID
InitializeFormSet (
IN OUT HII_FORMSET *FormSet
)
{
LIST_ENTRY *Link;
HII_FORMSET_STORAGE *Storage;
LIST_ENTRY *FormLink;
HII_STATEMENT *Question;
HII_FORM *Form;
if (FormSet == NULL) {
return;
}
//
// Load Storage for all questions with storage
//
Link = GetFirstNode (&FormSet->StorageListHead);
while (!IsNull (&FormSet->StorageListHead, Link)) {
Storage = HII_STORAGE_FROM_LINK (Link);
LoadFormSetStorage (FormSet, Storage);
Link = GetNextNode (&FormSet->StorageListHead, Link);
}
//
// Get Current Value for all no storage questions
//
FormLink = GetFirstNode (&FormSet->FormListHead);
while (!IsNull (&FormSet->FormListHead, FormLink)) {
Form = HII_FORM_FROM_LINK (FormLink);
Link = GetFirstNode (&Form->StatementListHead);
while (!IsNull (&Form->StatementListHead, Link)) {
Question = HII_STATEMENT_FROM_LINK (Link);
if (Question->Storage == NULL) {
RetrieveQuestion (FormSet, Form, Question);
}
Link = GetNextNode (&Form->StatementListHead, Link);
}
FormLink = GetNextNode (&FormSet->FormListHead, FormLink);
}
}
/**
Free resources allocated for a FormSet.
@param[in,out] FormSet Pointer of the FormSet
**/
VOID
DestroyFormSet (
IN OUT HII_FORMSET *FormSet
)
{
LIST_ENTRY *Link;
HII_FORMSET_STORAGE *Storage;
HII_FORMSET_DEFAULTSTORE *DefaultStore;
HII_FORM *Form;
if (FormSet->IfrBinaryData == NULL) {
//
// Uninitialized FormSet
//
FreePool (FormSet);
return;
}
//
// Free IFR binary buffer
//
FreePool (FormSet->IfrBinaryData);
//
// Free FormSet Storage
//
if (FormSet->StorageListHead.ForwardLink != NULL) {
while (!IsListEmpty (&FormSet->StorageListHead)) {
Link = GetFirstNode (&FormSet->StorageListHead);
Storage = HII_STORAGE_FROM_LINK (Link);
RemoveEntryList (&Storage->Link);
if (Storage != NULL) {
FreePool (Storage);
}
}
}
//
// Free FormSet Default Store
//
if (FormSet->DefaultStoreListHead.ForwardLink != NULL) {
while (!IsListEmpty (&FormSet->DefaultStoreListHead)) {
Link = GetFirstNode (&FormSet->DefaultStoreListHead);
DefaultStore = HII_FORMSET_DEFAULTSTORE_FROM_LINK (Link);
RemoveEntryList (&DefaultStore->Link);
FreePool (DefaultStore);
}
}
//
// Free Forms
//
if (FormSet->FormListHead.ForwardLink != NULL) {
while (!IsListEmpty (&FormSet->FormListHead)) {
Link = GetFirstNode (&FormSet->FormListHead);
Form = HII_FORM_FROM_LINK (Link);
RemoveEntryList (&Form->Link);
DestroyForm (FormSet, Form);
}
}
FreePool (FormSet);
}
/**
Submit data for a form.
@param[in] FormSet FormSet which contains the Form.
@param[in] Form Form to submit.
@retval EFI_SUCCESS The function completed successfully.
@retval Others Other errors occur.
**/
EFI_STATUS
SubmitForm (
IN HII_FORMSET *FormSet,
IN HII_FORM *Form
)
{
EFI_STATUS Status;
EFI_HII_CONFIG_ROUTING_PROTOCOL *HiiConfigRouting;
LIST_ENTRY *Link;
EFI_STRING ConfigResp;
EFI_STRING Progress;
HII_FORMSET_STORAGE *Storage;
HII_FORM_CONFIG_REQUEST *ConfigInfo;
if ((FormSet == NULL) || (Form == NULL)) {
return EFI_INVALID_PARAMETER;
}
Status = NoSubmitCheck (FormSet, &Form, NULL);
if (EFI_ERROR (Status)) {
return Status;
}
Link = GetFirstNode (&Form->ConfigRequestHead);
while (!IsNull (&Form->ConfigRequestHead, Link)) {
ConfigInfo = HII_FORM_CONFIG_REQUEST_FROM_LINK (Link);
Link = GetNextNode (&Form->ConfigRequestHead, Link);
Storage = ConfigInfo->Storage;
if (Storage->Type == EFI_HII_VARSTORE_EFI_VARIABLE) {
continue;
}
//
// Skip if there is no RequestElement
//
if (ConfigInfo->ElementCount == 0) {
continue;
}
Status = StorageToConfigResp (ConfigInfo->Storage, &ConfigResp, ConfigInfo->ConfigRequest);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->LocateProtocol (
&gEfiHiiConfigRoutingProtocolGuid,
NULL,
(VOID **)&HiiConfigRouting
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = HiiConfigRouting->RouteConfig (
HiiConfigRouting,
ConfigResp,
&Progress
);
if (EFI_ERROR (Status)) {
FreePool (ConfigResp);
continue;
}
FreePool (ConfigResp);
}
return Status;
}
/**
Save Question Value to the memory, but not to storage.
@param[in] FormSet FormSet data structure.
@param[in] Form Form data structure.
@param[in,out] Question Pointer to the Question.
@param[in] QuestionValue New Question Value to be set.
@retval EFI_SUCCESS The question value has been set successfully.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
EFI_STATUS
SetQuestionValue (
IN HII_FORMSET *FormSet,
IN HII_FORM *Form,
IN OUT HII_STATEMENT *Question,
IN HII_STATEMENT_VALUE *QuestionValue
)
{
UINT8 *Src;
UINTN BufferLen;
UINTN StorageWidth;
HII_FORMSET_STORAGE *Storage;
CHAR16 *ValueStr;
BOOLEAN IsBufferStorage;
UINT8 *TemBuffer;
CHAR16 *TemName;
CHAR16 *TemString;
UINTN Index;
HII_NAME_VALUE_NODE *Node;
EFI_STATUS Status;
if ((FormSet == NULL) || (Form == NULL) || (Question == NULL) || (QuestionValue == NULL)) {
return EFI_INVALID_PARAMETER;
}
Status = EFI_SUCCESS;
Node = NULL;
//
// If Question value is provided by an Expression, then it is read only
//
if ((Question->ValueExpression != NULL) || (Question->Value.Type != QuestionValue->Type)) {
return EFI_INVALID_PARAMETER;
}
//
// Before set question value, evaluate its write expression.
//
if ((Question->WriteExpression != NULL) && (Form->FormType == STANDARD_MAP_FORM_TYPE)) {
Status = EvaluateHiiExpression (FormSet, Form, Question->WriteExpression);
if (EFI_ERROR (Status)) {
return Status;
}
}
Storage = Question->Storage;
if (Storage != NULL) {
StorageWidth = Question->StorageWidth;
if (Question->Value.Type == EFI_IFR_TYPE_BUFFER) {
Question->Value.BufferLen = QuestionValue->BufferLen;
Question->Value.Buffer = AllocateCopyPool (QuestionValue->BufferLen, QuestionValue->Buffer);
if (Question->Value.Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Question->Value.BufferValueType = QuestionValue->BufferValueType;
Src = Question->Value.Buffer;
} else if (Question->Value.Type == EFI_IFR_TYPE_STRING) {
Question->Value.Value.string = QuestionValue->Value.string;
TemString = HiiGetString (FormSet->HiiHandle, QuestionValue->Value.string, NULL);
if (TemString == NULL) {
return EFI_ABORTED;
}
Question->Value.BufferLen = Question->StorageWidth;
Question->Value.Buffer = AllocateZeroPool (Question->StorageWidth);
if (Question->Value.Buffer == NULL) {
FreePool (TemString);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Question->Value.Buffer, TemString, StrSize (TemString));
Src = Question->Value.Buffer;
FreePool (TemString);
} else {
CopyMem (&Question->Value.Value, &QuestionValue->Value, sizeof (EFI_IFR_TYPE_VALUE));
Src = (UINT8 *)&Question->Value.Value;
}
if ((Storage->Type == EFI_HII_VARSTORE_BUFFER) || (Storage->Type == EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER)) {
IsBufferStorage = TRUE;
} else {
IsBufferStorage = FALSE;
}
if (IsBufferStorage) {
//
// If the Question refer to bit filed, copy the value in related bit filed to storage edit buffer.
//
if (Question->QuestionReferToBitField) {
SetBitsQuestionValue (Question, Storage->Buffer + Question->VarStoreInfo.VarOffset, (UINT32)(*Src));
} else {
CopyMem (Storage->Buffer + Question->VarStoreInfo.VarOffset, Src, StorageWidth);
}
} else {
if (Question->Value.Type == EFI_IFR_TYPE_STRING) {
//
// Allocate enough string buffer.
//
ValueStr = NULL;
BufferLen = ((StrLen ((CHAR16 *)Src) * 4) + 1) * sizeof (CHAR16);
ValueStr = AllocatePool (BufferLen);
if (ValueStr == NULL) {
if (Question->Value.Buffer != NULL) {
FreePool (Question->Value.Buffer);
}
return EFI_OUT_OF_RESOURCES;
}
//
// Convert Unicode String to Config String, e.g. "ABCD" => "0041004200430044"
//
TemName = (CHAR16 *)Src;
TemString = ValueStr;
for ( ; *TemName != L'\0'; TemName++) {
UnicodeValueToStringS (
TemString,
BufferLen - ((UINTN)TemString - (UINTN)ValueStr),
PREFIX_ZERO | RADIX_HEX,
*TemName,
4
);
TemString += StrnLenS (TemString, (BufferLen - ((UINTN)TemString - (UINTN)ValueStr)) / sizeof (CHAR16));
}
} else {
BufferLen = StorageWidth * 2 + 1;
ValueStr = AllocateZeroPool (BufferLen * sizeof (CHAR16));
if (ValueStr == NULL) {
if (Question->Value.Buffer != NULL) {
FreePool (Question->Value.Buffer);
}
return EFI_OUT_OF_RESOURCES;
}
//
// Convert Buffer to Hex String
//
TemBuffer = Src + StorageWidth - 1;
TemString = ValueStr;
for (Index = 0; Index < StorageWidth; Index++, TemBuffer--) {
UnicodeValueToStringS (
TemString,
BufferLen * sizeof (CHAR16) - ((UINTN)TemString - (UINTN)ValueStr),
PREFIX_ZERO | RADIX_HEX,
*TemBuffer,
2
);
TemString += StrnLenS (TemString, BufferLen - ((UINTN)TemString - (UINTN)ValueStr) / sizeof (CHAR16));
}
}
Status = SetValueByName (Storage, Question->VariableName, ValueStr, &Node);
FreePool (ValueStr);
if (EFI_ERROR (Status)) {
if (Question->Value.Buffer != NULL) {
FreePool (Question->Value.Buffer);
}
return Status;
}
}
} else {
if (Question->Value.Type == EFI_IFR_TYPE_BUFFER) {
Question->Value.BufferLen = QuestionValue->BufferLen;
Question->Value.Buffer = AllocateCopyPool (QuestionValue->BufferLen, QuestionValue->Buffer);
if (Question->Value.Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Question->Value.BufferValueType = QuestionValue->BufferValueType;
} else if (Question->Value.Type == EFI_IFR_TYPE_STRING) {
Question->Value.Value.string = QuestionValue->Value.string;
TemString = HiiGetString (FormSet->HiiHandle, QuestionValue->Value.string, NULL);
if (TemString == NULL) {
return EFI_ABORTED;
}
Question->Value.BufferLen = (UINT16)StrSize (TemString);
Question->Value.Buffer = AllocateZeroPool (QuestionValue->BufferLen);
if (Question->Value.Buffer == NULL) {
FreePool (TemString);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Question->Value.Buffer, TemString, StrSize (TemString));
FreePool (TemString);
} else {
CopyMem (&Question->Value.Value, &QuestionValue->Value, sizeof (EFI_IFR_TYPE_VALUE));
}
}
return Status;
}
/**
Get Question's current Value from storage.
@param[in] FormSet FormSet data structure.
@param[in] Form Form data structure.
@param[in,out] Question Question to be initialized.
@return the current Question Value in storage if success.
@return NULL if Question is not found or any error occurs.
**/
HII_STATEMENT_VALUE *
RetrieveQuestion (
IN HII_FORMSET *FormSet,
IN HII_FORM *Form,
IN OUT HII_STATEMENT *Question
)
{
EFI_STATUS Status;
EFI_HII_CONFIG_ROUTING_PROTOCOL *HiiConfigRouting;
EFI_BROWSER_ACTION_REQUEST ActionRequest;
HII_FORMSET_STORAGE *Storage;
HII_STATEMENT_VALUE *QuestionValue;
EFI_IFR_TYPE_VALUE *TypeValue;
EFI_TIME EfiTime;
BOOLEAN Enabled;
BOOLEAN Pending;
UINT8 *Dst;
UINTN StorageWidth;
CHAR16 *ConfigRequest;
CHAR16 *Progress;
CHAR16 *Result;
CHAR16 *ValueStr;
UINTN Length;
BOOLEAN IsBufferStorage;
CHAR16 *NewHiiString;
if ((FormSet == NULL) || (Form == NULL) || (Question == NULL)) {
return NULL;
}
Status = EFI_SUCCESS;
ValueStr = NULL;
Result = NULL;
QuestionValue = AllocateZeroPool (sizeof (HII_STATEMENT_VALUE));
if (QuestionValue == NULL) {
return NULL;
}
QuestionValue->Type = Question->Value.Type;
QuestionValue->BufferLen = Question->Value.BufferLen;
if (QuestionValue->BufferLen != 0) {
QuestionValue->Buffer = AllocateZeroPool (QuestionValue->BufferLen);
if (QuestionValue->Buffer == NULL) {
FreePool (QuestionValue);
return NULL;
}
}
//
// Question value is provided by RTC
//
Storage = Question->Storage;
StorageWidth = Question->StorageWidth;
if (Storage == NULL) {
//
// It's a Question without storage, or RTC date/time
//
if ((Question->Operand == EFI_IFR_DATE_OP) || (Question->Operand == EFI_IFR_TIME_OP)) {
//
// Date and time define the same Flags bit
//
switch (Question->ExtraData.Flags & EFI_QF_DATE_STORAGE) {
case QF_DATE_STORAGE_TIME:
Status = gRT->GetTime (&EfiTime, NULL);
break;
case QF_DATE_STORAGE_WAKEUP:
Status = gRT->GetWakeupTime (&Enabled, &Pending, &EfiTime);
break;
case QF_DATE_STORAGE_NORMAL:
default:
goto ON_ERROR;
}
if (EFI_ERROR (Status)) {
if (Question->Operand == EFI_IFR_DATE_OP) {
QuestionValue->Value.date.Year = 0xff;
QuestionValue->Value.date.Month = 0xff;
QuestionValue->Value.date.Day = 0xff;
} else {
QuestionValue->Value.time.Hour = 0xff;
QuestionValue->Value.time.Minute = 0xff;
QuestionValue->Value.time.Second = 0xff;
}
return QuestionValue;
}
if (Question->Operand == EFI_IFR_DATE_OP) {
QuestionValue->Value.date.Year = EfiTime.Year;
QuestionValue->Value.date.Month = EfiTime.Month;
QuestionValue->Value.date.Day = EfiTime.Day;
} else {
QuestionValue->Value.time.Hour = EfiTime.Hour;
QuestionValue->Value.time.Minute = EfiTime.Minute;
QuestionValue->Value.time.Second = EfiTime.Second;
}
} else {
if (((Question->QuestionFlags & EFI_IFR_FLAG_CALLBACK) != EFI_IFR_FLAG_CALLBACK) ||
(FormSet->ConfigAccess == NULL))
{
goto ON_ERROR;
}
if (QuestionValue->Type == EFI_IFR_TYPE_BUFFER) {
//
// For OrderedList, passing in the value buffer to Callback()
//
TypeValue = (EFI_IFR_TYPE_VALUE *)QuestionValue->Buffer;
} else {
TypeValue = &QuestionValue->Value;
}
ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
Status = FormSet->ConfigAccess->Callback (
FormSet->ConfigAccess,
EFI_BROWSER_ACTION_RETRIEVE,
Question->QuestionId,
QuestionValue->Type,
TypeValue,
&ActionRequest
);
if (!EFI_ERROR (Status) && (QuestionValue->Type == EFI_IFR_TYPE_STRING)) {
if (TypeValue->string == 0) {
goto ON_ERROR;
}
NewHiiString = GetTokenString (TypeValue->string, FormSet->HiiHandle);
if (NewHiiString == NULL) {
goto ON_ERROR;
}
QuestionValue->Buffer = AllocatePool (StrSize (NewHiiString));
if (QuestionValue->Buffer == NULL) {
FreePool (NewHiiString);
goto ON_ERROR;
}
CopyMem (QuestionValue->Buffer, NewHiiString, StrSize (NewHiiString));
QuestionValue->BufferLen = (UINT16)StrSize (NewHiiString);
FreePool (NewHiiString);
}
}
return QuestionValue;
}
//
// Question value is provided by EFI variable
//
if (Storage->Type == EFI_HII_VARSTORE_EFI_VARIABLE) {
if ((QuestionValue->Type != EFI_IFR_TYPE_BUFFER) && (QuestionValue->Type != EFI_IFR_TYPE_STRING)) {
Dst = QuestionValue->Buffer;
StorageWidth = QuestionValue->BufferLen;
} else {
Dst = (UINT8 *)&QuestionValue->Value;
StorageWidth = sizeof (EFI_IFR_TYPE_VALUE);
}
Status = gRT->GetVariable (
Question->VariableName,
&Storage->Guid,
NULL,
&StorageWidth,
Dst
);
return QuestionValue;
}
Status = gBS->LocateProtocol (
&gEfiHiiConfigRoutingProtocolGuid,
NULL,
(VOID **)&HiiConfigRouting
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
if (QuestionValue->BufferLen != 0) {
Dst = QuestionValue->Buffer;
} else {
Dst = (UINT8 *)&QuestionValue->Value;
}
if ((Storage->Type == EFI_HII_VARSTORE_BUFFER) || (Storage->Type == EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER)) {
IsBufferStorage = TRUE;
} else {
IsBufferStorage = FALSE;
}
Storage = GetFstStgFromVarId (FormSet, Question->VarStoreId);
if (Storage == NULL) {
goto ON_ERROR;
}
//
// ::= + || + "&" +
//
if (IsBufferStorage) {
Length = StrLen (Storage->ConfigHdr);
Length += StrLen (Question->BlockName);
} else {
Length = StrLen (Storage->ConfigHdr);
Length += StrLen (Question->VariableName) + 1;
}
ConfigRequest = AllocatePool ((Length + 1) * sizeof (CHAR16));
if (ConfigRequest == NULL) {
goto ON_ERROR;
}
StrCpyS (ConfigRequest, Length + 1, Storage->ConfigHdr);
if (IsBufferStorage) {
StrCatS (ConfigRequest, Length + 1, Question->BlockName);
} else {
StrCatS (ConfigRequest, Length + 1, L"&");
StrCatS (ConfigRequest, Length + 1, Question->VariableName);
}
//
// Request current settings from Configuration Driver
//
Status = HiiConfigRouting->ExtractConfig (
HiiConfigRouting,
ConfigRequest,
&Progress,
&Result
);
FreePool (ConfigRequest);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
if (IsBufferStorage) {
ValueStr = StrStr (Result, L"&VALUE");
if (ValueStr == NULL) {
FreePool (Result);
goto ON_ERROR;
}
ValueStr = ValueStr + 6;
} else {
ValueStr = Result + Length;
}
if (*ValueStr != '=') {
FreePool (Result);
goto ON_ERROR;
}
ValueStr++;
Status = BufferToQuestionValue (Question, ValueStr, QuestionValue);
if (EFI_ERROR (Status)) {
FreePool (Result);
goto ON_ERROR;
}
if (Result != NULL) {
FreePool (Result);
}
return QuestionValue;
ON_ERROR:
if (QuestionValue->Buffer != NULL) {
FreePool (QuestionValue->Buffer);
}
FreePool (QuestionValue);
return NULL;
}