1 #include "sensordatahandler.hpp"
2 
3 #include "sensorhandler.hpp"
4 
5 #include <bitset>
6 #include <filesystem>
7 #include <ipmid/types.hpp>
8 #include <ipmid/utils.hpp>
9 #include <optional>
10 #include <sdbusplus/message/types.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 
13 namespace ipmi
14 {
15 namespace sensor
16 {
17 
18 using namespace phosphor::logging;
19 using InternalFailure =
20     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
21 
22 static constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
23 static constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
24 static constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
25 
26 /** @brief get the D-Bus service and service path
27  *  @param[in] bus - The Dbus bus object
28  *  @param[in] interface - interface to the service
29  *  @param[in] path - interested path in the list of objects
30  *  @return pair of service path and service
31  */
32 ServicePath getServiceAndPath(sdbusplus::bus_t& bus,
33                               const std::string& interface,
34                               const std::string& path)
35 {
36     auto depth = 0;
37     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
38                                           MAPPER_INTERFACE, "GetSubTree");
39     mapperCall.append("/");
40     mapperCall.append(depth);
41     mapperCall.append(std::vector<Interface>({interface}));
42 
43     auto mapperResponseMsg = bus.call(mapperCall);
44     if (mapperResponseMsg.is_method_error())
45     {
46         log<level::ERR>("Mapper GetSubTree failed",
47                         entry("PATH=%s", path.c_str()),
48                         entry("INTERFACE=%s", interface.c_str()));
49         elog<InternalFailure>();
50     }
51 
52     MapperResponseType mapperResponse;
53     mapperResponseMsg.read(mapperResponse);
54     if (mapperResponse.empty())
55     {
56         log<level::ERR>("Invalid mapper response",
57                         entry("PATH=%s", path.c_str()),
58                         entry("INTERFACE=%s", interface.c_str()));
59         elog<InternalFailure>();
60     }
61 
62     if (path.empty())
63     {
64         // Get the first one if the path is not in list.
65         return std::make_pair(mapperResponse.begin()->first,
66                               mapperResponse.begin()->second.begin()->first);
67     }
68     const auto& iter = mapperResponse.find(path);
69     if (iter == mapperResponse.end())
70     {
71         log<level::ERR>("Couldn't find D-Bus path",
72                         entry("PATH=%s", path.c_str()),
73                         entry("INTERFACE=%s", interface.c_str()));
74         elog<InternalFailure>();
75     }
76     return std::make_pair(iter->first, iter->second.begin()->first);
77 }
78 
79 AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData)
80 {
81     Assertion assertionStates =
82         (static_cast<Assertion>(cmdData.assertOffset8_14)) << 8 |
83         cmdData.assertOffset0_7;
84     Deassertion deassertionStates =
85         (static_cast<Deassertion>(cmdData.deassertOffset8_14)) << 8 |
86         cmdData.deassertOffset0_7;
87     return std::make_pair(assertionStates, deassertionStates);
88 }
89 
90 ipmi_ret_t updateToDbus(IpmiUpdateData& msg)
91 {
92     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
93     try
94     {
95         auto serviceResponseMsg = bus.call(msg);
96         if (serviceResponseMsg.is_method_error())
97         {
98             log<level::ERR>("Error in D-Bus call");
99             return IPMI_CC_UNSPECIFIED_ERROR;
100         }
101     }
102     catch (const InternalFailure& e)
103     {
104         commit<InternalFailure>();
105         return IPMI_CC_UNSPECIFIED_ERROR;
106     }
107     return IPMI_CC_OK;
108 }
109 
110 namespace get
111 {
112 
113 SensorName nameParentLeaf(const Info& sensorInfo)
114 {
115     const auto pos = sensorInfo.sensorPath.find_last_of('/');
116     const auto leaf = sensorInfo.sensorPath.substr(pos + 1);
117 
118     const auto remaining = sensorInfo.sensorPath.substr(0, pos);
119 
120     const auto parentPos = remaining.find_last_of('/');
121     auto parent = remaining.substr(parentPos + 1);
122 
123     parent += "_" + leaf;
124     return parent;
125 }
126 
127 GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
128                                      const InstancePath& path,
129                                      const DbusInterface& interface)
130 {
131     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
132     GetSensorResponse response{};
133 
134     enableScanning(&response);
135 
136     auto service = ipmi::getService(bus, interface, path);
137 
138     const auto& interfaceList = sensorInfo.propertyInterfaces;
139 
140     for (const auto& interface : interfaceList)
141     {
142         for (const auto& property : interface.second)
143         {
144             auto propValue = ipmi::getDbusProperty(
145                 bus, service, path, interface.first, property.first);
146 
147             for (const auto& value : std::get<OffsetValueMap>(property.second))
148             {
149                 if (propValue == value.second.assert)
150                 {
151                     setOffset(value.first, &response);
152                     break;
153                 }
154             }
155         }
156     }
157 
158     return response;
159 }
160 
161 GetSensorResponse mapDbusToEventdata2(const Info& sensorInfo)
162 {
163     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
164     GetSensorResponse response{};
165 
166     enableScanning(&response);
167 
168     auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
169                                     sensorInfo.sensorPath);
170 
171     const auto& interfaceList = sensorInfo.propertyInterfaces;
172 
173     for (const auto& interface : interfaceList)
174     {
175         for (const auto& property : interface.second)
176         {
177             auto propValue =
178                 ipmi::getDbusProperty(bus, service, sensorInfo.sensorPath,
179                                       interface.first, property.first);
180 
181             for (const auto& value : std::get<OffsetValueMap>(property.second))
182             {
183                 if (propValue == value.second.assert)
184                 {
185                     setReading(value.first, &response);
186                     break;
187                 }
188             }
189         }
190     }
191 
192     return response;
193 }
194 
195 #ifndef FEATURE_SENSORS_CACHE
196 GetSensorResponse assertion(const Info& sensorInfo)
197 {
198     return mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
199                               sensorInfo.sensorInterface);
200 }
201 
202 GetSensorResponse eventdata2(const Info& sensorInfo)
203 {
204     return mapDbusToEventdata2(sensorInfo);
205 }
206 #else
207 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
208                                            const PropertyMap& /*properties*/)
209 {
210     // The assertion may contain multiple properties
211     // So we have to get the properties from DBus anyway
212     auto response = mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
213                                        sensorInfo.sensorInterface);
214 
215     if (!sensorCacheMap[id].has_value())
216     {
217         sensorCacheMap[id] = SensorData{};
218     }
219     sensorCacheMap[id]->response = response;
220     return response;
221 }
222 
223 std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
224                                             const PropertyMap& /*properties*/)
225 {
226     // The eventdata2 may contain multiple properties
227     // So we have to get the properties from DBus anyway
228     auto response = mapDbusToEventdata2(sensorInfo);
229 
230     if (!sensorCacheMap[id].has_value())
231     {
232         sensorCacheMap[id] = SensorData{};
233     }
234     sensorCacheMap[id]->response = response;
235     return response;
236 }
237 
238 #endif // FEATURE_SENSORS_CACHE
239 
240 } // namespace get
241 
242 namespace set
243 {
244 
245 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
246                            const std::string& sensorPath,
247                            const std::string& command,
248                            const std::string& sensorInterface)
249 {
250     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
251     using namespace std::string_literals;
252 
253     auto dbusService = getService(bus, sensorInterface, sensorPath);
254 
255     return bus.new_method_call(dbusService.c_str(), sensorPath.c_str(),
256                                updateInterface.c_str(), command.c_str());
257 }
258 
259 ipmi_ret_t eventdata(const SetSensorReadingReq&, const Info& sensorInfo,
260                      uint8_t data)
261 {
262     auto msg =
263         makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
264                     "Set", sensorInfo.sensorInterface);
265 
266     const auto& interface = sensorInfo.propertyInterfaces.begin();
267     msg.append(interface->first);
268     for (const auto& property : interface->second)
269     {
270         msg.append(property.first);
271         const auto& iter = std::get<OffsetValueMap>(property.second).find(data);
272         if (iter == std::get<OffsetValueMap>(property.second).end())
273         {
274             log<level::ERR>("Invalid event data");
275             return IPMI_CC_PARM_OUT_OF_RANGE;
276         }
277         msg.append(iter->second.assert);
278     }
279     return updateToDbus(msg);
280 }
281 
282 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
283 {
284     std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
285     std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
286     auto bothSet = assertionSet ^ deassertionSet;
287 
288     const auto& interface = sensorInfo.propertyInterfaces.begin();
289 
290     for (const auto& property : interface->second)
291     {
292         std::optional<Value> tmp;
293         for (const auto& value : std::get<OffsetValueMap>(property.second))
294         {
295             if (bothSet.size() <= value.first || !bothSet.test(value.first))
296             {
297                 // A BIOS shouldn't do this but ignore if they do.
298                 continue;
299             }
300 
301             if (assertionSet.test(value.first))
302             {
303                 tmp = value.second.assert;
304                 break;
305             }
306             if (deassertionSet.test(value.first))
307             {
308                 tmp = value.second.deassert;
309                 break;
310             }
311         }
312 
313         if (tmp)
314         {
315             auto msg = makeDbusMsg("org.freedesktop.DBus.Properties",
316                                    sensorInfo.sensorPath, "Set",
317                                    sensorInfo.sensorInterface);
318             msg.append(interface->first);
319             msg.append(property.first);
320             msg.append(*tmp);
321 
322             auto rc = updateToDbus(msg);
323             if (rc)
324             {
325                 return rc;
326             }
327         }
328     }
329 
330     return IPMI_CC_OK;
331 }
332 
333 } // namespace set
334 
335 namespace notify
336 {
337 
338 IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
339                            const std::string&, const std::string& command,
340                            const std::string&)
341 {
342     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
343     using namespace std::string_literals;
344 
345     static const auto dbusPath = "/xyz/openbmc_project/inventory"s;
346     std::string dbusService = ipmi::getService(bus, updateInterface, dbusPath);
347 
348     return bus.new_method_call(dbusService.c_str(), dbusPath.c_str(),
349                                updateInterface.c_str(), command.c_str());
350 }
351 
352 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
353 {
354     auto msg = makeDbusMsg(sensorInfo.sensorInterface, sensorInfo.sensorPath,
355                            "Notify", sensorInfo.sensorInterface);
356 
357     std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
358     std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
359     ipmi::sensor::ObjectMap objects;
360     ipmi::sensor::InterfaceMap interfaces;
361     for (const auto& interface : sensorInfo.propertyInterfaces)
362     {
363         // An interface with no properties - It is possible that the sensor
364         // object on DBUS implements a DBUS interface with no properties.
365         // Make sure we add the interface to the list if interfaces on the
366         // object with an empty property map.
367         if (interface.second.empty())
368         {
369             interfaces.emplace(interface.first, ipmi::sensor::PropertyMap{});
370             continue;
371         }
372         // For a property like functional state the result will be
373         // calculated based on the true value of all conditions.
374         for (const auto& property : interface.second)
375         {
376             ipmi::sensor::PropertyMap props;
377             bool valid = false;
378             auto result = true;
379             for (const auto& value : std::get<OffsetValueMap>(property.second))
380             {
381                 if (assertionSet.test(value.first))
382                 {
383                     // Skip update if skipOn is ASSERT
384                     if (SkipAssertion::ASSERT == value.second.skip)
385                     {
386                         return IPMI_CC_OK;
387                     }
388                     result = result && std::get<bool>(value.second.assert);
389                     valid = true;
390                 }
391                 else if (deassertionSet.test(value.first))
392                 {
393                     // Skip update if skipOn is DEASSERT
394                     if (SkipAssertion::DEASSERT == value.second.skip)
395                     {
396                         return IPMI_CC_OK;
397                     }
398                     result = result && std::get<bool>(value.second.deassert);
399                     valid = true;
400                 }
401             }
402             for (const auto& value :
403                  std::get<PreReqOffsetValueMap>(property.second))
404             {
405                 if (assertionSet.test(value.first))
406                 {
407                     result = result && std::get<bool>(value.second.assert);
408                 }
409                 else if (deassertionSet.test(value.first))
410                 {
411                     result = result && std::get<bool>(value.second.deassert);
412                 }
413             }
414             if (valid)
415             {
416                 props.emplace(property.first, result);
417                 interfaces.emplace(interface.first, std::move(props));
418             }
419         }
420     }
421 
422     objects.emplace(sensorInfo.sensorPath, std::move(interfaces));
423     msg.append(std::move(objects));
424     return updateToDbus(msg);
425 }
426 
427 } // namespace notify
428 
429 namespace inventory
430 {
431 
432 namespace get
433 {
434 
435 #ifndef FEATURE_SENSORS_CACHE
436 
437 GetSensorResponse assertion(const Info& sensorInfo)
438 {
439     namespace fs = std::filesystem;
440 
441     fs::path path{ipmi::sensor::inventoryRoot};
442     path += sensorInfo.sensorPath;
443 
444     return ipmi::sensor::get::mapDbusToAssertion(
445         sensorInfo, path.string(),
446         sensorInfo.propertyInterfaces.begin()->first);
447 }
448 
449 #else
450 
451 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
452                                            const PropertyMap& /*properties*/)
453 {
454     // The assertion may contain multiple properties
455     // So we have to get the properties from DBus anyway
456     namespace fs = std::filesystem;
457 
458     fs::path path{ipmi::sensor::inventoryRoot};
459     path += sensorInfo.sensorPath;
460 
461     auto response = ipmi::sensor::get::mapDbusToAssertion(
462         sensorInfo, path.string(),
463         sensorInfo.propertyInterfaces.begin()->first);
464 
465     if (!sensorCacheMap[id].has_value())
466     {
467         sensorCacheMap[id] = SensorData{};
468     }
469     sensorCacheMap[id]->response = response;
470     return response;
471 }
472 
473 #endif
474 
475 } // namespace get
476 
477 } // namespace inventory
478 } // namespace sensor
479 } // namespace ipmi
480