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