|  | /* | 
|  | // 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 "IntelCPUSensor.hpp" | 
|  | #include "Thresholds.hpp" | 
|  | #include "Utils.hpp" | 
|  | #include "VariantVisitors.hpp" | 
|  |  | 
|  | #include <peci.h> | 
|  |  | 
|  | #include <boost/algorithm/string/replace.hpp> | 
|  | #include <boost/asio/error.hpp> | 
|  | #include <boost/asio/io_context.hpp> | 
|  | #include <boost/asio/steady_timer.hpp> | 
|  | #include <boost/container/flat_map.hpp> | 
|  | #include <boost/container/flat_set.hpp> | 
|  | #include <phosphor-logging/lg2.hpp> | 
|  | #include <sdbusplus/asio/connection.hpp> | 
|  | #include <sdbusplus/asio/object_server.hpp> | 
|  | #include <sdbusplus/bus/match.hpp> | 
|  | #include <sdbusplus/message.hpp> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <array> | 
|  | #include <cctype> | 
|  | #include <cerrno> | 
|  | #include <chrono> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <cstring> | 
|  | #include <filesystem> | 
|  | #include <fstream> | 
|  | #include <functional> | 
|  | #include <ios> | 
|  | #include <iterator> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <regex> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <variant> | 
|  | #include <vector> | 
|  |  | 
|  | // clang-format off | 
|  | // this needs to be included last or we'll have build issues | 
|  | #include <linux/peci-ioctl.h> | 
|  | #if !defined(PECI_MBX_INDEX_DDR_DIMM_TEMP) | 
|  | #define PECI_MBX_INDEX_DDR_DIMM_TEMP MBX_INDEX_DDR_DIMM_TEMP | 
|  | #endif | 
|  | // clang-format on | 
|  |  | 
|  | boost::container::flat_map<std::string, std::shared_ptr<IntelCPUSensor>> | 
|  | gCpuSensors; | 
|  | boost::container::flat_map<std::string, | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface>> | 
|  | inventoryIfaces; | 
|  |  | 
|  | enum State | 
|  | { | 
|  | OFF,  // host powered down | 
|  | ON,   // host powered on | 
|  | READY // host powered on and mem test passed - fully ready | 
|  | }; | 
|  |  | 
|  | struct CPUConfig | 
|  | { | 
|  | CPUConfig(const uint64_t& bus, const uint64_t& addr, | 
|  | const std::string& name, const State& state) : | 
|  | bus(bus), addr(addr), name(name), state(state) | 
|  | {} | 
|  | int bus; | 
|  | int addr; | 
|  | std::string name; | 
|  | State state; | 
|  |  | 
|  | bool operator<(const CPUConfig& rhs) const | 
|  | { | 
|  | // NOLINTNEXTLINE | 
|  | return (name < rhs.name); | 
|  | } | 
|  | }; | 
|  |  | 
|  | static constexpr const char* peciDev = "/dev/peci-"; | 
|  | static constexpr const char* peciDevPath = "/sys/bus/peci/devices/"; | 
|  | static constexpr const char* rescanPath = "/sys/bus/peci/rescan"; | 
|  | static constexpr const unsigned int rankNumMax = 8; | 
|  |  | 
|  | static constexpr auto sensorTypes{std::to_array<const char*>({"XeonCPU"})}; | 
|  | static constexpr auto hiddenProps{std::to_array<const char*>( | 
|  | {IntelCPUSensor::labelTcontrol, "Tthrottle", "Tjmax"})}; | 
|  |  | 
|  | void detectCpuAsync( | 
|  | boost::asio::steady_timer& pingTimer, | 
|  | boost::asio::steady_timer& creationTimer, boost::asio::io_context& io, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, | 
|  | boost::container::flat_set<CPUConfig>& cpuConfigs, | 
|  | ManagedObjectType& sensorConfigs); | 
|  |  | 
|  | std::string createSensorName(const std::string& label, const std::string& item, | 
|  | const int& cpuId) | 
|  | { | 
|  | std::string sensorName = label; | 
|  | if (item != "input") | 
|  | { | 
|  | sensorName += " " + item; | 
|  | } | 
|  |  | 
|  | std::string cpuStr = "CPU" + std::to_string(cpuId); | 
|  | constexpr const char* subLabel = "DIMM"; | 
|  | std::size_t found = label.find(subLabel); | 
|  | if (found != std::string::npos) | 
|  | { | 
|  | sensorName = cpuStr + " " + sensorName; | 
|  | } | 
|  | else | 
|  | { | 
|  | sensorName += " " + cpuStr; | 
|  | } | 
|  | // converting to Upper Camel case whole name | 
|  | bool isWordEnd = true; | 
|  | std::transform(sensorName.begin(), sensorName.end(), sensorName.begin(), | 
|  | [&isWordEnd](int c) { | 
|  | if (std::isspace(c) != 0) | 
|  | { | 
|  | isWordEnd = true; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (isWordEnd) | 
|  | { | 
|  | isWordEnd = false; | 
|  | return std::toupper(c); | 
|  | } | 
|  | } | 
|  | return c; | 
|  | }); | 
|  | return sensorName; | 
|  | } | 
|  |  | 
|  | bool createSensors(boost::asio::io_context& io, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, | 
|  | boost::container::flat_set<CPUConfig>& cpuConfigs, | 
|  | ManagedObjectType& sensorConfigs) | 
|  | { | 
|  | bool available = false; | 
|  | for (const CPUConfig& cpu : cpuConfigs) | 
|  | { | 
|  | if (cpu.state != State::OFF) | 
|  | { | 
|  | available = true; | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface>& iface = | 
|  | inventoryIfaces[cpu.name]; | 
|  | if (iface != nullptr) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | iface = objectServer.add_interface( | 
|  | cpuInventoryPath + std::string("/") + cpu.name, | 
|  | "xyz.openbmc_project.Inventory.Item"); | 
|  | iface->register_property("PrettyName", cpu.name); | 
|  | iface->register_property("Present", true); | 
|  | iface->initialize(); | 
|  | } | 
|  | } | 
|  | if (!available) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (sensorConfigs.empty()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::vector<std::filesystem::path> hwmonNamePaths; | 
|  | findFiles(std::filesystem::path(peciDevPath), | 
|  | R"(peci-\d+/\d+-.+/peci[-_].+/hwmon/hwmon\d+/name$)", | 
|  | hwmonNamePaths, 6); | 
|  | if (hwmonNamePaths.empty()) | 
|  | { | 
|  | lg2::error("No CPU sensors in system"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | boost::container::flat_set<std::string> scannedDirectories; | 
|  | boost::container::flat_set<std::string> createdSensors; | 
|  |  | 
|  | for (const std::filesystem::path& hwmonNamePath : hwmonNamePaths) | 
|  | { | 
|  | auto hwmonDirectory = hwmonNamePath.parent_path(); | 
|  |  | 
|  | auto ret = scannedDirectories.insert(hwmonDirectory.string()); | 
|  | if (!ret.second) | 
|  | { | 
|  | continue; // already searched this path | 
|  | } | 
|  |  | 
|  | std::filesystem::path::iterator it = hwmonNamePath.begin(); | 
|  | std::advance(it, 6); // pick the 6th part for a PECI client device name | 
|  | std::string deviceName = *it; | 
|  |  | 
|  | size_t bus = 0; | 
|  | size_t addr = 0; | 
|  | if (!getDeviceBusAddr(deviceName, bus, addr)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::ifstream nameFile(hwmonNamePath); | 
|  | if (!nameFile.good()) | 
|  | { | 
|  | lg2::error("Failure reading '{PATH}'", "PATH", hwmonNamePath); | 
|  | continue; | 
|  | } | 
|  | std::string hwmonName; | 
|  | std::getline(nameFile, hwmonName); | 
|  | nameFile.close(); | 
|  | if (hwmonName.empty()) | 
|  | { | 
|  | // shouldn't have an empty name file | 
|  | continue; | 
|  | } | 
|  | lg2::debug("Checking: '{PATH}': '{NAME}'", "PATH", hwmonNamePath, | 
|  | "NAME", hwmonName); | 
|  |  | 
|  | std::string sensorType; | 
|  | const SensorData* sensorData = nullptr; | 
|  | const std::string* interfacePath = nullptr; | 
|  | const SensorBaseConfiguration* baseConfiguration = nullptr; | 
|  |  | 
|  | for (const auto& [path, cfgData] : sensorConfigs) | 
|  | { | 
|  | sensorData = &cfgData; | 
|  | for (const char* type : sensorTypes) | 
|  | { | 
|  | sensorType = type; | 
|  | auto sensorBase = | 
|  | sensorData->find(configInterfaceName(sensorType)); | 
|  | if (sensorBase != sensorData->end()) | 
|  | { | 
|  | baseConfiguration = &(*sensorBase); | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (baseConfiguration == nullptr) | 
|  | { | 
|  | lg2::error("error finding base configuration for '{NAME}'", | 
|  | "NAME", hwmonName); | 
|  | continue; | 
|  | } | 
|  | auto configurationBus = baseConfiguration->second.find("Bus"); | 
|  | auto configurationAddress = | 
|  | baseConfiguration->second.find("Address"); | 
|  |  | 
|  | if (configurationBus == baseConfiguration->second.end() || | 
|  | configurationAddress == baseConfiguration->second.end()) | 
|  | { | 
|  | lg2::error("error finding bus or address in configuration"); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (std::get<uint64_t>(configurationBus->second) != bus || | 
|  | std::get<uint64_t>(configurationAddress->second) != addr) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | interfacePath = &path.str; | 
|  | break; | 
|  | } | 
|  | if (interfacePath == nullptr) | 
|  | { | 
|  | lg2::error("failed to find match for '{NAME}'", "NAME", hwmonName); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | auto findCpuId = baseConfiguration->second.find("CpuID"); | 
|  | if (findCpuId == baseConfiguration->second.end()) | 
|  | { | 
|  | lg2::error("could not determine CPU ID for '{NAME}'", "NAME", | 
|  | hwmonName); | 
|  | continue; | 
|  | } | 
|  | int cpuId = | 
|  | std::visit(VariantToUnsignedIntVisitor(), findCpuId->second); | 
|  |  | 
|  | auto directory = hwmonNamePath.parent_path(); | 
|  | std::vector<std::filesystem::path> inputPaths; | 
|  | if (!findFiles(directory, R"((temp|power)\d+_(input|average|cap)$)", | 
|  | inputPaths, 0)) | 
|  | { | 
|  | lg2::error("No temperature sensors in system"); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // iterate through all found temp sensors | 
|  | for (const auto& inputPath : inputPaths) | 
|  | { | 
|  | auto fileParts = splitFileName(inputPath); | 
|  | if (!fileParts) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | auto& [type, nr, item] = *fileParts; | 
|  | auto inputPathStr = inputPath.string(); | 
|  | auto labelPath = | 
|  | boost::replace_all_copy(inputPathStr, item, "label"); | 
|  | std::ifstream labelFile(labelPath); | 
|  | if (!labelFile.good()) | 
|  | { | 
|  | lg2::error("Failure reading '{PATH}'", "PATH", labelPath); | 
|  | continue; | 
|  | } | 
|  | std::string label; | 
|  | std::getline(labelFile, label); | 
|  | labelFile.close(); | 
|  |  | 
|  | std::string sensorName = createSensorName(label, item, cpuId); | 
|  |  | 
|  | auto findSensor = gCpuSensors.find(sensorName); | 
|  | if (findSensor != gCpuSensors.end()) | 
|  | { | 
|  | lg2::debug("Skipped: '{PATH}': '{NAME}' is already created", | 
|  | "PATH", inputPath, "NAME", sensorName); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // check hidden properties | 
|  | bool show = true; | 
|  | for (const char* prop : hiddenProps) | 
|  | { | 
|  | if (label == prop) | 
|  | { | 
|  | show = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Find if there is DtsCritOffset is configured in config file | 
|  | * set it if configured or else set it to 0 | 
|  | */ | 
|  | double dtsOffset = 0; | 
|  | if (label == "DTS") | 
|  | { | 
|  | auto findThrOffset = | 
|  | baseConfiguration->second.find("DtsCritOffset"); | 
|  | if (findThrOffset != baseConfiguration->second.end()) | 
|  | { | 
|  | dtsOffset = std::visit(VariantToDoubleVisitor(), | 
|  | findThrOffset->second); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<thresholds::Threshold> sensorThresholds; | 
|  | std::string labelHead = label.substr(0, label.find(' ')); | 
|  | parseThresholdsFromConfig(*sensorData, sensorThresholds, | 
|  | &labelHead); | 
|  | if (sensorThresholds.empty()) | 
|  | { | 
|  | if (!parseThresholdsFromAttr(sensorThresholds, inputPathStr, | 
|  | IntelCPUSensor::sensorScaleFactor, | 
|  | dtsOffset, 0)) | 
|  | { | 
|  | lg2::error("error populating thresholds for '{NAME}'", | 
|  | "NAME", sensorName); | 
|  | } | 
|  | } | 
|  | auto& sensorPtr = gCpuSensors[sensorName]; | 
|  | // make sure destructor fires before creating a new one | 
|  | sensorPtr = nullptr; | 
|  | sensorPtr = std::make_shared<IntelCPUSensor>( | 
|  | inputPathStr, sensorType, objectServer, dbusConnection, io, | 
|  | sensorName, std::move(sensorThresholds), *interfacePath, cpuId, | 
|  | show, dtsOffset); | 
|  | sensorPtr->setupRead(); | 
|  | createdSensors.insert(sensorName); | 
|  | lg2::debug("Mapped: '{PATH}' to '{NAME}'", "PATH", inputPath, | 
|  | "NAME", sensorName); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (static_cast<unsigned int>(!createdSensors.empty()) != 0U) | 
|  | { | 
|  | if (createdSensors.size() == 1) | 
|  | { | 
|  | lg2::info("Sensor is created"); | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::info("Sensors are created"); | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool exportDevice(const CPUConfig& config) | 
|  | { | 
|  | std::ostringstream hex; | 
|  | hex << std::hex << config.addr; | 
|  | const std::string& addrHexStr = hex.str(); | 
|  | std::string busStr = std::to_string(config.bus); | 
|  |  | 
|  | std::string parameters = "peci-client 0x" + addrHexStr; | 
|  | std::string devPath = peciDevPath; | 
|  | std::string delDevice = devPath + "peci-" + busStr + "/delete_device"; | 
|  | std::string newDevice = devPath + "peci-" + busStr + "/new_device"; | 
|  | std::string newClient = devPath + busStr + "-" + addrHexStr + "/driver"; | 
|  |  | 
|  | std::filesystem::path devicePath(newDevice); | 
|  | const std::string& dir = devicePath.parent_path().string(); | 
|  | for (const auto& path : std::filesystem::directory_iterator(dir)) | 
|  | { | 
|  | if (!std::filesystem::is_directory(path)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const std::string& directoryName = path.path().filename(); | 
|  | if (directoryName.starts_with(busStr) && | 
|  | directoryName.ends_with(addrHexStr)) | 
|  | { | 
|  | lg2::debug("'{PARAMETERS}' on bus '{BUS}' is already exported", | 
|  | "PARAMETERS", parameters, "BUS", busStr); | 
|  |  | 
|  | std::ofstream delDeviceFile(delDevice); | 
|  | if (!delDeviceFile.good()) | 
|  | { | 
|  | lg2::error("Error opening '{DEVICE}'", "DEVICE", delDevice); | 
|  | return false; | 
|  | } | 
|  | delDeviceFile << parameters; | 
|  | delDeviceFile.close(); | 
|  |  | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::ofstream deviceFile(newDevice); | 
|  | if (!deviceFile.good()) | 
|  | { | 
|  | lg2::error("Error opening '{DEVICE}'", "DEVICE", newDevice); | 
|  | return false; | 
|  | } | 
|  | deviceFile << parameters; | 
|  | deviceFile.close(); | 
|  |  | 
|  | if (!std::filesystem::exists(newClient)) | 
|  | { | 
|  | lg2::error("Error creating '{CLIENT}'", "CLIENT", newClient); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | lg2::info("'{PARAMETERS}' on bus '{BUS}' is exported", "PARAMETERS", | 
|  | parameters, "BUS", busStr); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void detectCpu(boost::asio::steady_timer& pingTimer, | 
|  | boost::asio::steady_timer& creationTimer, | 
|  | boost::asio::io_context& io, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, | 
|  | boost::container::flat_set<CPUConfig>& cpuConfigs, | 
|  | ManagedObjectType& sensorConfigs) | 
|  | { | 
|  | size_t rescanDelaySeconds = 0; | 
|  | static bool keepPinging = false; | 
|  | int peciFd = -1; | 
|  |  | 
|  | for (CPUConfig& config : cpuConfigs) | 
|  | { | 
|  | if (config.state == State::READY) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::fstream rescan{rescanPath, std::ios::out}; | 
|  | if (rescan.is_open()) | 
|  | { | 
|  | std::vector<std::filesystem::path> peciPaths; | 
|  | std::ostringstream searchPath; | 
|  | searchPath << std::hex << "peci-" << config.bus << "/" << config.bus | 
|  | << "-" << config.addr; | 
|  | findFiles(std::filesystem::path(peciDevPath + searchPath.str()), | 
|  | R"(peci_cpu.dimmtemp.+/hwmon/hwmon\d+/name$)", peciPaths, | 
|  | 3); | 
|  | if (!peciPaths.empty()) | 
|  | { | 
|  | config.state = State::READY; | 
|  | rescanDelaySeconds = 1; | 
|  | } | 
|  | else | 
|  | { | 
|  | findFiles(std::filesystem::path(peciDevPath + searchPath.str()), | 
|  | R"(peci_cpu.cputemp.+/hwmon/hwmon\d+/name$)", | 
|  | peciPaths, 3); | 
|  | if (!peciPaths.empty()) | 
|  | { | 
|  | config.state = State::ON; | 
|  | rescanDelaySeconds = 3; | 
|  | } | 
|  | else | 
|  | { | 
|  | // https://www.kernel.org/doc/html/latest/admin-guide/abi-testing.html#abi-sys-bus-peci-rescan | 
|  | rescan << "1"; | 
|  | } | 
|  | } | 
|  | if (config.state != State::READY) | 
|  | { | 
|  | keepPinging = true; | 
|  | } | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string peciDevPath = peciDev + std::to_string(config.bus); | 
|  |  | 
|  | peci_SetDevName(peciDevPath.data()); | 
|  |  | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) | 
|  | if ((peci_Lock(&peciFd, PECI_NO_WAIT) != PECI_CC_SUCCESS) || | 
|  | (peciFd < 0)) | 
|  | { | 
|  | lg2::error("unable to open '{PATH}', '{ERRNO}'", "PATH", | 
|  | peciDevPath, "ERRNO", std::strerror(errno)); | 
|  | detectCpuAsync(pingTimer, creationTimer, io, objectServer, | 
|  | dbusConnection, cpuConfigs, sensorConfigs); | 
|  | return; | 
|  | } | 
|  |  | 
|  | State newState = State::OFF; | 
|  |  | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) | 
|  | if (peci_Ping(config.addr) == PECI_CC_SUCCESS) | 
|  | { | 
|  | bool dimmReady = false; | 
|  | for (unsigned int rank = 0; rank < rankNumMax; rank++) | 
|  | { | 
|  | std::array<uint8_t, 8> pkgConfig{}; | 
|  | uint8_t cc = 0; | 
|  |  | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) | 
|  | if (peci_RdPkgConfig(config.addr, PECI_MBX_INDEX_DDR_DIMM_TEMP, | 
|  | rank, 4, pkgConfig.data(), &cc) == | 
|  | PECI_CC_SUCCESS) | 
|  | { | 
|  | // Depending on CPU generation, both 0 and 0xFF can be used | 
|  | // to indicate no DIMM presence | 
|  | if (((pkgConfig[0] != 0xFF) && (pkgConfig[0] != 0U)) || | 
|  | ((pkgConfig[1] != 0xFF) && (pkgConfig[1] != 0U))) | 
|  | { | 
|  | dimmReady = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (dimmReady) | 
|  | { | 
|  | newState = State::READY; | 
|  | } | 
|  | else | 
|  | { | 
|  | newState = State::ON; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (config.state != newState) | 
|  | { | 
|  | if (newState != State::OFF) | 
|  | { | 
|  | if (config.state == State::OFF) | 
|  | { | 
|  | std::array<uint8_t, 8> pkgConfig{}; | 
|  | uint8_t cc = 0; | 
|  |  | 
|  | if (peci_RdPkgConfig(config.addr, PECI_MBX_INDEX_CPU_ID, 0, | 
|  | 4, pkgConfig.data(), &cc) == | 
|  | PECI_CC_SUCCESS) | 
|  | { | 
|  | lg2::info("'{NAME}' is detected", "NAME", config.name); | 
|  | if (!exportDevice(config)) | 
|  | { | 
|  | newState = State::OFF; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | newState = State::OFF; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (newState == State::ON) | 
|  | { | 
|  | rescanDelaySeconds = 3; | 
|  | } | 
|  | else if (newState == State::READY) | 
|  | { | 
|  | rescanDelaySeconds = 5; | 
|  | lg2::info("DIMM(s) on '{NAME}' is/are detected", "NAME", | 
|  | config.name); | 
|  | } | 
|  | } | 
|  |  | 
|  | config.state = newState; | 
|  | } | 
|  |  | 
|  | if (config.state != State::READY) | 
|  | { | 
|  | keepPinging = true; | 
|  | } | 
|  |  | 
|  | lg2::debug("'{NAME}', state: '{STATE}'", "NAME", config.name, "STATE", | 
|  | config.state); | 
|  | peci_Unlock(peciFd); | 
|  | } | 
|  |  | 
|  | if (rescanDelaySeconds != 0U) | 
|  | { | 
|  | creationTimer.expires_after(std::chrono::seconds(rescanDelaySeconds)); | 
|  | creationTimer.async_wait([&](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; // we're being canceled | 
|  | } | 
|  |  | 
|  | if (!createSensors(io, objectServer, dbusConnection, cpuConfigs, | 
|  | sensorConfigs) || | 
|  | keepPinging) | 
|  | { | 
|  | detectCpuAsync(pingTimer, creationTimer, io, objectServer, | 
|  | dbusConnection, cpuConfigs, sensorConfigs); | 
|  | } | 
|  | }); | 
|  | } | 
|  | else if (keepPinging) | 
|  | { | 
|  | detectCpuAsync(pingTimer, creationTimer, io, objectServer, | 
|  | dbusConnection, cpuConfigs, sensorConfigs); | 
|  | } | 
|  | } | 
|  |  | 
|  | void detectCpuAsync( | 
|  | boost::asio::steady_timer& pingTimer, | 
|  | boost::asio::steady_timer& creationTimer, boost::asio::io_context& io, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, | 
|  | boost::container::flat_set<CPUConfig>& cpuConfigs, | 
|  | ManagedObjectType& sensorConfigs) | 
|  | { | 
|  | pingTimer.expires_after(std::chrono::seconds(1)); | 
|  | pingTimer.async_wait([&](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; // we're being canceled | 
|  | } | 
|  |  | 
|  | detectCpu(pingTimer, creationTimer, io, objectServer, dbusConnection, | 
|  | cpuConfigs, sensorConfigs); | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool getCpuConfig(const std::shared_ptr<sdbusplus::asio::connection>& systemBus, | 
|  | boost::container::flat_set<CPUConfig>& cpuConfigs, | 
|  | ManagedObjectType& sensorConfigs, | 
|  | sdbusplus::asio::object_server& objectServer) | 
|  | { | 
|  | bool useCache = false; | 
|  | sensorConfigs.clear(); | 
|  | // use new data the first time, then refresh | 
|  | for (const char* type : sensorTypes) | 
|  | { | 
|  | if (!getSensorConfiguration(type, systemBus, sensorConfigs, useCache)) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | useCache = true; | 
|  | } | 
|  |  | 
|  | // check PECI client addresses and names from CPU configuration | 
|  | // before starting ping operation | 
|  | for (const char* type : sensorTypes) | 
|  | { | 
|  | for (const auto& [path, cfgData] : sensorConfigs) | 
|  | { | 
|  | for (const auto& [intf, cfg] : cfgData) | 
|  | { | 
|  | if (intf != configInterfaceName(type)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | auto findName = cfg.find("Name"); | 
|  | if (findName == cfg.end()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | std::string nameRaw = | 
|  | std::visit(VariantToStringVisitor(), findName->second); | 
|  | std::string name = | 
|  | std::regex_replace(nameRaw, illegalDbusRegex, "_"); | 
|  |  | 
|  | auto present = std::optional<bool>(); | 
|  | // if we can't detect it via gpio, we set presence later | 
|  | for (const auto& [suppIntf, suppCfg] : cfgData) | 
|  | { | 
|  | if (suppIntf.find("PresenceGpio") != std::string::npos) | 
|  | { | 
|  | present = cpuIsPresent(suppCfg); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!inventoryIfaces.contains(name) && present) | 
|  | { | 
|  | auto iface = objectServer.add_interface( | 
|  | cpuInventoryPath + std::string("/") + name, | 
|  | "xyz.openbmc_project.Inventory.Item"); | 
|  | iface->register_property("PrettyName", name); | 
|  | iface->register_property("Present", *present); | 
|  | iface->initialize(); | 
|  | inventoryIfaces[name] = std::move(iface); | 
|  | } | 
|  |  | 
|  | auto findBus = cfg.find("Bus"); | 
|  | if (findBus == cfg.end()) | 
|  | { | 
|  | lg2::error("Can't find 'Bus' setting in '{NAME}'", "NAME", | 
|  | name); | 
|  | continue; | 
|  | } | 
|  | uint64_t bus = | 
|  | std::visit(VariantToUnsignedIntVisitor(), findBus->second); | 
|  |  | 
|  | auto findAddress = cfg.find("Address"); | 
|  | if (findAddress == cfg.end()) | 
|  | { | 
|  | lg2::error("Can't find 'Address' setting in '{NAME}'", | 
|  | "NAME", name); | 
|  | continue; | 
|  | } | 
|  | uint64_t addr = std::visit(VariantToUnsignedIntVisitor(), | 
|  | findAddress->second); | 
|  |  | 
|  | lg2::debug( | 
|  | "bus: {BUS}, addr: {ADDR}, name: {NAME}, type: {TYPE}", | 
|  | "BUS", bus, "ADDR", addr, "NAME", name, "TYPE", type); | 
|  |  | 
|  | cpuConfigs.emplace(bus, addr, name, State::OFF); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (static_cast<unsigned int>(!cpuConfigs.empty()) != 0U) | 
|  | { | 
|  | if (cpuConfigs.size() == 1) | 
|  | { | 
|  | lg2::info("CPU config is parsed"); | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::info("CPU configs are parsed"); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int main() | 
|  | { | 
|  | boost::asio::io_context io; | 
|  | auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); | 
|  | boost::container::flat_set<CPUConfig> cpuConfigs; | 
|  |  | 
|  | sdbusplus::asio::object_server objectServer(systemBus, true); | 
|  | objectServer.add_manager("/xyz/openbmc_project/sensors"); | 
|  | boost::asio::steady_timer pingTimer(io); | 
|  | boost::asio::steady_timer creationTimer(io); | 
|  | boost::asio::steady_timer filterTimer(io); | 
|  | ManagedObjectType sensorConfigs; | 
|  |  | 
|  | filterTimer.expires_after(std::chrono::seconds(1)); | 
|  | filterTimer.async_wait([&](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; // we're being canceled | 
|  | } | 
|  |  | 
|  | if (getCpuConfig(systemBus, cpuConfigs, sensorConfigs, objectServer)) | 
|  | { | 
|  | detectCpuAsync(pingTimer, creationTimer, io, objectServer, | 
|  | systemBus, cpuConfigs, sensorConfigs); | 
|  | } | 
|  | }); | 
|  |  | 
|  | std::function<void(sdbusplus::message_t&)> eventHandler = | 
|  | [&](sdbusplus::message_t& message) { | 
|  | if (message.is_method_error()) | 
|  | { | 
|  | lg2::error("callback method error"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | lg2::debug("'{PATH}' is changed", "PATH", message.get_path()); | 
|  |  | 
|  | // this implicitly cancels the timer | 
|  | filterTimer.expires_after(std::chrono::seconds(1)); | 
|  | filterTimer.async_wait([&](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; // we're being canceled | 
|  | } | 
|  |  | 
|  | if (getCpuConfig(systemBus, cpuConfigs, sensorConfigs, | 
|  | objectServer)) | 
|  | { | 
|  | detectCpuAsync(pingTimer, creationTimer, io, objectServer, | 
|  | systemBus, cpuConfigs, sensorConfigs); | 
|  | } | 
|  | }); | 
|  | }; | 
|  |  | 
|  | std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = | 
|  | setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler); | 
|  |  | 
|  | systemBus->request_name("xyz.openbmc_project.IntelCPUSensor"); | 
|  |  | 
|  | setupManufacturingModeMatch(*systemBus); | 
|  | io.run(); | 
|  | return 0; | 
|  | } |