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 = lbdUpdateSensorTree("/xyz/openbmc_project/sensors",
106                                          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         return invalidSensorNumber;
275     }
276 }
277 
278 uint8_t getSensorEventTypeFromPath(const std::string& path)
279 {
280     uint8_t sensorEventType = 0;
281     std::string type = getSensorTypeStringFromPath(path);
282     auto findSensor = sensorTypes.find(type.c_str());
283     if (findSensor != sensorTypes.end())
284     {
285         sensorEventType = static_cast<uint8_t>(
286             std::get<sensorEventTypeCodes>(findSensor->second));
287     }
288 
289     return sensorEventType;
290 }
291 
292 std::string getPathFromSensorNumber(uint16_t sensorNum)
293 {
294     std::shared_ptr<SensorNumMap> sensorNumMapPtr;
295     details::getSensorNumMap(sensorNumMapPtr);
296     if (!sensorNumMapPtr)
297     {
298         return std::string();
299     }
300 
301     try
302     {
303         return sensorNumMapPtr->left.at(sensorNum);
304     }
305     catch (const std::out_of_range& e)
306     {
307         return std::string();
308     }
309 }
310 
311 namespace ipmi
312 {
313 
314 std::map<std::string, std::vector<std::string>>
315     getObjectInterfaces(const char* path)
316 {
317     std::map<std::string, std::vector<std::string>> interfacesResponse;
318     std::vector<std::string> interfaces;
319     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
320 
321     sdbusplus::message_t getObjectMessage =
322         dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
323                               "/xyz/openbmc_project/object_mapper",
324                               "xyz.openbmc_project.ObjectMapper", "GetObject");
325     getObjectMessage.append(path, interfaces);
326 
327     try
328     {
329         sdbusplus::message_t response = dbus->call(getObjectMessage);
330         response.read(interfacesResponse);
331     }
332     catch (const std::exception& e)
333     {
334         phosphor::logging::log<phosphor::logging::level::ERR>(
335             "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
336             phosphor::logging::entry("WHAT=%s", e.what()));
337     }
338 
339     return interfacesResponse;
340 }
341 
342 std::map<std::string, Value> getEntityManagerProperties(const char* path,
343                                                         const char* interface)
344 {
345     std::map<std::string, Value> properties;
346     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
347 
348     sdbusplus::message_t getProperties =
349         dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
350                               "org.freedesktop.DBus.Properties", "GetAll");
351     getProperties.append(interface);
352 
353     try
354     {
355         sdbusplus::message_t response = dbus->call(getProperties);
356         response.read(properties);
357     }
358     catch (const std::exception& e)
359     {
360         phosphor::logging::log<phosphor::logging::level::ERR>(
361             "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
362             phosphor::logging::entry("INTF=%s", interface),
363             phosphor::logging::entry("WHAT=%s", e.what()));
364     }
365 
366     return properties;
367 }
368 
369 // Fetch the ipmiDecoratorPaths to get the list of dbus objects that
370 // have ipmi decorator to prevent unnessary dbus call to fetch the info
371 std::optional<std::unordered_set<std::string>>&
372     getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx)
373 {
374     static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
375 
376     if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
377     {
378         return ipmiDecoratorPaths;
379     }
380 
381     boost::system::error_code ec;
382     std::vector<std::string> paths =
383         (*ctx)->bus->yield_method_call<std::vector<std::string>>(
384             (*ctx)->yield, ec, "xyz.openbmc_project.ObjectMapper",
385             "/xyz/openbmc_project/object_mapper",
386             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
387             int32_t(0),
388             std::array<const char*, 1>{
389                 "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
390     if (ec)
391     {
392         return ipmiDecoratorPaths;
393     }
394 
395     ipmiDecoratorPaths = std::unordered_set<std::string>(paths.begin(),
396                                                          paths.end());
397     return ipmiDecoratorPaths;
398 }
399 
400 const std::string* getSensorConfigurationInterface(
401     const std::map<std::string, std::vector<std::string>>&
402         sensorInterfacesResponse)
403 {
404     auto entityManagerService =
405         sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
406     if (entityManagerService == sensorInterfacesResponse.end())
407     {
408         return nullptr;
409     }
410 
411     // Find the fan configuration first (fans can have multiple configuration
412     // interfaces).
413     for (const auto& entry : entityManagerService->second)
414     {
415         if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
416             entry == "xyz.openbmc_project.Configuration.I2CFan" ||
417             entry == "xyz.openbmc_project.Configuration.NuvotonFan")
418         {
419             return &entry;
420         }
421     }
422 
423     for (const auto& entry : entityManagerService->second)
424     {
425         if (boost::algorithm::starts_with(entry,
426                                           "xyz.openbmc_project.Configuration."))
427         {
428             return &entry;
429         }
430     }
431 
432     return nullptr;
433 }
434 
435 // Follow Association properties for Sensor back to the Board dbus object to
436 // check for an EntityId and EntityInstance property.
437 void updateIpmiFromAssociation(
438     const std::string& path,
439     const std::unordered_set<std::string>& ipmiDecoratorPaths,
440     const DbusInterfaceMap& sensorMap, uint8_t& entityId,
441     uint8_t& entityInstance)
442 {
443     namespace fs = std::filesystem;
444 
445     auto sensorAssociationObject =
446         sensorMap.find("xyz.openbmc_project.Association.Definitions");
447     if (sensorAssociationObject == sensorMap.end())
448     {
449         if constexpr (debug)
450         {
451             std::fprintf(stderr, "path=%s, no association interface found\n",
452                          path.c_str());
453         }
454 
455         return;
456     }
457 
458     auto associationObject =
459         sensorAssociationObject->second.find("Associations");
460     if (associationObject == sensorAssociationObject->second.end())
461     {
462         if constexpr (debug)
463         {
464             std::fprintf(stderr, "path=%s, no association records found\n",
465                          path.c_str());
466         }
467 
468         return;
469     }
470 
471     std::vector<Association> associationValues =
472         std::get<std::vector<Association>>(associationObject->second);
473 
474     // loop through the Associations looking for the right one:
475     for (const auto& entry : associationValues)
476     {
477         // forward, reverse, endpoint
478         const std::string& forward = std::get<0>(entry);
479         const std::string& reverse = std::get<1>(entry);
480         const std::string& endpoint = std::get<2>(entry);
481 
482         // We only currently concern ourselves with chassis+all_sensors.
483         if (!(forward == "chassis" && reverse == "all_sensors"))
484         {
485             continue;
486         }
487 
488         // the endpoint is the board entry provided by
489         // Entity-Manager. so let's grab its properties if it has
490         // the right interface.
491 
492         // just try grabbing the properties first.
493         ipmi::PropertyMap::iterator entityIdProp;
494         ipmi::PropertyMap::iterator entityInstanceProp;
495         if (ipmiDecoratorPaths.contains(endpoint))
496         {
497             std::map<std::string, Value> ipmiProperties =
498                 getEntityManagerProperties(
499                     endpoint.c_str(),
500                     "xyz.openbmc_project.Inventory.Decorator.Ipmi");
501 
502             entityIdProp = ipmiProperties.find("EntityId");
503             entityInstanceProp = ipmiProperties.find("EntityInstance");
504             if (entityIdProp != ipmiProperties.end())
505             {
506                 entityId = static_cast<uint8_t>(
507                     std::get<uint64_t>(entityIdProp->second));
508             }
509             if (entityInstanceProp != ipmiProperties.end())
510             {
511                 entityInstance = static_cast<uint8_t>(
512                     std::get<uint64_t>(entityInstanceProp->second));
513             }
514         }
515 
516         // Now check the entity-manager entry for this sensor to see
517         // if it has its own value and use that instead.
518         //
519         // In theory, checking this first saves us from checking
520         // both, except in most use-cases identified, there won't be
521         // a per sensor override, so we need to always check both.
522         std::string sensorNameFromPath = fs::path(path).filename();
523 
524         std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
525 
526         // Download the interfaces for the sensor from
527         // Entity-Manager to find the name of the configuration
528         // interface.
529         std::map<std::string, std::vector<std::string>>
530             sensorInterfacesResponse =
531                 getObjectInterfaces(sensorConfigPath.c_str());
532 
533         const std::string* configurationInterface =
534             getSensorConfigurationInterface(sensorInterfacesResponse);
535 
536         // If there are multi association path settings and only one path exist,
537         // we need to continue if cannot find configuration interface for this
538         // sensor.
539         if (!configurationInterface)
540         {
541             continue;
542         }
543 
544         // We found a configuration interface.
545         std::map<std::string, Value> configurationProperties =
546             getEntityManagerProperties(sensorConfigPath.c_str(),
547                                        configurationInterface->c_str());
548 
549         entityIdProp = configurationProperties.find("EntityId");
550         entityInstanceProp = configurationProperties.find("EntityInstance");
551         if (entityIdProp != configurationProperties.end())
552         {
553             entityId =
554                 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
555         }
556         if (entityInstanceProp != configurationProperties.end())
557         {
558             entityInstance = static_cast<uint8_t>(
559                 std::get<uint64_t>(entityInstanceProp->second));
560         }
561 
562         // stop searching Association records.
563         break;
564     } // end for Association vectors.
565 
566     if constexpr (debug)
567     {
568         std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
569                      path.c_str(), entityId, entityInstance);
570     }
571 }
572 
573 } // namespace ipmi
574