mirror of https://github.com/acidanthera/audk.git
527 lines
18 KiB
C
527 lines
18 KiB
C
/** @file
|
|
DXE capsule process.
|
|
|
|
Caution: This module requires additional review when modified.
|
|
This module will have external input - capsule image.
|
|
This external input must be validated carefully to avoid security issue like
|
|
buffer overflow, integer overflow.
|
|
|
|
ProcessCapsules(), ProcessTheseCapsules() will receive untrusted
|
|
input and do basic validation.
|
|
|
|
Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>
|
|
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 <PiDxe.h>
|
|
#include <Protocol/EsrtManagement.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
#include <Library/UefiRuntimeServicesTableLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/UefiLib.h>
|
|
#include <Library/PcdLib.h>
|
|
#include <Library/HobLib.h>
|
|
#include <Library/ReportStatusCodeLib.h>
|
|
#include <Library/CapsuleLib.h>
|
|
|
|
#include <IndustryStandard/WindowsUxCapsule.h>
|
|
|
|
/**
|
|
Return if this FMP is a system FMP or a device FMP, based upon CapsuleHeader.
|
|
|
|
@param[in] CapsuleHeader A pointer to EFI_CAPSULE_HEADER
|
|
|
|
@retval TRUE It is a system FMP.
|
|
@retval FALSE It is a device FMP.
|
|
**/
|
|
BOOLEAN
|
|
IsFmpCapsule (
|
|
IN EFI_CAPSULE_HEADER *CapsuleHeader
|
|
);
|
|
|
|
/**
|
|
Validate Fmp capsules layout.
|
|
|
|
Caution: This function may receive untrusted input.
|
|
|
|
This function assumes the caller validated the capsule by using
|
|
IsValidCapsuleHeader(), so that all fields in EFI_CAPSULE_HEADER are correct.
|
|
The capsule buffer size is CapsuleHeader->CapsuleImageSize.
|
|
|
|
This function validates the fields in EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER
|
|
and EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.
|
|
|
|
This function need support nested FMP capsule.
|
|
|
|
@param[in] CapsuleHeader Points to a capsule header.
|
|
@param[out] EmbeddedDriverCount The EmbeddedDriverCount in the FMP capsule.
|
|
|
|
@retval EFI_SUCESS Input capsule is a correct FMP capsule.
|
|
@retval EFI_INVALID_PARAMETER Input capsule is not a correct FMP capsule.
|
|
**/
|
|
EFI_STATUS
|
|
ValidateFmpCapsule (
|
|
IN EFI_CAPSULE_HEADER *CapsuleHeader,
|
|
OUT UINT16 *EmbeddedDriverCount OPTIONAL
|
|
);
|
|
|
|
/**
|
|
Validate if it is valid capsule header
|
|
|
|
This function assumes the caller provided correct CapsuleHeader pointer
|
|
and CapsuleSize.
|
|
|
|
This function validates the fields in EFI_CAPSULE_HEADER.
|
|
|
|
@param[in] CapsuleHeader Points to a capsule header.
|
|
@param[in] CapsuleSize Size of the whole capsule image.
|
|
|
|
**/
|
|
BOOLEAN
|
|
IsValidCapsuleHeader (
|
|
IN EFI_CAPSULE_HEADER *CapsuleHeader,
|
|
IN UINT64 CapsuleSize
|
|
);
|
|
|
|
extern BOOLEAN mDxeCapsuleLibEndOfDxe;
|
|
BOOLEAN mNeedReset;
|
|
|
|
VOID **mCapsulePtr;
|
|
EFI_STATUS *mCapsuleStatusArray;
|
|
UINT32 mCapsuleTotalNumber;
|
|
|
|
/**
|
|
This function initializes the mCapsulePtr, mCapsuleStatusArray and mCapsuleTotalNumber.
|
|
**/
|
|
VOID
|
|
InitCapsulePtr (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_PEI_HOB_POINTERS HobPointer;
|
|
UINTN Index;
|
|
|
|
//
|
|
// Find all capsule images from hob
|
|
//
|
|
HobPointer.Raw = GetHobList ();
|
|
while ((HobPointer.Raw = GetNextHob (EFI_HOB_TYPE_UEFI_CAPSULE, HobPointer.Raw)) != NULL) {
|
|
if (!IsValidCapsuleHeader((VOID *)(UINTN)HobPointer.Capsule->BaseAddress, HobPointer.Capsule->Length)) {
|
|
HobPointer.Header->HobType = EFI_HOB_TYPE_UNUSED; // Mark this hob as invalid
|
|
} else {
|
|
mCapsuleTotalNumber++;
|
|
}
|
|
HobPointer.Raw = GET_NEXT_HOB (HobPointer);
|
|
}
|
|
|
|
DEBUG ((DEBUG_INFO, "mCapsuleTotalNumber - 0x%x\n", mCapsuleTotalNumber));
|
|
|
|
if (mCapsuleTotalNumber == 0) {
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Init temp Capsule Data table.
|
|
//
|
|
mCapsulePtr = (VOID **) AllocateZeroPool (sizeof (VOID *) * mCapsuleTotalNumber);
|
|
if (mCapsulePtr == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "Allocate mCapsulePtr fail!\n"));
|
|
mCapsuleTotalNumber = 0;
|
|
return ;
|
|
}
|
|
mCapsuleStatusArray = (EFI_STATUS *) AllocateZeroPool (sizeof (EFI_STATUS) * mCapsuleTotalNumber);
|
|
if (mCapsuleStatusArray == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "Allocate mCapsuleStatusArray fail!\n"));
|
|
FreePool (mCapsulePtr);
|
|
mCapsulePtr = NULL;
|
|
mCapsuleTotalNumber = 0;
|
|
return ;
|
|
}
|
|
SetMemN (mCapsuleStatusArray, sizeof (EFI_STATUS) * mCapsuleTotalNumber, EFI_NOT_READY);
|
|
|
|
//
|
|
// Find all capsule images from hob
|
|
//
|
|
HobPointer.Raw = GetHobList ();
|
|
Index = 0;
|
|
while ((HobPointer.Raw = GetNextHob (EFI_HOB_TYPE_UEFI_CAPSULE, HobPointer.Raw)) != NULL) {
|
|
mCapsulePtr [Index++] = (VOID *) (UINTN) HobPointer.Capsule->BaseAddress;
|
|
HobPointer.Raw = GET_NEXT_HOB (HobPointer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
This function returns if all capsule images are processed.
|
|
|
|
@retval TRUE All capsule images are processed.
|
|
@retval FALSE Not all capsule images are processed.
|
|
**/
|
|
BOOLEAN
|
|
AreAllImagesProcessed (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
for (Index = 0; Index < mCapsuleTotalNumber; Index++) {
|
|
if (mCapsuleStatusArray[Index] == EFI_NOT_READY) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
This function populates capsule in the configuration table.
|
|
**/
|
|
VOID
|
|
PopulateCapsuleInConfigurationTable (
|
|
VOID
|
|
)
|
|
{
|
|
VOID **CapsulePtrCache;
|
|
EFI_GUID *CapsuleGuidCache;
|
|
EFI_CAPSULE_HEADER *CapsuleHeader;
|
|
EFI_CAPSULE_TABLE *CapsuleTable;
|
|
UINT32 CacheIndex;
|
|
UINT32 CacheNumber;
|
|
UINT32 CapsuleNumber;
|
|
UINTN Index;
|
|
UINTN Size;
|
|
EFI_STATUS Status;
|
|
|
|
if (mCapsuleTotalNumber == 0) {
|
|
return ;
|
|
}
|
|
|
|
CapsulePtrCache = NULL;
|
|
CapsuleGuidCache = NULL;
|
|
CacheIndex = 0;
|
|
CacheNumber = 0;
|
|
|
|
CapsulePtrCache = (VOID **) AllocateZeroPool (sizeof (VOID *) * mCapsuleTotalNumber);
|
|
if (CapsulePtrCache == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "Allocate CapsulePtrCache fail!\n"));
|
|
return ;
|
|
}
|
|
CapsuleGuidCache = (EFI_GUID *) AllocateZeroPool (sizeof (EFI_GUID) * mCapsuleTotalNumber);
|
|
if (CapsuleGuidCache == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "Allocate CapsuleGuidCache fail!\n"));
|
|
FreePool (CapsulePtrCache);
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// Capsules who have CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE always are used for operating
|
|
// System to have information persist across a system reset. EFI System Table must
|
|
// point to an array of capsules that contains the same CapsuleGuid value. And agents
|
|
// searching for this type capsule will look in EFI System Table and search for the
|
|
// capsule's Guid and associated pointer to retrieve the data. Two steps below describes
|
|
// how to sorting the capsules by the unique guid and install the array to EFI System Table.
|
|
// Firstly, Loop for all coalesced capsules, record unique CapsuleGuids and cache them in an
|
|
// array for later sorting capsules by CapsuleGuid.
|
|
//
|
|
for (Index = 0; Index < mCapsuleTotalNumber; Index++) {
|
|
CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index];
|
|
if ((CapsuleHeader->Flags & CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) != 0) {
|
|
//
|
|
// For each capsule, we compare it with known CapsuleGuid in the CacheArray.
|
|
// If already has the Guid, skip it. Whereas, record it in the CacheArray as
|
|
// an additional one.
|
|
//
|
|
CacheIndex = 0;
|
|
while (CacheIndex < CacheNumber) {
|
|
if (CompareGuid(&CapsuleGuidCache[CacheIndex],&CapsuleHeader->CapsuleGuid)) {
|
|
break;
|
|
}
|
|
CacheIndex++;
|
|
}
|
|
if (CacheIndex == CacheNumber) {
|
|
CopyMem(&CapsuleGuidCache[CacheNumber++],&CapsuleHeader->CapsuleGuid,sizeof(EFI_GUID));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Secondly, for each unique CapsuleGuid in CacheArray, gather all coalesced capsules
|
|
// whose guid is the same as it, and malloc memory for an array which preceding
|
|
// with UINT32. The array fills with entry point of capsules that have the same
|
|
// CapsuleGuid, and UINT32 represents the size of the array of capsules. Then install
|
|
// this array into EFI System Table, so that agents searching for this type capsule
|
|
// will look in EFI System Table and search for the capsule's Guid and associated
|
|
// pointer to retrieve the data.
|
|
//
|
|
for (CacheIndex = 0; CacheIndex < CacheNumber; CacheIndex++) {
|
|
CapsuleNumber = 0;
|
|
for (Index = 0; Index < mCapsuleTotalNumber; Index++) {
|
|
CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index];
|
|
if ((CapsuleHeader->Flags & CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) != 0) {
|
|
if (CompareGuid (&CapsuleGuidCache[CacheIndex], &CapsuleHeader->CapsuleGuid)) {
|
|
//
|
|
// Cache Caspuleheader to the array, this array is uniqued with certain CapsuleGuid.
|
|
//
|
|
CapsulePtrCache[CapsuleNumber++] = (VOID*)CapsuleHeader;
|
|
}
|
|
}
|
|
}
|
|
if (CapsuleNumber != 0) {
|
|
Size = sizeof(EFI_CAPSULE_TABLE) + (CapsuleNumber - 1) * sizeof(VOID*);
|
|
CapsuleTable = AllocateRuntimePool (Size);
|
|
if (CapsuleTable == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "Allocate CapsuleTable (%g) fail!\n", &CapsuleGuidCache[CacheIndex]));
|
|
continue;
|
|
}
|
|
CapsuleTable->CapsuleArrayNumber = CapsuleNumber;
|
|
CopyMem(&CapsuleTable->CapsulePtr[0], CapsulePtrCache, CapsuleNumber * sizeof(VOID*));
|
|
Status = gBS->InstallConfigurationTable (&CapsuleGuidCache[CacheIndex], (VOID*)CapsuleTable);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "InstallConfigurationTable (%g) fail!\n", &CapsuleGuidCache[CacheIndex]));
|
|
}
|
|
}
|
|
}
|
|
|
|
FreePool(CapsuleGuidCache);
|
|
FreePool(CapsulePtrCache);
|
|
}
|
|
|
|
/**
|
|
|
|
This routine is called to process capsules.
|
|
|
|
Caution: This function may receive untrusted input.
|
|
|
|
Each individual capsule result is recorded in capsule record variable.
|
|
|
|
@param[in] FirstRound TRUE: First round. Need skip the FMP capsules with non zero EmbeddedDriverCount.
|
|
FALSE: Process rest FMP capsules.
|
|
|
|
@retval EFI_SUCCESS There is no error when processing capsules.
|
|
@retval EFI_OUT_OF_RESOURCES No enough resource to process capsules.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ProcessTheseCapsules (
|
|
IN BOOLEAN FirstRound
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_CAPSULE_HEADER *CapsuleHeader;
|
|
UINT32 Index;
|
|
BOOLEAN DisplayCapsuleExist;
|
|
ESRT_MANAGEMENT_PROTOCOL *EsrtManagement;
|
|
UINT16 EmbeddedDriverCount;
|
|
|
|
REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeProcessCapsulesBegin)));
|
|
|
|
if (FirstRound) {
|
|
InitCapsulePtr ();
|
|
}
|
|
|
|
if (mCapsuleTotalNumber == 0) {
|
|
//
|
|
// We didn't find a hob, so had no errors.
|
|
//
|
|
DEBUG ((DEBUG_ERROR, "We can not find capsule data in capsule update boot mode.\n"));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (AreAllImagesProcessed ()) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Check the capsule flags,if contains CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE, install
|
|
// capsuleTable to configure table with EFI_CAPSULE_GUID
|
|
//
|
|
if (FirstRound) {
|
|
PopulateCapsuleInConfigurationTable ();
|
|
}
|
|
|
|
REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdatingFirmware)));
|
|
|
|
//
|
|
// If Windows UX capsule exist, process it first
|
|
//
|
|
DisplayCapsuleExist = FALSE;
|
|
for (Index = 0; Index < mCapsuleTotalNumber; Index++) {
|
|
CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index];
|
|
if (CompareGuid (&CapsuleHeader->CapsuleGuid, &gWindowsUxCapsuleGuid)) {
|
|
DEBUG ((DEBUG_INFO, "ProcessCapsuleImage (Ux) - 0x%x\n", CapsuleHeader));
|
|
DisplayCapsuleExist = TRUE;
|
|
DEBUG ((DEBUG_INFO, "Display logo capsule is found.\n"));
|
|
Status = ProcessCapsuleImage (CapsuleHeader);
|
|
mCapsuleStatusArray [Index] = EFI_SUCCESS;
|
|
DEBUG((DEBUG_INFO, "ProcessCapsuleImage (Ux) - %r\n", Status));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!DisplayCapsuleExist) {
|
|
//
|
|
// Display Capsule not found. Display the default string.
|
|
//
|
|
Print (L"Updating the firmware ......\r\n");
|
|
}
|
|
|
|
//
|
|
// All capsules left are recognized by platform.
|
|
//
|
|
for (Index = 0; Index < mCapsuleTotalNumber; Index++) {
|
|
if (mCapsuleStatusArray [Index] != EFI_NOT_READY) {
|
|
// already processed
|
|
continue;
|
|
}
|
|
CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index];
|
|
if (!CompareGuid (&CapsuleHeader->CapsuleGuid, &gWindowsUxCapsuleGuid)) {
|
|
//
|
|
// Call capsule library to process capsule image.
|
|
//
|
|
EmbeddedDriverCount = 0;
|
|
if (IsFmpCapsule(CapsuleHeader)) {
|
|
Status = ValidateFmpCapsule (CapsuleHeader, &EmbeddedDriverCount);
|
|
if (EFI_ERROR(Status)) {
|
|
DEBUG((DEBUG_ERROR, "ValidateFmpCapsule failed. Ignore!\n"));
|
|
mCapsuleStatusArray [Index] = EFI_ABORTED;
|
|
continue;
|
|
}
|
|
} else {
|
|
mCapsuleStatusArray [Index] = EFI_ABORTED;
|
|
continue;
|
|
}
|
|
|
|
if ((!FirstRound) || (EmbeddedDriverCount == 0)) {
|
|
DEBUG((DEBUG_INFO, "ProcessCapsuleImage - 0x%x\n", CapsuleHeader));
|
|
Status = ProcessCapsuleImage (CapsuleHeader);
|
|
mCapsuleStatusArray [Index] = Status;
|
|
DEBUG((DEBUG_INFO, "ProcessCapsuleImage - %r\n", Status));
|
|
|
|
if (Status != EFI_NOT_READY) {
|
|
if (EFI_ERROR(Status)) {
|
|
REPORT_STATUS_CODE(EFI_ERROR_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdateFirmwareFailed)));
|
|
DEBUG ((DEBUG_ERROR, "Capsule process failed!\n"));
|
|
Print (L"Firmware update failed...\r\n");
|
|
} else {
|
|
REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdateFirmwareSuccess)));
|
|
}
|
|
|
|
if ((CapsuleHeader->Flags & PcdGet16(PcdSystemRebootAfterCapsuleProcessFlag)) != 0 ||
|
|
IsFmpCapsule(CapsuleHeader)) {
|
|
mNeedReset = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Status = gBS->LocateProtocol(&gEsrtManagementProtocolGuid, NULL, (VOID **)&EsrtManagement);
|
|
//
|
|
// Always sync ESRT Cache from FMP Instance
|
|
//
|
|
if (!EFI_ERROR(Status)) {
|
|
EsrtManagement->SyncEsrtFmp();
|
|
}
|
|
Status = EFI_SUCCESS;
|
|
|
|
REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeProcessCapsulesEnd)));
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Do reset system.
|
|
**/
|
|
VOID
|
|
DoResetSystem (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeResettingSystem)));
|
|
|
|
Print(L"Capsule Request Cold Reboot.\n");
|
|
DEBUG((DEBUG_INFO, "Capsule Request Cold Reboot."));
|
|
|
|
for (Index = 5; Index > 0; Index--) {
|
|
Print(L"\rResetting system in %d seconds ...", Index);
|
|
DEBUG((DEBUG_INFO, "\rResetting system in %d seconds ...", Index));
|
|
gBS->Stall(1000000);
|
|
}
|
|
|
|
gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
|
|
|
|
CpuDeadLoop();
|
|
}
|
|
|
|
/**
|
|
|
|
This routine is called to process capsules.
|
|
|
|
Caution: This function may receive untrusted input.
|
|
|
|
The capsules reported in EFI_HOB_UEFI_CAPSULE are processed.
|
|
If there is no EFI_HOB_UEFI_CAPSULE, this routine does nothing.
|
|
|
|
This routine should be called twice in BDS.
|
|
1) The first call must be before EndOfDxe. The system capsules is processed.
|
|
If device capsule FMP protocols are exposted at this time and device FMP
|
|
capsule has zero EmbeddedDriverCount, the device capsules are processed.
|
|
Each individual capsule result is recorded in capsule record variable.
|
|
System may reset in this function, if reset is required by capsule and
|
|
all capsules are processed.
|
|
If not all capsules are processed, reset will be defered to second call.
|
|
|
|
2) The second call must be after EndOfDxe and after ConnectAll, so that all
|
|
device capsule FMP protocols are exposed.
|
|
The system capsules are skipped. If the device capsules are NOT processed
|
|
in first call, they are processed here.
|
|
Each individual capsule result is recorded in capsule record variable.
|
|
System may reset in this function, if reset is required by capsule
|
|
processed in first call and second call.
|
|
|
|
@retval EFI_SUCCESS There is no error when processing capsules.
|
|
@retval EFI_OUT_OF_RESOURCES No enough resource to process capsules.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ProcessCapsules (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
if (!mDxeCapsuleLibEndOfDxe) {
|
|
Status = ProcessTheseCapsules(TRUE);
|
|
|
|
//
|
|
// Reboot System if and only if all capsule processed.
|
|
// If not, defer reset to 2nd process.
|
|
//
|
|
if (mNeedReset && AreAllImagesProcessed()) {
|
|
DoResetSystem();
|
|
}
|
|
} else {
|
|
Status = ProcessTheseCapsules(FALSE);
|
|
//
|
|
// Reboot System if required after all capsule processed
|
|
//
|
|
if (mNeedReset) {
|
|
DoResetSystem();
|
|
}
|
|
}
|
|
return Status;
|
|
}
|