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 "dbus-sdr/sdrutils.hpp"
18 
19 #include <ipmid/utils.hpp>
20 #include <nlohmann/json.hpp>
21 #include <phosphor-logging/lg2.hpp>
22 
23 #include <fstream>
24 #include <optional>
25 #include <unordered_set>
26 
27 #ifdef FEATURE_HYBRID_SENSORS
28 
29 #include <ipmid/utils.hpp>
30 namespace ipmi
31 {
32 namespace sensor
33 {
34 extern const IdInfoMap sensors;
35 } // namespace sensor
36 } // namespace ipmi
37 
38 #endif
39 
40 boost::container::flat_map<
41     const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
42     sensorTypes{
43         {{"temperature", std::make_pair(SensorTypeCodes::temperature,
44                                         SensorEventTypeCodes::threshold)},
45          {"voltage", std::make_pair(SensorTypeCodes::voltage,
46                                     SensorEventTypeCodes::threshold)},
47          {"current", std::make_pair(SensorTypeCodes::current,
48                                     SensorEventTypeCodes::threshold)},
49          {"fan_tach", std::make_pair(SensorTypeCodes::fan,
50                                      SensorEventTypeCodes::threshold)},
51          {"fan_pwm", std::make_pair(SensorTypeCodes::fan,
52                                     SensorEventTypeCodes::threshold)},
53          {"intrusion", std::make_pair(SensorTypeCodes::physical_security,
54                                       SensorEventTypeCodes::sensorSpecified)},
55          {"processor", std::make_pair(SensorTypeCodes::processor,
56                                       SensorEventTypeCodes::sensorSpecified)},
57          {"power", std::make_pair(SensorTypeCodes::other,
58                                   SensorEventTypeCodes::threshold)},
59          {"memory", std::make_pair(SensorTypeCodes::memory,
60                                    SensorEventTypeCodes::sensorSpecified)},
61          {"state", std::make_pair(SensorTypeCodes::power_unit,
62                                   SensorEventTypeCodes::sensorSpecified)},
63          {"buttons", std::make_pair(SensorTypeCodes::buttons,
64                                     SensorEventTypeCodes::sensorSpecified)},
65          {"watchdog", std::make_pair(SensorTypeCodes::watchdog2,
66                                      SensorEventTypeCodes::sensorSpecified)},
67          {"entity", std::make_pair(SensorTypeCodes::entity,
68                                    SensorEventTypeCodes::sensorSpecified)},
69          {"energy", std::make_pair(SensorTypeCodes::other,
70                                    SensorEventTypeCodes::threshold)}}};
71 
72 namespace details
73 {
74 
75 // IPMI supports a smaller number of sensors than are available via Redfish.
76 // Trim the list of sensors, via a configuration file.
77 // Read the IPMI Sensor Filtering section in docs/configuration.md for
78 // a more detailed description.
filterSensors(SensorSubTree & subtree)79 static void filterSensors(SensorSubTree& subtree)
80 {
81     constexpr const char* filterFilename =
82         "/usr/share/ipmi-providers/sensor_filter.json";
83     std::ifstream filterFile(filterFilename);
84     if (!filterFile.good())
85     {
86         return;
87     }
88     nlohmann::json sensorFilterJSON =
89         nlohmann::json::parse(filterFile, nullptr, false);
90     nlohmann::json::iterator svcFilterit =
91         sensorFilterJSON.find("ServiceFilter");
92     if (svcFilterit == sensorFilterJSON.end())
93     {
94         return;
95     }
96 
97     subtree.erase(std::remove_if(subtree.begin(), subtree.end(),
98                                  [svcFilterit](SensorSubTree::value_type& kv) {
99                                      auto& [_, serviceToIfaces] = kv;
100 
101                                      for (auto service = svcFilterit->begin();
102                                           service != svcFilterit->end();
103                                           ++service)
104                                      {
105                                          serviceToIfaces.erase(*service);
106                                      }
107                                      return serviceToIfaces.empty();
108                                  }),
109                   subtree.end());
110 }
111 
getSensorSubtree(std::shared_ptr<SensorSubTree> & subtree)112 uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
113 {
114     static std::shared_ptr<SensorSubTree> sensorTreePtr;
115     static uint16_t sensorUpdatedIndex = 0;
116     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
117     static sdbusplus::bus::match_t sensorAdded(
118         *dbus,
119         "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
120         "sensors/'",
121         [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
122 
123     static sdbusplus::bus::match_t sensorRemoved(
124         *dbus,
125         "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
126         "openbmc_project/sensors/'",
127         [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
128 
129     if (sensorTreePtr)
130     {
131         subtree = sensorTreePtr;
132         return sensorUpdatedIndex;
133     }
134 
135     sensorTreePtr = std::make_shared<SensorSubTree>();
136 
137     static constexpr const int32_t depth = 2;
138 
139     auto lbdUpdateSensorTree = [&dbus](const char* path,
140                                        const auto& interfaces) {
141         auto mapperCall = dbus->new_method_call(
142             "xyz.openbmc_project.ObjectMapper",
143             "/xyz/openbmc_project/object_mapper",
144             "xyz.openbmc_project.ObjectMapper", "GetSubTree");
145         SensorSubTree sensorTreePartial;
146 
147         mapperCall.append(path, depth, interfaces);
148 
149         try
150         {
151             auto mapperReply = dbus->call(mapperCall);
152             mapperReply.read(sensorTreePartial);
153         }
154         catch (const sdbusplus::exception_t& e)
155         {
156             lg2::error("Failed to update subtree, path: {PATH}, error: {ERROR}",
157                        "PATH", path, "ERROR", e);
158             return false;
159         }
160         if constexpr (debug)
161         {
162             std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n",
163                          sensorTreePartial.size(), path);
164         }
165         sensorTreePtr->merge(std::move(sensorTreePartial));
166         return true;
167     };
168 
169     // Add sensors to SensorTree
170     static constexpr const std::array sensorInterfaces = {
171         "xyz.openbmc_project.Sensor.Value",
172         "xyz.openbmc_project.Sensor.ValueMutability",
173         "xyz.openbmc_project.Sensor.Threshold.Warning",
174         "xyz.openbmc_project.Sensor.Threshold.Critical"};
175     static constexpr const std::array vrInterfaces = {
176         "xyz.openbmc_project.Control.VoltageRegulatorMode"};
177 
178     bool sensorRez =
179         lbdUpdateSensorTree("/xyz/openbmc_project/sensors", sensorInterfaces);
180 
181 #ifdef FEATURE_HYBRID_SENSORS
182 
183     if (!ipmi::sensor::sensors.empty())
184     {
185         for (const auto& sensor : ipmi::sensor::sensors)
186         {
187             // Threshold sensors should not be emplaced in here.
188             if (boost::starts_with(sensor.second.sensorPath,
189                                    "/xyz/openbmc_project/sensors/"))
190             {
191                 continue;
192             }
193 
194             // The bus service name is not listed in ipmi::sensor::Info. Give it
195             // an empty string. For those function using non-threshold sensors,
196             // the bus service name will be retrieved in an alternative way.
197             boost::container::flat_map<std::string, std::vector<std::string>>
198                 connectionMap{
199                     {"", {sensor.second.propertyInterfaces.begin()->first}}};
200             sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
201         }
202     }
203 
204 #endif
205 
206     // Error if searching for sensors failed.
207     if (!sensorRez)
208     {
209         return sensorUpdatedIndex;
210     }
211 
212     filterSensors(*sensorTreePtr);
213     // Add VR control as optional search path.
214     (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces);
215 
216     subtree = sensorTreePtr;
217     sensorUpdatedIndex++;
218     // The SDR is being regenerated, wipe the old stats
219     sdrStatsTable.wipeTable();
220     sdrWriteTable.wipeTable();
221     return sensorUpdatedIndex;
222 }
223 
getSensorNumMap(std::shared_ptr<SensorNumMap> & sensorNumMap)224 bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
225 {
226     static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
227     bool sensorNumMapUpated = false;
228     static uint16_t prevSensorUpdatedIndex = 0;
229     std::shared_ptr<SensorSubTree> sensorTree;
230     uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
231     if (!sensorTree)
232     {
233         return sensorNumMapUpated;
234     }
235 
236     if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
237     {
238         sensorNumMap = sensorNumMapPtr;
239         return sensorNumMapUpated;
240     }
241     prevSensorUpdatedIndex = curSensorUpdatedIndex;
242 
243     sensorNumMapPtr = std::make_shared<SensorNumMap>();
244 
245     uint16_t sensorNum = 0;
246     uint16_t sensorIndex = 0;
247     for (const auto& sensor : *sensorTree)
248     {
249         sensorNumMapPtr->insert(
250             SensorNumMap::value_type(sensorNum, sensor.first));
251         sensorIndex++;
252         if (sensorIndex == maxSensorsPerLUN)
253         {
254             sensorIndex = lun1Sensor0;
255         }
256         else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
257         {
258             // Skip assigning LUN 0x2 any sensors
259             sensorIndex = lun3Sensor0;
260         }
261         else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
262         {
263             // this is an error, too many IPMI sensors
264             throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
265         }
266         sensorNum = sensorIndex;
267     }
268     sensorNumMap = sensorNumMapPtr;
269     sensorNumMapUpated = true;
270     return sensorNumMapUpated;
271 }
272 } // namespace details
273 
getSensorSubtree(SensorSubTree & subtree)274 bool getSensorSubtree(SensorSubTree& subtree)
275 {
276     std::shared_ptr<SensorSubTree> sensorTree;
277     details::getSensorSubtree(sensorTree);
278     if (!sensorTree)
279     {
280         return false;
281     }
282 
283     subtree = *sensorTree;
284     return true;
285 }
286 
287 #ifdef FEATURE_HYBRID_SENSORS
288 // Static sensors are listed in sensor-gen.cpp.
289 ipmi::sensor::IdInfoMap::const_iterator
findStaticSensor(const std::string & path)290     findStaticSensor(const std::string& path)
291 {
292     return std::find_if(
293         ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
294         [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
295             return findSensor.second.sensorPath == path;
296         });
297 }
298 #endif
299 
getSensorTypeStringFromPath(const std::string & path)300 std::string getSensorTypeStringFromPath(const std::string& path)
301 {
302     // get sensor type string from path, path is defined as
303     // /xyz/openbmc_project/sensors/<type>/label
304     size_t typeEnd = path.rfind("/");
305     if (typeEnd == std::string::npos)
306     {
307         return path;
308     }
309     size_t typeStart = path.rfind("/", typeEnd - 1);
310     if (typeStart == std::string::npos)
311     {
312         return path;
313     }
314     // Start at the character after the '/'
315     typeStart++;
316     return path.substr(typeStart, typeEnd - typeStart);
317 }
318 
getSensorTypeFromPath(const std::string & path)319 uint8_t getSensorTypeFromPath(const std::string& path)
320 {
321     uint8_t sensorType = 0;
322     std::string type = getSensorTypeStringFromPath(path);
323     auto findSensor = sensorTypes.find(type.c_str());
324     if (findSensor != sensorTypes.end())
325     {
326         sensorType =
327             static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second));
328     } // else default 0x0 RESERVED
329 
330     return sensorType;
331 }
332 
getSensorNumberFromPath(const std::string & path)333 uint16_t getSensorNumberFromPath(const std::string& path)
334 {
335     std::shared_ptr<SensorNumMap> sensorNumMapPtr;
336     details::getSensorNumMap(sensorNumMapPtr);
337     if (!sensorNumMapPtr)
338     {
339         return invalidSensorNumber;
340     }
341 
342     try
343     {
344         return sensorNumMapPtr->right.at(path);
345     }
346     catch (const std::out_of_range& e)
347     {
348         return invalidSensorNumber;
349     }
350 }
351 
getSensorEventTypeFromPath(const std::string & path)352 uint8_t getSensorEventTypeFromPath(const std::string& path)
353 {
354     uint8_t sensorEventType = 0;
355     std::string type = getSensorTypeStringFromPath(path);
356     auto findSensor = sensorTypes.find(type.c_str());
357     if (findSensor != sensorTypes.end())
358     {
359         sensorEventType = static_cast<uint8_t>(
360             std::get<sensorEventTypeCodes>(findSensor->second));
361     }
362 
363     return sensorEventType;
364 }
365 
getPathFromSensorNumber(uint16_t sensorNum)366 std::string getPathFromSensorNumber(uint16_t sensorNum)
367 {
368     std::shared_ptr<SensorNumMap> sensorNumMapPtr;
369     details::getSensorNumMap(sensorNumMapPtr);
370     if (!sensorNumMapPtr)
371     {
372         return std::string();
373     }
374 
375     try
376     {
377         return sensorNumMapPtr->left.at(sensorNum);
378     }
379     catch (const std::out_of_range& e)
380     {
381         return std::string();
382     }
383 }
384 
385 namespace ipmi
386 {
387 
388 std::optional<std::map<std::string, std::vector<std::string>>>
getObjectInterfaces(const char * path)389     getObjectInterfaces(const char* path)
390 {
391     std::map<std::string, std::vector<std::string>> interfacesResponse;
392     std::vector<std::string> interfaces;
393     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
394 
395     sdbusplus::message_t getObjectMessage =
396         dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
397                               "/xyz/openbmc_project/object_mapper",
398                               "xyz.openbmc_project.ObjectMapper", "GetObject");
399     getObjectMessage.append(path, interfaces);
400 
401     try
402     {
403         sdbusplus::message_t response = dbus->call(getObjectMessage);
404         response.read(interfacesResponse);
405     }
406     catch (const std::exception& e)
407     {
408         return std::nullopt;
409     }
410 
411     return interfacesResponse;
412 }
413 
414 std::map<std::string, Value>
getEntityManagerProperties(const char * path,const char * interface)415     getEntityManagerProperties(const char* path, const char* interface)
416 {
417     std::map<std::string, Value> properties;
418     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
419 
420     sdbusplus::message_t getProperties =
421         dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
422                               "org.freedesktop.DBus.Properties", "GetAll");
423     getProperties.append(interface);
424 
425     try
426     {
427         sdbusplus::message_t response = dbus->call(getProperties);
428         response.read(properties);
429     }
430     catch (const std::exception& e)
431     {
432         lg2::error("Failed to GetAll, path: {PATH}, interface: {INTERFACE}, "
433                    "error: {ERROR}",
434                    "PATH", path, "INTERFACE", interface, "ERROR", e);
435     }
436 
437     return properties;
438 }
439 
440 // Fetch the ipmiDecoratorPaths to get the list of dbus objects that
441 // have ipmi decorator to prevent unnessary dbus call to fetch the info
442 std::optional<std::unordered_set<std::string>>&
getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr> & ctx)443     getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx)
444 {
445     static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
446 
447     if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
448     {
449         return ipmiDecoratorPaths;
450     }
451 
452     boost::system::error_code ec;
453     std::vector<std::string> paths =
454         (*ctx)->bus->yield_method_call<std::vector<std::string>>(
455             (*ctx)->yield, ec, "xyz.openbmc_project.ObjectMapper",
456             "/xyz/openbmc_project/object_mapper",
457             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
458             int32_t(0),
459             std::array<const char*, 1>{
460                 "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
461     if (ec)
462     {
463         return ipmiDecoratorPaths;
464     }
465 
466     ipmiDecoratorPaths =
467         std::unordered_set<std::string>(paths.begin(), paths.end());
468     return ipmiDecoratorPaths;
469 }
470 
getSensorConfigurationInterface(const std::map<std::string,std::vector<std::string>> & sensorInterfacesResponse)471 const std::string* getSensorConfigurationInterface(
472     const std::map<std::string, std::vector<std::string>>&
473         sensorInterfacesResponse)
474 {
475     auto entityManagerService =
476         sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
477     if (entityManagerService == sensorInterfacesResponse.end())
478     {
479         return nullptr;
480     }
481 
482     // Find the fan configuration first (fans can have multiple configuration
483     // interfaces).
484     for (const auto& entry : entityManagerService->second)
485     {
486         if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
487             entry == "xyz.openbmc_project.Configuration.I2CFan" ||
488             entry == "xyz.openbmc_project.Configuration.NuvotonFan")
489         {
490             return &entry;
491         }
492     }
493 
494     for (const auto& entry : entityManagerService->second)
495     {
496         if (boost::algorithm::starts_with(entry,
497                                           "xyz.openbmc_project.Configuration."))
498         {
499             return &entry;
500         }
501     }
502 
503     return nullptr;
504 }
505 
506 // Follow Association properties for Sensor back to the Board dbus object to
507 // check for an EntityId and EntityInstance property.
updateIpmiFromAssociation(const std::string & path,const std::unordered_set<std::string> & ipmiDecoratorPaths,const DbusInterfaceMap & sensorMap,uint8_t & entityId,uint8_t & entityInstance)508 void updateIpmiFromAssociation(
509     const std::string& path,
510     const std::unordered_set<std::string>& ipmiDecoratorPaths,
511     const DbusInterfaceMap& sensorMap, uint8_t& entityId,
512     uint8_t& entityInstance)
513 {
514     namespace fs = std::filesystem;
515 
516     auto sensorAssociationObject =
517         sensorMap.find("xyz.openbmc_project.Association.Definitions");
518     if (sensorAssociationObject == sensorMap.end())
519     {
520         if constexpr (debug)
521         {
522             std::fprintf(stderr, "path=%s, no association interface found\n",
523                          path.c_str());
524         }
525 
526         return;
527     }
528 
529     auto associationObject =
530         sensorAssociationObject->second.find("Associations");
531     if (associationObject == sensorAssociationObject->second.end())
532     {
533         if constexpr (debug)
534         {
535             std::fprintf(stderr, "path=%s, no association records found\n",
536                          path.c_str());
537         }
538 
539         return;
540     }
541 
542     std::vector<Association> associationValues =
543         std::get<std::vector<Association>>(associationObject->second);
544 
545     // loop through the Associations looking for the right one:
546     for (const auto& entry : associationValues)
547     {
548         // forward, reverse, endpoint
549         const std::string& forward = std::get<0>(entry);
550         const std::string& reverse = std::get<1>(entry);
551         const std::string& endpoint = std::get<2>(entry);
552 
553         // We only currently concern ourselves with chassis+all_sensors.
554         if (!(forward == "chassis" && reverse == "all_sensors"))
555         {
556             continue;
557         }
558 
559         // the endpoint is the board entry provided by
560         // Entity-Manager. so let's grab its properties if it has
561         // the right interface.
562 
563         // just try grabbing the properties first.
564         ipmi::PropertyMap::iterator entityIdProp;
565         ipmi::PropertyMap::iterator entityInstanceProp;
566         if (ipmiDecoratorPaths.contains(endpoint))
567         {
568             std::map<std::string, Value> ipmiProperties =
569                 getEntityManagerProperties(
570                     endpoint.c_str(),
571                     "xyz.openbmc_project.Inventory.Decorator.Ipmi");
572 
573             entityIdProp = ipmiProperties.find("EntityId");
574             entityInstanceProp = ipmiProperties.find("EntityInstance");
575             if (entityIdProp != ipmiProperties.end())
576             {
577                 entityId = static_cast<uint8_t>(
578                     std::get<uint64_t>(entityIdProp->second));
579             }
580             if (entityInstanceProp != ipmiProperties.end())
581             {
582                 entityInstance = static_cast<uint8_t>(
583                     std::get<uint64_t>(entityInstanceProp->second));
584             }
585         }
586 
587         // Now check the entity-manager entry for this sensor to see
588         // if it has its own value and use that instead.
589         //
590         // In theory, checking this first saves us from checking
591         // both, except in most use-cases identified, there won't be
592         // a per sensor override, so we need to always check both.
593         std::string sensorNameFromPath = fs::path(path).filename();
594 
595         std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
596 
597         // Download the interfaces for the sensor from
598         // Entity-Manager to find the name of the configuration
599         // interface.
600         std::optional<std::map<std::string, std::vector<std::string>>>
601             sensorInterfacesResponseOpt =
602                 getObjectInterfaces(sensorConfigPath.c_str());
603 
604         if (!sensorInterfacesResponseOpt.has_value())
605         {
606             lg2::debug("Failed to GetObject, path: {PATH}", "PATH",
607                        sensorConfigPath);
608             continue;
609         }
610 
611         const std::string* configurationInterface =
612             getSensorConfigurationInterface(
613                 sensorInterfacesResponseOpt.value());
614 
615         // If there are multi association path settings and only one path exist,
616         // we need to continue if cannot find configuration interface for this
617         // sensor.
618         if (!configurationInterface)
619         {
620             continue;
621         }
622 
623         // We found a configuration interface.
624         std::map<std::string, Value> configurationProperties =
625             getEntityManagerProperties(sensorConfigPath.c_str(),
626                                        configurationInterface->c_str());
627 
628         entityIdProp = configurationProperties.find("EntityId");
629         entityInstanceProp = configurationProperties.find("EntityInstance");
630         if (entityIdProp != configurationProperties.end())
631         {
632             entityId =
633                 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
634         }
635         if (entityInstanceProp != configurationProperties.end())
636         {
637             entityInstance = static_cast<uint8_t>(
638                 std::get<uint64_t>(entityInstanceProp->second));
639         }
640 
641         // stop searching Association records.
642         break;
643     } // end for Association vectors.
644 
645     if constexpr (debug)
646     {
647         std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
648                      path.c_str(), entityId, entityInstance);
649     }
650 }
651 
652 } // namespace ipmi
653