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