1 /*
2 // Copyright (c) 2019 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 "DeviceMgmt.hpp"
18 #include "PSUEvent.hpp"
19 #include "PSUSensor.hpp"
20 #include "PwmSensor.hpp"
21 #include "SensorPaths.hpp"
22 #include "Thresholds.hpp"
23 #include "Utils.hpp"
24 #include "VariantVisitors.hpp"
25 
26 #include <boost/algorithm/string/case_conv.hpp>
27 #include <boost/algorithm/string/replace.hpp>
28 #include <boost/asio/error.hpp>
29 #include <boost/asio/io_context.hpp>
30 #include <boost/asio/post.hpp>
31 #include <boost/asio/steady_timer.hpp>
32 #include <boost/container/flat_map.hpp>
33 #include <boost/container/flat_set.hpp>
34 #include <sdbusplus/asio/connection.hpp>
35 #include <sdbusplus/asio/object_server.hpp>
36 #include <sdbusplus/bus.hpp>
37 #include <sdbusplus/bus/match.hpp>
38 #include <sdbusplus/exception.hpp>
39 #include <sdbusplus/message.hpp>
40 #include <sdbusplus/message/native_types.hpp>
41 
42 #include <algorithm>
43 #include <array>
44 #include <cctype>
45 #include <chrono>
46 #include <cmath>
47 #include <cstddef>
48 #include <cstdint>
49 #include <exception>
50 #include <filesystem>
51 #include <fstream>
52 #include <functional>
53 #include <iostream>
54 #include <memory>
55 #include <regex>
56 #include <stdexcept>
57 #include <string>
58 #include <string_view>
59 #include <utility>
60 #include <variant>
61 #include <vector>
62 
63 static constexpr bool debug = false;
64 static std::regex i2cDevRegex(R"((\/i2c\-\d+\/\d+-[a-fA-F0-9]{4,4})(\/|$))");
65 
66 static const I2CDeviceTypeMap sensorTypes{
67     {"ADC128D818", I2CDeviceType{"adc128d818", true}},
68     {"ADM1266", I2CDeviceType{"adm1266", true}},
69     {"ADM1272", I2CDeviceType{"adm1272", true}},
70     {"ADM1275", I2CDeviceType{"adm1275", true}},
71     {"ADM1278", I2CDeviceType{"adm1278", true}},
72     {"ADM1293", I2CDeviceType{"adm1293", true}},
73     {"ADS1015", I2CDeviceType{"ads1015", true}},
74     {"ADS7830", I2CDeviceType{"ads7830", true}},
75     {"AHE50DC_FAN", I2CDeviceType{"ahe50dc_fan", true}},
76     {"BMR490", I2CDeviceType{"bmr490", true}},
77     {"cffps", I2CDeviceType{"cffps", true}},
78     {"cffps1", I2CDeviceType{"cffps", true}},
79     {"cffps2", I2CDeviceType{"cffps", true}},
80     {"cffps3", I2CDeviceType{"cffps", true}},
81     {"DPS800", I2CDeviceType{"dps800", true}},
82     {"INA219", I2CDeviceType{"ina219", true}},
83     {"INA230", I2CDeviceType{"ina230", true}},
84     {"INA238", I2CDeviceType{"ina238", true}},
85     {"IPSPS1", I2CDeviceType{"ipsps1", true}},
86     {"IR38060", I2CDeviceType{"ir38060", true}},
87     {"IR38164", I2CDeviceType{"ir38164", true}},
88     {"IR38263", I2CDeviceType{"ir38263", true}},
89     {"ISL28022", I2CDeviceType{"isl28022", true}},
90     {"ISL68137", I2CDeviceType{"isl68137", true}},
91     {"ISL68220", I2CDeviceType{"isl68220", true}},
92     {"ISL68223", I2CDeviceType{"isl68223", true}},
93     {"ISL69225", I2CDeviceType{"isl69225", true}},
94     {"ISL69243", I2CDeviceType{"isl69243", true}},
95     {"ISL69260", I2CDeviceType{"isl69260", true}},
96     {"LM25066", I2CDeviceType{"lm25066", true}},
97     {"LTC2945", I2CDeviceType{"ltc2945", true}},
98     {"LTC4286", I2CDeviceType{"ltc4286", true}},
99     {"LTC4287", I2CDeviceType{"ltc4287", true}},
100     {"MAX5970", I2CDeviceType{"max5970", true}},
101     {"MAX11607", I2CDeviceType{"max11607", false}},
102     {"MAX11615", I2CDeviceType{"max11615", false}},
103     {"MAX11617", I2CDeviceType{"max11617", false}},
104     {"MAX16601", I2CDeviceType{"max16601", true}},
105     {"MAX20710", I2CDeviceType{"max20710", true}},
106     {"MAX20730", I2CDeviceType{"max20730", true}},
107     {"MAX20734", I2CDeviceType{"max20734", true}},
108     {"MAX20796", I2CDeviceType{"max20796", true}},
109     {"MAX34451", I2CDeviceType{"max34451", true}},
110     {"MP2856", I2CDeviceType{"mp2856", true}},
111     {"MP2857", I2CDeviceType{"mp2857", true}},
112     {"MP2971", I2CDeviceType{"mp2971", true}},
113     {"MP2973", I2CDeviceType{"mp2973", true}},
114     {"MP2975", I2CDeviceType{"mp2975", true}},
115     {"MP5023", I2CDeviceType{"mp5023", true}},
116     {"MP5990", I2CDeviceType{"mp5990", true}},
117     {"MPQ8785", I2CDeviceType{"mpq8785", true}},
118     {"NCP4200", I2CDeviceType{"ncp4200", true}},
119     {"PLI1209BC", I2CDeviceType{"pli1209bc", true}},
120     {"pmbus", I2CDeviceType{"pmbus", true}},
121     {"PXE1610", I2CDeviceType{"pxe1610", true}},
122     {"RAA228000", I2CDeviceType{"raa228000", true}},
123     {"RAA228004", I2CDeviceType{"raa228004", true}},
124     {"RAA228228", I2CDeviceType{"raa228228", true}},
125     {"RAA228620", I2CDeviceType{"raa228620", true}},
126     {"RAA229001", I2CDeviceType{"raa229001", true}},
127     {"RAA229004", I2CDeviceType{"raa229004", true}},
128     {"RAA229126", I2CDeviceType{"raa229126", true}},
129     {"RTQ6056", I2CDeviceType{"rtq6056", false}},
130     {"SBRMI", I2CDeviceType{"sbrmi", true}},
131     {"smpro_hwmon", I2CDeviceType{"smpro", false}},
132     {"TDA38640", I2CDeviceType{"tda38640", true}},
133     {"TPS53679", I2CDeviceType{"tps53679", true}},
134     {"TPS546D24", I2CDeviceType{"tps546d24", true}},
135     {"XDP710", I2CDeviceType{"xdp710", true}},
136     {"XDPE11280", I2CDeviceType{"xdpe11280", true}},
137     {"XDPE12284", I2CDeviceType{"xdpe12284", true}},
138     {"XDPE152C4", I2CDeviceType{"xdpe152c4", true}},
139 };
140 
141 enum class DevTypes
142 {
143     Unknown = 0,
144     HWMON,
145     IIO
146 };
147 
148 struct DevParams
149 {
150     unsigned int matchIndex = 0;
151     std::string matchRegEx;
152     std::string nameRegEx;
153 };
154 
155 namespace fs = std::filesystem;
156 
157 static boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>
158     sensors;
159 static boost::container::flat_map<std::string, std::unique_ptr<PSUCombineEvent>>
160     combineEvents;
161 static boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
162     pwmSensors;
163 static boost::container::flat_map<std::string, std::string> sensorTable;
164 static boost::container::flat_map<std::string, PSUProperty> labelMatch;
165 static EventPathList eventMatch;
166 static EventPathList limitEventMatch;
167 
168 static boost::container::flat_map<size_t, bool> cpuPresence;
169 static boost::container::flat_map<DevTypes, DevParams> devParamMap;
170 
171 // Function CheckEvent will check each attribute from eventMatch table in the
172 // sysfs. If the attributes exists in sysfs, then store the complete path
173 // of the attribute into eventPathList.
checkEvent(const std::string & directory,const EventPathList & eventMatch,EventPathList & eventPathList)174 void checkEvent(const std::string& directory, const EventPathList& eventMatch,
175                 EventPathList& eventPathList)
176 {
177     for (const auto& match : eventMatch)
178     {
179         const std::vector<std::string>& eventAttrs = match.second;
180         const std::string& eventName = match.first;
181         for (const auto& eventAttr : eventAttrs)
182         {
183             std::string eventPath = directory;
184             eventPath += "/";
185             eventPath += eventAttr;
186 
187             std::ifstream eventFile(eventPath);
188             if (!eventFile.good())
189             {
190                 continue;
191             }
192 
193             eventPathList[eventName].push_back(eventPath);
194         }
195     }
196 }
197 
198 // Check Group Events which contains more than one targets in each combine
199 // events.
checkGroupEvent(const std::string & directory,GroupEventPathList & groupEventPathList)200 void checkGroupEvent(const std::string& directory,
201                      GroupEventPathList& groupEventPathList)
202 {
203     EventPathList pathList;
204     std::vector<fs::path> eventPaths;
205     if (!findFiles(fs::path(directory), R"(fan\d+_(alarm|fault))", eventPaths))
206     {
207         return;
208     }
209 
210     for (const auto& eventPath : eventPaths)
211     {
212         std::string attrName = eventPath.filename();
213         pathList[attrName.substr(0, attrName.find('_'))].push_back(eventPath);
214     }
215     groupEventPathList["FanFault"] = pathList;
216 }
217 
218 // Function checkEventLimits will check all the psu related xxx_input attributes
219 // in sysfs to see if xxx_crit_alarm xxx_lcrit_alarm xxx_max_alarm
220 // xxx_min_alarm exist, then store the existing paths of the alarm attributes
221 // to eventPathList.
checkEventLimits(const std::string & sensorPathStr,const EventPathList & limitEventMatch,EventPathList & eventPathList)222 void checkEventLimits(const std::string& sensorPathStr,
223                       const EventPathList& limitEventMatch,
224                       EventPathList& eventPathList)
225 {
226     auto attributePartPos = sensorPathStr.find_last_of('_');
227     if (attributePartPos == std::string::npos)
228     {
229         // There is no '_' in the string, skip it
230         return;
231     }
232     auto attributePart =
233         std::string_view(sensorPathStr).substr(attributePartPos + 1);
234     if (attributePart != "input")
235     {
236         // If the sensor is not xxx_input, skip it
237         return;
238     }
239 
240     auto prefixPart = sensorPathStr.substr(0, attributePartPos + 1);
241     for (const auto& limitMatch : limitEventMatch)
242     {
243         const std::vector<std::string>& limitEventAttrs = limitMatch.second;
244         const std::string& eventName = limitMatch.first;
245         for (const auto& limitEventAttr : limitEventAttrs)
246         {
247             auto limitEventPath = prefixPart + limitEventAttr;
248             std::ifstream eventFile(limitEventPath);
249             if (!eventFile.good())
250             {
251                 continue;
252             }
253             eventPathList[eventName].push_back(limitEventPath);
254         }
255     }
256 }
257 
checkPWMSensor(const fs::path & sensorPath,std::string & labelHead,const std::string & interfacePath,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,sdbusplus::asio::object_server & objectServer,const std::string & psuName)258 static void checkPWMSensor(
259     const fs::path& sensorPath, std::string& labelHead,
260     const std::string& interfacePath,
261     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
262     sdbusplus::asio::object_server& objectServer, const std::string& psuName)
263 {
264     if (!labelHead.starts_with("fan"))
265     {
266         return;
267     }
268     std::string labelHeadIndex = labelHead.substr(3);
269 
270     const std::string& sensorPathStr = sensorPath.string();
271     const std::string& pwmPathStr =
272         boost::replace_all_copy(sensorPathStr, "input", "target");
273     std::ifstream pwmFile(pwmPathStr);
274     if (!pwmFile.good())
275     {
276         return;
277     }
278 
279     auto findPWMSensor = pwmSensors.find(psuName + labelHead);
280     if (findPWMSensor != pwmSensors.end())
281     {
282         return;
283     }
284 
285     std::string name = "Pwm_";
286     name += psuName;
287     name += "_Fan_";
288     name += labelHeadIndex;
289 
290     std::string objPath = interfacePath;
291     objPath += "_Fan_";
292     objPath += labelHeadIndex;
293 
294     pwmSensors[psuName + labelHead] = std::make_unique<PwmSensor>(
295         name, pwmPathStr, dbusConnection, objectServer, objPath, "PSU");
296 }
297 
createSensorsCallback(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const ManagedObjectType & sensorConfigs,const std::shared_ptr<boost::container::flat_set<std::string>> & sensorsChanged,bool activateOnly)298 static void createSensorsCallback(
299     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
300     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
301     const ManagedObjectType& sensorConfigs,
302     const std::shared_ptr<boost::container::flat_set<std::string>>&
303         sensorsChanged,
304     bool activateOnly)
305 {
306     int numCreated = 0;
307     bool firstScan = sensorsChanged == nullptr;
308 
309     auto devices = instantiateDevices(sensorConfigs, sensors, sensorTypes);
310 
311     std::vector<fs::path> pmbusPaths;
312     findFiles(fs::path("/sys/bus/iio/devices"), "name", pmbusPaths);
313     findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths);
314     if (pmbusPaths.empty())
315     {
316         std::cerr << "No PSU sensors in system\n";
317         return;
318     }
319 
320     boost::container::flat_set<std::string> directories;
321     for (const auto& pmbusPath : pmbusPaths)
322     {
323         EventPathList eventPathList;
324         GroupEventPathList groupEventPathList;
325 
326         std::ifstream nameFile(pmbusPath);
327         if (!nameFile.good())
328         {
329             std::cerr << "Failure finding pmbus path " << pmbusPath << "\n";
330             continue;
331         }
332 
333         std::string pmbusName;
334         std::getline(nameFile, pmbusName);
335         nameFile.close();
336 
337         if (sensorTypes.find(pmbusName) == sensorTypes.end())
338         {
339             // To avoid this error message, add your driver name to
340             // the pmbusNames vector at the top of this file.
341             std::cerr << "Driver name " << pmbusName
342                       << " not found in sensor whitelist\n";
343             continue;
344         }
345 
346         auto directory = pmbusPath.parent_path();
347 
348         auto ret = directories.insert(directory.string());
349         if (!ret.second)
350         {
351             std::cerr << "Duplicate path " << directory.string() << "\n";
352             continue; // check if path has already been searched
353         }
354 
355         DevTypes devType = DevTypes::HWMON;
356         std::string deviceName;
357         if (directory.parent_path() == "/sys/class/hwmon")
358         {
359             std::string devicePath = fs::canonical(directory / "device");
360             std::smatch match;
361             // Find /i2c-<bus>/<bus>-<address> match in device path
362             std::regex_search(devicePath, match, i2cDevRegex);
363             if (match.empty())
364             {
365                 std::cerr << "Found bad device path " << devicePath << "\n";
366                 continue;
367             }
368             // Extract <bus>-<address>
369             std::string matchStr = match[1];
370             deviceName = matchStr.substr(matchStr.find_last_of('/') + 1);
371         }
372         else
373         {
374             deviceName = fs::canonical(directory).parent_path().stem();
375             devType = DevTypes::IIO;
376         }
377 
378         size_t bus = 0;
379         size_t addr = 0;
380         if (!getDeviceBusAddr(deviceName, bus, addr))
381         {
382             continue;
383         }
384 
385         const SensorBaseConfigMap* baseConfig = nullptr;
386         const SensorData* sensorData = nullptr;
387         const std::string* interfacePath = nullptr;
388         std::string sensorType;
389         size_t thresholdConfSize = 0;
390 
391         for (const auto& [path, cfgData] : sensorConfigs)
392         {
393             sensorData = &cfgData;
394             for (const auto& [type, dt] : sensorTypes)
395             {
396                 auto sensorBase = sensorData->find(configInterfaceName(type));
397                 if (sensorBase != sensorData->end())
398                 {
399                     baseConfig = &sensorBase->second;
400                     sensorType = type;
401                     break;
402                 }
403             }
404             if (baseConfig == nullptr)
405             {
406                 std::cerr << "error finding base configuration for "
407                           << deviceName << "\n";
408                 continue;
409             }
410 
411             auto configBus = baseConfig->find("Bus");
412             auto configAddress = baseConfig->find("Address");
413 
414             if (configBus == baseConfig->end() ||
415                 configAddress == baseConfig->end())
416             {
417                 std::cerr << "error finding necessary entry in configuration\n";
418                 continue;
419             }
420 
421             const uint64_t* confBus =
422                 std::get_if<uint64_t>(&(configBus->second));
423             const uint64_t* confAddr =
424                 std::get_if<uint64_t>(&(configAddress->second));
425             if (confBus == nullptr || confAddr == nullptr)
426             {
427                 std::cerr
428                     << "Cannot get bus or address, invalid configuration\n";
429                 continue;
430             }
431 
432             if ((*confBus != bus) || (*confAddr != addr))
433             {
434                 if constexpr (debug)
435                 {
436                     std::cerr << "Configuration skipping " << *confBus << "-"
437                               << *confAddr << " because not " << bus << "-"
438                               << addr << "\n";
439                 }
440                 continue;
441             }
442 
443             std::vector<thresholds::Threshold> confThresholds;
444             if (!parseThresholdsFromConfig(*sensorData, confThresholds))
445             {
446                 std::cerr << "error populating total thresholds\n";
447             }
448             thresholdConfSize = confThresholds.size();
449 
450             interfacePath = &path.str;
451             break;
452         }
453         if (interfacePath == nullptr)
454         {
455             // To avoid this error message, add your export map entry,
456             // from Entity Manager, to sensorTypes at the top of this file.
457             std::cerr << "failed to find match for " << deviceName << "\n";
458             continue;
459         }
460 
461         auto findI2CDev = devices.find(*interfacePath);
462 
463         std::shared_ptr<I2CDevice> i2cDev;
464         if (findI2CDev != devices.end())
465         {
466             if (activateOnly && !findI2CDev->second.second)
467             {
468                 continue;
469             }
470             i2cDev = findI2CDev->second.first;
471         }
472 
473         auto findPSUName = baseConfig->find("Name");
474         if (findPSUName == baseConfig->end())
475         {
476             std::cerr << "could not determine configuration name for "
477                       << deviceName << "\n";
478             continue;
479         }
480         const std::string* psuName =
481             std::get_if<std::string>(&(findPSUName->second));
482         if (psuName == nullptr)
483         {
484             std::cerr << "Cannot find psu name, invalid configuration\n";
485             continue;
486         }
487 
488         auto findCPU = baseConfig->find("CPURequired");
489         if (findCPU != baseConfig->end())
490         {
491             size_t index = std::visit(VariantToIntVisitor(), findCPU->second);
492             auto presenceFind = cpuPresence.find(index);
493             if (presenceFind == cpuPresence.end() || !presenceFind->second)
494             {
495                 continue;
496             }
497         }
498 
499         // on rescans, only update sensors we were signaled by
500         if (!firstScan)
501         {
502             std::string psuNameStr = "/" + escapeName(*psuName);
503             auto it =
504                 std::find_if(sensorsChanged->begin(), sensorsChanged->end(),
505                              [psuNameStr](std::string& s) {
506                                  return s.ends_with(psuNameStr);
507                              });
508 
509             if (it == sensorsChanged->end())
510             {
511                 continue;
512             }
513             sensorsChanged->erase(it);
514         }
515         checkEvent(directory.string(), eventMatch, eventPathList);
516         checkGroupEvent(directory.string(), groupEventPathList);
517 
518         PowerState readState = getPowerState(*baseConfig);
519 
520         /* Check if there are more sensors in the same interface */
521         int i = 1;
522         std::vector<std::string> psuNames;
523         do
524         {
525             // Individual string fields: Name, Name1, Name2, Name3, ...
526             psuNames.push_back(
527                 escapeName(std::get<std::string>(findPSUName->second)));
528             findPSUName = baseConfig->find("Name" + std::to_string(i++));
529         } while (findPSUName != baseConfig->end());
530 
531         std::vector<fs::path> sensorPaths;
532         if (!findFiles(directory, devParamMap[devType].matchRegEx, sensorPaths,
533                        0))
534         {
535             std::cerr << "No PSU non-label sensor in PSU\n";
536             continue;
537         }
538 
539         /* read max value in sysfs for in, curr, power, temp, ... */
540         if (!findFiles(directory, R"(\w\d+_max$)", sensorPaths, 0))
541         {
542             if constexpr (debug)
543             {
544                 std::cerr << "No max name in PSU \n";
545             }
546         }
547 
548         float pollRate = getPollRate(*baseConfig, PSUSensor::defaultSensorPoll);
549 
550         /* Find array of labels to be exposed if it is defined in config */
551         std::vector<std::string> findLabels;
552         auto findLabelObj = baseConfig->find("Labels");
553         if (findLabelObj != baseConfig->end())
554         {
555             findLabels =
556                 std::get<std::vector<std::string>>(findLabelObj->second);
557         }
558 
559         std::regex sensorNameRegEx(devParamMap[devType].nameRegEx);
560         std::smatch matches;
561 
562         for (const auto& sensorPath : sensorPaths)
563         {
564             bool maxLabel = false;
565             std::string labelHead;
566             std::string sensorPathStr = sensorPath.string();
567             std::string sensorNameStr = sensorPath.filename();
568             std::string sensorNameSubStr;
569             if (std::regex_search(sensorNameStr, matches, sensorNameRegEx))
570             {
571                 // hwmon *_input filename without number:
572                 // in, curr, power, temp, ...
573                 // iio in_*_raw filename without number:
574                 // voltage, temp, pressure, ...
575                 sensorNameSubStr = matches[devParamMap[devType].matchIndex];
576             }
577             else
578             {
579                 std::cerr << "Could not extract the alpha prefix from "
580                           << sensorNameStr;
581                 continue;
582             }
583 
584             std::string labelPath;
585 
586             if (devType == DevTypes::HWMON)
587             {
588                 /* find and differentiate _max and _input to replace "label" */
589                 size_t pos = sensorPathStr.find('_');
590                 if (pos != std::string::npos)
591                 {
592                     std::string sensorPathStrMax = sensorPathStr.substr(pos);
593                     if (sensorPathStrMax == "_max")
594                     {
595                         labelPath = boost::replace_all_copy(sensorPathStr,
596                                                             "max", "label");
597                         maxLabel = true;
598                     }
599                     else
600                     {
601                         labelPath = boost::replace_all_copy(sensorPathStr,
602                                                             "input", "label");
603                         maxLabel = false;
604                     }
605                 }
606                 else
607                 {
608                     continue;
609                 }
610 
611                 std::ifstream labelFile(labelPath);
612                 if (!labelFile.good())
613                 {
614                     if constexpr (debug)
615                     {
616                         std::cerr << "Input file " << sensorPath
617                                   << " has no corresponding label file\n";
618                     }
619                     // hwmon *_input filename with number:
620                     // temp1, temp2, temp3, ...
621                     labelHead =
622                         sensorNameStr.substr(0, sensorNameStr.find('_'));
623                 }
624                 else
625                 {
626                     std::string label;
627                     std::getline(labelFile, label);
628                     labelFile.close();
629                     auto findSensor = sensors.find(label);
630                     if (findSensor != sensors.end())
631                     {
632                         continue;
633                     }
634 
635                     // hwmon corresponding *_label file contents:
636                     // vin1, vout1, ...
637                     labelHead = label.substr(0, label.find(' '));
638                 }
639 
640                 /* append "max" for labelMatch */
641                 if (maxLabel)
642                 {
643                     labelHead.insert(0, "max");
644                 }
645 
646                 checkPWMSensor(sensorPath, labelHead, *interfacePath,
647                                dbusConnection, objectServer, psuNames[0]);
648             }
649             else if (devType == DevTypes::IIO)
650             {
651                 auto findIIOHyphen = sensorNameStr.find_last_of('_');
652                 labelHead = sensorNameStr.substr(0, findIIOHyphen);
653             }
654 
655             if constexpr (debug)
656             {
657                 std::cerr << "Sensor type=\"" << sensorNameSubStr
658                           << "\" label=\"" << labelHead << "\"\n";
659             }
660 
661             if (!findLabels.empty())
662             {
663                 /* Check if this labelHead is enabled in config file */
664                 if (std::find(findLabels.begin(), findLabels.end(),
665                               labelHead) == findLabels.end())
666                 {
667                     if constexpr (debug)
668                     {
669                         std::cerr << "could not find " << labelHead
670                                   << " in the Labels list\n";
671                     }
672                     continue;
673                 }
674             }
675 
676             auto findProperty = labelMatch.find(sensorNameSubStr);
677             if (findProperty == labelMatch.end())
678             {
679                 if constexpr (debug)
680                 {
681                     std::cerr << "Could not find matching default property for "
682                               << sensorNameSubStr << "\n";
683                 }
684                 continue;
685             }
686 
687             // Protect the hardcoded labelMatch list from changes,
688             // by making a copy and modifying that instead.
689             // Avoid bleedthrough of one device's customizations to
690             // the next device, as each should be independently customizable.
691             PSUProperty psuProperty = findProperty->second;
692 
693             // Use label head as prefix for reading from config file,
694             // example if temp1: temp1_Name, temp1_Scale, temp1_Min, ...
695             std::string keyName = labelHead + "_Name";
696             std::string keyScale = labelHead + "_Scale";
697             std::string keyMin = labelHead + "_Min";
698             std::string keyMax = labelHead + "_Max";
699             std::string keyOffset = labelHead + "_Offset";
700             std::string keyPowerState = labelHead + "_PowerState";
701 
702             bool customizedName = false;
703             auto findCustomName = baseConfig->find(keyName);
704             if (findCustomName != baseConfig->end())
705             {
706                 try
707                 {
708                     psuProperty.labelTypeName = std::visit(
709                         VariantToStringVisitor(), findCustomName->second);
710                 }
711                 catch (const std::invalid_argument&)
712                 {
713                     std::cerr << "Unable to parse " << keyName << "\n";
714                     continue;
715                 }
716 
717                 // All strings are valid, including empty string
718                 customizedName = true;
719             }
720 
721             bool customizedScale = false;
722             auto findCustomScale = baseConfig->find(keyScale);
723             if (findCustomScale != baseConfig->end())
724             {
725                 try
726                 {
727                     psuProperty.sensorScaleFactor = std::visit(
728                         VariantToUnsignedIntVisitor(), findCustomScale->second);
729                 }
730                 catch (const std::invalid_argument&)
731                 {
732                     std::cerr << "Unable to parse " << keyScale << "\n";
733                     continue;
734                 }
735 
736                 // Avoid later division by zero
737                 if (psuProperty.sensorScaleFactor > 0)
738                 {
739                     customizedScale = true;
740                 }
741                 else
742                 {
743                     std::cerr << "Unable to accept " << keyScale << "\n";
744                     continue;
745                 }
746             }
747 
748             auto findCustomMin = baseConfig->find(keyMin);
749             if (findCustomMin != baseConfig->end())
750             {
751                 try
752                 {
753                     psuProperty.minReading = std::visit(
754                         VariantToDoubleVisitor(), findCustomMin->second);
755                 }
756                 catch (const std::invalid_argument&)
757                 {
758                     std::cerr << "Unable to parse " << keyMin << "\n";
759                     continue;
760                 }
761             }
762 
763             auto findCustomMax = baseConfig->find(keyMax);
764             if (findCustomMax != baseConfig->end())
765             {
766                 try
767                 {
768                     psuProperty.maxReading = std::visit(
769                         VariantToDoubleVisitor(), findCustomMax->second);
770                 }
771                 catch (const std::invalid_argument&)
772                 {
773                     std::cerr << "Unable to parse " << keyMax << "\n";
774                     continue;
775                 }
776             }
777 
778             auto findCustomOffset = baseConfig->find(keyOffset);
779             if (findCustomOffset != baseConfig->end())
780             {
781                 try
782                 {
783                     psuProperty.sensorOffset = std::visit(
784                         VariantToDoubleVisitor(), findCustomOffset->second);
785                 }
786                 catch (const std::invalid_argument&)
787                 {
788                     std::cerr << "Unable to parse " << keyOffset << "\n";
789                     continue;
790                 }
791             }
792 
793             // if we find label head power state set ,override the powerstate.
794             auto findPowerState = baseConfig->find(keyPowerState);
795             if (findPowerState != baseConfig->end())
796             {
797                 std::string powerState = std::visit(VariantToStringVisitor(),
798                                                     findPowerState->second);
799                 setReadState(powerState, readState);
800             }
801             if (!(psuProperty.minReading < psuProperty.maxReading))
802             {
803                 std::cerr << "Min must be less than Max\n";
804                 continue;
805             }
806 
807             // If the sensor name is being customized by config file,
808             // then prefix/suffix composition becomes not necessary,
809             // and in fact not wanted, because it gets in the way.
810             std::string psuNameFromIndex;
811             std::string nameIndexStr = "1";
812             if (!customizedName)
813             {
814                 /* Find out sensor name index for this label */
815                 std::regex rgx("[A-Za-z]+([0-9]+)");
816                 size_t nameIndex{0};
817                 if (std::regex_search(labelHead, matches, rgx))
818                 {
819                     nameIndexStr = matches[1];
820                     nameIndex = std::stoi(nameIndexStr);
821 
822                     // Decrement to preserve alignment, because hwmon
823                     // human-readable filenames and labels use 1-based
824                     // numbering, but the "Name", "Name1", "Name2", etc. naming
825                     // convention (the psuNames vector) uses 0-based numbering.
826                     if (nameIndex > 0)
827                     {
828                         --nameIndex;
829                     }
830                 }
831                 else
832                 {
833                     nameIndex = 0;
834                 }
835 
836                 if (psuNames.size() <= nameIndex)
837                 {
838                     std::cerr << "Could not pair " << labelHead
839                               << " with a Name field\n";
840                     continue;
841                 }
842 
843                 psuNameFromIndex = psuNames[nameIndex];
844 
845                 if constexpr (debug)
846                 {
847                     std::cerr << "Sensor label head " << labelHead
848                               << " paired with " << psuNameFromIndex
849                               << " at index " << nameIndex << "\n";
850                 }
851             }
852 
853             if (devType == DevTypes::HWMON)
854             {
855                 checkEventLimits(sensorPathStr, limitEventMatch, eventPathList);
856             }
857 
858             // Similarly, if sensor scaling factor is being customized,
859             // then the below power-of-10 constraint becomes unnecessary,
860             // as config should be able to specify an arbitrary divisor.
861             unsigned int factor = psuProperty.sensorScaleFactor;
862             if (!customizedScale)
863             {
864                 // Preserve existing usage of hardcoded labelMatch table below
865                 factor = std::pow(10.0, factor);
866 
867                 /* Change first char of substring to uppercase */
868                 char firstChar =
869                     static_cast<char>(std::toupper(sensorNameSubStr[0]));
870                 std::string strScaleFactor =
871                     firstChar + sensorNameSubStr.substr(1) + "ScaleFactor";
872 
873                 // Preserve existing configs by accepting earlier syntax,
874                 // example CurrScaleFactor, PowerScaleFactor, ...
875                 auto findScaleFactor = baseConfig->find(strScaleFactor);
876                 if (findScaleFactor != baseConfig->end())
877                 {
878                     factor = std::visit(VariantToIntVisitor(),
879                                         findScaleFactor->second);
880                 }
881 
882                 if constexpr (debug)
883                 {
884                     std::cerr << "Sensor scaling factor " << factor
885                               << " string " << strScaleFactor << "\n";
886                 }
887             }
888 
889             std::vector<thresholds::Threshold> sensorThresholds;
890             if (!parseThresholdsFromConfig(*sensorData, sensorThresholds,
891                                            &labelHead))
892             {
893                 std::cerr << "error populating thresholds for "
894                           << sensorNameSubStr << "\n";
895             }
896 
897             auto findSensorUnit = sensorTable.find(sensorNameSubStr);
898             if (findSensorUnit == sensorTable.end())
899             {
900                 std::cerr << sensorNameSubStr
901                           << " is not a recognized sensor type\n";
902                 continue;
903             }
904 
905             if constexpr (debug)
906             {
907                 std::cerr << "Sensor properties: Name \""
908                           << psuProperty.labelTypeName << "\" Scale "
909                           << psuProperty.sensorScaleFactor << " Min "
910                           << psuProperty.minReading << " Max "
911                           << psuProperty.maxReading << " Offset "
912                           << psuProperty.sensorOffset << "\n";
913             }
914 
915             std::string sensorName = psuProperty.labelTypeName;
916             if (customizedName)
917             {
918                 if (sensorName.empty())
919                 {
920                     // Allow selective disabling of an individual sensor,
921                     // by customizing its name to an empty string.
922                     std::cerr << "Sensor disabled, empty string\n";
923                     continue;
924                 }
925             }
926             else
927             {
928                 // Sensor name not customized, do prefix/suffix composition,
929                 // preserving default behavior by using psuNameFromIndex.
930                 sensorName = psuNameFromIndex + " " + psuProperty.labelTypeName;
931 
932                 // The labelTypeName of a fan can be:
933                 // "Fan Speed 1", "Fan Speed 2", "Fan Speed 3" ...
934                 if (labelHead == "fan" + nameIndexStr)
935                 {
936                     sensorName += nameIndexStr;
937                 }
938             }
939 
940             if constexpr (debug)
941             {
942                 std::cerr << "Sensor name \"" << sensorName << "\" path \""
943                           << sensorPathStr << "\" type \"" << sensorType
944                           << "\"\n";
945             }
946             // destruct existing one first if already created
947 
948             auto& sensor = sensors[sensorName];
949             if (!activateOnly)
950             {
951                 sensor = nullptr;
952             }
953 
954             if (sensor != nullptr)
955             {
956                 sensor->activate(sensorPathStr, i2cDev);
957             }
958             else
959             {
960                 sensors[sensorName] = std::make_shared<PSUSensor>(
961                     sensorPathStr, sensorType, objectServer, dbusConnection, io,
962                     sensorName, std::move(sensorThresholds), *interfacePath,
963                     readState, findSensorUnit->second, factor,
964                     psuProperty.maxReading, psuProperty.minReading,
965                     psuProperty.sensorOffset, labelHead, thresholdConfSize,
966                     pollRate, i2cDev);
967                 sensors[sensorName]->setupRead();
968                 ++numCreated;
969                 if constexpr (debug)
970                 {
971                     std::cerr
972                         << "Created " << numCreated << " sensors so far\n";
973                 }
974             }
975         }
976 
977         if (devType == DevTypes::HWMON)
978         {
979             // OperationalStatus event
980             combineEvents[*psuName + "OperationalStatus"] = nullptr;
981             combineEvents[*psuName + "OperationalStatus"] =
982                 std::make_unique<PSUCombineEvent>(
983                     objectServer, dbusConnection, io, *psuName, readState,
984                     eventPathList, groupEventPathList, "OperationalStatus",
985                     pollRate);
986         }
987     }
988 
989     if constexpr (debug)
990     {
991         std::cerr << "Created total of " << numCreated << " sensors\n";
992     }
993 }
994 
995 static void
getPresentCpus(std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)996     getPresentCpus(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
997 {
998     static const int depth = 2;
999     static const int numKeys = 1;
1000     GetSubTreeType cpuSubTree;
1001 
1002     try
1003     {
1004         auto getItems = dbusConnection->new_method_call(
1005             mapper::busName, mapper::path, mapper::interface, mapper::subtree);
1006         getItems.append(cpuInventoryPath, static_cast<int32_t>(depth),
1007                         std::array<const char*, numKeys>{
1008                             "xyz.openbmc_project.Inventory.Item"});
1009         auto getItemsResp = dbusConnection->call(getItems);
1010         getItemsResp.read(cpuSubTree);
1011     }
1012     catch (sdbusplus::exception_t& e)
1013     {
1014         std::cerr << "error getting inventory item subtree: " << e.what()
1015                   << "\n";
1016         return;
1017     }
1018 
1019     for (const auto& [path, objDict] : cpuSubTree)
1020     {
1021         auto obj = sdbusplus::message::object_path(path).filename();
1022         boost::to_lower(obj);
1023 
1024         if (!obj.starts_with("cpu") || objDict.empty())
1025         {
1026             continue;
1027         }
1028         const std::string& owner = objDict.begin()->first;
1029 
1030         std::variant<bool> respValue;
1031         try
1032         {
1033             auto getPresence = dbusConnection->new_method_call(
1034                 owner.c_str(), path.c_str(), "org.freedesktop.DBus.Properties",
1035                 "Get");
1036             getPresence.append("xyz.openbmc_project.Inventory.Item", "Present");
1037             auto resp = dbusConnection->call(getPresence);
1038             resp.read(respValue);
1039         }
1040         catch (sdbusplus::exception_t& e)
1041         {
1042             std::cerr << "Error in getting CPU presence: " << e.what() << "\n";
1043             continue;
1044         }
1045 
1046         auto* present = std::get_if<bool>(&respValue);
1047         if (present != nullptr && *present)
1048         {
1049             int cpuIndex = 0;
1050             try
1051             {
1052                 cpuIndex = std::stoi(obj.substr(obj.size() - 1));
1053             }
1054             catch (const std::exception& e)
1055             {
1056                 std::cerr << "Error converting CPU index, " << e.what() << '\n';
1057                 continue;
1058             }
1059             cpuPresence[cpuIndex] = *present;
1060         }
1061     }
1062 }
1063 
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const std::shared_ptr<boost::container::flat_set<std::string>> & sensorsChanged,bool activateOnly)1064 void createSensors(
1065     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
1066     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
1067     const std::shared_ptr<boost::container::flat_set<std::string>>&
1068         sensorsChanged,
1069     bool activateOnly)
1070 {
1071     auto getter = std::make_shared<GetSensorConfiguration>(
1072         dbusConnection, [&io, &objectServer, &dbusConnection, sensorsChanged,
1073                          activateOnly](const ManagedObjectType& sensorConfigs) {
1074             createSensorsCallback(io, objectServer, dbusConnection,
1075                                   sensorConfigs, sensorsChanged, activateOnly);
1076         });
1077     std::vector<std::string> types(sensorTypes.size());
1078     for (const auto& [type, dt] : sensorTypes)
1079     {
1080         types.push_back(type);
1081     }
1082     getter->getConfiguration(types);
1083 }
1084 
propertyInitialize()1085 void propertyInitialize()
1086 {
1087     sensorTable = {{"power", sensor_paths::unitWatts},
1088                    {"curr", sensor_paths::unitAmperes},
1089                    {"temp", sensor_paths::unitDegreesC},
1090                    {"in", sensor_paths::unitVolts},
1091                    {"voltage", sensor_paths::unitVolts},
1092                    {"fan", sensor_paths::unitRPMs}};
1093 
1094     labelMatch = {
1095         {"pin", PSUProperty("Input Power", 3000, 0, 6, 0)},
1096         {"pout", PSUProperty("Output Power", 3000, 0, 6, 0)},
1097         {"power", PSUProperty("Output Power", 3000, 0, 6, 0)},
1098         {"maxpin", PSUProperty("Max Input Power", 3000, 0, 6, 0)},
1099         {"vin", PSUProperty("Input Voltage", 300, 0, 3, 0)},
1100         {"maxvin", PSUProperty("Max Input Voltage", 300, 0, 3, 0)},
1101         {"in_voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)},
1102         {"voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)},
1103         {"vout", PSUProperty("Output Voltage", 255, 0, 3, 0)},
1104         {"vmon", PSUProperty("Auxiliary Input Voltage", 255, 0, 3, 0)},
1105         {"in", PSUProperty("Output Voltage", 255, 0, 3, 0)},
1106         {"iin", PSUProperty("Input Current", 20, 0, 3, 0)},
1107         {"iout", PSUProperty("Output Current", 255, 0, 3, 0)},
1108         {"curr", PSUProperty("Output Current", 255, 0, 3, 0)},
1109         {"maxiout", PSUProperty("Max Output Current", 255, 0, 3, 0)},
1110         {"temp", PSUProperty("Temperature", 127, -128, 3, 0)},
1111         {"maxtemp", PSUProperty("Max Temperature", 127, -128, 3, 0)},
1112         {"fan", PSUProperty("Fan Speed ", 30000, 0, 0, 0)}};
1113 
1114     limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}},
1115                        {"Failure", {"crit_alarm", "lcrit_alarm"}}};
1116 
1117     eventMatch = {{"PredictiveFailure", {"power1_alarm"}},
1118                   {"Failure", {"in2_alarm"}},
1119                   {"ACLost", {"in1_beep"}},
1120                   {"ConfigureError", {"in1_fault"}}};
1121 
1122     devParamMap = {
1123         {DevTypes::HWMON, {1, R"(\w\d+_input$)", "([A-Za-z]+)[0-9]*_"}},
1124         {DevTypes::IIO,
1125          {2, R"(\w+_(raw|input)$)", "^(in|out)_([A-Za-z]+)[0-9]*_"}}};
1126 }
1127 
powerStateChanged(PowerState type,bool newState,boost::container::flat_map<std::string,std::shared_ptr<PSUSensor>> & sensors,boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)1128 static void powerStateChanged(
1129     PowerState type, bool newState,
1130     boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>&
1131         sensors,
1132     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
1133     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
1134 {
1135     if (newState)
1136     {
1137         createSensors(io, objectServer, dbusConnection, nullptr, true);
1138     }
1139     else
1140     {
1141         for (auto& [path, sensor] : sensors)
1142         {
1143             if (sensor != nullptr && sensor->readState == type)
1144             {
1145                 sensor->deactivate();
1146             }
1147         }
1148     }
1149 }
1150 
main()1151 int main()
1152 {
1153     boost::asio::io_context io;
1154     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1155 
1156     sdbusplus::asio::object_server objectServer(systemBus, true);
1157     objectServer.add_manager("/xyz/openbmc_project/sensors");
1158     objectServer.add_manager("/xyz/openbmc_project/control");
1159     systemBus->request_name("xyz.openbmc_project.PSUSensor");
1160     auto sensorsChanged =
1161         std::make_shared<boost::container::flat_set<std::string>>();
1162 
1163     propertyInitialize();
1164 
1165     auto powerCallBack = [&io, &objectServer,
1166                           &systemBus](PowerState type, bool state) {
1167         powerStateChanged(type, state, sensors, io, objectServer, systemBus);
1168     };
1169 
1170     setupPowerMatchCallback(systemBus, powerCallBack);
1171 
1172     boost::asio::post(io, [&]() {
1173         createSensors(io, objectServer, systemBus, nullptr, false);
1174     });
1175     boost::asio::steady_timer filterTimer(io);
1176     std::function<void(sdbusplus::message_t&)> eventHandler =
1177         [&](sdbusplus::message_t& message) {
1178             if (message.is_method_error())
1179             {
1180                 std::cerr << "callback method error\n";
1181                 return;
1182             }
1183             sensorsChanged->insert(message.get_path());
1184             filterTimer.expires_after(std::chrono::seconds(3));
1185             filterTimer.async_wait([&](const boost::system::error_code& ec) {
1186                 if (ec == boost::asio::error::operation_aborted)
1187                 {
1188                     return;
1189                 }
1190                 if (ec)
1191                 {
1192                     std::cerr << "timer error\n";
1193                 }
1194                 createSensors(io, objectServer, systemBus, sensorsChanged,
1195                               false);
1196             });
1197         };
1198 
1199     boost::asio::steady_timer cpuFilterTimer(io);
1200     std::function<void(sdbusplus::message_t&)> cpuPresenceHandler =
1201         [&](sdbusplus::message_t& message) {
1202             std::string path = message.get_path();
1203             boost::to_lower(path);
1204 
1205             sdbusplus::message::object_path cpuPath(path);
1206             std::string cpuName = cpuPath.filename();
1207             if (!cpuName.starts_with("cpu"))
1208             {
1209                 return;
1210             }
1211             size_t index = 0;
1212             try
1213             {
1214                 index = std::stoi(path.substr(path.size() - 1));
1215             }
1216             catch (const std::invalid_argument&)
1217             {
1218                 std::cerr << "Found invalid path " << path << "\n";
1219                 return;
1220             }
1221 
1222             std::string objectName;
1223             boost::container::flat_map<std::string, std::variant<bool>> values;
1224             message.read(objectName, values);
1225             auto findPresence = values.find("Present");
1226             if (findPresence == values.end())
1227             {
1228                 return;
1229             }
1230             try
1231             {
1232                 cpuPresence[index] = std::get<bool>(findPresence->second);
1233             }
1234             catch (const std::bad_variant_access& err)
1235             {
1236                 return;
1237             }
1238 
1239             if (!cpuPresence[index])
1240             {
1241                 return;
1242             }
1243             cpuFilterTimer.expires_after(std::chrono::seconds(1));
1244             cpuFilterTimer.async_wait([&](const boost::system::error_code& ec) {
1245                 if (ec == boost::asio::error::operation_aborted)
1246                 {
1247                     return;
1248                 }
1249                 if (ec)
1250                 {
1251                     std::cerr << "timer error\n";
1252                     return;
1253                 }
1254                 createSensors(io, objectServer, systemBus, nullptr, false);
1255             });
1256         };
1257 
1258     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
1259         setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
1260 
1261     matches.emplace_back(std::make_unique<sdbusplus::bus::match_t>(
1262         static_cast<sdbusplus::bus_t&>(*systemBus),
1263         "type='signal',member='PropertiesChanged',path_namespace='" +
1264             std::string(cpuInventoryPath) +
1265             "',arg0namespace='xyz.openbmc_project.Inventory.Item'",
1266         cpuPresenceHandler));
1267 
1268     getPresentCpus(systemBus);
1269 
1270     setupManufacturingModeMatch(*systemBus);
1271     io.run();
1272 }
1273