| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MEMORY_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MEMORY_H_ | |
| /* | |
| // Copyright (c) 2018 Intel Corporation | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| */ | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <system_error> // NOLINT | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "collection.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "hex_utils.hpp" | |
| #include "json_utils.hpp" | |
| #include "system_utils.hpp" | |
| #include "external_storer.hpp" | |
| #ifdef HEALTH_POPULATE | |
| #include "health.hpp" | |
| #endif /* HEALTH_POPULATE */ | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| namespace redfish { | |
| inline std::string translateMemoryTypeToRedfish(const std::string& memoryType) { | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR") { | |
| return "DDR"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR2") { | |
| return "DDR2"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR3") { | |
| return "DDR3"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR4") { | |
| return "DDR4"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR4E_SDRAM") { | |
| return "DDR4E_SDRAM"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR5") { | |
| return "DDR5"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.LPDDR4_SDRAM") { | |
| return "LPDDR4_SDRAM"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.LPDDR3_SDRAM") { | |
| return "LPDDR3_SDRAM"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR2_SDRAM_FB_DIMM") { | |
| return "DDR2_SDRAM_FB_DIMM"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR2_SDRAM_FB_DIMM_" | |
| "PROB") { | |
| return "DDR2_SDRAM_FB_DIMM_PROBE"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.DDR_SGRAM") { | |
| return "DDR_SGRAM"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.ROM") { | |
| return "ROM"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.SDRAM") { | |
| return "SDRAM"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.EDO") { | |
| return "EDO"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.FastPageMode") { | |
| return "FastPageMode"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.PipelinedNibble") { | |
| return "PipelinedNibble"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.Logical") { | |
| return "Logical"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.HBM") { | |
| return "HBM"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.HBM2") { | |
| return "HBM2"; | |
| } | |
| if (memoryType == "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.HBM3") { | |
| return "HBM3"; | |
| } | |
| if (memoryType == | |
| "xyz.openbmc_project.Inventory.Item.Dimm.DeviceType.HBM3E") { | |
| return "HBM3E"; | |
| } | |
| // This is values like Other or Unknown | |
| // Also D-Bus values: | |
| // DRAM | |
| // EDRAM | |
| // VRAM | |
| // SRAM | |
| // RAM | |
| // FLASH | |
| // EEPROM | |
| // FEPROM | |
| // EPROM | |
| // CDRAM | |
| // ThreeDRAM | |
| // RDRAM | |
| // FBD2 | |
| // LPDDR_SDRAM | |
| // LPDDR2_SDRAM | |
| // LPDDR5_SDRAM | |
| return ""; | |
| } | |
| inline void dimmPropToHex(const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const char* key, const uint16_t* value, | |
| const nlohmann::json::json_pointer& jsonPtr) { | |
| if (value == nullptr) { | |
| return; | |
| } | |
| aResp->res.jsonValue[jsonPtr][key] = "0x" + intToHexString(*value, 4); | |
| } | |
| inline void getPersistentMemoryProperties( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const dbus::utility::DBusPropertiesMap& properties, | |
| const nlohmann::json::json_pointer& jsonPtr) { | |
| const uint16_t* moduleManufacturerID = nullptr; | |
| const uint16_t* moduleProductID = nullptr; | |
| const uint16_t* subsystemVendorID = nullptr; | |
| const uint16_t* subsystemDeviceID = nullptr; | |
| const uint64_t* volatileRegionSizeLimitInKiB = nullptr; | |
| const uint64_t* pmRegionSizeLimitInKiB = nullptr; | |
| const uint64_t* volatileSizeInKiB = nullptr; | |
| const uint64_t* pmSizeInKiB = nullptr; | |
| const uint64_t* cacheSizeInKB = nullptr; | |
| const uint64_t* voltaileRegionMaxSizeInKib = nullptr; | |
| const uint64_t* pmRegionMaxSizeInKiB = nullptr; | |
| const uint64_t* allocationIncrementInKiB = nullptr; | |
| const uint64_t* allocationAlignmentInKiB = nullptr; | |
| const uint64_t* volatileRegionNumberLimit = nullptr; | |
| const uint64_t* pmRegionNumberLimit = nullptr; | |
| const uint64_t* spareDeviceCount = nullptr; | |
| const bool* isSpareDeviceInUse = nullptr; | |
| const bool* isRankSpareEnabled = nullptr; | |
| const std::vector<uint32_t>* maxAveragePowerLimitmW = nullptr; | |
| const bool* configurationLocked = nullptr; | |
| const std::string* allowedMemoryModes = nullptr; | |
| const std::string* memoryMedia = nullptr; | |
| const bool* configurationLockCapable = nullptr; | |
| const bool* dataLockCapable = nullptr; | |
| const bool* passphraseCapable = nullptr; | |
| const uint64_t* maxPassphraseCount = nullptr; | |
| const uint64_t* passphraseLockLimit = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), properties, "ModuleManufacturerID", | |
| moduleManufacturerID, "ModuleProductID", moduleProductID, | |
| "SubsystemVendorID", subsystemVendorID, "SubsystemDeviceID", | |
| subsystemDeviceID, "VolatileRegionSizeLimitInKiB", | |
| volatileRegionSizeLimitInKiB, "PmRegionSizeLimitInKiB", | |
| pmRegionSizeLimitInKiB, "VolatileSizeInKiB", volatileSizeInKiB, | |
| "PmSizeInKiB", pmSizeInKiB, "CacheSizeInKB", cacheSizeInKB, | |
| "VoltaileRegionMaxSizeInKib", voltaileRegionMaxSizeInKib, | |
| "PmRegionMaxSizeInKiB", pmRegionMaxSizeInKiB, "AllocationIncrementInKiB", | |
| allocationIncrementInKiB, "AllocationAlignmentInKiB", | |
| allocationAlignmentInKiB, "VolatileRegionNumberLimit", | |
| volatileRegionNumberLimit, "PmRegionNumberLimit", pmRegionNumberLimit, | |
| "SpareDeviceCount", spareDeviceCount, "IsSpareDeviceInUse", | |
| isSpareDeviceInUse, "IsRankSpareEnabled", isRankSpareEnabled, | |
| "MaxAveragePowerLimitmW", maxAveragePowerLimitmW, "ConfigurationLocked", | |
| configurationLocked, "AllowedMemoryModes", allowedMemoryModes, | |
| "MemoryMedia", memoryMedia, "ConfigurationLockCapable", | |
| configurationLockCapable, "DataLockCapable", dataLockCapable, | |
| "PassphraseCapable", passphraseCapable, "MaxPassphraseCount", | |
| maxPassphraseCount, "PassphraseLockLimit", passphraseLockLimit); | |
| if (!success) { | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| dimmPropToHex(aResp, "ModuleManufacturerID", moduleManufacturerID, jsonPtr); | |
| dimmPropToHex(aResp, "ModuleProductID", moduleProductID, jsonPtr); | |
| dimmPropToHex(aResp, "MemorySubsystemControllerManufacturerID", | |
| subsystemVendorID, jsonPtr); | |
| dimmPropToHex(aResp, "MemorySubsystemControllerProductID", subsystemDeviceID, | |
| jsonPtr); | |
| if (volatileRegionSizeLimitInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["VolatileRegionSizeLimitMiB"] = | |
| (*volatileRegionSizeLimitInKiB) >> 10; | |
| } | |
| if (pmRegionSizeLimitInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["PersistentRegionSizeLimitMiB"] = | |
| (*pmRegionSizeLimitInKiB) >> 10; | |
| } | |
| if (volatileSizeInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["VolatileSizeMiB"] = | |
| (*volatileSizeInKiB) >> 10; | |
| } | |
| if (pmSizeInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["NonVolatileSizeMiB"] = (*pmSizeInKiB) >> 10; | |
| } | |
| if (cacheSizeInKB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["CacheSizeMiB"] = (*cacheSizeInKB >> 10); | |
| } | |
| if (voltaileRegionMaxSizeInKib != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["VolatileRegionSizeMaxMiB"] = | |
| (*voltaileRegionMaxSizeInKib) >> 10; | |
| } | |
| if (pmRegionMaxSizeInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["PersistentRegionSizeMaxMiB"] = | |
| (*pmRegionMaxSizeInKiB) >> 10; | |
| } | |
| if (allocationIncrementInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["AllocationIncrementMiB"] = | |
| (*allocationIncrementInKiB) >> 10; | |
| } | |
| if (allocationAlignmentInKiB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["AllocationAlignmentMiB"] = | |
| (*allocationAlignmentInKiB) >> 10; | |
| } | |
| if (volatileRegionNumberLimit != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["VolatileRegionNumberLimit"] = | |
| *volatileRegionNumberLimit; | |
| } | |
| if (pmRegionNumberLimit != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["PersistentRegionNumberLimit"] = | |
| *pmRegionNumberLimit; | |
| } | |
| if (spareDeviceCount != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SpareDeviceCount"] = *spareDeviceCount; | |
| } | |
| if (isSpareDeviceInUse != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["IsSpareDeviceEnabled"] = *isSpareDeviceInUse; | |
| } | |
| if (isRankSpareEnabled != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["IsRankSpareEnabled"] = *isRankSpareEnabled; | |
| } | |
| if (maxAveragePowerLimitmW != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["MaxTDPMilliWatts"] = *maxAveragePowerLimitmW; | |
| } | |
| if (configurationLocked != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["ConfigurationLocked"] = *configurationLocked; | |
| } | |
| if (allowedMemoryModes != nullptr) { | |
| constexpr const std::array<const char*, 3> values{"Volatile", "PMEM", | |
| "Block"}; | |
| for (const char* v : values) { | |
| if (allowedMemoryModes->ends_with(v)) { | |
| aResp->res.jsonValue[jsonPtr]["OperatingMemoryModes"].emplace_back(v); | |
| break; | |
| } | |
| } | |
| } | |
| if (memoryMedia != nullptr) { | |
| constexpr const std::array<const char*, 3> values{"DRAM", "NAND", | |
| "Intel3DXPoint"}; | |
| for (const char* v : values) { | |
| if (memoryMedia->ends_with(v)) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryMedia"].emplace_back(v); | |
| break; | |
| } | |
| } | |
| } | |
| if (configurationLockCapable != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SecurityCapabilities"] | |
| ["ConfigurationLockCapable"] = | |
| *configurationLockCapable; | |
| } | |
| if (dataLockCapable != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SecurityCapabilities"]["DataLockCapable"] = | |
| *dataLockCapable; | |
| } | |
| if (passphraseCapable != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SecurityCapabilities"]["PassphraseCapable"] = | |
| *passphraseCapable; | |
| } | |
| if (maxPassphraseCount != nullptr) { | |
| aResp->res | |
| .jsonValue[jsonPtr]["SecurityCapabilities"]["MaxPassphraseCount"] = | |
| *maxPassphraseCount; | |
| } | |
| if (passphraseLockLimit != nullptr) { | |
| aResp->res | |
| .jsonValue[jsonPtr]["SecurityCapabilities"]["PassphraseLockLimit"] = | |
| *passphraseLockLimit; | |
| } | |
| } | |
| inline void getProcessorLinks( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& objectPath, | |
| const nlohmann::json::json_pointer& jsonPtr) { | |
| BMCWEB_LOG_DEBUG << "Get Memory -- Processor association"; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| dbus_utils::getProperty<std::vector<std::string>>( | |
| "xyz.openbmc_project.ObjectMapper", objectPath + "/processor", | |
| "xyz.openbmc_project.Association", "endpoints", requestContext, | |
| [asyncResp, jsonPtr](const boost::system::error_code ec, | |
| const std::vector<std::string>& processorList) { | |
| if (ec) { | |
| return; | |
| } | |
| if (processorList.empty()) { | |
| return; | |
| } | |
| nlohmann::json::array_t processorLinks; | |
| for (std::string_view processor : processorList) { | |
| sdbusplus::message::object_path processorPath(processor.data()); | |
| // Define as `std::string` for easier concatenation later. | |
| const std::string processorName = processorPath.filename(); | |
| if (processorName.empty()) { | |
| BMCWEB_LOG_ERROR << "filename() is empty in " << processorPath.str; | |
| return; | |
| } | |
| nlohmann::json::object_t processorObject; | |
| processorObject["@odata.id"] = | |
| "/redfish/v1/Systems/system/Processors/" + processorName; | |
| processorLinks.emplace_back(processorObject); | |
| } | |
| asyncResp->res.jsonValue[jsonPtr]["Links"]["Processors"] = | |
| std::move(processorLinks); | |
| }); | |
| } | |
| inline void assembleDimmProperties( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const dbus::utility::DBusPropertiesMap& properties, | |
| const nlohmann::json::json_pointer& jsonPtr) { | |
| const uint16_t* memoryDataWidth = nullptr; | |
| const size_t* memorySizeInKB = nullptr; | |
| const std::string* partNumber = nullptr; | |
| const std::string* serialNumber = nullptr; | |
| const std::string* manufacturer = nullptr; | |
| const uint16_t* revisionCode = nullptr; | |
| const bool* present = nullptr; | |
| const uint16_t* memoryTotalWidth = nullptr; | |
| const std::string* ecc = nullptr; | |
| const std::string* formFactor = nullptr; | |
| const std::vector<uint16_t>* allowedSpeedsMT = nullptr; | |
| const size_t* memoryAttributes = nullptr; | |
| const uint16_t* memoryConfiguredSpeedInMhz = nullptr; | |
| const std::string* memoryType = nullptr; | |
| const uint8_t* channel = nullptr; | |
| const uint8_t* memoryController = nullptr; | |
| const uint8_t* slot = nullptr; | |
| const uint8_t* socket = nullptr; | |
| const std::string* sparePartNumber = nullptr; | |
| const std::string* model = nullptr; | |
| const std::string* locationCode = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), properties, "MemoryDataWidth", | |
| memoryDataWidth, "MemorySizeInKB", memorySizeInKB, "PartNumber", | |
| partNumber, "SerialNumber", serialNumber, "Manufacturer", manufacturer, | |
| "RevisionCode", revisionCode, "Present", present, "MemoryTotalWidth", | |
| memoryTotalWidth, "ECC", ecc, "FormFactor", formFactor, "AllowedSpeedsMT", | |
| allowedSpeedsMT, "MemoryAttributes", memoryAttributes, | |
| "MemoryConfiguredSpeedInMhz", memoryConfiguredSpeedInMhz, "MemoryType", | |
| memoryType, "Channel", channel, "MemoryController", memoryController, | |
| "Slot", slot, "Socket", socket, "SparePartNumber", sparePartNumber, | |
| "Model", model, "LocationCode", locationCode); | |
| if (!success) { | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| if (memoryDataWidth != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["DataWidthBits"] = *memoryDataWidth; | |
| } | |
| if (memorySizeInKB != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["CapacityMiB"] = (*memorySizeInKB >> 10); | |
| } | |
| if (partNumber != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["PartNumber"] = *partNumber; | |
| } | |
| if (serialNumber != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SerialNumber"] = *serialNumber; | |
| } | |
| if (manufacturer != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["Manufacturer"] = *manufacturer; | |
| } | |
| if (revisionCode != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["FirmwareRevision"] = | |
| std::to_string(*revisionCode); | |
| } | |
| if (present != nullptr && !*present) { | |
| aResp->res.jsonValue[jsonPtr]["Status"]["State"] = "Absent"; | |
| } | |
| if (memoryTotalWidth != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["BusWidthBits"] = *memoryTotalWidth; | |
| } | |
| if (ecc != nullptr) { | |
| constexpr const std::array<const char*, 4> values{ | |
| "NoECC", "SingleBitECC", "MultiBitECC", "AddressParity"}; | |
| for (const char* v : values) { | |
| if (ecc->ends_with(v)) { | |
| aResp->res.jsonValue[jsonPtr]["ErrorCorrection"] = v; | |
| break; | |
| } | |
| } | |
| } | |
| if (formFactor != nullptr) { | |
| constexpr const std::array<const char*, 11> values{ | |
| "RDIMM", "UDIMM", "SO_DIMM", "LRDIMM", | |
| "Mini_RDIMM", "Mini_UDIMM", "SO_RDIMM_72b", "SO_UDIMM_72b", | |
| "SO_DIMM_16b", "SO_DIMM_32b", "Die"}; | |
| for (const char* v : values) { | |
| if (formFactor->ends_with(v)) { | |
| aResp->res.jsonValue[jsonPtr]["BaseModuleType"] = v; | |
| break; | |
| } | |
| } | |
| } | |
| if (allowedSpeedsMT != nullptr) { | |
| nlohmann::json& jValue = aResp->res.jsonValue[jsonPtr]["AllowedSpeedsMHz"]; | |
| jValue = nlohmann::json::array(); | |
| for (uint16_t subVal : *allowedSpeedsMT) { | |
| jValue.emplace_back(subVal); | |
| } | |
| } | |
| if (memoryAttributes != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["RankCount"] = | |
| static_cast<size_t>(*memoryAttributes); | |
| } | |
| if (memoryConfiguredSpeedInMhz != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["OperatingSpeedMhz"] = | |
| *memoryConfiguredSpeedInMhz; | |
| } | |
| if (memoryType != nullptr) { | |
| std::string memoryDeviceType = translateMemoryTypeToRedfish(*memoryType); | |
| // Values like "Unknown" or "Other" will return empty | |
| // so just leave off | |
| if (!memoryDeviceType.empty()) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryDeviceType"] = memoryDeviceType; | |
| } | |
| if (memoryType->find("DDR") != std::string::npos) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryType"] = "DRAM"; | |
| } else if (memoryType->ends_with("Logical")) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryType"] = "IntelOptane"; | |
| } | |
| } | |
| if (channel != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryLocation"]["Channel"] = *channel; | |
| } | |
| if (memoryController != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryLocation"]["MemoryController"] = | |
| *memoryController; | |
| } | |
| if (slot != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryLocation"]["Slot"] = *slot; | |
| } | |
| if (socket != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["MemoryLocation"]["Socket"] = *socket; | |
| } | |
| if (sparePartNumber != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["SparePartNumber"] = *sparePartNumber; | |
| } | |
| if (model != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["Model"] = *model; | |
| } | |
| if (locationCode != nullptr) { | |
| aResp->res.jsonValue[jsonPtr]["Location"]["PartLocation"]["ServiceLabel"] = | |
| *locationCode; | |
| } | |
| getPersistentMemoryProperties(aResp, properties, jsonPtr); | |
| } | |
| inline void getAllDimmsCallback( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::optional<std::string>& dimmId, const std::string& systemPath, | |
| const std::unordered_map<std::string, dbus::utility::ManagedObjectType>& | |
| managedObjectsByService) { | |
| BMCWEB_LOG_DEBUG << "getAllDimmsCallback entered"; | |
| nlohmann::json& jsonValue = asyncResp->res.jsonValue; | |
| for (const auto& [_, objects] : managedObjectsByService) { | |
| const bool multiHost = !systemPath.empty(); | |
| // Default systemName is system and change if system is multi-host | |
| std::string systemName = "system"; | |
| if (multiHost) { | |
| systemName = sdbusplus::message::object_path(systemPath).filename(); | |
| } | |
| for (const auto& [objectPath, interfaces] : objects) { | |
| // For multi-host systems, the object path of the Dimm must be a | |
| // descendant of the systemPath. (e.g. if systemPath = /a/b, then | |
| // objectPath must be some pattern like /a/b/..) | |
| if (multiHost && !std::string(objectPath).starts_with(systemPath)) { | |
| continue; | |
| } | |
| std::string thisDimmID = objectPath.filename(); | |
| if (thisDimmID.empty()) { | |
| continue; | |
| } | |
| if (dimmId && *dimmId != thisDimmID) { | |
| continue; | |
| } | |
| bool hasDimmInterface = false; | |
| for (const auto& [interface, _] : interfaces) { | |
| if (interface == "xyz.openbmc_project.Inventory.Item.Dimm") { | |
| hasDimmInterface = true; | |
| break; | |
| } | |
| } | |
| if (!hasDimmInterface) { | |
| continue; | |
| } | |
| BMCWEB_LOG_DEBUG << "Found a dimm; objectPath=" << objectPath.str; | |
| nlohmann::json::json_pointer healthPtr; | |
| nlohmann::json::json_pointer dimmPtr; | |
| if (!dimmId) { | |
| size_t index = jsonValue["Members"].size(); | |
| healthPtr = "/Members"_json_pointer / index / "Status"; | |
| dimmPtr = "/Members"_json_pointer / index; | |
| } else { | |
| healthPtr = "/Status"_json_pointer; | |
| dimmPtr = ""_json_pointer; | |
| } | |
| jsonValue[dimmPtr]["Id"] = thisDimmID; | |
| jsonValue[dimmPtr]["Name"] = "DIMM Slot"; | |
| jsonValue[dimmPtr]["Status"]["State"] = "Enabled"; | |
| jsonValue[dimmPtr]["Status"]["Health"] = "OK"; | |
| jsonValue[dimmPtr]["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Systems", systemName, "Memory", thisDimmID); | |
| jsonValue[dimmPtr]["@odata.type"] = "#Memory.v1_11_0.Memory"; | |
| #ifdef HEALTH_POPULATE | |
| auto dimmHealth = std::make_shared<HealthPopulate>( | |
| asyncResp, | |
| absl::StrCat("/redfish/v1/Systems/", systemName, "/Memory/", | |
| thisDimmID), | |
| objectPath, "Memory", thisDimmID); | |
| #endif /* HEALTH_POPULATE */ | |
| // Inserting the "Metrics" JSON stanza | |
| std::string metricsUrl = jsonValue[dimmPtr]["@odata.id"]; | |
| metricsUrl += "/MemoryMetrics"; | |
| nlohmann::json::object_t metricsObj; | |
| metricsObj["@odata.id"] = std::move(metricsUrl); | |
| jsonValue[dimmPtr]["Metrics"] = std::move(metricsObj); | |
| for (const auto& [interface, properties] : interfaces) { | |
| assembleDimmProperties(asyncResp, properties, dimmPtr); | |
| if (interface == "xyz.openbmc_project.Inventory.Connector.Slot") { | |
| jsonValue[dimmPtr]["Location"]["PartLocation"]["LocationType"] = | |
| "Slot"; | |
| } | |
| if (interface == "xyz.openbmc_project.Inventory.Connector.Embedded") { | |
| jsonValue[dimmPtr]["Location"]["PartLocation"]["LocationType"] = | |
| "Embedded"; | |
| } | |
| } | |
| getProcessorLinks(asyncResp, objectPath, dimmPtr); | |
| } | |
| if (!dimmId) { | |
| jsonValue["Members@odata.count"] = jsonValue["Members"].size(); | |
| } | |
| } | |
| auto it = asyncResp->res.jsonValue.find("Members"); | |
| if (it == asyncResp->res.jsonValue.end()) { | |
| return; | |
| } | |
| auto* members = it->get_ptr<nlohmann::json::array_t*>(); | |
| if (members == nullptr) { | |
| BMCWEB_LOG_ERROR << "Members is not array?!"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (!json_util::sortJsonArrayByKey("@odata.id", *members)) { | |
| BMCWEB_LOG_ERROR << "Unable to sort the DIMM collection"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| } | |
| inline void getAllDimms(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::optional<std::string>& dimmId, | |
| const std::string& systemPath, | |
| std::vector<std::string>&& services) { | |
| if (!dimmId) { | |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| } | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getManagedObjectsInEachService( | |
| std::move(services), {"/xyz/openbmc_project/inventory"}, context, | |
| [dimmId{dimmId}, systemPath, asyncResp{asyncResp}]( | |
| const std::unordered_map<std::string, | |
| dbus::utility::ManagedObjectType>& | |
| managedObjectsByService) { | |
| getAllDimmsCallback(asyncResp, dimmId, systemPath, | |
| managedObjectsByService); | |
| }); | |
| } | |
| // If |dimmId| is set, will only keep the first matched dimmId. | |
| inline void getDimmData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::optional<std::string>& dimmId, | |
| const std::string& subtreeRoot, | |
| const std::string& systemPath) { | |
| constexpr std::array<std::string_view, 1> dimmInterfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Dimm"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| subtreeRoot, 0, dimmInterfaces, requestContext, | |
| [asyncResp{asyncResp}, dimmId, systemPath]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG | |
| << "Collect services that implement DIMM interface for " | |
| << dimmId.value_or("all DIMMs"); | |
| bool foundGivenDimm = false; | |
| std::unordered_set<std::string> dimmServices; | |
| for (const auto& [path, object] : subtree) { | |
| BMCWEB_LOG_DEBUG << "Object path=" << path; | |
| sdbusplus::message::object_path objectPath(path); | |
| for (const auto& [service, interfaces] : object) { | |
| for (const std::string& interface : interfaces) { | |
| if (interface == "xyz.openbmc_project.Inventory.Item.Dimm") { | |
| if (dimmId && objectPath.filename() != *dimmId) { | |
| continue; | |
| } | |
| BMCWEB_LOG_DEBUG << "Added DIMM services " << service; | |
| dimmServices.insert(service); | |
| foundGivenDimm = true; | |
| } | |
| } | |
| } | |
| // Fetch the first matched DIMM | |
| if (dimmId && foundGivenDimm) { | |
| break; | |
| } | |
| } | |
| if (dimmId && !foundGivenDimm) { | |
| messages::resourceNotFound(asyncResp->res, "Memory", *dimmId); | |
| return; | |
| } | |
| std::vector<std::string> dimmServicesVec = {dimmServices.begin(), | |
| dimmServices.end()}; | |
| getAllDimms(asyncResp, dimmId, systemPath, std::move(dimmServicesVec)); | |
| }); | |
| } | |
| inline void handleMemoryCollection( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath, const query_param::Query& delegated) { | |
| const bool multiHost = !systemPath.empty(); | |
| // Default systemName is system and change if system is multi-host | |
| std::string systemName = "system"; | |
| // The subtree to query for Dimms changes if single or multi-host | |
| std::string subtreeRoot = "/xyz/openbmc_project/inventory"; | |
| if (multiHost) { | |
| systemName = sdbusplus::message::object_path(systemPath).filename(); | |
| subtreeRoot = systemPath; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#MemoryCollection.MemoryCollection"; | |
| asyncResp->res.jsonValue["Name"] = "Memory Module Collection"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| "/redfish/v1/Systems/" + systemName + "/Memory"; | |
| constexpr std::array<std::string_view, 1> interfaces{ | |
| "xyz.openbmc_project.Inventory.Item.Dimm"}; | |
| if (delegated.expandLevel > 0 && | |
| delegated.expandType != query_param::ExpandType::None) { | |
| BMCWEB_LOG_DEBUG << "Use efficient expand handler"; | |
| getDimmData(asyncResp, std::nullopt, subtreeRoot, systemPath); | |
| } else { | |
| BMCWEB_LOG_DEBUG << "Use default expand handler"; | |
| collection_util::getCollectionMembers( | |
| asyncResp, | |
| boost::urls::url("/redfish/v1/Systems/" + systemName + "/Memory"), | |
| interfaces, subtreeRoot.c_str()); | |
| } | |
| } | |
| inline void handleMemoryCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| query_param::Query delegated; | |
| query_param::QueryCapabilities capabilities = { | |
| #ifdef EFFICIENT_EXPAND_MEMORY_ENABLED | |
| .canDelegateExpandLevel = 1, | |
| #else | |
| .canDelegateExpandLevel = 0, | |
| #endif | |
| }; | |
| if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, | |
| capabilities)) { | |
| return; | |
| } | |
| redfish::system_utils::getSystemInformation( | |
| asyncResp, systemName, | |
| [delegated](const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath) { | |
| handleMemoryCollection(asyncResp, systemPath, delegated); | |
| }); | |
| } | |
| inline void requestRoutesMemoryCollection(App& app) { | |
| /** | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Memory/") | |
| .privileges(redfish::privileges::getMemoryCollection) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleMemoryCollectionGet, std::ref(app))); | |
| } | |
| // Constructs hook with known-good settings for use with MemoryMetrics | |
| inline external_storer::Hook makeDimmHook() { | |
| const std::string pathBase{"Systems/system/Memory"}; | |
| const std::string emptyString; | |
| const std::vector<std::string> emptyList; | |
| return {pathBase, emptyString, emptyList, emptyList}; | |
| } | |
| // Remembers the ExternalStorer hook and one instance per DIMM in this system | |
| inline std::shared_ptr<external_storer::Hook> rememberDimmHook( | |
| const std::string& dimmId) { | |
| static std::shared_ptr<external_storer::Hook> hookMemory = nullptr; | |
| if (!hookMemory) { | |
| // If not already remembered by static variable, create hook | |
| hookMemory = std::make_shared<external_storer::Hook>(makeDimmHook()); | |
| } | |
| // External storer NEVER sends dbus calls, so you will never leave this | |
| // thread. | |
| auto respGet = std::make_shared<bmcweb::AsyncResp>(nullptr); | |
| hookMemory->handleGetInstance(respGet, dimmId); | |
| if (respGet->res.result() == boost::beast::http::status::ok) { | |
| // Already exists, good, nothing more needs to be done | |
| return hookMemory; | |
| } | |
| boost::beast::http::request<boost::beast::http::string_body> upBody; | |
| std::error_code ec; | |
| // Create instance, with name of DIMM, and no further customizations | |
| auto upJson = nlohmann::json::object(); | |
| upJson["Id"] = dimmId; | |
| // Must supply 4th argument to avoid throwing exceptions | |
| upBody.body() = | |
| upJson.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace); | |
| crow::Request reqCreate{upBody, ec}; | |
| // External storer NEVER sends dbus calls, so you will never leave this | |
| // thread. | |
| auto respCreate = std::make_shared<bmcweb::AsyncResp>(nullptr); | |
| hookMemory->handleCreateInstance(reqCreate, respCreate); | |
| if (respCreate->res.result() != boost::beast::http::status::created) { | |
| BMCWEB_LOG_ERROR << "Problem creating instance for " << dimmId; | |
| return nullptr; | |
| } | |
| // The instance should now be usable | |
| return hookMemory; | |
| } | |
| inline nlohmann::json fakeMemoryMetrics(const std::string& dimm) { | |
| nlohmann::json::object_t metricsObj; | |
| metricsObj["Id"] = "Metrics"; | |
| metricsObj["Name"] = "Memory Metrics"; | |
| nlohmann::json::object_t currentPeriod; | |
| currentPeriod["CorrectableECCErrorCount"] = 0; | |
| currentPeriod["UncorrectableECCErrorCount"] = 0; | |
| currentPeriod["IndeterminateCorrectableErrorCount"] = 0; | |
| currentPeriod["IndeterminateUncorrectableErrorCount"] = 0; | |
| metricsObj["CurrentPeriod"] = std::move(currentPeriod); | |
| // ExternalStorer would normally apply this as invariant if successful | |
| // The caller already applies "@odata.type" as invariant no matter what | |
| std::string url = "/redfish/v1/Systems/system/Memory/"; | |
| url += dimm; | |
| url += "/MemoryMetrics"; | |
| metricsObj["@odata.id"] = std::move(url); | |
| return metricsObj; | |
| } | |
| inline int countDimm() { | |
| // TODO(): This is a STUB | |
| return 256; | |
| } | |
| inline bool checkDimmIndex(const std::string& dimmId) { | |
| int bound = countDimm(); | |
| // extract index from end of dimmId | |
| const std::string index = | |
| dimmId.substr(dimmId.find_last_not_of("0123456789") + 1); | |
| if (index.empty()) { | |
| BMCWEB_LOG_ERROR << "Unable to extract index from dimmId"; | |
| return false; | |
| } | |
| int64_t index_value = 0; | |
| if (!absl::SimpleAtoi(index, &index_value)) { | |
| BMCWEB_LOG_ERROR << "DIMM index not parseable"; | |
| return false; | |
| } | |
| if (index_value >= bound) { | |
| BMCWEB_LOG_ERROR << "DIMM index out of range"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| inline bool getExternalStorerMemoryMetrics( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const std::string& dimmId) { | |
| if (!checkDimmIndex(dimmId)) { | |
| BMCWEB_LOG_ERROR << "Problem checking DIMM index for " << dimmId; | |
| return false; | |
| } | |
| std::shared_ptr<external_storer::Hook> hook = rememberDimmHook(dimmId); | |
| if (!hook) { | |
| BMCWEB_LOG_ERROR << "Problem getting hook for " << dimmId; | |
| return false; | |
| } | |
| std::string emptyString; | |
| hook->handleGetEntry(aResp, ""_json_pointer, dimmId, emptyString, | |
| "MemoryMetrics"); | |
| if (aResp->res.result() == boost::beast::http::status::not_found) { | |
| // Fake up a synthetic response to replace only the 404 error | |
| aResp->res.jsonValue = fakeMemoryMetrics(dimmId); | |
| aResp->res.result(boost::beast::http::status::ok); | |
| } | |
| if (aResp->res.result() != boost::beast::http::status::ok) { | |
| BMCWEB_LOG_ERROR << "Problem getting file for " << dimmId; | |
| return false; | |
| } | |
| // Apply invariants, to override whatever might have been in the file | |
| aResp->res.jsonValue["@odata.type"] = "#MemoryMetrics.v1_5_0.MemoryMetrics"; | |
| return true; | |
| } | |
| inline void getSingleHostMemoryMetrics( | |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp, | |
| const std::string& dimmId) { | |
| BMCWEB_LOG_DEBUG << "Get available memory metrics for DIMM."; | |
| // This is the integration point for ExternalStorer with MemoryMetrics. | |
| // If locally-existing file exists, use ExternalStorer to return it. | |
| if (getExternalStorerMemoryMetrics(aResp, dimmId)) { | |
| BMCWEB_LOG_DEBUG << "Successful local file read for " << dimmId; | |
| return; | |
| } | |
| // No more falling back to D-Bus query, instead, simply give user 404 | |
| BMCWEB_LOG_WARNING << "Memory metrics not found for " << dimmId; | |
| messages::resourceNotFound(aResp->res, "MemoryMetrics", dimmId); | |
| } | |
| inline void handleMemory(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath, | |
| const std::string& dimmId) { | |
| const bool multiHost = !systemPath.empty(); | |
| // Default systemName is system and change if system is multi-host | |
| std::string systemName = "system"; | |
| // The subtree to query for Dimms changes if single or multi-host | |
| std::string subtreeRoot = "/xyz/openbmc_project/inventory"; | |
| if (multiHost) { | |
| systemName = sdbusplus::message::object_path(systemPath).filename(); | |
| subtreeRoot = systemPath; | |
| } | |
| getDimmData(asyncResp, dimmId, subtreeRoot, systemPath); | |
| } | |
| inline void handleMemoryGet(App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, | |
| const std::string& dimmId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| redfish::system_utils::getSystemInformation( | |
| asyncResp, systemName, | |
| [dimmId](const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath) { | |
| handleMemory(asyncResp, systemPath, dimmId); | |
| }); | |
| } | |
| inline void requestRoutesMemory(App& app) { | |
| /** | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Memory/<str>/") | |
| .privileges(redfish::privileges::getMemory) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleMemoryGet, std::ref(app))); | |
| } | |
| inline void handleMemoryMetricsGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, const std::string& dimmId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#MemoryMetrics.v1_5_0.MemoryMetrics"; | |
| asyncResp->res.jsonValue["@odata.id"] = absl::StrCat( | |
| "/redfish/v1/Systems/", systemName, "/Memory/", dimmId, "/MemoryMetrics"); | |
| asyncResp->res.jsonValue["Id"] = "Metrics"; | |
| asyncResp->res.jsonValue["Name"] = "Memory Metrics"; | |
| redfish::system_utils::getSystemInformation( | |
| asyncResp, systemName, | |
| [dimmId](const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemPath) { | |
| const bool multiHost = !systemPath.empty(); | |
| // Multihost will be provided by plugins. | |
| if (!multiHost) { | |
| getSingleHostMemoryMetrics(asyncResp, dimmId); | |
| } | |
| }); | |
| } | |
| inline void requestRoutesMemoryMetrics(App& app) { | |
| /** | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Memory/<str>/MemoryMetrics/") | |
| .privileges(redfish::privileges::getMemoryMetrics) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleMemoryMetricsGet, std::ref(app))); | |
| } | |
| } // namespace redfish | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MEMORY_H_ |