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