/** @file ProcessorSubClass.c Copyright (c) 2022, Ampere Computing LLC. All rights reserved. Copyright (c) 2021, NUVIA Inc. All rights reserved.
Copyright (c) 2015, Hisilicon Limited. All rights reserved. Copyright (c) 2015, Linaro Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SmbiosProcessor.h" extern UINT8 ProcessorSubClassStrings[]; #define CACHE_SOCKETED_SHIFT 3 #define CACHE_LOCATION_SHIFT 5 #define CACHE_ENABLED_SHIFT 7 #define CACHE_OPERATION_MODE_SHIFT 8 typedef enum { CacheModeWriteThrough = 0, ///< Cache is write-through CacheModeWriteBack, ///< Cache is write-back CacheModeVariesWithAddress, ///< Cache mode varies by address CacheModeUnknown, ///< Cache mode is unknown CacheModeMax } CACHE_OPERATION_MODE; typedef enum { CacheLocationInternal = 0, ///< Cache is internal to the processor CacheLocationExternal, ///< Cache is external to the processor CacheLocationReserved, ///< Reserved CacheLocationUnknown, ///< Cache location is unknown CacheLocationMax } CACHE_LOCATION; EFI_HII_HANDLE mHiiHandle; EFI_SMBIOS_PROTOCOL *mSmbios; SMBIOS_TABLE_TYPE4 mSmbiosProcessorTableTemplate = { { // Hdr EFI_SMBIOS_TYPE_PROCESSOR_INFORMATION, // Type sizeof (SMBIOS_TABLE_TYPE4), // Length 0 // Handle }, 1, // Socket CentralProcessor, // ProcessorType ProcessorFamilyIndicatorFamily2, // ProcessorFamily 2, // ProcessorManufacture { // ProcessorId { // Signature 0 }, { // FeatureFlags 0 } }, 3, // ProcessorVersion { // Voltage 0 }, 0, // ExternalClock 0, // MaxSpeed 0, // CurrentSpeed 0, // Status ProcessorUpgradeUnknown, // ProcessorUpgrade 0xFFFF, // L1CacheHandle 0xFFFF, // L2CacheHandle 0xFFFF, // L3CacheHandle 4, // SerialNumber 5, // AssetTag 6, // PartNumber 0, // CoreCount 0, // EnabledCoreCount 0, // ThreadCount 0, // ProcessorCharacteristics ProcessorFamilyARM, // ProcessorFamily2 0, // CoreCount2 0, // EnabledCoreCount2 0 // ThreadCount2 }; /** Sets the HII variable `StringId` is `Pcd` isn't empty. @param Pcd The FixedAtBuild PCD that contains the string to fetch. @param StringId The string identifier to set. **/ #define SET_HII_STRING_IF_PCD_NOT_EMPTY(Pcd, StringId) \ do { \ CHAR16 *Str; \ Str = (CHAR16*)PcdGetPtr (Pcd); \ if (StrLen (Str) > 0) { \ HiiSetString (mHiiHandle, StringId, Str, NULL); \ } \ } while (0) /** Fetches the specified processor's frequency in Hz. @param ProcessorNumber The processor number @return The clock frequency in MHz **/ UINT16 GetCpuFrequency ( IN UINT8 ProcessorNumber ) { return (UINT16)(OemGetCpuFreq (ProcessorNumber) / 1000 / 1000); } /** Gets a description of the specified cache. @param[in] CacheLevel Zero-based cache level (e.g. L1 cache is 0). @param[in] DataCache Cache is a data cache. @param[in] UnifiedCache Cache is a unified cache. @param[out] CacheSocketStr The description of the specified cache @return The number of Unicode characters in CacheSocketStr not including the terminating NUL. **/ UINTN GetCacheSocketStr ( IN UINT8 CacheLevel, IN BOOLEAN DataCache, IN BOOLEAN UnifiedCache, OUT CHAR16 *CacheSocketStr ) { UINTN CacheSocketStrLen; if ((CacheLevel == CpuCacheL1) && !DataCache && !UnifiedCache) { CacheSocketStrLen = UnicodeSPrint ( CacheSocketStr, SMBIOS_STRING_MAX_LENGTH - 1, L"L%x Instruction Cache", CacheLevel ); } else if ((CacheLevel == CpuCacheL1) && DataCache) { CacheSocketStrLen = UnicodeSPrint ( CacheSocketStr, SMBIOS_STRING_MAX_LENGTH - 1, L"L%x Data Cache", CacheLevel ); } else { CacheSocketStrLen = UnicodeSPrint ( CacheSocketStr, SMBIOS_STRING_MAX_LENGTH - 1, L"L%x Cache", CacheLevel ); } return CacheSocketStrLen; } /** Fills in the Type 7 record with the cache architecture information read from the CPU registers. @param[in] CacheLevel Cache level (e.g. L1, L2). @param[in] DataCache Cache is a data cache. @param[in] UnifiedCache Cache is a unified cache. @param[out] Type7Record The Type 7 record to fill in. **/ VOID ConfigureCacheArchitectureInformation ( IN UINT8 CacheLevel, IN BOOLEAN DataCache, IN BOOLEAN UnifiedCache, OUT SMBIOS_TABLE_TYPE7 *Type7Record ) { UINT8 Associativity; UINT32 CacheSize32; UINT16 CacheSize16; UINT64 CacheSize64; if (!DataCache && !UnifiedCache) { Type7Record->SystemCacheType = CacheTypeInstruction; } else if (DataCache) { Type7Record->SystemCacheType = CacheTypeData; } else if (UnifiedCache) { Type7Record->SystemCacheType = CacheTypeUnified; } else { ASSERT (FALSE); } CacheSize64 = SmbiosProcessorGetCacheSize ( CacheLevel, DataCache, UnifiedCache ); Associativity = SmbiosProcessorGetCacheAssociativity ( CacheLevel, DataCache, UnifiedCache ); CacheSize64 /= 1024; // Minimum granularity is 1K // Encode the cache size into the format SMBIOS wants if (CacheSize64 < MAX_INT16) { CacheSize16 = CacheSize64; CacheSize32 = CacheSize16; } else if ((CacheSize64 / 64) < MAX_INT16) { CacheSize16 = (1 << 15) | (CacheSize64 / 64); CacheSize32 = (1 << 31) | (CacheSize64 / 64); } else { if ((CacheSize64 / 1024) <= 2047) { CacheSize32 = CacheSize64; } else { CacheSize32 = (1 << 31) | (CacheSize64 / 64); } CacheSize16 = -1; } Type7Record->MaximumCacheSize = CacheSize16; Type7Record->InstalledSize = CacheSize16; Type7Record->MaximumCacheSize2 = CacheSize32; Type7Record->InstalledSize2 = CacheSize32; switch (Associativity) { case 2: Type7Record->Associativity = CacheAssociativity2Way; break; case 4: Type7Record->Associativity = CacheAssociativity4Way; break; case 8: Type7Record->Associativity = CacheAssociativity8Way; break; case 12: Type7Record->Associativity = CacheAssociativity12Way; break; case 16: Type7Record->Associativity = CacheAssociativity16Way; break; case 20: Type7Record->Associativity = CacheAssociativity20Way; break; case 24: Type7Record->Associativity = CacheAssociativity24Way; break; case 32: Type7Record->Associativity = CacheAssociativity32Way; break; case 48: Type7Record->Associativity = CacheAssociativity48Way; break; case 64: Type7Record->Associativity = CacheAssociativity64Way; break; default: Type7Record->Associativity = CacheAssociativityOther; break; } Type7Record->CacheConfiguration = (CacheModeUnknown << CACHE_OPERATION_MODE_SHIFT) | (1 << CACHE_ENABLED_SHIFT) | (CacheLocationUnknown << CACHE_LOCATION_SHIFT) | (0 << CACHE_SOCKETED_SHIFT) | (CacheLevel - 1); } /** Allocates and initializes an SMBIOS_TABLE_TYPE7 structure. @param[in] CacheLevel The cache level (L1-L7). @param[in] DataCache Cache is a data cache. @param[in] UnifiedCache Cache is a unified cache. @return A pointer to the Type 7 structure. Returns NULL on failure. **/ SMBIOS_TABLE_TYPE7 * AllocateAndInitCacheInformation ( IN UINT8 CacheLevel, IN BOOLEAN DataCache, IN BOOLEAN UnifiedCache ) { SMBIOS_TABLE_TYPE7 *Type7Record; EFI_STRING CacheSocketStr; UINTN CacheSocketStrLen; UINTN StringBufferSize; CHAR8 *OptionalStrStart; UINTN TableSize; // Allocate and fetch the cache description StringBufferSize = sizeof (CHAR16) * SMBIOS_STRING_MAX_LENGTH; CacheSocketStr = AllocateZeroPool (StringBufferSize); if (CacheSocketStr == NULL) { return NULL; } CacheSocketStrLen = GetCacheSocketStr ( CacheLevel, DataCache, UnifiedCache, CacheSocketStr ); TableSize = sizeof (SMBIOS_TABLE_TYPE7) + CacheSocketStrLen + 1 + 1; Type7Record = AllocateZeroPool (TableSize); if (Type7Record == NULL) { FreePool (CacheSocketStr); return NULL; } Type7Record->Hdr.Type = EFI_SMBIOS_TYPE_CACHE_INFORMATION; Type7Record->Hdr.Length = sizeof (SMBIOS_TABLE_TYPE7); Type7Record->Hdr.Handle = SMBIOS_HANDLE_PI_RESERVED; Type7Record->SocketDesignation = 1; Type7Record->SupportedSRAMType.Unknown = 1; Type7Record->CurrentSRAMType.Unknown = 1; Type7Record->CacheSpeed = 0; Type7Record->ErrorCorrectionType = CacheErrorUnknown; OptionalStrStart = (CHAR8 *)(Type7Record + 1); UnicodeStrToAsciiStrS (CacheSocketStr, OptionalStrStart, CacheSocketStrLen + 1); FreePool (CacheSocketStr); return Type7Record; } /** Add Type 7 SMBIOS Record for Cache Information. @param[in] ProcessorIndex Processor number of specified processor. @param[out] L1CacheHandle Pointer to the handle of the L1 Cache SMBIOS record. @param[out] L2CacheHandle Pointer to the handle of the L2 Cache SMBIOS record. @param[out] L3CacheHandle Pointer to the handle of the L3 Cache SMBIOS record. **/ VOID AddSmbiosCacheTypeTable ( IN UINTN ProcessorIndex, OUT EFI_SMBIOS_HANDLE *L1CacheHandle, OUT EFI_SMBIOS_HANDLE *L2CacheHandle, OUT EFI_SMBIOS_HANDLE *L3CacheHandle ) { EFI_STATUS Status; SMBIOS_TABLE_TYPE7 *Type7Record; EFI_SMBIOS_HANDLE SmbiosHandle; UINT8 CacheLevel; UINT8 MaxCacheLevel; BOOLEAN DataCacheType; BOOLEAN SeparateCaches; Status = EFI_SUCCESS; MaxCacheLevel = 0; // See if there's an L1 cache present. MaxCacheLevel = SmbiosProcessorGetMaxCacheLevel (); if (MaxCacheLevel < 1) { return; } for (CacheLevel = 1; CacheLevel <= MaxCacheLevel; CacheLevel++) { Type7Record = NULL; SeparateCaches = SmbiosProcessorHasSeparateCaches (CacheLevel); // At each level of cache, we can have a single type (unified, instruction or data), // or two types - separate data and instruction caches. If we have separate // instruction and data caches, then on the first iteration (CacheSubLevel = 0) // process the instruction cache. for (DataCacheType = 0; DataCacheType <= 1; DataCacheType++) { // If there's no separate data/instruction cache, skip the second iteration if ((DataCacheType == 1) && !SeparateCaches) { continue; } Type7Record = AllocateAndInitCacheInformation ( CacheLevel, DataCacheType, !SeparateCaches ); if (Type7Record == NULL) { continue; } ConfigureCacheArchitectureInformation ( CacheLevel, DataCacheType, !SeparateCaches, Type7Record ); // Allow the platform to fill in other information such as speed, SRAM type etc. if (!OemGetCacheInformation ( ProcessorIndex, CacheLevel, DataCacheType, !SeparateCaches, Type7Record )) { continue; } SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED; // Finally, install the table Status = mSmbios->Add ( mSmbios, NULL, &SmbiosHandle, (EFI_SMBIOS_TABLE_HEADER *)Type7Record ); if (EFI_ERROR (Status)) { continue; } // Config L1/L2/L3 Cache Handle switch (CacheLevel) { case CpuCacheL1: *L1CacheHandle = SmbiosHandle; break; case CpuCacheL2: *L2CacheHandle = SmbiosHandle; break; case CpuCacheL3: *L3CacheHandle = SmbiosHandle; break; default: break; } } } } /** Allocates a Type 4 Processor Information structure and sets the strings following the data fields. @param[out] Type4Record The Type 4 structure to allocate and initialize @param[in] ProcessorIndex The index of the processor @param[in] Populated Whether the specified processor is populated. @retval EFI_SUCCESS The Type 4 structure was successfully allocated and the strings initialized. @retval EFI_OUT_OF_RESOURCES Could not allocate memory needed. **/ EFI_STATUS AllocateType4AndSetProcessorInformationStrings ( SMBIOS_TABLE_TYPE4 **Type4Record, UINT8 ProcessorIndex, BOOLEAN Populated ) { EFI_STATUS Status; EFI_STRING_ID ProcessorManu; EFI_STRING_ID ProcessorVersion; EFI_STRING_ID SerialNumber; EFI_STRING_ID AssetTag; EFI_STRING_ID PartNumber; EFI_STRING ProcessorStr; EFI_STRING ProcessorManuStr; EFI_STRING ProcessorVersionStr; EFI_STRING SerialNumberStr; EFI_STRING AssetTagStr; EFI_STRING PartNumberStr; CHAR8 *OptionalStrStart; CHAR8 *StrStart; UINTN ProcessorStrLen; UINTN ProcessorManuStrLen; UINTN ProcessorVersionStrLen; UINTN SerialNumberStrLen; UINTN AssetTagStrLen; UINTN PartNumberStrLen; UINTN TotalSize; UINTN StringBufferSize; Status = EFI_SUCCESS; ProcessorManuStr = NULL; ProcessorVersionStr = NULL; SerialNumberStr = NULL; AssetTagStr = NULL; PartNumberStr = NULL; ProcessorManu = STRING_TOKEN (STR_PROCESSOR_MANUFACTURE); ProcessorVersion = STRING_TOKEN (STR_PROCESSOR_VERSION); SerialNumber = STRING_TOKEN (STR_PROCESSOR_SERIAL_NUMBER); AssetTag = STRING_TOKEN (STR_PROCESSOR_ASSET_TAG); PartNumber = STRING_TOKEN (STR_PROCESSOR_PART_NUMBER); SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorManufacturer, ProcessorManu); SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorAssetTag, AssetTag); if (StrLen ((CHAR16 *)FixedPcdGetPtr (PcdProcessorSerialNumber)) > 0) { HiiSetString (mHiiHandle, SerialNumber, (CHAR16 *)FixedPcdGetPtr (PcdProcessorSerialNumber), NULL); } else { OemUpdateSmbiosInfo (mHiiHandle, SerialNumber, ProcessorSerialNumType04); } if (StrLen ((CHAR16 *)FixedPcdGetPtr (PcdProcessorPartNumber)) > 0) { HiiSetString (mHiiHandle, PartNumber, (CHAR16 *)FixedPcdGetPtr (PcdProcessorPartNumber), NULL); } else { OemUpdateSmbiosInfo (mHiiHandle, PartNumber, ProcessorPartNumType04); } if (StrLen ((CHAR16 *)FixedPcdGetPtr (PcdProcessorVersion)) > 0) { HiiSetString (mHiiHandle, ProcessorVersion, (CHAR16 *)FixedPcdGetPtr (PcdProcessorVersion), NULL); } else { OemUpdateSmbiosInfo (mHiiHandle, ProcessorVersion, ProcessorVersionType04); } // Processor Designation StringBufferSize = sizeof (CHAR16) * SMBIOS_STRING_MAX_LENGTH; ProcessorStr = AllocateZeroPool (StringBufferSize); if (ProcessorStr == NULL) { return EFI_OUT_OF_RESOURCES; } ProcessorStrLen = UnicodeSPrint ( ProcessorStr, StringBufferSize, L"CPU%02d", ProcessorIndex + 1 ); // Processor Manufacture ProcessorManuStr = HiiGetPackageString (&gEfiCallerIdGuid, ProcessorManu, NULL); ProcessorManuStrLen = StrLen (ProcessorManuStr); // Processor Version ProcessorVersionStr = HiiGetPackageString (&gEfiCallerIdGuid, ProcessorVersion, NULL); ProcessorVersionStrLen = StrLen (ProcessorVersionStr); // Serial Number SerialNumberStr = HiiGetPackageString (&gEfiCallerIdGuid, SerialNumber, NULL); SerialNumberStrLen = StrLen (SerialNumberStr); // Asset Tag AssetTagStr = HiiGetPackageString (&gEfiCallerIdGuid, AssetTag, NULL); AssetTagStrLen = StrLen (AssetTagStr); // Part Number PartNumberStr = HiiGetPackageString (&gEfiCallerIdGuid, PartNumber, NULL); PartNumberStrLen = StrLen (PartNumberStr); TotalSize = sizeof (SMBIOS_TABLE_TYPE4) + ProcessorStrLen + 1 + ProcessorManuStrLen + 1 + ProcessorVersionStrLen + 1 + SerialNumberStrLen + 1 + AssetTagStrLen + 1 + PartNumberStrLen + 1 + 1; *Type4Record = AllocateZeroPool (TotalSize); if (*Type4Record == NULL) { Status = EFI_OUT_OF_RESOURCES; goto Exit; } CopyMem (*Type4Record, &mSmbiosProcessorTableTemplate, sizeof (SMBIOS_TABLE_TYPE4)); OptionalStrStart = (CHAR8 *)(*Type4Record + 1); UnicodeStrToAsciiStrS ( ProcessorStr, OptionalStrStart, ProcessorStrLen + 1 ); StrStart = OptionalStrStart + ProcessorStrLen + 1; UnicodeStrToAsciiStrS ( ProcessorManuStr, StrStart, ProcessorManuStrLen + 1 ); StrStart += ProcessorManuStrLen + 1; UnicodeStrToAsciiStrS ( ProcessorVersionStr, StrStart, ProcessorVersionStrLen + 1 ); StrStart += ProcessorVersionStrLen + 1; UnicodeStrToAsciiStrS ( SerialNumberStr, StrStart, SerialNumberStrLen + 1 ); StrStart += SerialNumberStrLen + 1; UnicodeStrToAsciiStrS ( AssetTagStr, StrStart, AssetTagStrLen + 1 ); StrStart += AssetTagStrLen + 1; UnicodeStrToAsciiStrS ( PartNumberStr, StrStart, PartNumberStrLen + 1 ); Exit: FreePool (ProcessorStr); FreePool (ProcessorManuStr); FreePool (ProcessorVersionStr); FreePool (SerialNumberStr); FreePool (AssetTagStr); FreePool (PartNumberStr); return Status; } /** Add Type 4 SMBIOS Record for Processor Information. @param[in] ProcessorIndex Processor index of specified processor. **/ EFI_STATUS AddSmbiosProcessorTypeTable ( IN UINTN ProcessorIndex ) { EFI_STATUS Status; SMBIOS_TABLE_TYPE4 *Type4Record; EFI_SMBIOS_HANDLE SmbiosHandle; EFI_SMBIOS_HANDLE L1CacheHandle; EFI_SMBIOS_HANDLE L2CacheHandle; EFI_SMBIOS_HANDLE L3CacheHandle; UINT8 *LegacyVoltage; PROCESSOR_STATUS_DATA ProcessorStatus; UINT64 *ProcessorId; PROCESSOR_CHARACTERISTIC_FLAGS ProcessorCharacteristics; OEM_MISC_PROCESSOR_DATA MiscProcessorData; BOOLEAN ProcessorPopulated; Type4Record = NULL; MiscProcessorData.Voltage = 0; MiscProcessorData.CurrentSpeed = 0; MiscProcessorData.CoreCount = 0; MiscProcessorData.CoresEnabled = 0; MiscProcessorData.ThreadCount = 0; MiscProcessorData.MaxSpeed = 0; L1CacheHandle = 0xFFFF; L2CacheHandle = 0xFFFF; L3CacheHandle = 0xFFFF; ProcessorPopulated = OemIsProcessorPresent (ProcessorIndex); Status = AllocateType4AndSetProcessorInformationStrings ( &Type4Record, ProcessorIndex, ProcessorPopulated ); if (EFI_ERROR (Status)) { return Status; } OemGetProcessorInformation ( ProcessorIndex, &ProcessorStatus, (PROCESSOR_CHARACTERISTIC_FLAGS *) &Type4Record->ProcessorCharacteristics, &MiscProcessorData ); if (ProcessorPopulated) { AddSmbiosCacheTypeTable ( ProcessorIndex, &L1CacheHandle, &L2CacheHandle, &L3CacheHandle ); } LegacyVoltage = (UINT8 *)&Type4Record->Voltage; *LegacyVoltage = MiscProcessorData.Voltage; Type4Record->CurrentSpeed = MiscProcessorData.CurrentSpeed; Type4Record->MaxSpeed = MiscProcessorData.MaxSpeed; Type4Record->Status = ProcessorStatus.Data; Type4Record->L1CacheHandle = L1CacheHandle; Type4Record->L2CacheHandle = L2CacheHandle; Type4Record->L3CacheHandle = L3CacheHandle; Type4Record->CoreCount = MiscProcessorData.CoreCount; Type4Record->CoreCount2 = MiscProcessorData.CoreCount; Type4Record->EnabledCoreCount = MiscProcessorData.CoresEnabled; Type4Record->EnabledCoreCount2 = MiscProcessorData.CoresEnabled; Type4Record->ThreadCount = MiscProcessorData.ThreadCount; Type4Record->ThreadCount2 = MiscProcessorData.ThreadCount; Type4Record->CurrentSpeed = GetCpuFrequency (ProcessorIndex); Type4Record->ExternalClock = (UINT16)(SmbiosGetExternalClockFrequency () / 1000 / 1000); ProcessorId = (UINT64 *)&Type4Record->ProcessorId; *ProcessorId = SmbiosGetProcessorId (); ProcessorCharacteristics = SmbiosGetProcessorCharacteristics (); Type4Record->ProcessorCharacteristics |= *((UINT64 *)&ProcessorCharacteristics); Type4Record->ProcessorFamily = SmbiosGetProcessorFamily (); Type4Record->ProcessorFamily2 = SmbiosGetProcessorFamily2 (); SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED; Status = mSmbios->Add ( mSmbios, NULL, &SmbiosHandle, (EFI_SMBIOS_TABLE_HEADER *)Type4Record ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "[%a]:[%dL] Smbios Type04 Table Log Failed! %r \n", __func__, DEBUG_LINE_NUMBER, Status )); } FreePool (Type4Record); return Status; } /** Standard EFI driver point. @param ImageHandle Handle for the image of this driver @param SystemTable Pointer to the EFI System Table @retval EFI_SUCCESS The data was successfully stored. **/ EFI_STATUS EFIAPI ProcessorSubClassEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; UINT32 ProcessorIndex; // // Locate dependent protocols // Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&mSmbios); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Could not locate SMBIOS protocol. %r\n", Status)); return Status; } // // Add our default strings to the HII database. They will be modified later. // mHiiHandle = HiiAddPackages ( &gEfiCallerIdGuid, NULL, ProcessorSubClassStrings, NULL, NULL ); if (mHiiHandle == NULL) { return EFI_OUT_OF_RESOURCES; } // // Add SMBIOS tables for populated sockets. // for (ProcessorIndex = 0; ProcessorIndex < OemGetMaxProcessors (); ProcessorIndex++) { Status = AddSmbiosProcessorTypeTable (ProcessorIndex); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Add Processor Type Table Failed! %r.\n", Status)); return Status; } } return Status; }