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