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