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::bus& 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::bus 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::bus 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::bus 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::bus 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& cmdData, 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& sensorPath,
340                            const std::string& command,
341                            const std::string& sensorInterface)
342 {
343     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
344     using namespace std::string_literals;
345 
346     static const auto dbusPath = "/xyz/openbmc_project/inventory"s;
347     std::string dbusService = ipmi::getService(bus, updateInterface, dbusPath);
348 
349     return bus.new_method_call(dbusService.c_str(), dbusPath.c_str(),
350                                updateInterface.c_str(), command.c_str());
351 }
352 
353 ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
354 {
355     auto msg = makeDbusMsg(sensorInfo.sensorInterface, sensorInfo.sensorPath,
356                            "Notify", sensorInfo.sensorInterface);
357 
358     std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
359     std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
360     ipmi::sensor::ObjectMap objects;
361     ipmi::sensor::InterfaceMap interfaces;
362     for (const auto& interface : sensorInfo.propertyInterfaces)
363     {
364         // An interface with no properties - It is possible that the sensor
365         // object on DBUS implements a DBUS interface with no properties.
366         // Make sure we add the interface to the list if interfaces on the
367         // object with an empty property map.
368         if (interface.second.empty())
369         {
370             interfaces.emplace(interface.first, ipmi::sensor::PropertyMap{});
371             continue;
372         }
373         // For a property like functional state the result will be
374         // calculated based on the true value of all conditions.
375         for (const auto& property : interface.second)
376         {
377             ipmi::sensor::PropertyMap props;
378             bool valid = false;
379             auto result = true;
380             for (const auto& value : std::get<OffsetValueMap>(property.second))
381             {
382                 if (assertionSet.test(value.first))
383                 {
384                     // Skip update if skipOn is ASSERT
385                     if (SkipAssertion::ASSERT == value.second.skip)
386                     {
387                         return IPMI_CC_OK;
388                     }
389                     result = result && std::get<bool>(value.second.assert);
390                     valid = true;
391                 }
392                 else if (deassertionSet.test(value.first))
393                 {
394                     // Skip update if skipOn is DEASSERT
395                     if (SkipAssertion::DEASSERT == value.second.skip)
396                     {
397                         return IPMI_CC_OK;
398                     }
399                     result = result && std::get<bool>(value.second.deassert);
400                     valid = true;
401                 }
402             }
403             for (const auto& value :
404                  std::get<PreReqOffsetValueMap>(property.second))
405             {
406                 if (assertionSet.test(value.first))
407                 {
408                     result = result && std::get<bool>(value.second.assert);
409                 }
410                 else if (deassertionSet.test(value.first))
411                 {
412                     result = result && std::get<bool>(value.second.deassert);
413                 }
414             }
415             if (valid)
416             {
417                 props.emplace(property.first, result);
418                 interfaces.emplace(interface.first, std::move(props));
419             }
420         }
421     }
422 
423     objects.emplace(sensorInfo.sensorPath, std::move(interfaces));
424     msg.append(std::move(objects));
425     return updateToDbus(msg);
426 }
427 
428 } // namespace notify
429 
430 namespace inventory
431 {
432 
433 namespace get
434 {
435 
436 #ifndef FEATURE_SENSORS_CACHE
437 
438 GetSensorResponse assertion(const Info& sensorInfo)
439 {
440     namespace fs = std::filesystem;
441 
442     fs::path path{ipmi::sensor::inventoryRoot};
443     path += sensorInfo.sensorPath;
444 
445     return ipmi::sensor::get::mapDbusToAssertion(
446         sensorInfo, path.string(),
447         sensorInfo.propertyInterfaces.begin()->first);
448 }
449 
450 #else
451 
452 std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
453                                            const PropertyMap& /*properties*/)
454 {
455     // The assertion may contain multiple properties
456     // So we have to get the properties from DBus anyway
457     namespace fs = std::filesystem;
458 
459     fs::path path{ipmi::sensor::inventoryRoot};
460     path += sensorInfo.sensorPath;
461 
462     auto response = ipmi::sensor::get::mapDbusToAssertion(
463         sensorInfo, path.string(),
464         sensorInfo.propertyInterfaces.begin()->first);
465 
466     if (!sensorCacheMap[id].has_value())
467     {
468         sensorCacheMap[id] = SensorData{};
469     }
470     sensorCacheMap[id]->response = response;
471     return response;
472 }
473 
474 #endif
475 
476 } // namespace get
477 
478 } // namespace inventory
479 } // namespace sensor
480 } // namespace ipmi
481