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