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