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