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