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