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