mirror of https://github.com/acidanthera/audk.git
1131 lines
39 KiB
C
1131 lines
39 KiB
C
/**@file
|
|
This file implements functions related to Config Access Protocols installed by
|
|
by HII Thunk Modules. These Config access Protocols are used to thunk UEFI Config
|
|
Access Callback to Framework HII Callback and EFI Variable Set/Get operations.
|
|
|
|
Copyright (c) 2008, Intel Corporation
|
|
All rights reserved. This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
#include "HiiDatabase.h"
|
|
#include "UefiIfrParser.h"
|
|
|
|
BOOLEAN mHiiPackageListUpdated = FALSE;
|
|
|
|
HII_VENDOR_DEVICE_PATH mUefiHiiVendorDevicePath = {
|
|
{
|
|
{
|
|
{
|
|
HARDWARE_DEVICE_PATH,
|
|
HW_VENDOR_DP,
|
|
{
|
|
(UINT8) (sizeof (HII_VENDOR_DEVICE_PATH_NODE)),
|
|
(UINT8) ((sizeof (HII_VENDOR_DEVICE_PATH_NODE)) >> 8)
|
|
}
|
|
},
|
|
EFI_CALLER_ID_GUID
|
|
},
|
|
0,
|
|
0
|
|
},
|
|
{
|
|
END_DEVICE_PATH_TYPE,
|
|
END_ENTIRE_DEVICE_PATH_SUBTYPE,
|
|
{
|
|
(UINT8) (sizeof (EFI_DEVICE_PATH_PROTOCOL)),
|
|
(UINT8) ((sizeof (EFI_DEVICE_PATH_PROTOCOL)) >> 8)
|
|
}
|
|
}
|
|
};
|
|
|
|
CONFIG_ACCESS_PRIVATE gConfigAccessPrivateTempate = {
|
|
CONFIG_ACCESS_PRIVATE_SIGNATURE,
|
|
{
|
|
ThunkExtractConfig,
|
|
ThunkRouteConfig,
|
|
ThunkCallback
|
|
}, //ConfigAccessProtocol
|
|
NULL, //FormCallbackProtocol
|
|
NULL
|
|
};
|
|
|
|
/**
|
|
Get the first EFI_IFR_VARSTORE from the FormSet.
|
|
|
|
@param FormSet The Form Set.
|
|
|
|
@retval FORMSET_STORAGE * Return the first EFI_IFR_VARSTORE.
|
|
@retval NULL If the Form Set does not have EFI_IFR_VARSTORE.
|
|
**/
|
|
FORMSET_STORAGE *
|
|
GetFirstStorageOfFormSet (
|
|
IN CONST FORM_BROWSER_FORMSET * FormSet
|
|
)
|
|
{
|
|
LIST_ENTRY *StorageList;
|
|
FORMSET_STORAGE *Storage;
|
|
|
|
StorageList = GetFirstNode (&FormSet->StorageListHead);
|
|
|
|
if (!IsNull (&FormSet->StorageListHead, StorageList)) {
|
|
Storage = FORMSET_STORAGE_FROM_LINK (StorageList);
|
|
return Storage;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Get the FORM_BROWSER_STATEMENT that matches the Question's value.
|
|
|
|
@param FormSet The Form Set.
|
|
@param QuestionId QuestionId
|
|
|
|
@retval FORM_BROWSER_STATEMENT* FORM_BROWSER_STATEMENT that match Question's value.
|
|
@retval NULL If the Form Set does not have EFI_IFR_VARSTORE.
|
|
**/
|
|
FORM_BROWSER_STATEMENT *
|
|
GetStorageFromQuestionId (
|
|
IN CONST FORM_BROWSER_FORMSET * FormSet,
|
|
IN EFI_QUESTION_ID QuestionId
|
|
)
|
|
{
|
|
LIST_ENTRY *FormList;
|
|
LIST_ENTRY *StatementList;
|
|
FORM_BROWSER_FORM *Form;
|
|
FORM_BROWSER_STATEMENT *Statement;
|
|
|
|
FormList = GetFirstNode (&FormSet->FormListHead);
|
|
|
|
while (!IsNull (&FormSet->FormListHead, FormList)) {
|
|
Form = FORM_BROWSER_FORM_FROM_LINK (FormList);
|
|
|
|
StatementList = GetFirstNode (&Form->StatementListHead);
|
|
|
|
while (!IsNull (&Form->StatementListHead, StatementList)) {
|
|
Statement = FORM_BROWSER_STATEMENT_FROM_LINK (StatementList);
|
|
if ((QuestionId == Statement->QuestionId) && (Statement->Storage != NULL)) {
|
|
//
|
|
// UEFI Question ID is unique in a FormSet.
|
|
//
|
|
ASSERT (Statement->Storage->Type == EFI_HII_VARSTORE_BUFFER);
|
|
return Statement;
|
|
}
|
|
StatementList = GetNextNode (&Form->StatementListHead, StatementList);
|
|
}
|
|
|
|
FormList = GetNextNode (&FormSet->FormListHead, FormList);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Get the EFI_IFR_VARSTORE based the ID.
|
|
|
|
@param FormSet The Form Set.
|
|
|
|
@retval FORMSET_STORAGE * The EFI_IFR_VARSTORE with the ID.
|
|
@retval NULL If the Form Set does not have EFI_IFR_VARSTORE with such ID.
|
|
**/
|
|
FORMSET_STORAGE *
|
|
GetStorageFromVarStoreId (
|
|
IN CONST FORM_BROWSER_FORMSET * FormSet,
|
|
IN EFI_VARSTORE_ID VarStoreId
|
|
)
|
|
{
|
|
LIST_ENTRY *StorageList;
|
|
FORMSET_STORAGE *Storage;
|
|
|
|
StorageList = GetFirstNode (&FormSet->StorageListHead);
|
|
|
|
while (!IsNull (&FormSet->StorageListHead, StorageList)) {
|
|
Storage = FORMSET_STORAGE_FROM_LINK (StorageList);
|
|
|
|
if (VarStoreId == Storage->VarStoreId) {
|
|
return Storage;
|
|
}
|
|
|
|
StorageList = GetNextNode (&FormSet->StorageListHead, StorageList);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Get the EFI_IFR_VARSTORE based the <ConfigHdr> string in a <ConfigRequest>
|
|
or a <ConfigResp> string.
|
|
|
|
@param FormSet The Form Set.
|
|
@param ConfigString The Configuration String which is defined by UEFI HII.
|
|
|
|
@retval FORMSET_STORAGE * The EFI_IFR_VARSTORE where the Question's value is stored.
|
|
@retval NULL If the Form Set does not have EFI_IFR_VARSTORE with such ID.
|
|
**/
|
|
FORMSET_STORAGE *
|
|
GetStorageFromConfigString (
|
|
IN CONST FORM_BROWSER_FORMSET *FormSet,
|
|
IN CONST EFI_STRING ConfigString
|
|
)
|
|
{
|
|
LIST_ENTRY *StorageList;
|
|
FORMSET_STORAGE *Storage;
|
|
CHAR16 *Name;
|
|
|
|
StorageList = GetFirstNode (&FormSet->StorageListHead);
|
|
|
|
while (!IsNull (&FormSet->StorageListHead, StorageList)) {
|
|
Storage = FORMSET_STORAGE_FROM_LINK (StorageList);
|
|
|
|
if ((Storage->VarStoreId == FormSet->DefaultVarStoreId) && (FormSet->OriginalDefaultVarStoreName != NULL)) {
|
|
Name = FormSet->OriginalDefaultVarStoreName;
|
|
} else {
|
|
Name = Storage->Name;
|
|
}
|
|
|
|
if (HiiIsConfigHdrMatch (ConfigString, &Storage->Guid, Name)) {
|
|
return Storage;
|
|
}
|
|
|
|
StorageList = GetNextNode (&FormSet->StorageListHead, StorageList);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
This function installs a EFI_CONFIG_ACCESS_PROTOCOL instance for a form package registered
|
|
by a module using Framework HII Protocol Interfaces.
|
|
|
|
UEFI HII require EFI_HII_CONFIG_ACCESS_PROTOCOL to be installed on a EFI_HANDLE, so
|
|
that Setup Utility can load the Buffer Storage using this protocol.
|
|
|
|
@param Packages The Package List.
|
|
@param ThunkContext The Thunk Context.
|
|
|
|
@retval EFI_SUCCESS The Config Access Protocol is installed successfully.
|
|
@retval EFI_OUT_RESOURCE There is not enough memory.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
InstallDefaultConfigAccessProtocol (
|
|
IN CONST EFI_HII_PACKAGES *Packages,
|
|
IN OUT HII_THUNK_CONTEXT *ThunkContext
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CONFIG_ACCESS_PRIVATE *ConfigAccessInstance;
|
|
HII_VENDOR_DEVICE_PATH *HiiVendorPath;
|
|
|
|
ASSERT (ThunkContext->IfrPackageCount != 0);
|
|
|
|
ConfigAccessInstance = AllocateCopyPool (
|
|
sizeof (CONFIG_ACCESS_PRIVATE),
|
|
&gConfigAccessPrivateTempate
|
|
);
|
|
ASSERT (ConfigAccessInstance != NULL);
|
|
|
|
//
|
|
// Use memory address as unique ID to distinguish from different device paths
|
|
// This function may be called multi times by the framework HII driver.
|
|
//
|
|
HiiVendorPath = AllocateCopyPool (
|
|
sizeof (HII_VENDOR_DEVICE_PATH),
|
|
&mUefiHiiVendorDevicePath
|
|
);
|
|
ASSERT (HiiVendorPath != NULL);
|
|
|
|
HiiVendorPath->Node.UniqueId = (UINT64) ((UINTN) HiiVendorPath);
|
|
|
|
Status = gBS->InstallMultipleProtocolInterfaces (
|
|
&ThunkContext->UefiHiiDriverHandle,
|
|
&gEfiDevicePathProtocolGuid,
|
|
HiiVendorPath,
|
|
&gEfiHiiConfigAccessProtocolGuid,
|
|
&ConfigAccessInstance->ConfigAccessProtocol,
|
|
NULL
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
ConfigAccessInstance->ThunkContext = ThunkContext;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
This function un-installs the EFI_CONFIG_ACCESS_PROTOCOL instance for a form package registered
|
|
by a module using Framework HII Protocol Interfaces.
|
|
|
|
ASSERT if no Config Access is found for such pakcage list or failed to uninstall the protocol.
|
|
|
|
@param ThunkContext The Thunk Context.
|
|
|
|
**/
|
|
VOID
|
|
UninstallDefaultConfigAccessProtocol (
|
|
IN HII_THUNK_CONTEXT *ThunkContext
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_HII_CONFIG_ACCESS_PROTOCOL *ConfigAccess;
|
|
HII_VENDOR_DEVICE_PATH *HiiVendorPath;
|
|
|
|
Status = gBS->HandleProtocol (
|
|
ThunkContext->UefiHiiDriverHandle,
|
|
&gEfiHiiConfigAccessProtocolGuid,
|
|
(VOID **) &ConfigAccess
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
Status = gBS->HandleProtocol (
|
|
ThunkContext->UefiHiiDriverHandle,
|
|
&gEfiDevicePathProtocolGuid,
|
|
(VOID **) &HiiVendorPath
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
Status = gBS->UninstallMultipleProtocolInterfaces (
|
|
ThunkContext->UefiHiiDriverHandle,
|
|
&gEfiDevicePathProtocolGuid,
|
|
HiiVendorPath,
|
|
&gEfiHiiConfigAccessProtocolGuid,
|
|
ConfigAccess,
|
|
NULL
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
Wrap the EFI_HII_CONFIG_ACCESS_PROTOCOL.ExtractConfig to a call to EFI_FORM_CALLBACK_PROTOCOL.NvRead.
|
|
|
|
@param BufferStorage The key with all attributes needed to call EFI_FORM_CALLBACK_PROTOCOL.NvRead.
|
|
@param FwFormCallBack The EFI_FORM_CALLBACK_PROTOCOL registered by Framework HII module.
|
|
@param Data The data read.
|
|
@param DataSize The size of data.
|
|
|
|
@retval EFI_STATUS The status returned by the EFI_FORM_CALLBACK_PROTOCOL.NvWrite.
|
|
@retval EFI_INVALID_PARAMETER If the EFI_FORM_CALLBACK_PROTOCOL.NvRead return the size information of the data
|
|
does not match what has been recorded early in he BUFFER_STORAGE_ENTRY.
|
|
**/
|
|
EFI_STATUS
|
|
CallFormCallBack (
|
|
IN FORMSET_STORAGE *BufferStorage,
|
|
IN EFI_FORM_CALLBACK_PROTOCOL *FwFormCallBack,
|
|
OUT VOID **Data,
|
|
OUT UINTN *DataSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
*DataSize = 0;
|
|
*Data = NULL;
|
|
|
|
Status = FwFormCallBack->NvRead (
|
|
FwFormCallBack,
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
NULL,
|
|
DataSize,
|
|
*Data
|
|
);
|
|
if (Status == EFI_BUFFER_TOO_SMALL) {
|
|
if (BufferStorage->Size != *DataSize) {
|
|
ASSERT (FALSE);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
*Data = AllocateZeroPool (*DataSize);
|
|
if (*Data == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = FwFormCallBack->NvRead (
|
|
FwFormCallBack,
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
NULL,
|
|
DataSize,
|
|
*Data
|
|
);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Wrap the EFI_HII_CONFIG_ACCESS_PROTOCOL.ExtractConfig to a call to UEFI Variable Get Service.
|
|
|
|
@param BufferStorage The key with all attributes needed to call a UEFI Variable Get Service.
|
|
@param Data The data read.
|
|
@param DataSize The size of data.
|
|
|
|
If the UEFI Variable Get Service return the size information of the data
|
|
does not match what has been recorded early in he BUFFER_STORAGE_ENTRY.
|
|
then ASSERT.
|
|
|
|
@retval EFI_STATUS The status returned by the UEFI Variable Get Service.
|
|
@retval EFI_INVALID_PARAMETER If the UEFI Variable Get Service return the size information of the data
|
|
does not match what has been recorded early in he BUFFER_STORAGE_ENTRY.
|
|
**/
|
|
|
|
EFI_STATUS
|
|
GetUefiVariable (
|
|
IN FORMSET_STORAGE *BufferStorage,
|
|
OUT VOID **Data,
|
|
OUT UINTN *DataSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
*DataSize = 0;
|
|
*Data = NULL;
|
|
Status = gRT->GetVariable (
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
NULL,
|
|
DataSize,
|
|
*Data
|
|
);
|
|
if (Status == EFI_BUFFER_TOO_SMALL) {
|
|
|
|
if (BufferStorage->Size != *DataSize) {
|
|
ASSERT (FALSE);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
*Data = AllocateZeroPool (*DataSize);
|
|
if (*Data == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = gRT->GetVariable (
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
NULL,
|
|
DataSize,
|
|
*Data
|
|
);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
|
|
This function implement the EFI_HII_CONFIG_ACCESS_PROTOCOL.ExtractConfig
|
|
so that data can be read from the data storage such as UEFI Variable or module's
|
|
customized storage exposed by EFI_FRAMEWORK_CALLBACK.
|
|
|
|
@param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL
|
|
@param Request A null-terminated Unicode string in <ConfigRequest> format. Note that this
|
|
includes the routing information as well as the configurable name / value pairs. It is
|
|
invalid for this string to be in <MultiConfigRequest> 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_INVALID_PARAMETER If there is no Buffer Storage for this Config Access instance.
|
|
@retval EFI_SUCCESS The setting is retrived successfully.
|
|
@retval !EFI_SUCCESS The error returned by UEFI Get Variable or Framework Form Callback Nvread.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ThunkExtractConfig (
|
|
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
|
|
IN CONST EFI_STRING Request,
|
|
OUT EFI_STRING *Progress,
|
|
OUT EFI_STRING *Results
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CONFIG_ACCESS_PRIVATE *ConfigAccess;
|
|
FORMSET_STORAGE *BufferStorage;
|
|
VOID *Data;
|
|
UINTN DataSize;
|
|
|
|
if (Request == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
Data = NULL;
|
|
ConfigAccess = CONFIG_ACCESS_PRIVATE_FROM_PROTOCOL (This);
|
|
|
|
BufferStorage = GetStorageFromConfigString (ConfigAccess->ThunkContext->FormSet, Request);
|
|
|
|
if (BufferStorage == NULL) {
|
|
*Progress = (EFI_STRING) Request;
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if (ConfigAccess->ThunkContext->NvMapOverride == NULL) {
|
|
//
|
|
// NvMapOverride is not used. Get the Storage data from EFI Variable or Framework Form Callback.
|
|
//
|
|
if (ConfigAccess->FormCallbackProtocol == NULL ||
|
|
ConfigAccess->FormCallbackProtocol->NvRead == NULL) {
|
|
Status = GetUefiVariable (
|
|
BufferStorage,
|
|
&Data,
|
|
&DataSize
|
|
);
|
|
} else {
|
|
Status = CallFormCallBack (
|
|
BufferStorage,
|
|
ConfigAccess->FormCallbackProtocol,
|
|
&Data,
|
|
&DataSize
|
|
);
|
|
}
|
|
} else {
|
|
//
|
|
// Use the NvMapOverride.
|
|
//
|
|
DataSize = BufferStorage->Size;
|
|
Data = AllocateCopyPool (DataSize, ConfigAccess->ThunkContext->NvMapOverride);
|
|
|
|
if (Data != NULL) {
|
|
Status = EFI_SUCCESS;
|
|
} else {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = mHiiConfigRoutingProtocol->BlockToConfig (
|
|
mHiiConfigRoutingProtocol,
|
|
Request,
|
|
Data,
|
|
DataSize,
|
|
Results,
|
|
Progress
|
|
);
|
|
}
|
|
|
|
if (Data != NULL) {
|
|
FreePool (Data);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
This function implement the EFI_HII_CONFIG_ACCESS_PROTOCOL.RouteConfig
|
|
so that data can be written to the data storage such as UEFI Variable or module's
|
|
customized storage exposed by EFI_FRAMEWORK_CALLBACK.
|
|
|
|
@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_INVALID_PARAMETER If there is no Buffer Storage for this Config Access instance.
|
|
@retval EFI_SUCCESS The setting is saved successfully.
|
|
@retval !EFI_SUCCESS The error returned by UEFI Set Variable or Framework Form Callback Nvwrite.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ThunkRouteConfig (
|
|
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
|
|
IN CONST EFI_STRING Configuration,
|
|
OUT EFI_STRING *Progress
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CONFIG_ACCESS_PRIVATE *ConfigAccess;
|
|
FORMSET_STORAGE *BufferStorage;
|
|
VOID *Data;
|
|
UINTN DataSize;
|
|
UINTN DataSize2;
|
|
UINTN LastModifiedByteIndex;
|
|
BOOLEAN ResetRequired;
|
|
BOOLEAN DataAllocated;
|
|
|
|
if (Configuration == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Data = NULL;
|
|
ConfigAccess = CONFIG_ACCESS_PRIVATE_FROM_PROTOCOL (This);
|
|
|
|
BufferStorage = GetStorageFromConfigString (ConfigAccess->ThunkContext->FormSet, Configuration);
|
|
|
|
if (BufferStorage == NULL) {
|
|
*Progress = Configuration;
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
DataSize2 = BufferStorage->Size;
|
|
if (ConfigAccess->ThunkContext->NvMapOverride == NULL) {
|
|
DataAllocated = TRUE;
|
|
if (ConfigAccess->FormCallbackProtocol == NULL ||
|
|
ConfigAccess->FormCallbackProtocol->NvRead == NULL) {
|
|
Status = GetUefiVariable (
|
|
BufferStorage,
|
|
&Data,
|
|
&DataSize
|
|
);
|
|
} else {
|
|
Status = CallFormCallBack (
|
|
BufferStorage,
|
|
ConfigAccess->FormCallbackProtocol,
|
|
&Data,
|
|
&DataSize
|
|
);
|
|
}
|
|
} else {
|
|
//
|
|
// ConfigToBlock will convert the Config String and update the NvMapOverride accordingly.
|
|
//
|
|
Status = EFI_SUCCESS;
|
|
Data = ConfigAccess->ThunkContext->NvMapOverride;
|
|
DataSize = DataSize2;
|
|
DataAllocated = FALSE;
|
|
}
|
|
if (EFI_ERROR (Status) || (DataSize2 != DataSize)) {
|
|
if (Data == NULL) {
|
|
Data = AllocateZeroPool (DataSize2);
|
|
}
|
|
}
|
|
|
|
Status = mHiiConfigRoutingProtocol->ConfigToBlock (
|
|
mHiiConfigRoutingProtocol,
|
|
Configuration,
|
|
Data,
|
|
&LastModifiedByteIndex,
|
|
Progress
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Done;
|
|
}
|
|
|
|
if (ConfigAccess->ThunkContext->NvMapOverride == NULL) {
|
|
if (ConfigAccess->FormCallbackProtocol == NULL ||
|
|
ConfigAccess->FormCallbackProtocol->NvWrite == NULL) {
|
|
Status = gRT->SetVariable (
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
|
|
DataSize2,
|
|
Data
|
|
);
|
|
} else {
|
|
Status = ConfigAccess->FormCallbackProtocol->NvWrite (
|
|
ConfigAccess->FormCallbackProtocol,
|
|
BufferStorage->Name,
|
|
&BufferStorage->Guid,
|
|
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
|
|
DataSize2,
|
|
Data,
|
|
&ResetRequired
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
Done:
|
|
if (DataAllocated && (Data != NULL)) {
|
|
FreePool (Data);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Build the EFI_IFR_DATA_ARRAY which will be used to pass to
|
|
EFI_FORM_CALLBACK_PROTOCOL.Callback. Check definition of EFI_IFR_DATA_ARRAY
|
|
for details.
|
|
|
|
ASSERT if the Question Type is not EFI_IFR_TYPE_NUM_SIZE_* or EFI_IFR_TYPE_STRING.
|
|
|
|
@param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL
|
|
@param QuestionId The Question ID.
|
|
@param Type The Question Type.
|
|
@param Value The Question Value.
|
|
@param NvMapAllocated On output indicates if a buffer is allocated for NvMap.
|
|
|
|
@return A pointer to EFI_IFR_DATA_ARRAY. The caller must free this buffer allocated.
|
|
**/
|
|
EFI_IFR_DATA_ARRAY *
|
|
CreateIfrDataArray (
|
|
IN CONFIG_ACCESS_PRIVATE *ConfigAccess,
|
|
IN EFI_QUESTION_ID QuestionId,
|
|
IN UINT8 Type,
|
|
IN EFI_IFR_TYPE_VALUE *Value,
|
|
OUT BOOLEAN *NvMapAllocated
|
|
)
|
|
{
|
|
EFI_IFR_DATA_ARRAY *IfrDataArray;
|
|
EFI_IFR_DATA_ENTRY *IfrDataEntry;
|
|
UINTN BrowserDataSize;
|
|
FORMSET_STORAGE *BufferStorage;
|
|
UINTN Size;
|
|
EFI_STRING String;
|
|
FORM_BROWSER_STATEMENT *Statement;
|
|
|
|
*NvMapAllocated = FALSE;
|
|
|
|
String = NULL;
|
|
|
|
switch (Type) {
|
|
case EFI_IFR_TYPE_NUM_SIZE_8:
|
|
case EFI_IFR_TYPE_NUM_SIZE_16:
|
|
case EFI_IFR_TYPE_NUM_SIZE_32:
|
|
case EFI_IFR_TYPE_NUM_SIZE_64:
|
|
case EFI_IFR_TYPE_BOOLEAN:
|
|
Size = sizeof (*Value);
|
|
break;
|
|
|
|
case EFI_IFR_TYPE_STRING:
|
|
if (Value->string == 0) {
|
|
Size = 0;
|
|
} else {
|
|
String = HiiGetString (ConfigAccess->ThunkContext->UefiHiiHandle, Value->string, NULL);
|
|
ASSERT (String != NULL);
|
|
|
|
Size = StrSize (String);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT (FALSE);
|
|
Size = 0;
|
|
break;
|
|
}
|
|
|
|
IfrDataArray = AllocateZeroPool (sizeof (EFI_IFR_DATA_ARRAY) + sizeof (EFI_IFR_DATA_ENTRY) + Size);
|
|
ASSERT (IfrDataArray != NULL);
|
|
IfrDataArray->EntryCount = 1;
|
|
IfrDataEntry = (EFI_IFR_DATA_ENTRY *) (IfrDataArray + 1);
|
|
|
|
Statement = GetStorageFromQuestionId (ConfigAccess->ThunkContext->FormSet, QuestionId);
|
|
|
|
if (Statement == NULL || Statement->Storage == NULL) {
|
|
//
|
|
// The QuestionId is not associated with a Buffer Storage.
|
|
// Try to get the first Buffer Storage then.
|
|
//
|
|
BufferStorage = GetFirstStorageOfFormSet (ConfigAccess->ThunkContext->FormSet);
|
|
} else {
|
|
BufferStorage = Statement->Storage;
|
|
IfrDataEntry->OpCode = Statement->Operand;
|
|
}
|
|
|
|
if (BufferStorage != NULL) {
|
|
BrowserDataSize = BufferStorage->Size;
|
|
IfrDataEntry->Length = (UINT8) (sizeof (EFI_IFR_DATA_ENTRY) + Size);
|
|
|
|
if (ConfigAccess->ThunkContext->NvMapOverride == NULL) {
|
|
*NvMapAllocated = TRUE;
|
|
IfrDataArray->NvRamMap = AllocateZeroPool (BrowserDataSize);
|
|
} else {
|
|
*NvMapAllocated = FALSE;
|
|
IfrDataArray->NvRamMap = ConfigAccess->ThunkContext->NvMapOverride;
|
|
}
|
|
|
|
ASSERT (HiiGetBrowserData (&BufferStorage->Guid, BufferStorage->Name, BrowserDataSize, (UINT8 *) IfrDataArray->NvRamMap));
|
|
|
|
switch (Type) {
|
|
case EFI_IFR_TYPE_NUM_SIZE_8:
|
|
case EFI_IFR_TYPE_NUM_SIZE_16:
|
|
case EFI_IFR_TYPE_NUM_SIZE_32:
|
|
case EFI_IFR_TYPE_NUM_SIZE_64:
|
|
case EFI_IFR_TYPE_BOOLEAN:
|
|
CopyMem (&IfrDataEntry->Data, &(Value->u8), sizeof (*Value));
|
|
break;
|
|
|
|
case EFI_IFR_TYPE_STRING:
|
|
if (Size != 0) {
|
|
ASSERT (String != NULL);
|
|
StrCpy ((CHAR16 *) &IfrDataEntry->Data, String);
|
|
FreePool (String);
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT (FALSE);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Need to fiil in the information for the rest of field for EFI_IFR_DATA_ENTRY.
|
|
// It seems that no implementation is found to use other fields. Leave them uninitialized for now.
|
|
//
|
|
//UINT8 OpCode; // Likely a string, numeric, or one-of
|
|
//UINT8 Length; // Length of the EFI_IFR_DATA_ENTRY packet
|
|
//UINT16 Flags; // Flags settings to determine what behavior is desired from the browser after the callback
|
|
//VOID *Data; // The data in the form based on the op-code type - this is not a pointer to the data, the data follows immediately
|
|
// If the OpCode is a OneOf or Numeric type - Data is a UINT16 value
|
|
// If the OpCode is a String type - Data is a CHAR16[x] type
|
|
// If the OpCode is a Checkbox type - Data is a UINT8 value
|
|
// If the OpCode is a NV Access type - Data is a FRAMEWORK_EFI_IFR_NV_DATA structure
|
|
}
|
|
|
|
return IfrDataArray;
|
|
}
|
|
|
|
/**
|
|
If a NvMapOverride is passed in to EFI_FORM_BROWSER_PROTOCOL.SendForm, the Form Browser
|
|
needs to be informed when data changed in NvMapOverride. This function will invoke
|
|
SetBrowserData () to set internal data of Form Browser.
|
|
|
|
@param ConfigAccess The Config Access Private Context.
|
|
@param QuestionId The Question Id that invokes the callback.
|
|
|
|
|
|
**/
|
|
VOID
|
|
SyncBrowserDataForNvMapOverride (
|
|
IN CONST CONFIG_ACCESS_PRIVATE *ConfigAccess,
|
|
IN EFI_QUESTION_ID QuestionId
|
|
)
|
|
{
|
|
FORMSET_STORAGE *BufferStorage;
|
|
BOOLEAN CheckFlag;
|
|
UINTN BrowserDataSize;
|
|
FORM_BROWSER_STATEMENT *Statement;
|
|
|
|
if (ConfigAccess->ThunkContext->NvMapOverride != NULL) {
|
|
|
|
Statement = GetStorageFromQuestionId (ConfigAccess->ThunkContext->FormSet, QuestionId);
|
|
|
|
if (Statement == NULL || Statement->Storage == NULL) {
|
|
//
|
|
// QuestionId is a statement without Storage.
|
|
// 1) It is a Goto.
|
|
//
|
|
//
|
|
BufferStorage = GetFirstStorageOfFormSet (ConfigAccess->ThunkContext->FormSet);
|
|
} else {
|
|
BufferStorage = Statement->Storage;
|
|
}
|
|
|
|
//
|
|
// If NvMapOverride is not NULL, this Form must have at least one Buffer Type Variable Storage.
|
|
//
|
|
ASSERT (BufferStorage != NULL);
|
|
|
|
BrowserDataSize = BufferStorage->Size;
|
|
|
|
CheckFlag = HiiSetBrowserData (&BufferStorage->Guid, BufferStorage->Name, BrowserDataSize, ConfigAccess->ThunkContext->NvMapOverride, NULL);
|
|
ASSERT (CheckFlag);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
Free up resource allocated for a EFI_IFR_DATA_ARRAY by CreateIfrDataArray ().
|
|
|
|
@param Array The EFI_IFR_DATA_ARRAY allocated.
|
|
@param NvMapAllocated If the NvRamMap is allocated for EFI_IFR_DATA_ARRAY.
|
|
|
|
**/
|
|
VOID
|
|
DestroyIfrDataArray (
|
|
IN EFI_IFR_DATA_ARRAY *Array,
|
|
IN BOOLEAN NvMapAllocated
|
|
)
|
|
{
|
|
if (Array != NULL) {
|
|
if (NvMapAllocated) {
|
|
FreePool (Array->NvRamMap);
|
|
}
|
|
|
|
FreePool (Array);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Get the ONE_OF_OPTION_MAP_ENTRY for a QuestionId that invokes the
|
|
EFI_FORM_CALLBACK_PROTOCOL.Callback. The information is needed as
|
|
the callback mechanism for EFI_IFR_ONE_OF_OPTION is changed from
|
|
EFI_IFR_ONE_OF_OPTION in Framework IFR. Check EFI_IFR_GUID_OPTIONKEY
|
|
for detailed information.
|
|
|
|
@param ThunkContext The Thunk Context.
|
|
@param QuestionId The Question Id.
|
|
@param Type The Question Type.
|
|
@param Value The One Of Option's value.
|
|
|
|
@return The ONE_OF_OPTION_MAP_ENTRY found.
|
|
@retval NULL If no entry is found.
|
|
**/
|
|
ONE_OF_OPTION_MAP_ENTRY *
|
|
GetOneOfOptionMapEntry (
|
|
IN HII_THUNK_CONTEXT *ThunkContext,
|
|
IN EFI_QUESTION_ID QuestionId,
|
|
IN UINT8 Type,
|
|
IN EFI_IFR_TYPE_VALUE *Value
|
|
)
|
|
{
|
|
LIST_ENTRY *Link;
|
|
LIST_ENTRY *Link2;
|
|
ONE_OF_OPTION_MAP_ENTRY *OneOfOptionMapEntry;
|
|
ONE_OF_OPTION_MAP *OneOfOptionMap;
|
|
FORM_BROWSER_FORMSET *FormSet;
|
|
|
|
FormSet = ThunkContext->FormSet;
|
|
|
|
Link = GetFirstNode (&FormSet->OneOfOptionMapListHead);
|
|
|
|
while (!IsNull (&FormSet->OneOfOptionMapListHead, Link)) {
|
|
OneOfOptionMap = ONE_OF_OPTION_MAP_FROM_LINK(Link);
|
|
if (OneOfOptionMap->QuestionId == QuestionId) {
|
|
ASSERT (OneOfOptionMap->ValueType == Type);
|
|
|
|
Link2 = GetFirstNode (&OneOfOptionMap->OneOfOptionMapEntryListHead);
|
|
|
|
while (!IsNull (&OneOfOptionMap->OneOfOptionMapEntryListHead, Link2)) {
|
|
OneOfOptionMapEntry = ONE_OF_OPTION_MAP_ENTRY_FROM_LINK (Link2);
|
|
|
|
if (CompareMem (Value, &OneOfOptionMapEntry->Value, sizeof (EFI_IFR_TYPE_VALUE)) == 0) {
|
|
return OneOfOptionMapEntry;
|
|
}
|
|
|
|
Link2 = GetNextNode (&OneOfOptionMap->OneOfOptionMapEntryListHead, Link2);
|
|
}
|
|
}
|
|
|
|
Link = GetNextNode (&FormSet->OneOfOptionMapListHead, Link);
|
|
}
|
|
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Functions which are registered to receive notification of
|
|
database events have this prototype. The actual event is encoded
|
|
in NotifyType. The following table describes how PackageType,
|
|
PackageGuid, Handle, and Package are used for each of the
|
|
notification types.
|
|
|
|
If any Pakcage List in database is updated, mHiiPackageListUpdated
|
|
will be set. If mHiiPackageListUpdated is set, Framework ThunkCallback()
|
|
will force the UEFI Setup Browser to save the uncommitted data. This
|
|
is needed as Framework's Callback function may dynamically update
|
|
opcode in a Package List. UEFI Setup Browser will quit itself and reparse
|
|
the Package List's IFR and display it. UEFI Config Access's implementation
|
|
is required to save the modified (SetBrowserData or directly save the data
|
|
to NV storage). But Framework HII Modules is not aware of this rule. Therefore,
|
|
we will enforce the rule in ThunkCallback (). The side effect of force saving
|
|
of NV data is the NV flag in browser may not flag a update as data has already
|
|
been saved to NV storage.
|
|
|
|
@param PackageType Package type of the notification.
|
|
|
|
@param PackageGuid If PackageType is
|
|
EFI_HII_PACKAGE_TYPE_GUID, then this is
|
|
the pointer to the GUID from the Guid
|
|
field of EFI_HII_PACKAGE_GUID_HEADER.
|
|
Otherwise, it must be NULL.
|
|
|
|
@param Package Points to the package referred to by the
|
|
notification Handle The handle of the package
|
|
list which contains the specified package.
|
|
|
|
@param Handle The HII handle.
|
|
|
|
@param NotifyType The type of change concerning the
|
|
database. See
|
|
EFI_HII_DATABASE_NOTIFY_TYPE.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
FormUpdateNotify (
|
|
IN UINT8 PackageType,
|
|
IN CONST EFI_GUID *PackageGuid,
|
|
IN CONST EFI_HII_PACKAGE_HEADER *Package,
|
|
IN EFI_HII_HANDLE Handle,
|
|
IN EFI_HII_DATABASE_NOTIFY_TYPE NotifyType
|
|
)
|
|
{
|
|
mHiiPackageListUpdated = TRUE;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Wrap the EFI_HII_CONFIG_ACCESS_PROTOCOL.CallBack to EFI_FORM_CALLBACK_PROTOCOL.Callback. Therefor,
|
|
the framework HII module willl do no porting and work with a UEFI HII SetupBrowser.
|
|
|
|
@param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
|
|
@param Action Specifies the type of action taken by the browser. See EFI_BROWSER_ACTION_x.
|
|
@param QuestionId A unique value which is sent to the original exporting driver so that it can identify the
|
|
type of data to expect. The format of the data tends to vary based on the opcode that
|
|
generated the callback.
|
|
@param Type The type of value for the question. See EFI_IFR_TYPE_x in
|
|
EFI_IFR_ONE_OF_OPTION.
|
|
@param Value A pointer to the data being sent to the original exporting driver. The type is specified
|
|
by Type. Type EFI_IFR_TYPE_VALUE is defined in
|
|
EFI_IFR_ONE_OF_OPTION.
|
|
@param ActionRequest On return, points to the action requested by the callback function. Type
|
|
EFI_BROWSER_ACTION_REQUEST is specified in SendForm() in the Form
|
|
Browser Protocol.
|
|
|
|
@retval EFI_UNSUPPORTED If the Framework HII module does not register Callback although it specify the opcode under
|
|
focuse to be INTERRACTIVE.
|
|
@retval EFI_SUCCESS The callback complete successfully.
|
|
@retval !EFI_SUCCESS The error code returned by EFI_FORM_CALLBACK_PROTOCOL.Callback.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ThunkCallback (
|
|
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
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CONFIG_ACCESS_PRIVATE *ConfigAccess;
|
|
EFI_FORM_CALLBACK_PROTOCOL *FormCallbackProtocol;
|
|
EFI_HII_CALLBACK_PACKET *Packet;
|
|
EFI_IFR_DATA_ARRAY *Data;
|
|
EFI_IFR_DATA_ENTRY *DataEntry;
|
|
UINT16 KeyValue;
|
|
ONE_OF_OPTION_MAP_ENTRY *OneOfOptionMapEntry;
|
|
EFI_HANDLE NotifyHandle;
|
|
EFI_INPUT_KEY Key;
|
|
BOOLEAN NvMapAllocated;
|
|
|
|
ASSERT (This != NULL);
|
|
ASSERT (Value != NULL);
|
|
ASSERT (ActionRequest != NULL);
|
|
|
|
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
|
|
|
|
ConfigAccess = CONFIG_ACCESS_PRIVATE_FROM_PROTOCOL (This);
|
|
|
|
FormCallbackProtocol = ConfigAccess->FormCallbackProtocol;
|
|
if (FormCallbackProtocol == NULL) {
|
|
ASSERT (FALSE);
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
//
|
|
// Check if the QuestionId match a OneOfOption.
|
|
//
|
|
OneOfOptionMapEntry = GetOneOfOptionMapEntry (ConfigAccess->ThunkContext, QuestionId, Type, Value);
|
|
|
|
if (OneOfOptionMapEntry == NULL) {
|
|
//
|
|
// This is not a One-Of-Option opcode. QuestionId is the KeyValue
|
|
//
|
|
KeyValue = QuestionId;
|
|
} else {
|
|
//
|
|
// Otherwise, use the original Key specified in One Of Option in the Framework VFR syntax.
|
|
//
|
|
KeyValue = OneOfOptionMapEntry->FwKey;
|
|
}
|
|
|
|
//
|
|
// Build the EFI_IFR_DATA_ARRAY
|
|
//
|
|
Data = CreateIfrDataArray (ConfigAccess, QuestionId, Type, Value, &NvMapAllocated);
|
|
|
|
Status = mHiiDatabase->RegisterPackageNotify (
|
|
mHiiDatabase,
|
|
EFI_HII_PACKAGE_FORMS,
|
|
NULL,
|
|
FormUpdateNotify,
|
|
EFI_HII_DATABASE_NOTIFY_REMOVE_PACK,
|
|
&NotifyHandle
|
|
);
|
|
//
|
|
//Call the Framework Callback function.
|
|
//
|
|
Packet = NULL;
|
|
Status = FormCallbackProtocol->Callback (
|
|
FormCallbackProtocol,
|
|
KeyValue,
|
|
Data,
|
|
&Packet
|
|
);
|
|
SyncBrowserDataForNvMapOverride (ConfigAccess, QuestionId);
|
|
|
|
//
|
|
// Callback require browser to perform action
|
|
//
|
|
if (EFI_ERROR (Status)) {
|
|
if (Packet != NULL) {
|
|
do {
|
|
CreatePopUp (EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE, &Key, Packet->String, NULL);
|
|
} while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
|
|
}
|
|
//
|
|
// Error Code in Status is discarded.
|
|
//
|
|
} else {
|
|
if (Packet != NULL) {
|
|
if (Packet->DataArray.EntryCount == 1 && Packet->DataArray.NvRamMap == NULL) {
|
|
DataEntry = (EFI_IFR_DATA_ENTRY *) ((UINT8 *) Packet + sizeof (EFI_IFR_DATA_ARRAY));
|
|
if ((DataEntry->Flags & EXIT_REQUIRED) == EXIT_REQUIRED) {
|
|
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT;
|
|
}
|
|
|
|
if ((DataEntry->Flags & SAVE_REQUIRED) == SAVE_REQUIRED) {
|
|
Status = ConfigAccess->ConfigAccessProtocol.RouteConfig (
|
|
&ConfigAccess->ConfigAccessProtocol,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
FreePool (Packet);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unregister notify for Form package update
|
|
//
|
|
Status = mHiiDatabase->UnregisterPackageNotify (
|
|
mHiiDatabase,
|
|
NotifyHandle
|
|
);
|
|
//
|
|
// UEFI SetupBrowser behaves differently with Framework SetupBrowser when call back function
|
|
// update any forms in HII database. UEFI SetupBrowser will re-parse the displaying form package and load
|
|
// the values from variable storages. Framework SetupBrowser will only re-parse the displaying form packages.
|
|
// To make sure customer's previous changes is saved and the changing question behaves as expected, we
|
|
// issue a EFI_BROWSER_ACTION_REQUEST_SUBMIT to ask UEFI SetupBrowser to save the changes proceed to re-parse
|
|
// the form and load all the variable storages.
|
|
//
|
|
if (*ActionRequest == EFI_BROWSER_ACTION_REQUEST_NONE && mHiiPackageListUpdated) {
|
|
mHiiPackageListUpdated= FALSE;
|
|
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_SUBMIT;
|
|
} else {
|
|
if (ConfigAccess->ThunkContext->FormSet->SubClass == EFI_FRONT_PAGE_SUBCLASS ||
|
|
ConfigAccess->ThunkContext->FormSet->SubClass == EFI_SINGLE_USE_SUBCLASS) {
|
|
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
DestroyIfrDataArray (Data, NvMapAllocated);
|
|
|
|
return Status;
|
|
}
|
|
|