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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 } // namespace dbusUtility 689 } // namespace vpd 690