xref: /openbmc/openpower-vpd-parser/vpd-manager/include/utility/dbus_utility.hpp (revision fa5e4d325ef9cea3c841fe89d202c340f92bd8c6)
1 #pragma once
2 
3 #include "constants.hpp"
4 #include "logger.hpp"
5 #include "types.hpp"
6 
7 #include <chrono>
8 
9 namespace vpd
10 {
11 /**
12  * @brief The namespace defines utlity methods for generic D-Bus operations.
13  */
14 namespace dbusUtility
15 {
16 
17 /**
18  * @brief An API to get Map of service and interfaces for an object path.
19  *
20  * The API returns a Map of service name and interfaces for a given pair of
21  * object path and interface list. It can be used to determine service name
22  * which implemets a particular object path and interface.
23  *
24  * Note: It will be caller's responsibility to check for empty map returned and
25  * generate appropriate error.
26  *
27  * @param [in] objectPath - Object path under the service.
28  * @param [in] interfaces - Array of interface(s).
29  * @return - A Map of service name to object to interface(s), if success.
30  *           If failed,  empty map.
31  */
getObjectMap(const std::string & objectPath,std::span<const char * > interfaces)32 inline types::MapperGetObject getObjectMap(const std::string& objectPath,
33                                            std::span<const char*> interfaces)
34 {
35     types::MapperGetObject getObjectMap;
36 
37     // interface list is optional argument, hence no check required.
38     if (objectPath.empty())
39     {
40         logging::logMessage("Path value is empty, invalid call to GetObject");
41         return getObjectMap;
42     }
43 
44     try
45     {
46         auto bus = sdbusplus::bus::new_default();
47         auto method = bus.new_method_call(
48             "xyz.openbmc_project.ObjectMapper",
49             "/xyz/openbmc_project/object_mapper",
50             "xyz.openbmc_project.ObjectMapper", "GetObject");
51 
52         method.append(objectPath, interfaces);
53         auto result = bus.call(method);
54         result.read(getObjectMap);
55     }
56     catch (const sdbusplus::exception::SdBusError& e)
57     {
58         // logging::logMessage(e.what());
59         return getObjectMap;
60     }
61 
62     return getObjectMap;
63 }
64 
65 /**
66  * @brief An API to get property map for an interface.
67  *
68  * This API returns a map of property and its value with respect to a particular
69  * interface.
70  *
71  * Note: It will be caller's responsibility to check for empty map returned and
72  * generate appropriate error.
73  *
74  * @param[in] i_service - Service name.
75  * @param[in] i_objectPath - object path.
76  * @param[in] i_interface - Interface, for the properties to be listed.
77  *
78  * @return - A map of property and value of an interface, if success.
79  *           if failed, empty map.
80  */
getPropertyMap(const std::string & i_service,const std::string & i_objectPath,const std::string & i_interface)81 inline types::PropertyMap getPropertyMap(const std::string& i_service,
82                                          const std::string& i_objectPath,
83                                          const std::string& i_interface)
84 {
85     types::PropertyMap l_propertyValueMap;
86     if (i_service.empty() || i_objectPath.empty() || i_interface.empty())
87     {
88         logging::logMessage("Invalid parameters to get property map");
89         return l_propertyValueMap;
90     }
91 
92     try
93     {
94         auto l_bus = sdbusplus::bus::new_default();
95         auto l_method =
96             l_bus.new_method_call(i_service.c_str(), i_objectPath.c_str(),
97                                   "org.freedesktop.DBus.Properties", "GetAll");
98         l_method.append(i_interface);
99         auto l_result = l_bus.call(l_method);
100         l_result.read(l_propertyValueMap);
101     }
102     catch (const sdbusplus::exception::SdBusError& l_ex)
103     {
104         logging::logMessage(l_ex.what());
105     }
106 
107     return l_propertyValueMap;
108 }
109 
110 /**
111  * @brief API to get object subtree from D-bus.
112  *
113  * The API returns the map of object, services and interfaces in the
114  * subtree that implement a certain interface. If no interfaces are provided
115  * then all the objects, services and interfaces under the subtree will
116  * be returned.
117  *
118  * Note: Depth can be 0 and interfaces can be null.
119  * It will be caller's responsibility to check for empty vector returned
120  * and generate appropriate error.
121  *
122  * @param[in] i_objectPath - Path to search for an interface.
123  * @param[in] i_depth - Maximum depth of the tree to search.
124  * @param[in] i_interfaces - List of interfaces to search.
125  *
126  * @return - A map of object and its related services and interfaces, if
127  *           success. If failed, empty map.
128  */
129 
130 inline types::MapperGetSubTree
getObjectSubTree(const std::string & i_objectPath,const int & i_depth,const std::vector<std::string> & i_interfaces)131     getObjectSubTree(const std::string& i_objectPath, const int& i_depth,
132                      const std::vector<std::string>& i_interfaces)
133 {
134     types::MapperGetSubTree l_subTreeMap;
135 
136     if (i_objectPath.empty())
137     {
138         logging::logMessage("Object path is empty.");
139         return l_subTreeMap;
140     }
141 
142     try
143     {
144         auto l_bus = sdbusplus::bus::new_default();
145         auto l_method = l_bus.new_method_call(
146             constants::objectMapperService, constants::objectMapperPath,
147             constants::objectMapperInf, "GetSubTree");
148         l_method.append(i_objectPath, i_depth, i_interfaces);
149         auto l_result = l_bus.call(l_method);
150         l_result.read(l_subTreeMap);
151     }
152     catch (const sdbusplus::exception::SdBusError& l_ex)
153     {
154         logging::logMessage(l_ex.what());
155     }
156 
157     return l_subTreeMap;
158 }
159 
160 /**
161  * @brief An API to read property from Dbus.
162  *
163  * The caller of the API needs to validate the validatity and correctness of the
164  * type and value of data returned. The API will just fetch and retun the data
165  * without any data validation.
166  *
167  * Note: It will be caller's responsibility to check for empty value returned
168  * and generate appropriate error if required.
169  *
170  * @param [in] serviceName - Name of the Dbus service.
171  * @param [in] objectPath - Object path under the service.
172  * @param [in] interface - Interface under which property exist.
173  * @param [in] property - Property whose value is to be read.
174  * @return - Value read from Dbus, if success.
175  *           If failed, empty variant.
176  */
readDbusProperty(const std::string & serviceName,const std::string & objectPath,const std::string & interface,const std::string & property)177 inline types::DbusVariantType readDbusProperty(
178     const std::string& serviceName, const std::string& objectPath,
179     const std::string& interface, const std::string& property)
180 {
181     types::DbusVariantType propertyValue;
182 
183     // Mandatory fields to make a read dbus call.
184     if (serviceName.empty() || objectPath.empty() || interface.empty() ||
185         property.empty())
186     {
187         logging::logMessage(
188             "One of the parameter to make Dbus read call is empty.");
189         return propertyValue;
190     }
191 
192     try
193     {
194         auto bus = sdbusplus::bus::new_default();
195         auto method =
196             bus.new_method_call(serviceName.c_str(), objectPath.c_str(),
197                                 "org.freedesktop.DBus.Properties", "Get");
198         method.append(interface, property);
199 
200         auto result = bus.call(method);
201         result.read(propertyValue);
202     }
203     catch (const sdbusplus::exception::SdBusError& e)
204     {
205         return propertyValue;
206     }
207     return propertyValue;
208 }
209 
210 /**
211  * @brief An API to write property on Dbus.
212  *
213  * The caller of this API needs to handle exception thrown by this method to
214  * identify any write failure. The API in no other way indicate write  failure
215  * to the caller.
216  *
217  * Note: It will be caller's responsibility ho handle the exception thrown in
218  * case of write failure and generate appropriate error.
219  *
220  * @param [in] serviceName - Name of the Dbus service.
221  * @param [in] objectPath - Object path under the service.
222  * @param [in] interface - Interface under which property exist.
223  * @param [in] property - Property whose value is to be written.
224  * @param [in] propertyValue - The value to be written.
225  */
writeDbusProperty(const std::string & serviceName,const std::string & objectPath,const std::string & interface,const std::string & property,const types::DbusVariantType & propertyValue)226 inline void writeDbusProperty(
227     const std::string& serviceName, const std::string& objectPath,
228     const std::string& interface, const std::string& property,
229     const types::DbusVariantType& propertyValue)
230 {
231     // Mandatory fields to make a write dbus call.
232     if (serviceName.empty() || objectPath.empty() || interface.empty() ||
233         property.empty())
234     {
235         logging::logMessage(
236             "One of the parameter to make Dbus read call is empty.");
237 
238         // caller need to handle the throw to ensure Dbus write success.
239         throw std::runtime_error("Dbus write failed, Parameter empty");
240     }
241 
242     try
243     {
244         auto bus = sdbusplus::bus::new_default();
245         auto method =
246             bus.new_method_call(serviceName.c_str(), objectPath.c_str(),
247                                 "org.freedesktop.DBus.Properties", "Set");
248         method.append(interface, property, propertyValue);
249         bus.call(method);
250     }
251     catch (const sdbusplus::exception::SdBusError& e)
252     {
253         logging::logMessage(e.what());
254 
255         // caller needs to handle this throw to handle error in writing Dbus.
256         throw std::runtime_error("Dbus write failed");
257     }
258 }
259 
260 /**
261  * @brief API to publish data on PIM
262  *
263  * The API calls notify on PIM object to publlish VPD.
264  *
265  * @param[in] objectMap - Object, its interface and data.
266  * @return bool - Status of call to PIM notify.
267  */
callPIM(types::ObjectMap && objectMap)268 inline bool callPIM(types::ObjectMap&& objectMap)
269 {
270     try
271     {
272         for (const auto& l_objectKeyValue : objectMap)
273         {
274             auto l_nodeHandle = objectMap.extract(l_objectKeyValue.first);
275 
276             if (l_nodeHandle.key().str.find(constants::pimPath, 0) !=
277                 std::string::npos)
278             {
279                 l_nodeHandle.key() = l_nodeHandle.key().str.replace(
280                     0, std::strlen(constants::pimPath), "");
281                 objectMap.insert(std::move(l_nodeHandle));
282             }
283         }
284 
285         auto bus = sdbusplus::bus::new_default();
286         auto pimMsg =
287             bus.new_method_call(constants::pimServiceName, constants::pimPath,
288                                 constants::pimIntf, "Notify");
289         pimMsg.append(std::move(objectMap));
290         bus.call(pimMsg);
291     }
292     catch (const sdbusplus::exception::SdBusError& e)
293     {
294         return false;
295     }
296     return true;
297 }
298 
299 /**
300  * @brief API to check if a D-Bus service is running or not.
301  *
302  * Any failure in calling the method "NameHasOwner" implies that the service is
303  * not in a running state. Hence the API returns false in case of any exception
304  * as well.
305  *
306  * @param[in] i_serviceName - D-Bus service name whose status is to be checked.
307  * @return bool - True if the service is running, false otherwise.
308  */
isServiceRunning(const std::string & i_serviceName)309 inline bool isServiceRunning(const std::string& i_serviceName)
310 {
311     bool l_retVal = false;
312 
313     try
314     {
315         auto l_bus = sdbusplus::bus::new_default();
316         auto l_method = l_bus.new_method_call(
317             "org.freedesktop.DBus", "/org/freedesktop/DBus",
318             "org.freedesktop.DBus", "NameHasOwner");
319         l_method.append(i_serviceName);
320 
321         l_bus.call(l_method).read(l_retVal);
322     }
323     catch (const sdbusplus::exception::SdBusError& l_ex)
324     {
325         logging::logMessage(
326             "Call to check service status failed with exception: " +
327             std::string(l_ex.what()));
328     }
329 
330     return l_retVal;
331 }
332 
333 /**
334  * @brief API to call "GetAttribute" method uner BIOS manager.
335  *
336  * The API reads the given attribuute from BIOS and returns a tuple of both
337  * current as well as pending value for that attribute.
338  * The API return only the current attribute value if found.
339  * API returns an empty variant of type BiosAttributeCurrentValue in case of any
340  * error.
341  *
342  * @param[in] i_attributeName - Attribute to be read.
343  * @return Tuple of PLDM attribute Type, current attribute value and pending
344  * attribute value.
345  */
346 inline types::BiosAttributeCurrentValue
biosGetAttributeMethodCall(const std::string & i_attributeName)347     biosGetAttributeMethodCall(const std::string& i_attributeName)
348 {
349     auto l_bus = sdbusplus::bus::new_default();
350     auto l_method = l_bus.new_method_call(
351         constants::biosConfigMgrService, constants::biosConfigMgrObjPath,
352         constants::biosConfigMgrInterface, "GetAttribute");
353     l_method.append(i_attributeName);
354 
355     types::BiosGetAttrRetType l_attributeVal;
356     try
357     {
358         auto l_result = l_bus.call(l_method);
359         l_result.read(std::get<0>(l_attributeVal), std::get<1>(l_attributeVal),
360                       std::get<2>(l_attributeVal));
361     }
362     catch (const sdbusplus::exception::SdBusError& l_ex)
363     {
364         logging::logMessage(
365             "Failed to read BIOS Attribute: " + i_attributeName +
366             " due to error " + std::string(l_ex.what()));
367 
368         // TODO: Log an informational PEL here.
369     }
370 
371     return std::get<1>(l_attributeVal);
372 }
373 
374 /**
375  * @brief API to check if Chassis is powered on.
376  *
377  * This API queries Phosphor Chassis State Manager to know whether
378  * Chassis is powered on.
379  *
380  * @return true if chassis is powered on, false otherwise
381  */
isChassisPowerOn()382 inline bool isChassisPowerOn()
383 {
384     auto powerState = dbusUtility::readDbusProperty(
385         "xyz.openbmc_project.State.Chassis",
386         "/xyz/openbmc_project/state/chassis0",
387         "xyz.openbmc_project.State.Chassis", "CurrentPowerState");
388 
389     if (auto curPowerState = std::get_if<std::string>(&powerState))
390     {
391         if ("xyz.openbmc_project.State.Chassis.PowerState.On" == *curPowerState)
392         {
393             return true;
394         }
395         return false;
396     }
397 
398     /*
399         TODO: Add PEL.
400         Callout: Firmware callout
401         Type: Informational
402         Description: Chassis state can't be determined, defaulting to chassis
403         off. : e.what()
404     */
405     return false;
406 }
407 
408 /**
409  * @brief API to check if host is in running state.
410  *
411  * This API reads the current host state from D-bus and returns true if the host
412  * is running.
413  *
414  * @return true if host is in running state. false otherwise.
415  */
isHostRunning()416 inline bool isHostRunning()
417 {
418     const auto l_hostState = dbusUtility::readDbusProperty(
419         constants::hostService, constants::hostObjectPath,
420         constants::hostInterface, "CurrentHostState");
421 
422     if (const auto l_currHostState = std::get_if<std::string>(&l_hostState))
423     {
424         if (*l_currHostState == constants::hostRunningState)
425         {
426             return true;
427         }
428     }
429 
430     return false;
431 }
432 
433 /**
434  * @brief API to check if BMC is in ready state.
435  *
436  * This API reads the current state of BMC from D-bus and returns true if BMC is
437  * in ready state.
438  *
439  * @return true if BMC is ready, false otherwise.
440  */
isBMCReady()441 inline bool isBMCReady()
442 {
443     const auto l_bmcState = dbusUtility::readDbusProperty(
444         constants::bmcStateService, constants::bmcZeroStateObject,
445         constants::bmcStateInterface, constants::currentBMCStateProperty);
446 
447     if (const auto l_currBMCState = std::get_if<std::string>(&l_bmcState))
448     {
449         if (*l_currBMCState == constants::bmcReadyState)
450         {
451             return true;
452         }
453     }
454 
455     return false;
456 }
457 
458 /**
459  * @brief An API to enable BMC reboot guard
460  *
461  * This API does a D-Bus method call to enable BMC reboot guard.
462  *
463  * @return On success, returns 0, otherwise returns -1.
464  */
EnableRebootGuard()465 inline int EnableRebootGuard() noexcept
466 {
467     int l_rc{constants::FAILURE};
468     try
469     {
470         auto l_bus = sdbusplus::bus::new_default();
471         auto l_method = l_bus.new_method_call(
472             constants::systemdService, constants::systemdObjectPath,
473             constants::systemdManagerInterface, "StartUnit");
474         l_method.append("reboot-guard-enable.service", "replace");
475         l_bus.call_noreply(l_method);
476         l_rc = constants::SUCCESS;
477     }
478     catch (const sdbusplus::exception::SdBusError& l_ex)
479     {
480         std::string l_errMsg =
481             "D-Bus call to enable BMC reboot guard failed for reason: ";
482         l_errMsg += l_ex.what();
483 
484         logging::logMessage(l_errMsg);
485     }
486     return l_rc;
487 }
488 
489 /**
490  * @brief An API to disable BMC reboot guard
491  *
492  * This API disables BMC reboot guard. This API has an inbuilt re-try mechanism.
493  * If Disable Reboot Guard fails, this API attempts to Disable Reboot Guard for
494  * 3 more times at an interval of 333ms.
495  *
496  * @return On success, returns 0, otherwise returns -1.
497  */
DisableRebootGuard()498 inline int DisableRebootGuard() noexcept
499 {
500     int l_rc{constants::FAILURE};
501 
502     // A lambda which executes the DBus call to disable BMC reboot guard.
503     auto l_executeDisableRebootGuard = []() -> int {
504         int l_dBusCallRc{constants::FAILURE};
505         try
506         {
507             auto l_bus = sdbusplus::bus::new_default();
508             auto l_method = l_bus.new_method_call(
509                 constants::systemdService, constants::systemdObjectPath,
510                 constants::systemdManagerInterface, "StartUnit");
511             l_method.append("reboot-guard-disable.service", "replace");
512             l_bus.call_noreply(l_method);
513             l_dBusCallRc = constants::SUCCESS;
514         }
515         catch (const sdbusplus::exception::SdBusError& l_ex)
516         {}
517         return l_dBusCallRc;
518     };
519 
520     if (constants::FAILURE == l_executeDisableRebootGuard())
521     {
522         std::function<void()> l_retryDisableRebootGuard;
523 
524         // A lambda which tries to disable BMC reboot guard for 3 times at an
525         // interval of 333 ms.
526         l_retryDisableRebootGuard = [&]() {
527             constexpr int MAX_RETRIES{3};
528             static int l_numRetries{0};
529 
530             if (l_numRetries < MAX_RETRIES)
531             {
532                 l_numRetries++;
533                 if (constants::FAILURE == l_executeDisableRebootGuard())
534                 {
535                     // sleep for 333ms before next retry. This is just a random
536                     // value so that 3 re-tries * 333ms takes ~1 second in the
537                     // worst case.
538                     const std::chrono::milliseconds l_sleepTime{333};
539                     std::this_thread::sleep_for(l_sleepTime);
540                     l_retryDisableRebootGuard();
541                 }
542                 else
543                 {
544                     l_numRetries = 0;
545                     l_rc = constants::SUCCESS;
546                 }
547             }
548             else
549             {
550                 // Failed to Disable Reboot Guard even after 3 retries.
551                 logging::logMessage("Failed to Disable Reboot Guard after " +
552                                     std::to_string(MAX_RETRIES) + " re-tries");
553                 l_numRetries = 0;
554             }
555         };
556 
557         l_retryDisableRebootGuard();
558     }
559     else
560     {
561         l_rc = constants::SUCCESS;
562     }
563     return l_rc;
564 }
565 
566 } // namespace dbusUtility
567 } // namespace vpd
568