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