1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16
17 #include "commandutils.hpp"
18 #include "types.hpp"
19
20 #include <boost/algorithm/string.hpp>
21 #include <boost/bimap.hpp>
22 #include <boost/container/flat_map.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <sdbusplus/bus/match.hpp>
25
26 #include <cstdio>
27 #include <cstring>
28 #include <exception>
29 #include <filesystem>
30 #include <map>
31 #include <string>
32 #include <string_view>
33 #include <vector>
34
35 #pragma once
36
37 struct CmpStrVersion
38 {
operator ()CmpStrVersion39 bool operator()(std::string a, std::string b) const
40 {
41 return strverscmp(a.c_str(), b.c_str()) < 0;
42 }
43 };
44
45 using SensorSubTree = boost::container::flat_map<
46 std::string,
47 boost::container::flat_map<std::string, std::vector<std::string>>,
48 CmpStrVersion>;
49
50 using SensorNumMap = boost::bimap<int, std::string>;
51
52 static constexpr uint16_t maxSensorsPerLUN = 255;
53 static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3);
54 static constexpr uint16_t lun1Sensor0 = 0x100;
55 static constexpr uint16_t lun3Sensor0 = 0x300;
56 static constexpr uint16_t invalidSensorNumber = 0xFFFF;
57 static constexpr uint8_t reservedSensorNumber = 0xFF;
58
59 namespace details
60 {
61
62 // Enable/disable the logging of stats instrumentation
63 static constexpr bool enableInstrumentation = false;
64
65 class IPMIStatsEntry
66 {
67 private:
68 int numReadings = 0;
69 int numMissings = 0;
70 int numStreakRead = 0;
71 int numStreakMiss = 0;
72 double minValue = 0.0;
73 double maxValue = 0.0;
74 std::string sensorName;
75
76 public:
getName(void) const77 const std::string& getName(void) const
78 {
79 return sensorName;
80 }
81
updateName(std::string_view name)82 void updateName(std::string_view name)
83 {
84 sensorName = name;
85 }
86
87 // Returns true if this is the first successful reading
88 // This is so the caller can log the coefficients used
updateReading(double reading,int raw)89 bool updateReading(double reading, int raw)
90 {
91 if constexpr (!enableInstrumentation)
92 {
93 return false;
94 }
95
96 bool first = ((numReadings == 0) && (numMissings == 0));
97
98 // Sensors can use "nan" to indicate unavailable reading
99 if (!(std::isfinite(reading)))
100 {
101 // Only show this if beginning a new streak
102 if (numStreakMiss == 0)
103 {
104 std::cerr << "IPMI sensor " << sensorName
105 << ": Missing reading, byte=" << raw
106 << ", Reading counts good=" << numReadings
107 << " miss=" << numMissings
108 << ", Prior good streak=" << numStreakRead << "\n";
109 }
110
111 numStreakRead = 0;
112 ++numMissings;
113 ++numStreakMiss;
114
115 return first;
116 }
117
118 // Only show this if beginning a new streak and not the first time
119 if ((numStreakRead == 0) && (numReadings != 0))
120 {
121 std::cerr << "IPMI sensor " << sensorName
122 << ": Recovered reading, value=" << reading << " byte="
123 << raw << ", Reading counts good=" << numReadings
124 << " miss=" << numMissings
125 << ", Prior miss streak=" << numStreakMiss << "\n";
126 }
127
128 // Initialize min/max if the first successful reading
129 if (numReadings == 0)
130 {
131 std::cerr << "IPMI sensor " << sensorName
132 << ": First reading, value=" << reading << " byte=" << raw
133 << "\n";
134
135 minValue = reading;
136 maxValue = reading;
137 }
138
139 numStreakMiss = 0;
140 ++numReadings;
141 ++numStreakRead;
142
143 // Only provide subsequent output if new min/max established
144 if (reading < minValue)
145 {
146 std::cerr << "IPMI sensor " << sensorName
147 << ": Lowest reading, value=" << reading
148 << " byte=" << raw << "\n";
149
150 minValue = reading;
151 }
152
153 if (reading > maxValue)
154 {
155 std::cerr << "IPMI sensor " << sensorName
156 << ": Highest reading, value=" << reading
157 << " byte=" << raw << "\n";
158
159 maxValue = reading;
160 }
161
162 return first;
163 }
164 };
165
166 class IPMIStatsTable
167 {
168 private:
169 std::vector<IPMIStatsEntry> entries;
170
171 private:
padEntries(size_t index)172 void padEntries(size_t index)
173 {
174 char hexbuf[16];
175
176 // Pad vector until entries[index] becomes a valid index
177 while (entries.size() <= index)
178 {
179 // As name not known yet, use human-readable hex as name
180 IPMIStatsEntry newEntry;
181 sprintf(hexbuf, "0x%02zX", entries.size());
182 newEntry.updateName(hexbuf);
183
184 entries.push_back(std::move(newEntry));
185 }
186 }
187
188 public:
wipeTable(void)189 void wipeTable(void)
190 {
191 entries.clear();
192 }
193
getName(size_t index)194 const std::string& getName(size_t index)
195 {
196 padEntries(index);
197 return entries[index].getName();
198 }
199
updateName(size_t index,std::string_view name)200 void updateName(size_t index, std::string_view name)
201 {
202 padEntries(index);
203 entries[index].updateName(name);
204 }
205
updateReading(size_t index,double reading,int raw)206 bool updateReading(size_t index, double reading, int raw)
207 {
208 padEntries(index);
209 return entries[index].updateReading(reading, raw);
210 }
211 };
212
213 // This object is global singleton, used from a variety of places
214 inline IPMIStatsTable sdrStatsTable;
215
getSensorSubtree(std::shared_ptr<SensorSubTree> & subtree)216 inline static uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
217 {
218 static std::shared_ptr<SensorSubTree> sensorTreePtr;
219 static uint16_t sensorUpdatedIndex = 0;
220 sd_bus* bus = NULL;
221 int ret = sd_bus_default_system(&bus);
222 if (ret < 0)
223 {
224 phosphor::logging::log<phosphor::logging::level::ERR>(
225 "Failed to connect to system bus",
226 phosphor::logging::entry("ERRNO=0x%X", -ret));
227 sd_bus_unref(bus);
228 return sensorUpdatedIndex;
229 }
230 sdbusplus::bus_t dbus(bus);
231 static sdbusplus::bus::match_t sensorAdded(
232 dbus,
233 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
234 "sensors/'",
235 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
236
237 static sdbusplus::bus::match_t sensorRemoved(
238 dbus,
239 "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
240 "openbmc_project/sensors/'",
241 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
242
243 if (sensorTreePtr)
244 {
245 subtree = sensorTreePtr;
246 return sensorUpdatedIndex;
247 }
248
249 sensorTreePtr = std::make_shared<SensorSubTree>();
250
251 auto mapperCall =
252 dbus.new_method_call("xyz.openbmc_project.ObjectMapper",
253 "/xyz/openbmc_project/object_mapper",
254 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
255 static constexpr const auto depth = 2;
256 static constexpr std::array<const char*, 3> interfaces = {
257 "xyz.openbmc_project.Sensor.Value",
258 "xyz.openbmc_project.Sensor.Threshold.Warning",
259 "xyz.openbmc_project.Sensor.Threshold.Critical"};
260 mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces);
261
262 try
263 {
264 auto mapperReply = dbus.call(mapperCall);
265 mapperReply.read(*sensorTreePtr);
266 }
267 catch (const sdbusplus::exception_t& e)
268 {
269 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
270 return sensorUpdatedIndex;
271 }
272 subtree = sensorTreePtr;
273 sensorUpdatedIndex++;
274 // The SDR is being regenerated, wipe the old stats
275 sdrStatsTable.wipeTable();
276 return sensorUpdatedIndex;
277 }
278
getSensorNumMap(std::shared_ptr<SensorNumMap> & sensorNumMap)279 inline static bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
280 {
281 static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
282 bool sensorNumMapUpated = false;
283 static uint16_t prevSensorUpdatedIndex = 0;
284 std::shared_ptr<SensorSubTree> sensorTree;
285 uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
286 if (!sensorTree)
287 {
288 return sensorNumMapUpated;
289 }
290
291 if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
292 {
293 sensorNumMap = sensorNumMapPtr;
294 return sensorNumMapUpated;
295 }
296 prevSensorUpdatedIndex = curSensorUpdatedIndex;
297
298 sensorNumMapPtr = std::make_shared<SensorNumMap>();
299
300 uint16_t sensorNum = 0;
301 uint16_t sensorIndex = 0;
302 for (const auto& sensor : *sensorTree)
303 {
304 sensorNumMapPtr->insert(
305 SensorNumMap::value_type(sensorNum, sensor.first));
306 sensorIndex++;
307 if (sensorIndex == maxSensorsPerLUN)
308 {
309 sensorIndex = lun1Sensor0;
310 }
311 else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
312 {
313 // Skip assigning LUN 0x2 any sensors
314 sensorIndex = lun3Sensor0;
315 }
316 else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
317 {
318 // this is an error, too many IPMI sensors
319 throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
320 }
321 sensorNum = sensorIndex;
322 }
323 sensorNumMap = sensorNumMapPtr;
324 sensorNumMapUpated = true;
325 return sensorNumMapUpated;
326 }
327 } // namespace details
328
getSensorSubtree(SensorSubTree & subtree)329 inline static bool getSensorSubtree(SensorSubTree& subtree)
330 {
331 std::shared_ptr<SensorSubTree> sensorTree;
332 details::getSensorSubtree(sensorTree);
333 if (!sensorTree)
334 {
335 return false;
336 }
337
338 subtree = *sensorTree;
339 return true;
340 }
341
342 struct CmpStr
343 {
operator ()CmpStr344 bool operator()(const char* a, const char* b) const
345 {
346 return std::strcmp(a, b) < 0;
347 }
348 };
349
350 enum class SensorTypeCodes : uint8_t
351 {
352 reserved = 0x0,
353 temperature = 0x1,
354 voltage = 0x2,
355 current = 0x3,
356 fan = 0x4,
357 other = 0xB,
358 };
359
360 const static boost::container::flat_map<const char*, SensorTypeCodes, CmpStr>
361 sensorTypes{{{"temperature", SensorTypeCodes::temperature},
362 {"voltage", SensorTypeCodes::voltage},
363 {"current", SensorTypeCodes::current},
364 {"fan_tach", SensorTypeCodes::fan},
365 {"fan_pwm", SensorTypeCodes::fan},
366 {"power", SensorTypeCodes::other}}};
367
getSensorTypeStringFromPath(const std::string & path)368 inline static std::string getSensorTypeStringFromPath(const std::string& path)
369 {
370 // get sensor type string from path, path is defined as
371 // /xyz/openbmc_project/sensors/<type>/label
372 size_t typeEnd = path.rfind("/");
373 if (typeEnd == std::string::npos)
374 {
375 return path;
376 }
377 size_t typeStart = path.rfind("/", typeEnd - 1);
378 if (typeStart == std::string::npos)
379 {
380 return path;
381 }
382 // Start at the character after the '/'
383 typeStart++;
384 return path.substr(typeStart, typeEnd - typeStart);
385 }
386
getSensorTypeFromPath(const std::string & path)387 inline static uint8_t getSensorTypeFromPath(const std::string& path)
388 {
389 uint8_t sensorType = 0;
390 std::string type = getSensorTypeStringFromPath(path);
391 auto findSensor = sensorTypes.find(type.c_str());
392 if (findSensor != sensorTypes.end())
393 {
394 sensorType = static_cast<uint8_t>(findSensor->second);
395 } // else default 0x0 RESERVED
396
397 return sensorType;
398 }
399
getSensorNumberFromPath(const std::string & path)400 inline static uint16_t getSensorNumberFromPath(const std::string& path)
401 {
402 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
403 details::getSensorNumMap(sensorNumMapPtr);
404 if (!sensorNumMapPtr)
405 {
406 return invalidSensorNumber;
407 }
408
409 try
410 {
411 return sensorNumMapPtr->right.at(path);
412 }
413 catch (const std::out_of_range& e)
414 {
415 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
416 return invalidSensorNumber;
417 }
418 }
419
getSensorEventTypeFromPath(const std::string &)420 inline static uint8_t getSensorEventTypeFromPath(const std::string& /* path */)
421 {
422 // TODO: Add support for additional reading types as needed
423 return 0x1; // reading type = threshold
424 }
425
getPathFromSensorNumber(uint16_t sensorNum)426 inline static std::string getPathFromSensorNumber(uint16_t sensorNum)
427 {
428 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
429 details::getSensorNumMap(sensorNumMapPtr);
430 if (!sensorNumMapPtr)
431 {
432 return std::string();
433 }
434
435 try
436 {
437 return sensorNumMapPtr->left.at(sensorNum);
438 }
439 catch (const std::out_of_range& e)
440 {
441 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
442 return std::string();
443 }
444 }
445
446 namespace ipmi
447 {
448
449 static inline std::map<std::string, std::vector<std::string>>
getObjectInterfaces(const char * path)450 getObjectInterfaces(const char* path)
451 {
452 std::map<std::string, std::vector<std::string>> interfacesResponse;
453 std::vector<std::string> interfaces;
454 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
455
456 sdbusplus::message_t getObjectMessage =
457 dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
458 "/xyz/openbmc_project/object_mapper",
459 "xyz.openbmc_project.ObjectMapper", "GetObject");
460 getObjectMessage.append(path, interfaces);
461
462 try
463 {
464 sdbusplus::message_t response = dbus->call(getObjectMessage);
465 response.read(interfacesResponse);
466 }
467 catch (const std::exception& e)
468 {
469 phosphor::logging::log<phosphor::logging::level::ERR>(
470 "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
471 phosphor::logging::entry("WHAT=%s", e.what()));
472 }
473
474 return interfacesResponse;
475 }
476
477 static inline std::map<std::string, DbusVariant>
getEntityManagerProperties(const char * path,const char * interface)478 getEntityManagerProperties(const char* path, const char* interface)
479 {
480 std::map<std::string, DbusVariant> properties;
481 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
482
483 sdbusplus::message_t getProperties =
484 dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
485 "org.freedesktop.DBus.Properties", "GetAll");
486 getProperties.append(interface);
487
488 try
489 {
490 sdbusplus::message_t response = dbus->call(getProperties);
491 response.read(properties);
492 }
493 catch (const std::exception& e)
494 {
495 phosphor::logging::log<phosphor::logging::level::ERR>(
496 "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
497 phosphor::logging::entry("INTF=%s", interface),
498 phosphor::logging::entry("WHAT=%s", e.what()));
499 }
500
501 return properties;
502 }
503
getSensorConfigurationInterface(const std::map<std::string,std::vector<std::string>> & sensorInterfacesResponse)504 static inline const std::string* getSensorConfigurationInterface(
505 const std::map<std::string, std::vector<std::string>>&
506 sensorInterfacesResponse)
507 {
508 auto entityManagerService =
509 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
510 if (entityManagerService == sensorInterfacesResponse.end())
511 {
512 return nullptr;
513 }
514
515 // Find the fan configuration first (fans can have multiple configuration
516 // interfaces).
517 for (const auto& entry : entityManagerService->second)
518 {
519 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
520 entry == "xyz.openbmc_project.Configuration.I2CFan" ||
521 entry == "xyz.openbmc_project.Configuration.NuvotonFan")
522 {
523 return &entry;
524 }
525 }
526
527 for (const auto& entry : entityManagerService->second)
528 {
529 if (boost::algorithm::starts_with(entry,
530 "xyz.openbmc_project.Configuration."))
531 {
532 return &entry;
533 }
534 }
535
536 return nullptr;
537 }
538
539 // Follow Association properties for Sensor back to the Board dbus object to
540 // check for an EntityId and EntityInstance property.
updateIpmiFromAssociation(const std::string & path,const SensorMap & sensorMap,uint8_t & entityId,uint8_t & entityInstance)541 static inline void updateIpmiFromAssociation(
542 const std::string& path, const SensorMap& sensorMap, uint8_t& entityId,
543 uint8_t& entityInstance)
544 {
545 namespace fs = std::filesystem;
546
547 auto sensorAssociationObject =
548 sensorMap.find("xyz.openbmc_project.Association.Definitions");
549 if (sensorAssociationObject == sensorMap.end())
550 {
551 if constexpr (debug)
552 {
553 std::fprintf(stderr, "path=%s, no association interface found\n",
554 path.c_str());
555 }
556
557 return;
558 }
559
560 auto associationObject =
561 sensorAssociationObject->second.find("Associations");
562 if (associationObject == sensorAssociationObject->second.end())
563 {
564 if constexpr (debug)
565 {
566 std::fprintf(stderr, "path=%s, no association records found\n",
567 path.c_str());
568 }
569
570 return;
571 }
572
573 std::vector<Association> associationValues =
574 std::get<std::vector<Association>>(associationObject->second);
575
576 // loop through the Associations looking for the right one:
577 for (const auto& entry : associationValues)
578 {
579 // forward, reverse, endpoint
580 const std::string& forward = std::get<0>(entry);
581 const std::string& reverse = std::get<1>(entry);
582 const std::string& endpoint = std::get<2>(entry);
583
584 // We only currently concern ourselves with chassis+all_sensors.
585 if (!(forward == "chassis" && reverse == "all_sensors"))
586 {
587 continue;
588 }
589
590 // the endpoint is the board entry provided by
591 // Entity-Manager. so let's grab its properties if it has
592 // the right interface.
593
594 // just try grabbing the properties first.
595 std::map<std::string, DbusVariant> ipmiProperties =
596 getEntityManagerProperties(
597 endpoint.c_str(),
598 "xyz.openbmc_project.Inventory.Decorator.Ipmi");
599
600 auto entityIdProp = ipmiProperties.find("EntityId");
601 auto entityInstanceProp = ipmiProperties.find("EntityInstance");
602 if (entityIdProp != ipmiProperties.end())
603 {
604 entityId =
605 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
606 }
607 if (entityInstanceProp != ipmiProperties.end())
608 {
609 entityInstance = static_cast<uint8_t>(
610 std::get<uint64_t>(entityInstanceProp->second));
611 }
612
613 // Now check the entity-manager entry for this sensor to see
614 // if it has its own value and use that instead.
615 //
616 // In theory, checking this first saves us from checking
617 // both, except in most use-cases identified, there won't be
618 // a per sensor override, so we need to always check both.
619 std::string sensorNameFromPath = fs::path(path).filename();
620
621 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
622
623 // Download the interfaces for the sensor from
624 // Entity-Manager to find the name of the configuration
625 // interface.
626 std::map<std::string, std::vector<std::string>>
627 sensorInterfacesResponse =
628 getObjectInterfaces(sensorConfigPath.c_str());
629
630 const std::string* configurationInterface =
631 getSensorConfigurationInterface(sensorInterfacesResponse);
632
633 // We didnt' find a configuration interface for this sensor, but we
634 // followed the Association property to get here, so we're done
635 // searching.
636 if (!configurationInterface)
637 {
638 break;
639 }
640
641 // We found a configuration interface.
642 std::map<std::string, DbusVariant> configurationProperties =
643 getEntityManagerProperties(sensorConfigPath.c_str(),
644 configurationInterface->c_str());
645
646 entityIdProp = configurationProperties.find("EntityId");
647 entityInstanceProp = configurationProperties.find("EntityInstance");
648 if (entityIdProp != configurationProperties.end())
649 {
650 entityId =
651 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
652 }
653 if (entityInstanceProp != configurationProperties.end())
654 {
655 entityInstance = static_cast<uint8_t>(
656 std::get<uint64_t>(entityInstanceProp->second));
657 }
658
659 // stop searching Association records.
660 break;
661 } // end for Association vectors.
662
663 if constexpr (debug)
664 {
665 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
666 path.c_str(), entityId, entityInstance);
667 }
668 }
669
670 } // namespace ipmi
671