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