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 "commandutils.hpp"
18 
19 #include <boost/algorithm/string.hpp>
20 #include <boost/bimap.hpp>
21 #include <boost/container/flat_map.hpp>
22 #include <cstdio>
23 #include <cstring>
24 #include <exception>
25 #include <filesystem>
26 #include <map>
27 #include <phosphor-logging/log.hpp>
28 #include <sdbusplus/bus/match.hpp>
29 #include <string>
30 #include <vector>
31 
32 #pragma once
33 
34 struct CmpStrVersion
35 {
36     bool operator()(std::string a, std::string b) const
37     {
38         return strverscmp(a.c_str(), b.c_str()) < 0;
39     }
40 };
41 
42 using SensorSubTree = boost::container::flat_map<
43     std::string,
44     boost::container::flat_map<std::string, std::vector<std::string>>,
45     CmpStrVersion>;
46 
47 using SensorNumMap = boost::bimap<int, std::string>;
48 
49 namespace details
50 {
51 inline static bool getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
52 {
53     static std::shared_ptr<SensorSubTree> sensorTreePtr;
54     sd_bus* bus = NULL;
55     int ret = sd_bus_default_system(&bus);
56     if (ret < 0)
57     {
58         phosphor::logging::log<phosphor::logging::level::ERR>(
59             "Failed to connect to system bus",
60             phosphor::logging::entry("ERRNO=0x%X", -ret));
61         sd_bus_unref(bus);
62         return false;
63     }
64     sdbusplus::bus::bus dbus(bus);
65     static sdbusplus::bus::match::match sensorAdded(
66         dbus,
67         "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
68         "sensors/'",
69         [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
70 
71     static sdbusplus::bus::match::match sensorRemoved(
72         dbus,
73         "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
74         "openbmc_project/sensors/'",
75         [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
76 
77     bool sensorTreeUpdated = false;
78     if (sensorTreePtr)
79     {
80         subtree = sensorTreePtr;
81         return sensorTreeUpdated;
82     }
83 
84     sensorTreePtr = std::make_shared<SensorSubTree>();
85 
86     auto mapperCall =
87         dbus.new_method_call("xyz.openbmc_project.ObjectMapper",
88                              "/xyz/openbmc_project/object_mapper",
89                              "xyz.openbmc_project.ObjectMapper", "GetSubTree");
90     static constexpr const auto depth = 2;
91     static constexpr std::array<const char*, 3> interfaces = {
92         "xyz.openbmc_project.Sensor.Value",
93         "xyz.openbmc_project.Sensor.Threshold.Warning",
94         "xyz.openbmc_project.Sensor.Threshold.Critical"};
95     mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces);
96 
97     try
98     {
99         auto mapperReply = dbus.call(mapperCall);
100         mapperReply.read(*sensorTreePtr);
101     }
102     catch (sdbusplus::exception_t& e)
103     {
104         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
105         return sensorTreeUpdated;
106     }
107     subtree = sensorTreePtr;
108     sensorTreeUpdated = true;
109     return sensorTreeUpdated;
110 }
111 
112 inline static bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
113 {
114     static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
115     bool sensorNumMapUpated = false;
116 
117     std::shared_ptr<SensorSubTree> sensorTree;
118     bool sensorTreeUpdated = details::getSensorSubtree(sensorTree);
119     if (!sensorTree)
120     {
121         return sensorNumMapUpated;
122     }
123 
124     if (!sensorTreeUpdated && sensorNumMapPtr)
125     {
126         sensorNumMap = sensorNumMapPtr;
127         return sensorNumMapUpated;
128     }
129 
130     sensorNumMapPtr = std::make_shared<SensorNumMap>();
131 
132     uint8_t sensorNum = 0;
133     for (const auto& sensor : *sensorTree)
134     {
135         sensorNumMapPtr->insert(
136             SensorNumMap::value_type(sensorNum++, sensor.first));
137     }
138     sensorNumMap = sensorNumMapPtr;
139     sensorNumMapUpated = true;
140     return sensorNumMapUpated;
141 }
142 } // namespace details
143 
144 inline static bool getSensorSubtree(SensorSubTree& subtree)
145 {
146     std::shared_ptr<SensorSubTree> sensorTree;
147     details::getSensorSubtree(sensorTree);
148     if (!sensorTree)
149     {
150         return false;
151     }
152 
153     subtree = *sensorTree;
154     return true;
155 }
156 
157 struct CmpStr
158 {
159     bool operator()(const char* a, const char* b) const
160     {
161         return std::strcmp(a, b) < 0;
162     }
163 };
164 
165 enum class SensorTypeCodes : uint8_t
166 {
167     reserved = 0x0,
168     temperature = 0x1,
169     voltage = 0x2,
170     current = 0x3,
171     fan = 0x4,
172     other = 0xB,
173 };
174 
175 const static boost::container::flat_map<const char*, SensorTypeCodes, CmpStr>
176     sensorTypes{{{"temperature", SensorTypeCodes::temperature},
177                  {"voltage", SensorTypeCodes::voltage},
178                  {"current", SensorTypeCodes::current},
179                  {"fan_tach", SensorTypeCodes::fan},
180                  {"fan_pwm", SensorTypeCodes::fan},
181                  {"power", SensorTypeCodes::other}}};
182 
183 inline static std::string getSensorTypeStringFromPath(const std::string& path)
184 {
185     // get sensor type string from path, path is defined as
186     // /xyz/openbmc_project/sensors/<type>/label
187     size_t typeEnd = path.rfind("/");
188     if (typeEnd == std::string::npos)
189     {
190         return path;
191     }
192     size_t typeStart = path.rfind("/", typeEnd - 1);
193     if (typeStart == std::string::npos)
194     {
195         return path;
196     }
197     // Start at the character after the '/'
198     typeStart++;
199     return path.substr(typeStart, typeEnd - typeStart);
200 }
201 
202 inline static uint8_t getSensorTypeFromPath(const std::string& path)
203 {
204     uint8_t sensorType = 0;
205     std::string type = getSensorTypeStringFromPath(path);
206     auto findSensor = sensorTypes.find(type.c_str());
207     if (findSensor != sensorTypes.end())
208     {
209         sensorType = static_cast<uint8_t>(findSensor->second);
210     } // else default 0x0 RESERVED
211 
212     return sensorType;
213 }
214 
215 inline static uint8_t getSensorNumberFromPath(const std::string& path)
216 {
217     std::shared_ptr<SensorNumMap> sensorNumMapPtr;
218     details::getSensorNumMap(sensorNumMapPtr);
219     if (!sensorNumMapPtr)
220     {
221         return 0xFF;
222     }
223 
224     try
225     {
226         return sensorNumMapPtr->right.at(path);
227     }
228     catch (std::out_of_range& e)
229     {
230         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
231         return 0xFF;
232     }
233 }
234 
235 inline static uint8_t getSensorEventTypeFromPath(const std::string& path)
236 {
237     // TODO: Add support for additional reading types as needed
238     return 0x1; // reading type = threshold
239 }
240 
241 inline static std::string getPathFromSensorNumber(uint8_t sensorNum)
242 {
243     std::shared_ptr<SensorNumMap> sensorNumMapPtr;
244     details::getSensorNumMap(sensorNumMapPtr);
245     if (!sensorNumMapPtr)
246     {
247         return std::string();
248     }
249 
250     try
251     {
252         return sensorNumMapPtr->left.at(sensorNum);
253     }
254     catch (std::out_of_range& e)
255     {
256         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
257         return std::string();
258     }
259 }
260 
261 namespace ipmi
262 {
263 
264 static inline std::map<std::string, std::vector<std::string>>
265     getObjectInterfaces(const char* path)
266 {
267     std::map<std::string, std::vector<std::string>> interfacesResponse;
268     std::vector<std::string> interfaces;
269     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
270 
271     sdbusplus::message::message getObjectMessage =
272         dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
273                               "/xyz/openbmc_project/object_mapper",
274                               "xyz.openbmc_project.ObjectMapper", "GetObject");
275     getObjectMessage.append(path, interfaces);
276 
277     try
278     {
279         sdbusplus::message::message response = dbus->call(getObjectMessage);
280         response.read(interfacesResponse);
281     }
282     catch (const std::exception& e)
283     {
284         phosphor::logging::log<phosphor::logging::level::ERR>(
285             "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
286             phosphor::logging::entry("WHAT=%s", e.what()));
287     }
288 
289     return interfacesResponse;
290 }
291 
292 static inline std::map<std::string, DbusVariant>
293     getEntityManagerProperties(const char* path, const char* interface)
294 {
295     std::map<std::string, DbusVariant> properties;
296     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
297 
298     sdbusplus::message::message getProperties =
299         dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
300                               "org.freedesktop.DBus.Properties", "GetAll");
301     getProperties.append(interface);
302 
303     try
304     {
305         sdbusplus::message::message response = dbus->call(getProperties);
306         response.read(properties);
307     }
308     catch (const std::exception& e)
309     {
310         phosphor::logging::log<phosphor::logging::level::ERR>(
311             "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
312             phosphor::logging::entry("INTF=%s", interface),
313             phosphor::logging::entry("WHAT=%s", e.what()));
314     }
315 
316     return properties;
317 }
318 
319 static inline const std::string* getSensorConfigurationInterface(
320     const std::map<std::string, std::vector<std::string>>&
321         sensorInterfacesResponse)
322 {
323     auto entityManagerService =
324         sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
325     if (entityManagerService == sensorInterfacesResponse.end())
326     {
327         return nullptr;
328     }
329 
330     // Find the fan configuration first (fans can have multiple configuration
331     // interfaces).
332     for (const auto& entry : entityManagerService->second)
333     {
334         if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
335             entry == "xyz.openbmc_project.Configuration.I2CFan" ||
336             entry == "xyz.openbmc_project.Configuration.NuvotonFan")
337         {
338             return &entry;
339         }
340     }
341 
342     for (const auto& entry : entityManagerService->second)
343     {
344         if (boost::algorithm::starts_with(entry,
345                                           "xyz.openbmc_project.Configuration."))
346         {
347             return &entry;
348         }
349     }
350 
351     return nullptr;
352 }
353 
354 // Follow Association properties for Sensor back to the Board dbus object to
355 // check for an EntityId and EntityInstance property.
356 static inline void updateIpmiFromAssociation(const std::string& path,
357                                              const SensorMap& sensorMap,
358                                              uint8_t& entityId,
359                                              uint8_t& entityInstance)
360 {
361     namespace fs = std::filesystem;
362 
363     auto sensorAssociationObject =
364         sensorMap.find("xyz.openbmc_project.Association.Definitions");
365     if (sensorAssociationObject == sensorMap.end())
366     {
367         if constexpr (debug)
368         {
369             std::fprintf(stderr, "path=%s, no association interface found\n",
370                          path.c_str());
371         }
372 
373         return;
374     }
375 
376     auto associationObject =
377         sensorAssociationObject->second.find("Associations");
378     if (associationObject == sensorAssociationObject->second.end())
379     {
380         if constexpr (debug)
381         {
382             std::fprintf(stderr, "path=%s, no association records found\n",
383                          path.c_str());
384         }
385 
386         return;
387     }
388 
389     std::vector<Association> associationValues =
390         std::get<std::vector<Association>>(associationObject->second);
391 
392     // loop through the Associations looking for the right one:
393     for (const auto& entry : associationValues)
394     {
395         // forward, reverse, endpoint
396         const std::string& forward = std::get<0>(entry);
397         const std::string& reverse = std::get<1>(entry);
398         const std::string& endpoint = std::get<2>(entry);
399 
400         // We only currently concern ourselves with chassis+all_sensors.
401         if (!(forward == "chassis" && reverse == "all_sensors"))
402         {
403             continue;
404         }
405 
406         // the endpoint is the board entry provided by
407         // Entity-Manager. so let's grab its properties if it has
408         // the right interface.
409 
410         // just try grabbing the properties first.
411         std::map<std::string, DbusVariant> ipmiProperties =
412             getEntityManagerProperties(
413                 endpoint.c_str(),
414                 "xyz.openbmc_project.Inventory.Decorator.Ipmi");
415 
416         auto entityIdProp = ipmiProperties.find("EntityId");
417         auto entityInstanceProp = ipmiProperties.find("EntityInstance");
418         if (entityIdProp != ipmiProperties.end())
419         {
420             entityId =
421                 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
422         }
423         if (entityInstanceProp != ipmiProperties.end())
424         {
425             entityInstance = static_cast<uint8_t>(
426                 std::get<uint64_t>(entityInstanceProp->second));
427         }
428 
429         // Now check the entity-manager entry for this sensor to see
430         // if it has its own value and use that instead.
431         //
432         // In theory, checking this first saves us from checking
433         // both, except in most use-cases identified, there won't be
434         // a per sensor override, so we need to always check both.
435         std::string sensorNameFromPath = fs::path(path).filename();
436 
437         std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
438 
439         // Download the interfaces for the sensor from
440         // Entity-Manager to find the name of the configuration
441         // interface.
442         std::map<std::string, std::vector<std::string>>
443             sensorInterfacesResponse =
444                 getObjectInterfaces(sensorConfigPath.c_str());
445 
446         const std::string* configurationInterface =
447             getSensorConfigurationInterface(sensorInterfacesResponse);
448 
449         // We didnt' find a configuration interface for this sensor, but we
450         // followed the Association property to get here, so we're done
451         // searching.
452         if (!configurationInterface)
453         {
454             break;
455         }
456 
457         // We found a configuration interface.
458         std::map<std::string, DbusVariant> configurationProperties =
459             getEntityManagerProperties(sensorConfigPath.c_str(),
460                                        configurationInterface->c_str());
461 
462         entityIdProp = configurationProperties.find("EntityId");
463         entityInstanceProp = configurationProperties.find("EntityInstance");
464         if (entityIdProp != configurationProperties.end())
465         {
466             entityId =
467                 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
468         }
469         if (entityInstanceProp != configurationProperties.end())
470         {
471             entityInstance = static_cast<uint8_t>(
472                 std::get<uint64_t>(entityInstanceProp->second));
473         }
474 
475         // stop searching Association records.
476         break;
477     } // end for Association vectors.
478 
479     if constexpr (debug)
480     {
481         std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
482                      path.c_str(), entityId, entityInstance);
483     }
484 }
485 
486 } // namespace ipmi
487