xref: /openbmc/intel-ipmi-oem/include/sdrutils.hpp (revision 1bcced0820999e7e050257b9f78cf36b23eb762b)
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