xref: /openbmc/dbus-sensors/src/intel-cpu/IntelCPUSensorMain.cpp (revision 4ab892a4ba909f598f81c4947822be9502b19c5b)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "IntelCPUSensor.hpp"
18 #include "Thresholds.hpp"
19 #include "Utils.hpp"
20 #include "VariantVisitors.hpp"
21 
22 #include <peci.h>
23 
24 #include <boost/algorithm/string/replace.hpp>
25 #include <boost/asio/error.hpp>
26 #include <boost/asio/io_context.hpp>
27 #include <boost/asio/steady_timer.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <boost/container/flat_set.hpp>
30 #include <phosphor-logging/lg2.hpp>
31 #include <sdbusplus/asio/connection.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33 #include <sdbusplus/bus/match.hpp>
34 #include <sdbusplus/message.hpp>
35 
36 #include <algorithm>
37 #include <array>
38 #include <cctype>
39 #include <cerrno>
40 #include <chrono>
41 #include <cstddef>
42 #include <cstdint>
43 #include <cstring>
44 #include <filesystem>
45 #include <fstream>
46 #include <functional>
47 #include <ios>
48 #include <iterator>
49 #include <memory>
50 #include <optional>
51 #include <regex>
52 #include <sstream>
53 #include <string>
54 #include <utility>
55 #include <variant>
56 #include <vector>
57 
58 // clang-format off
59 // this needs to be included last or we'll have build issues
60 #include <linux/peci-ioctl.h>
61 #if !defined(PECI_MBX_INDEX_DDR_DIMM_TEMP)
62 #define PECI_MBX_INDEX_DDR_DIMM_TEMP MBX_INDEX_DDR_DIMM_TEMP
63 #endif
64 // clang-format on
65 
66 static constexpr bool debug = false;
67 
68 boost::container::flat_map<std::string, std::shared_ptr<IntelCPUSensor>>
69     gCpuSensors;
70 boost::container::flat_map<std::string,
71                            std::shared_ptr<sdbusplus::asio::dbus_interface>>
72     inventoryIfaces;
73 
74 enum State
75 {
76     OFF,  // host powered down
77     ON,   // host powered on
78     READY // host powered on and mem test passed - fully ready
79 };
80 
81 struct CPUConfig
82 {
CPUConfigCPUConfig83     CPUConfig(const uint64_t& bus, const uint64_t& addr,
84               const std::string& name, const State& state) :
85         bus(bus), addr(addr), name(name), state(state)
86     {}
87     int bus;
88     int addr;
89     std::string name;
90     State state;
91 
operator <CPUConfig92     bool operator<(const CPUConfig& rhs) const
93     {
94         // NOLINTNEXTLINE
95         return (name < rhs.name);
96     }
97 };
98 
99 static constexpr const char* peciDev = "/dev/peci-";
100 static constexpr const char* peciDevPath = "/sys/bus/peci/devices/";
101 static constexpr const char* rescanPath = "/sys/bus/peci/rescan";
102 static constexpr const unsigned int rankNumMax = 8;
103 
104 static constexpr auto sensorTypes{std::to_array<const char*>({"XeonCPU"})};
105 static constexpr auto hiddenProps{std::to_array<const char*>(
106     {IntelCPUSensor::labelTcontrol, "Tthrottle", "Tjmax"})};
107 
108 void detectCpuAsync(
109     boost::asio::steady_timer& pingTimer,
110     boost::asio::steady_timer& creationTimer, boost::asio::io_context& io,
111     sdbusplus::asio::object_server& objectServer,
112     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
113     boost::container::flat_set<CPUConfig>& cpuConfigs,
114     ManagedObjectType& sensorConfigs);
115 
createSensorName(const std::string & label,const std::string & item,const int & cpuId)116 std::string createSensorName(const std::string& label, const std::string& item,
117                              const int& cpuId)
118 {
119     std::string sensorName = label;
120     if (item != "input")
121     {
122         sensorName += " " + item;
123     }
124 
125     std::string cpuStr = "CPU" + std::to_string(cpuId);
126     constexpr const char* subLabel = "DIMM";
127     std::size_t found = label.find(subLabel);
128     if (found != std::string::npos)
129     {
130         sensorName = cpuStr + " " + sensorName;
131     }
132     else
133     {
134         sensorName += " " + cpuStr;
135     }
136     // converting to Upper Camel case whole name
137     bool isWordEnd = true;
138     std::transform(sensorName.begin(), sensorName.end(), sensorName.begin(),
139                    [&isWordEnd](int c) {
140                        if (std::isspace(c) != 0)
141                        {
142                            isWordEnd = true;
143                        }
144                        else
145                        {
146                            if (isWordEnd)
147                            {
148                                isWordEnd = false;
149                                return std::toupper(c);
150                            }
151                        }
152                        return c;
153                    });
154     return sensorName;
155 }
156 
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)157 bool createSensors(boost::asio::io_context& io,
158                    sdbusplus::asio::object_server& objectServer,
159                    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
160                    boost::container::flat_set<CPUConfig>& cpuConfigs,
161                    ManagedObjectType& sensorConfigs)
162 {
163     bool available = false;
164     for (const CPUConfig& cpu : cpuConfigs)
165     {
166         if (cpu.state != State::OFF)
167         {
168             available = true;
169             std::shared_ptr<sdbusplus::asio::dbus_interface>& iface =
170                 inventoryIfaces[cpu.name];
171             if (iface != nullptr)
172             {
173                 continue;
174             }
175             iface = objectServer.add_interface(
176                 cpuInventoryPath + std::string("/") + cpu.name,
177                 "xyz.openbmc_project.Inventory.Item");
178             iface->register_property("PrettyName", cpu.name);
179             iface->register_property("Present", true);
180             iface->initialize();
181         }
182     }
183     if (!available)
184     {
185         return false;
186     }
187 
188     if (sensorConfigs.empty())
189     {
190         return false;
191     }
192 
193     std::vector<std::filesystem::path> hwmonNamePaths;
194     findFiles(std::filesystem::path(peciDevPath),
195               R"(peci-\d+/\d+-.+/peci[-_].+/hwmon/hwmon\d+/name$)",
196               hwmonNamePaths, 6);
197     if (hwmonNamePaths.empty())
198     {
199         lg2::error("No CPU sensors in system");
200         return false;
201     }
202 
203     boost::container::flat_set<std::string> scannedDirectories;
204     boost::container::flat_set<std::string> createdSensors;
205 
206     for (const std::filesystem::path& hwmonNamePath : hwmonNamePaths)
207     {
208         auto hwmonDirectory = hwmonNamePath.parent_path();
209 
210         auto ret = scannedDirectories.insert(hwmonDirectory.string());
211         if (!ret.second)
212         {
213             continue; // already searched this path
214         }
215 
216         std::filesystem::path::iterator it = hwmonNamePath.begin();
217         std::advance(it, 6); // pick the 6th part for a PECI client device name
218         std::string deviceName = *it;
219 
220         size_t bus = 0;
221         size_t addr = 0;
222         if (!getDeviceBusAddr(deviceName, bus, addr))
223         {
224             continue;
225         }
226 
227         std::ifstream nameFile(hwmonNamePath);
228         if (!nameFile.good())
229         {
230             lg2::error("Failure reading '{PATH}'", "PATH", hwmonNamePath);
231             continue;
232         }
233         std::string hwmonName;
234         std::getline(nameFile, hwmonName);
235         nameFile.close();
236         if (hwmonName.empty())
237         {
238             // shouldn't have an empty name file
239             continue;
240         }
241         if (debug)
242         {
243             lg2::info("Checking: '{PATH}': '{NAME}'", "PATH", hwmonNamePath,
244                       "NAME", hwmonName);
245         }
246 
247         std::string sensorType;
248         const SensorData* sensorData = nullptr;
249         const std::string* interfacePath = nullptr;
250         const SensorBaseConfiguration* baseConfiguration = nullptr;
251 
252         for (const auto& [path, cfgData] : sensorConfigs)
253         {
254             sensorData = &cfgData;
255             for (const char* type : sensorTypes)
256             {
257                 sensorType = type;
258                 auto sensorBase =
259                     sensorData->find(configInterfaceName(sensorType));
260                 if (sensorBase != sensorData->end())
261                 {
262                     baseConfiguration = &(*sensorBase);
263                     break;
264                 }
265             }
266             if (baseConfiguration == nullptr)
267             {
268                 lg2::error("error finding base configuration for '{NAME}'",
269                            "NAME", hwmonName);
270                 continue;
271             }
272             auto configurationBus = baseConfiguration->second.find("Bus");
273             auto configurationAddress =
274                 baseConfiguration->second.find("Address");
275 
276             if (configurationBus == baseConfiguration->second.end() ||
277                 configurationAddress == baseConfiguration->second.end())
278             {
279                 lg2::error("error finding bus or address in configuration");
280                 continue;
281             }
282 
283             if (std::get<uint64_t>(configurationBus->second) != bus ||
284                 std::get<uint64_t>(configurationAddress->second) != addr)
285             {
286                 continue;
287             }
288 
289             interfacePath = &path.str;
290             break;
291         }
292         if (interfacePath == nullptr)
293         {
294             lg2::error("failed to find match for '{NAME}'", "NAME", hwmonName);
295             continue;
296         }
297 
298         auto findCpuId = baseConfiguration->second.find("CpuID");
299         if (findCpuId == baseConfiguration->second.end())
300         {
301             lg2::error("could not determine CPU ID for '{NAME}'", "NAME",
302                        hwmonName);
303             continue;
304         }
305         int cpuId =
306             std::visit(VariantToUnsignedIntVisitor(), findCpuId->second);
307 
308         auto directory = hwmonNamePath.parent_path();
309         std::vector<std::filesystem::path> inputPaths;
310         if (!findFiles(directory, R"((temp|power)\d+_(input|average|cap)$)",
311                        inputPaths, 0))
312         {
313             lg2::error("No temperature sensors in system");
314             continue;
315         }
316 
317         // iterate through all found temp sensors
318         for (const auto& inputPath : inputPaths)
319         {
320             auto fileParts = splitFileName(inputPath);
321             if (!fileParts)
322             {
323                 continue;
324             }
325             auto& [type, nr, item] = *fileParts;
326             auto inputPathStr = inputPath.string();
327             auto labelPath =
328                 boost::replace_all_copy(inputPathStr, item, "label");
329             std::ifstream labelFile(labelPath);
330             if (!labelFile.good())
331             {
332                 lg2::error("Failure reading '{PATH}'", "PATH", labelPath);
333                 continue;
334             }
335             std::string label;
336             std::getline(labelFile, label);
337             labelFile.close();
338 
339             std::string sensorName = createSensorName(label, item, cpuId);
340 
341             auto findSensor = gCpuSensors.find(sensorName);
342             if (findSensor != gCpuSensors.end())
343             {
344                 if (debug)
345                 {
346                     lg2::info("Skipped: '{PATH}': '{NAME}' is already created",
347                               "PATH", inputPath, "NAME", sensorName);
348                 }
349                 continue;
350             }
351 
352             // check hidden properties
353             bool show = true;
354             for (const char* prop : hiddenProps)
355             {
356                 if (label == prop)
357                 {
358                     show = false;
359                     break;
360                 }
361             }
362 
363             /*
364              * Find if there is DtsCritOffset is configured in config file
365              * set it if configured or else set it to 0
366              */
367             double dtsOffset = 0;
368             if (label == "DTS")
369             {
370                 auto findThrOffset =
371                     baseConfiguration->second.find("DtsCritOffset");
372                 if (findThrOffset != baseConfiguration->second.end())
373                 {
374                     dtsOffset = std::visit(VariantToDoubleVisitor(),
375                                            findThrOffset->second);
376                 }
377             }
378 
379             std::vector<thresholds::Threshold> sensorThresholds;
380             std::string labelHead = label.substr(0, label.find(' '));
381             parseThresholdsFromConfig(*sensorData, sensorThresholds,
382                                       &labelHead);
383             if (sensorThresholds.empty())
384             {
385                 if (!parseThresholdsFromAttr(sensorThresholds, inputPathStr,
386                                              IntelCPUSensor::sensorScaleFactor,
387                                              dtsOffset, 0))
388                 {
389                     lg2::error("error populating thresholds for '{NAME}'",
390                                "NAME", sensorName);
391                 }
392             }
393             auto& sensorPtr = gCpuSensors[sensorName];
394             // make sure destructor fires before creating a new one
395             sensorPtr = nullptr;
396             sensorPtr = std::make_shared<IntelCPUSensor>(
397                 inputPathStr, sensorType, objectServer, dbusConnection, io,
398                 sensorName, std::move(sensorThresholds), *interfacePath, cpuId,
399                 show, dtsOffset);
400             sensorPtr->setupRead();
401             createdSensors.insert(sensorName);
402             if (debug)
403             {
404                 lg2::info("Mapped: '{PATH}' to '{NAME}'", "PATH", inputPath,
405                           "NAME", sensorName);
406             }
407         }
408     }
409 
410     if (static_cast<unsigned int>(!createdSensors.empty()) != 0U)
411     {
412         if (createdSensors.size() == 1)
413         {
414             lg2::info("Sensor is created");
415         }
416         else
417         {
418             lg2::info("Sensors are created");
419         }
420     }
421 
422     return true;
423 }
424 
exportDevice(const CPUConfig & config)425 bool exportDevice(const CPUConfig& config)
426 {
427     std::ostringstream hex;
428     hex << std::hex << config.addr;
429     const std::string& addrHexStr = hex.str();
430     std::string busStr = std::to_string(config.bus);
431 
432     std::string parameters = "peci-client 0x" + addrHexStr;
433     std::string devPath = peciDevPath;
434     std::string delDevice = devPath + "peci-" + busStr + "/delete_device";
435     std::string newDevice = devPath + "peci-" + busStr + "/new_device";
436     std::string newClient = devPath + busStr + "-" + addrHexStr + "/driver";
437 
438     std::filesystem::path devicePath(newDevice);
439     const std::string& dir = devicePath.parent_path().string();
440     for (const auto& path : std::filesystem::directory_iterator(dir))
441     {
442         if (!std::filesystem::is_directory(path))
443         {
444             continue;
445         }
446 
447         const std::string& directoryName = path.path().filename();
448         if (directoryName.starts_with(busStr) &&
449             directoryName.ends_with(addrHexStr))
450         {
451             if (debug)
452             {
453                 lg2::info("'{PARAMETERS}' on bus '{BUS}' is already exported",
454                           "PARAMETERS", parameters, "BUS", busStr);
455             }
456 
457             std::ofstream delDeviceFile(delDevice);
458             if (!delDeviceFile.good())
459             {
460                 lg2::error("Error opening '{DEVICE}'", "DEVICE", delDevice);
461                 return false;
462             }
463             delDeviceFile << parameters;
464             delDeviceFile.close();
465 
466             break;
467         }
468     }
469 
470     std::ofstream deviceFile(newDevice);
471     if (!deviceFile.good())
472     {
473         lg2::error("Error opening '{DEVICE}'", "DEVICE", newDevice);
474         return false;
475     }
476     deviceFile << parameters;
477     deviceFile.close();
478 
479     if (!std::filesystem::exists(newClient))
480     {
481         lg2::error("Error creating '{CLIENT}'", "CLIENT", newClient);
482         return false;
483     }
484 
485     lg2::info("'{PARAMETERS}' on bus '{BUS}' is exported", "PARAMETERS",
486               parameters, "BUS", busStr);
487 
488     return true;
489 }
490 
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)491 void detectCpu(boost::asio::steady_timer& pingTimer,
492                boost::asio::steady_timer& creationTimer,
493                boost::asio::io_context& io,
494                sdbusplus::asio::object_server& objectServer,
495                std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
496                boost::container::flat_set<CPUConfig>& cpuConfigs,
497                ManagedObjectType& sensorConfigs)
498 {
499     size_t rescanDelaySeconds = 0;
500     static bool keepPinging = false;
501     int peciFd = -1;
502 
503     for (CPUConfig& config : cpuConfigs)
504     {
505         if (config.state == State::READY)
506         {
507             continue;
508         }
509 
510         std::fstream rescan{rescanPath, std::ios::out};
511         if (rescan.is_open())
512         {
513             std::vector<std::filesystem::path> peciPaths;
514             std::ostringstream searchPath;
515             searchPath << std::hex << "peci-" << config.bus << "/" << config.bus
516                        << "-" << config.addr;
517             findFiles(std::filesystem::path(peciDevPath + searchPath.str()),
518                       R"(peci_cpu.dimmtemp.+/hwmon/hwmon\d+/name$)", peciPaths,
519                       3);
520             if (!peciPaths.empty())
521             {
522                 config.state = State::READY;
523                 rescanDelaySeconds = 1;
524             }
525             else
526             {
527                 findFiles(std::filesystem::path(peciDevPath + searchPath.str()),
528                           R"(peci_cpu.cputemp.+/hwmon/hwmon\d+/name$)",
529                           peciPaths, 3);
530                 if (!peciPaths.empty())
531                 {
532                     config.state = State::ON;
533                     rescanDelaySeconds = 3;
534                 }
535                 else
536                 {
537                     // https://www.kernel.org/doc/html/latest/admin-guide/abi-testing.html#abi-sys-bus-peci-rescan
538                     rescan << "1";
539                 }
540             }
541             if (config.state != State::READY)
542             {
543                 keepPinging = true;
544             }
545 
546             continue;
547         }
548 
549         std::string peciDevPath = peciDev + std::to_string(config.bus);
550 
551         peci_SetDevName(peciDevPath.data());
552 
553         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
554         if ((peci_Lock(&peciFd, PECI_NO_WAIT) != PECI_CC_SUCCESS) ||
555             (peciFd < 0))
556         {
557             lg2::error("unable to open '{PATH}', '{ERRNO}'", "PATH",
558                        peciDevPath, "ERRNO", std::strerror(errno));
559             detectCpuAsync(pingTimer, creationTimer, io, objectServer,
560                            dbusConnection, cpuConfigs, sensorConfigs);
561             return;
562         }
563 
564         State newState = State::OFF;
565 
566         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
567         if (peci_Ping(config.addr) == PECI_CC_SUCCESS)
568         {
569             bool dimmReady = false;
570             for (unsigned int rank = 0; rank < rankNumMax; rank++)
571             {
572                 std::array<uint8_t, 8> pkgConfig{};
573                 uint8_t cc = 0;
574 
575                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
576                 if (peci_RdPkgConfig(config.addr, PECI_MBX_INDEX_DDR_DIMM_TEMP,
577                                      rank, 4, pkgConfig.data(), &cc) ==
578                     PECI_CC_SUCCESS)
579                 {
580                     // Depending on CPU generation, both 0 and 0xFF can be used
581                     // to indicate no DIMM presence
582                     if (((pkgConfig[0] != 0xFF) && (pkgConfig[0] != 0U)) ||
583                         ((pkgConfig[1] != 0xFF) && (pkgConfig[1] != 0U)))
584                     {
585                         dimmReady = true;
586                         break;
587                     }
588                 }
589                 else
590                 {
591                     break;
592                 }
593             }
594 
595             if (dimmReady)
596             {
597                 newState = State::READY;
598             }
599             else
600             {
601                 newState = State::ON;
602             }
603         }
604 
605         if (config.state != newState)
606         {
607             if (newState != State::OFF)
608             {
609                 if (config.state == State::OFF)
610                 {
611                     std::array<uint8_t, 8> pkgConfig{};
612                     uint8_t cc = 0;
613 
614                     if (peci_RdPkgConfig(config.addr, PECI_MBX_INDEX_CPU_ID, 0,
615                                          4, pkgConfig.data(), &cc) ==
616                         PECI_CC_SUCCESS)
617                     {
618                         lg2::info("'{NAME}' is detected", "NAME", config.name);
619                         if (!exportDevice(config))
620                         {
621                             newState = State::OFF;
622                         }
623                     }
624                     else
625                     {
626                         newState = State::OFF;
627                     }
628                 }
629 
630                 if (newState == State::ON)
631                 {
632                     rescanDelaySeconds = 3;
633                 }
634                 else if (newState == State::READY)
635                 {
636                     rescanDelaySeconds = 5;
637                     lg2::info("DIMM(s) on '{NAME}' is/are detected", "NAME",
638                               config.name);
639                 }
640             }
641 
642             config.state = newState;
643         }
644 
645         if (config.state != State::READY)
646         {
647             keepPinging = true;
648         }
649 
650         if (debug)
651         {
652             lg2::info("'{NAME}', state: '{STATE}'", "NAME", config.name,
653                       "STATE", config.state);
654         }
655         peci_Unlock(peciFd);
656     }
657 
658     if (rescanDelaySeconds != 0U)
659     {
660         creationTimer.expires_after(std::chrono::seconds(rescanDelaySeconds));
661         creationTimer.async_wait([&](const boost::system::error_code& ec) {
662             if (ec == boost::asio::error::operation_aborted)
663             {
664                 return; // we're being canceled
665             }
666 
667             if (!createSensors(io, objectServer, dbusConnection, cpuConfigs,
668                                sensorConfigs) ||
669                 keepPinging)
670             {
671                 detectCpuAsync(pingTimer, creationTimer, io, objectServer,
672                                dbusConnection, cpuConfigs, sensorConfigs);
673             }
674         });
675     }
676     else if (keepPinging)
677     {
678         detectCpuAsync(pingTimer, creationTimer, io, objectServer,
679                        dbusConnection, cpuConfigs, sensorConfigs);
680     }
681 }
682 
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)683 void detectCpuAsync(
684     boost::asio::steady_timer& pingTimer,
685     boost::asio::steady_timer& creationTimer, boost::asio::io_context& io,
686     sdbusplus::asio::object_server& objectServer,
687     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
688     boost::container::flat_set<CPUConfig>& cpuConfigs,
689     ManagedObjectType& sensorConfigs)
690 {
691     pingTimer.expires_after(std::chrono::seconds(1));
692     pingTimer.async_wait([&](const boost::system::error_code& ec) {
693         if (ec == boost::asio::error::operation_aborted)
694         {
695             return; // we're being canceled
696         }
697 
698         detectCpu(pingTimer, creationTimer, io, objectServer, dbusConnection,
699                   cpuConfigs, sensorConfigs);
700     });
701 }
702 
getCpuConfig(const std::shared_ptr<sdbusplus::asio::connection> & systemBus,boost::container::flat_set<CPUConfig> & cpuConfigs,ManagedObjectType & sensorConfigs,sdbusplus::asio::object_server & objectServer)703 bool getCpuConfig(const std::shared_ptr<sdbusplus::asio::connection>& systemBus,
704                   boost::container::flat_set<CPUConfig>& cpuConfigs,
705                   ManagedObjectType& sensorConfigs,
706                   sdbusplus::asio::object_server& objectServer)
707 {
708     bool useCache = false;
709     sensorConfigs.clear();
710     // use new data the first time, then refresh
711     for (const char* type : sensorTypes)
712     {
713         if (!getSensorConfiguration(type, systemBus, sensorConfigs, useCache))
714         {
715             return false;
716         }
717         useCache = true;
718     }
719 
720     // check PECI client addresses and names from CPU configuration
721     // before starting ping operation
722     for (const char* type : sensorTypes)
723     {
724         for (const auto& [path, cfgData] : sensorConfigs)
725         {
726             for (const auto& [intf, cfg] : cfgData)
727             {
728                 if (intf != configInterfaceName(type))
729                 {
730                     continue;
731                 }
732 
733                 auto findName = cfg.find("Name");
734                 if (findName == cfg.end())
735                 {
736                     continue;
737                 }
738                 std::string nameRaw =
739                     std::visit(VariantToStringVisitor(), findName->second);
740                 std::string name =
741                     std::regex_replace(nameRaw, illegalDbusRegex, "_");
742 
743                 auto present = std::optional<bool>();
744                 // if we can't detect it via gpio, we set presence later
745                 for (const auto& [suppIntf, suppCfg] : cfgData)
746                 {
747                     if (suppIntf.find("PresenceGpio") != std::string::npos)
748                     {
749                         present = cpuIsPresent(suppCfg);
750                         break;
751                     }
752                 }
753 
754                 if (inventoryIfaces.find(name) == inventoryIfaces.end() &&
755                     present)
756                 {
757                     auto iface = objectServer.add_interface(
758                         cpuInventoryPath + std::string("/") + name,
759                         "xyz.openbmc_project.Inventory.Item");
760                     iface->register_property("PrettyName", name);
761                     iface->register_property("Present", *present);
762                     iface->initialize();
763                     inventoryIfaces[name] = std::move(iface);
764                 }
765 
766                 auto findBus = cfg.find("Bus");
767                 if (findBus == cfg.end())
768                 {
769                     lg2::error("Can't find 'Bus' setting in '{NAME}'", "NAME",
770                                name);
771                     continue;
772                 }
773                 uint64_t bus =
774                     std::visit(VariantToUnsignedIntVisitor(), findBus->second);
775 
776                 auto findAddress = cfg.find("Address");
777                 if (findAddress == cfg.end())
778                 {
779                     lg2::error("Can't find 'Address' setting in '{NAME}'",
780                                "NAME", name);
781                     continue;
782                 }
783                 uint64_t addr = std::visit(VariantToUnsignedIntVisitor(),
784                                            findAddress->second);
785 
786                 if (debug)
787                 {
788                     lg2::info(
789                         "bus: {BUS}, addr: {ADDR}, name: {NAME}, type: {TYPE}",
790                         "BUS", bus, "ADDR", addr, "NAME", name, "TYPE", type);
791                 }
792 
793                 cpuConfigs.emplace(bus, addr, name, State::OFF);
794             }
795         }
796     }
797 
798     if (static_cast<unsigned int>(!cpuConfigs.empty()) != 0U)
799     {
800         if (cpuConfigs.size() == 1)
801         {
802             lg2::info("CPU config is parsed");
803         }
804         else
805         {
806             lg2::info("CPU configs are parsed");
807         }
808         return true;
809     }
810 
811     return false;
812 }
813 
main()814 int main()
815 {
816     boost::asio::io_context io;
817     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
818     boost::container::flat_set<CPUConfig> cpuConfigs;
819 
820     sdbusplus::asio::object_server objectServer(systemBus, true);
821     objectServer.add_manager("/xyz/openbmc_project/sensors");
822     boost::asio::steady_timer pingTimer(io);
823     boost::asio::steady_timer creationTimer(io);
824     boost::asio::steady_timer filterTimer(io);
825     ManagedObjectType sensorConfigs;
826 
827     filterTimer.expires_after(std::chrono::seconds(1));
828     filterTimer.async_wait([&](const boost::system::error_code& ec) {
829         if (ec == boost::asio::error::operation_aborted)
830         {
831             return; // we're being canceled
832         }
833 
834         if (getCpuConfig(systemBus, cpuConfigs, sensorConfigs, objectServer))
835         {
836             detectCpuAsync(pingTimer, creationTimer, io, objectServer,
837                            systemBus, cpuConfigs, sensorConfigs);
838         }
839     });
840 
841     std::function<void(sdbusplus::message_t&)> eventHandler =
842         [&](sdbusplus::message_t& message) {
843             if (message.is_method_error())
844             {
845                 lg2::error("callback method error");
846                 return;
847             }
848 
849             if (debug)
850             {
851                 lg2::info("'{PATH}' is changed", "PATH", message.get_path());
852             }
853 
854             // this implicitly cancels the timer
855             filterTimer.expires_after(std::chrono::seconds(1));
856             filterTimer.async_wait([&](const boost::system::error_code& ec) {
857                 if (ec == boost::asio::error::operation_aborted)
858                 {
859                     return; // we're being canceled
860                 }
861 
862                 if (getCpuConfig(systemBus, cpuConfigs, sensorConfigs,
863                                  objectServer))
864                 {
865                     detectCpuAsync(pingTimer, creationTimer, io, objectServer,
866                                    systemBus, cpuConfigs, sensorConfigs);
867                 }
868             });
869         };
870 
871     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
872         setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
873 
874     systemBus->request_name("xyz.openbmc_project.IntelCPUSensor");
875 
876     setupManufacturingModeMatch(*systemBus);
877     io.run();
878     return 0;
879 }
880