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