1 // SPDX-License-Identifier: Apache-2.0 2 3 /**@file functions.cpp*/ 4 5 #include "config.h" 6 7 #include "functions.hpp" 8 9 #include <nlohmann/json.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <sdbusplus/bus.hpp> 12 #include <sdbusplus/bus/match.hpp> 13 #include <sdbusplus/exception.hpp> 14 #include <sdbusplus/message.hpp> 15 #include <sdeventplus/event.hpp> 16 #include <xyz/openbmc_project/Common/error.hpp> 17 18 #include <filesystem> 19 #include <fstream> 20 #include <functional> 21 #include <iostream> 22 #include <map> 23 #include <memory> 24 #include <string> 25 #include <variant> 26 #include <vector> 27 28 namespace functions 29 { 30 namespace process_hostfirmware 31 { 32 33 using namespace phosphor::logging; 34 using InterfacesPropertiesMap = 35 std::map<std::string, 36 std::map<std::string, std::variant<std::vector<std::string>>>>; 37 using ManagedObjectType = 38 std::map<sdbusplus::message::object_path, InterfacesPropertiesMap>; 39 40 /** 41 * @brief Returns the managed objects for a given service 42 */ 43 ManagedObjectType getManagedObjects(sdbusplus::bus_t& bus, 44 const std::string& service, 45 const std::string& managerPath) 46 47 { 48 auto method = bus.new_method_call(service.c_str(), managerPath.c_str(), 49 "org.freedesktop.DBus.ObjectManager", 50 "GetManagedObjects"); 51 52 ManagedObjectType objects; 53 54 try 55 { 56 auto reply = bus.call(method); 57 reply.read(objects); 58 } 59 catch (const sdbusplus::exception_t& e) 60 { 61 return ManagedObjectType{}; 62 } 63 return objects; 64 } 65 66 /** 67 * @brief Issue callbacks safely 68 * 69 * std::function can be empty, so this wrapper method checks for that prior to 70 * calling it to avoid std::bad_function_call 71 * 72 * @tparam Sig the types of the std::function arguments 73 * @tparam Args the deduced argument types 74 * @param[in] callback the callback being wrapped 75 * @param[in] args the callback arguments 76 */ 77 template <typename... Sig, typename... Args> 78 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args) 79 { 80 if (callback) 81 { 82 callback(std::forward<Args>(args)...); 83 } 84 } 85 86 /** 87 * @brief Get file extensions for Compatible 88 * 89 * IBM host firmware can be deployed as blobs (files) in a filesystem. Host 90 * firmware blobs for different values of 91 * xyz.openbmc_project.Inventory.Decorator.Compatible are packaged with 92 * different filename extensions. getExtensionsForIbmCompatibleSystem maintains 93 * the mapping from a given value of 94 * xyz.openbmc_project.Inventory.Decorator.Compatible to an array of filename 95 * extensions. 96 * 97 * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and 98 * the extensions parameter is reset with the map entry. If no mapping is found 99 * getExtensionsForIbmCompatibleSystem returns false and extensions is 100 * unmodified. 101 * 102 * @param[in] extensionMap a map of 103 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 104 * extensions. 105 * @param[in] ibmCompatibleSystem The names property of an instance of 106 * xyz.openbmc_project.Inventory.Decorator.Compatible 107 * @param[out] extensions the host firmware blob file extensions 108 * @return true if an entry was found, otherwise false 109 */ 110 bool getExtensionsForIbmCompatibleSystem( 111 const std::map<std::string, std::vector<std::string>>& extensionMap, 112 const std::vector<std::string>& ibmCompatibleSystem, 113 std::vector<std::string>& extensions) 114 { 115 for (const auto& system : ibmCompatibleSystem) 116 { 117 auto extensionMapIterator = extensionMap.find(system); 118 if (extensionMapIterator != extensionMap.end()) 119 { 120 extensions = extensionMapIterator->second; 121 return true; 122 } 123 } 124 125 return false; 126 } 127 128 /** 129 * @brief Write host firmware well-known name 130 * 131 * A wrapper around std::filesystem::create_symlink that avoids EEXIST by 132 * deleting any pre-existing file. 133 * 134 * @param[in] linkTarget The link target argument to 135 * std::filesystem::create_symlink 136 * @param[in] linkPath The link path argument to std::filesystem::create_symlink 137 * @param[in] errorCallback A callback made in the event of filesystem errors. 138 */ 139 void writeLink(const std::filesystem::path& linkTarget, 140 const std::filesystem::path& linkPath, 141 const ErrorCallbackType& errorCallback) 142 { 143 std::error_code ec; 144 145 // remove files with the same name as the symlink to be created, 146 // otherwise symlink will fail with EEXIST. 147 if (!std::filesystem::remove(linkPath, ec)) 148 { 149 if (ec) 150 { 151 makeCallback(errorCallback, linkPath, ec); 152 return; 153 } 154 } 155 156 std::filesystem::create_symlink(linkTarget, linkPath, ec); 157 if (ec) 158 { 159 makeCallback(errorCallback, linkPath, ec); 160 return; 161 } 162 } 163 164 /** 165 * @brief Find host firmware blob files that need well-known names 166 * 167 * The IBM host firmware runtime looks for data and/or additional code while 168 * bootstrapping in files with well-known names. findLinks uses the provided 169 * extensions argument to find host firmware blob files that require a 170 * well-known name. When a blob is found, issue the provided callback 171 * (typically a function that will write a symlink). 172 * 173 * @param[in] hostFirmwareDirectory The directory in which findLinks should 174 * look for host firmware blob files that need well-known names. 175 * @param[in] extensions The extensions of the firmware blob files denote a 176 * host firmware blob file requires a well-known name. 177 * @param[in] errorCallback A callback made in the event of filesystem errors. 178 * @param[in] linkCallback A callback made when host firmware blob files 179 * needing a well known name are found. 180 */ 181 void findLinks(const std::filesystem::path& hostFirmwareDirectory, 182 const std::vector<std::string>& extensions, 183 const ErrorCallbackType& errorCallback, 184 const LinkCallbackType& linkCallback) 185 { 186 std::error_code ec; 187 std::filesystem::directory_iterator directoryIterator( 188 hostFirmwareDirectory, ec); 189 if (ec) 190 { 191 makeCallback(errorCallback, hostFirmwareDirectory, ec); 192 return; 193 } 194 195 // Create a symlink for pnor.toc 196 static const auto tocLid = "81e00994.lid"; 197 auto tocLidPath = hostFirmwareDirectory / tocLid; 198 if (std::filesystem::exists(tocLidPath)) 199 { 200 static const auto tocName = "pnor.toc"; 201 auto tocLinkPath = hostFirmwareDirectory / tocName; 202 makeCallback(linkCallback, tocLid, tocLinkPath, errorCallback); 203 } 204 205 for (; directoryIterator != std::filesystem::end(directoryIterator); 206 directoryIterator.increment(ec)) 207 { 208 const auto& file = directoryIterator->path(); 209 if (ec) 210 { 211 makeCallback(errorCallback, file, ec); 212 // quit here if the increment call failed otherwise the loop may 213 // never finish 214 break; 215 } 216 217 if (std::find(extensions.begin(), extensions.end(), file.extension()) == 218 extensions.end()) 219 { 220 // this file doesn't have an extension or doesn't match any of the 221 // provided extensions. 222 continue; 223 } 224 225 auto linkPath(file.parent_path().append( 226 static_cast<const std::string&>(file.stem()))); 227 228 makeCallback(linkCallback, file.filename(), linkPath, errorCallback); 229 } 230 } 231 232 /** 233 * @brief Parse the elements json file and construct a string with the data to 234 * be used to update the bios attribute table. 235 * 236 * @param[in] elementsJsonFilePath - The path to the host firmware json file. 237 * @param[in] extensions - The extensions of the firmware blob files. 238 */ 239 std::string getBiosAttrStr(const std::filesystem::path& elementsJsonFilePath, 240 const std::vector<std::string>& extensions) 241 { 242 std::string biosAttrStr{}; 243 244 std::ifstream jsonFile(elementsJsonFilePath.c_str()); 245 if (!jsonFile) 246 { 247 return {}; 248 } 249 250 std::map<std::string, std::string> attr; 251 auto data = nlohmann::json::parse(jsonFile, nullptr, false); 252 if (data.is_discarded()) 253 { 254 log<level::ERR>("Error parsing JSON file", 255 entry("FILE=%s", elementsJsonFilePath.c_str())); 256 return {}; 257 } 258 259 // .get requires a non-const iterator 260 for (auto& iter : data["lids"]) 261 { 262 std::string name{}; 263 std::string lid{}; 264 265 try 266 { 267 name = iter["element_name"].get<std::string>(); 268 lid = iter["short_lid_name"].get<std::string>(); 269 } 270 catch (const std::exception& e) 271 { 272 // Possibly the element or lid name field was not found 273 log<level::ERR>("Error reading JSON field", 274 entry("FILE=%s", elementsJsonFilePath.c_str()), 275 entry("ERROR=%s", e.what())); 276 continue; 277 } 278 279 // The elements with the ipl extension have higher priority. Therefore 280 // Use operator[] to overwrite value if an entry for it already exists, 281 // and create a second entry with key name element_RT to specify it as 282 // a runtime element. 283 // Ex: if the JSON contains an entry A.P10 with lid name X, it'll create 284 // and try A=X. If the JSON also contained an entry A.P10.iplTime with 285 // lid name Y, the A entry would be overwritten to be A=Y and a second 286 // entry A_RT=X would be created. 287 constexpr auto iplExtension = ".iplTime"; 288 constexpr auto runtimeSuffix = "_RT"; 289 std::filesystem::path path(name); 290 if (path.extension() == iplExtension) 291 { 292 // Some elements have an additional extension, ex: .P10.iplTime 293 // Strip off the ipl extension with stem(), then check if there is 294 // an additional extension with extension(). 295 if (!path.stem().extension().empty()) 296 { 297 // Check if the extension matches the extensions for this system 298 if (std::find(extensions.begin(), extensions.end(), 299 path.stem().extension()) == extensions.end()) 300 { 301 continue; 302 } 303 } 304 // Get the element name without extensions by calling stem() twice 305 // since stem() returns the base name if no periods are found. 306 // Therefore both "element.P10" and "element.P10.iplTime" would 307 // become "element". 308 auto keyName = path.stem().stem(); 309 auto attrIt = attr.find(keyName); 310 if (attrIt != attr.end()) 311 { 312 // Copy the existing entry to a runtime entry 313 auto runtimeKeyName = keyName.string() + runtimeSuffix; 314 attr.insert({runtimeKeyName, attrIt->second}); 315 } 316 // Overwrite the existing element with the ipl entry 317 attr[keyName] = lid; 318 continue; 319 } 320 321 // Process all other extensions. The extension should match the list of 322 // supported extensions for this system. Use .insert() to only add 323 // entries that do not exist, so to not overwrite the values that may 324 // had been added that had the ipl extension. 325 if (std::find(extensions.begin(), extensions.end(), path.extension()) != 326 extensions.end()) 327 { 328 auto keyName = path.stem(); 329 auto attrIt = attr.find(keyName); 330 if (attrIt != attr.end()) 331 { 332 // The existing entry is an ipl entry, therefore create this 333 // entry as a runtime one. 334 auto runtimeKeyName = keyName.string() + runtimeSuffix; 335 attr.insert({runtimeKeyName, lid}); 336 } 337 else 338 { 339 attr.insert({path.stem(), lid}); 340 } 341 } 342 } 343 for (const auto& a : attr) 344 { 345 // Build the bios attribute string with format: 346 // "element1=lid1,element2=lid2,elementN=lidN," 347 biosAttrStr += a.first + "=" + a.second + ","; 348 349 // Create symlinks from the hostfw elements to their corresponding 350 // lid files if they don't exist 351 auto elementFilePath = 352 std::filesystem::path("/media/hostfw/running") / a.first; 353 if (!std::filesystem::exists(elementFilePath)) 354 { 355 std::error_code ec; 356 auto lidName = a.second + ".lid"; 357 std::filesystem::create_symlink(lidName, elementFilePath, ec); 358 if (ec) 359 { 360 log<level::ERR>("Error creating symlink", 361 entry("TARGET=%s", lidName.c_str()), 362 entry("LINK=%s", elementFilePath.c_str())); 363 } 364 } 365 } 366 367 // Delete the last comma of the bios attribute string 368 if (biosAttrStr.back() == ',') 369 { 370 return biosAttrStr.substr(0, biosAttrStr.length() - 1); 371 } 372 373 return biosAttrStr; 374 } 375 376 /** 377 * @brief Set the bios attribute table with details of the host firmware data 378 * for this system. 379 * 380 * @param[in] elementsJsonFilePath - The path to the host firmware json file. 381 * @param[in] extensions - The extensions of the firmware blob files. 382 */ 383 void setBiosAttr(const std::filesystem::path& elementsJsonFilePath, 384 const std::vector<std::string>& extensions) 385 { 386 auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions); 387 388 constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager"; 389 constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager"; 390 constexpr auto dbusAttrName = "hb_lid_ids"; 391 constexpr auto dbusAttrType = 392 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String"; 393 394 using PendingAttributesType = std::vector<std::pair< 395 std::string, std::tuple<std::string, std::variant<std::string>>>>; 396 PendingAttributesType pendingAttributes; 397 pendingAttributes.emplace_back(std::make_pair( 398 dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr))); 399 400 auto bus = sdbusplus::bus::new_default(); 401 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 402 MAPPER_INTERFACE, "GetObject"); 403 method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf})); 404 std::vector<std::pair<std::string, std::vector<std::string>>> response; 405 try 406 { 407 auto reply = bus.call(method); 408 reply.read(response); 409 if (response.empty()) 410 { 411 log<level::INFO>("Error reading mapper response", 412 entry("PATH=%s", biosConfigPath), 413 entry("INTERFACE=%s", biosConfigIntf)); 414 throw sdbusplus::xyz::openbmc_project::Common::Error:: 415 InternalFailure(); 416 } 417 auto method = bus.new_method_call((response.begin()->first).c_str(), 418 biosConfigPath, 419 SYSTEMD_PROPERTY_INTERFACE, "Set"); 420 method.append(biosConfigIntf, "PendingAttributes", 421 std::variant<PendingAttributesType>(pendingAttributes)); 422 bus.call(method); 423 } 424 catch (const sdbusplus::exception_t& e) 425 { 426 log<level::INFO>("Error setting the bios attribute", 427 entry("ERROR=%s", e.what()), 428 entry("ATTRIBUTE=%s", dbusAttrName)); 429 throw; 430 } 431 } 432 433 /** 434 * @brief Make callbacks on 435 * xyz.openbmc_project.Inventory.Decorator.Compatible instances. 436 * 437 * Look for an instance of xyz.openbmc_project.Inventory.Decorator.Compatible in 438 * the provided argument and if found, issue the provided callback. 439 * 440 * @param[in] interfacesAndProperties the interfaces in which to look for an 441 * instance of xyz.openbmc_project.Inventory.Decorator.Compatible 442 * @param[in] callback the user callback to make if 443 * xyz.openbmc_project.Inventory.Decorator.Compatible is found in 444 * interfacesAndProperties 445 * @return true if interfacesAndProperties contained an instance of 446 * xyz.openbmc_project.Inventory.Decorator.Compatible, false otherwise 447 */ 448 bool maybeCall( 449 const std::map< 450 std::string, 451 std::map<std::string, std::variant<std::vector<std::string>>>>& 452 interfacesAndProperties, 453 const MaybeCallCallbackType& callback) 454 { 455 using namespace std::string_literals; 456 457 static const auto interfaceName = 458 "xyz.openbmc_project.Inventory.Decorator.Compatible"s; 459 auto interfaceIterator = interfacesAndProperties.find(interfaceName); 460 if (interfaceIterator == interfacesAndProperties.cend()) 461 { 462 // Compatible interface not found, so instruct the caller to keep 463 // waiting or try again later. 464 return false; 465 } 466 auto propertyIterator = interfaceIterator->second.find("Names"s); 467 if (propertyIterator == interfaceIterator->second.cend()) 468 { 469 // The interface exists but the property doesn't. This is a bug in the 470 // Compatible implementation. The caller should not try again. 471 std::cerr << "Names property not implemented on " << interfaceName 472 << "\n"; 473 return true; 474 } 475 476 const auto& ibmCompatibleSystem = 477 std::get<std::vector<std::string>>(propertyIterator->second); 478 if (callback) 479 { 480 try 481 { 482 callback(ibmCompatibleSystem); 483 } 484 catch (const sdbusplus::exception_t& e) 485 { 486 return false; 487 } 488 } 489 490 // Compatible found and callback issued. 491 return true; 492 } 493 494 /** 495 * @brief Make callbacks on 496 * xyz.openbmc_project.Inventory.Decorator.Compatible instances. 497 * 498 * Look for an instance ofxyz.openbmc_project.Inventory.Decorator.Compatible in 499 * the provided argument and if found, issue the provided callback. 500 * 501 * @param[in] message the DBus message in which to look for an instance of 502 * xyz.openbmc_project.Inventory.Decorator.Compatible 503 * @param[in] callback the user callback to make if 504 * xyz.openbmc_project.Inventory.Decorator.Compatible is found in message 505 * @return true if message contained an instance of 506 * xyz.openbmc_project.Inventory.Decorator.Compatible, false otherwise 507 */ 508 bool maybeCallMessage(sdbusplus::message_t& message, 509 const MaybeCallCallbackType& callback) 510 { 511 std::map<std::string, 512 std::map<std::string, std::variant<std::vector<std::string>>>> 513 interfacesAndProperties; 514 sdbusplus::message::object_path _; 515 message.read(_, interfacesAndProperties); 516 return maybeCall(interfacesAndProperties, callback); 517 } 518 519 /** 520 * @brief Determine system support for host firmware well-known names. 521 * 522 * Using the provided extensionMap and 523 * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if well-known 524 * names for host firmware blob files are necessary and if so, create them. 525 * 526 * @param[in] extensionMap a map of 527 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 528 * extensions. 529 * @param[in] hostFirmwareDirectory The directory in which findLinks should look 530 * for host firmware blob files that need well-known names. 531 * @param[in] ibmCompatibleSystem The names property of an instance of 532 * xyz.openbmc_project.Inventory.Decorator.Compatible 533 * @param[in] errorCallback A callback made in the event of filesystem errors. 534 */ 535 void maybeMakeLinks( 536 const std::map<std::string, std::vector<std::string>>& extensionMap, 537 const std::filesystem::path& hostFirmwareDirectory, 538 const std::vector<std::string>& ibmCompatibleSystem, 539 const ErrorCallbackType& errorCallback) 540 { 541 std::vector<std::string> extensions; 542 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 543 extensions)) 544 { 545 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink); 546 } 547 } 548 549 /** 550 * @brief Determine system support for updating the bios attribute table. 551 * 552 * Using the provided extensionMap and 553 * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if the bios 554 * attribute table needs to be updated. 555 * 556 * @param[in] extensionMap a map of 557 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 558 * extensions. 559 * @param[in] elementsJsonFilePath The file path to the json file 560 * @param[in] ibmCompatibleSystem The names property of an instance of 561 * xyz.openbmc_project.Inventory.Decorator.Compatible 562 */ 563 void maybeSetBiosAttr( 564 const std::map<std::string, std::vector<std::string>>& extensionMap, 565 const std::filesystem::path& elementsJsonFilePath, 566 const std::vector<std::string>& ibmCompatibleSystem) 567 { 568 std::vector<std::string> extensions; 569 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 570 extensions)) 571 { 572 try 573 { 574 setBiosAttr(elementsJsonFilePath, extensions); 575 } 576 catch (const sdbusplus::exception_t& e) 577 { 578 throw; 579 } 580 } 581 } 582 583 /** 584 * @brief process host firmware 585 * 586 * Allocate a callback context and register for DBus.ObjectManager Interfaces 587 * added signals from entity manager. 588 * 589 * Check the current entity manager object tree for a 590 * xyz.openbmc_project.Inventory.Decorator.Compatible instance (entity manager 591 * will be dbus activated if it is not running). If one is found, determine if 592 * symlinks need to be created and create them. Instruct the program event loop 593 * to exit. 594 * 595 * If no instance of xyz.openbmc_project.Inventory.Decorator.Compatible is found 596 * return the callback context to main, where the program will sleep until the 597 * callback is invoked one or more times and instructs the program event loop to 598 * exit when xyz.openbmc_project.Inventory.Decorator.Compatible is added. 599 * 600 * @param[in] bus a DBus client connection 601 * @param[in] extensionMap a map of 602 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 603 * extensions. 604 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware 605 * should look for blob files. 606 * @param[in] errorCallback A callback made in the event of filesystem errors. 607 * @param[in] loop a program event loop 608 * @return nullptr if an instance of 609 * xyz.openbmc_project.Inventory.Decorator.Compatible is found, otherwise a 610 * pointer to an sdbusplus match object. 611 */ 612 std::shared_ptr<void> processHostFirmware( 613 sdbusplus::bus_t& bus, 614 std::map<std::string, std::vector<std::string>> extensionMap, 615 std::filesystem::path hostFirmwareDirectory, 616 ErrorCallbackType errorCallback, sdeventplus::Event& loop) 617 { 618 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't 619 // be transferred to the match callback because they are needed in the non 620 // async part of this function below, so they need to be moved to the heap. 621 auto pExtensionMap = 622 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 623 auto pHostFirmwareDirectory = 624 std::make_shared<decltype(hostFirmwareDirectory)>( 625 std::move(hostFirmwareDirectory)); 626 auto pErrorCallback = 627 std::make_shared<decltype(errorCallback)>(std::move(errorCallback)); 628 629 // register for a callback in case the Compatible interface has not yet been 630 // published by entity manager. 631 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match_t>( 632 bus, 633 sdbusplus::bus::match::rules::interfacesAdded() + 634 sdbusplus::bus::match::rules::sender( 635 "xyz.openbmc_project.EntityManager"), 636 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback, 637 &loop](auto& message) { 638 // bind the extension map, host firmware directory, and error 639 // callback to the maybeMakeLinks function. 640 auto maybeMakeLinksWithArgsBound = 641 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 642 std::cref(*pHostFirmwareDirectory), 643 std::placeholders::_1, std::cref(*pErrorCallback)); 644 645 // if the InterfacesAdded message contains an an instance of 646 // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see 647 // if links are necessary on this system and if so, create them. 648 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound)) 649 { 650 // The Compatible interface was found and the links were created 651 // if applicable. Instruct the event loop / subcommand to exit. 652 loop.exit(0); 653 } 654 }); 655 656 // now that we'll get a callback in the event of an InterfacesAdded signal 657 // (potentially containing 658 // xyz.openbmc_project.Inventory.Decorator.Compatible), activate entity 659 // manager if it isn't running and enumerate its objects 660 auto getManagedObjects = bus.new_method_call( 661 "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", 662 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 663 std::map<std::string, 664 std::map<std::string, std::variant<std::vector<std::string>>>> 665 interfacesAndProperties; 666 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 667 objects; 668 try 669 { 670 auto reply = bus.call(getManagedObjects); 671 reply.read(objects); 672 } 673 catch (const sdbusplus::exception_t& e) 674 { 675 // Error querying the EntityManager interface. Return the match to have 676 // the callback run if/when the interface appears in D-Bus. 677 return interfacesAddedMatch; 678 } 679 680 // bind the extension map, host firmware directory, and error callback to 681 // the maybeMakeLinks function. 682 auto maybeMakeLinksWithArgsBound = 683 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 684 std::cref(*pHostFirmwareDirectory), std::placeholders::_1, 685 std::cref(*pErrorCallback)); 686 687 for (const auto& pair : objects) 688 { 689 std::tie(std::ignore, interfacesAndProperties) = pair; 690 // if interfacesAndProperties contains an an instance of 691 // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see if 692 // links are necessary on this system and if so, create them 693 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound)) 694 { 695 // The Compatible interface is already on the bus and the links were 696 // created if applicable. Instruct the event loop to exit. 697 loop.exit(0); 698 // The match object isn't needed anymore, so destroy it on return. 699 return nullptr; 700 } 701 } 702 703 // The Compatible interface has not yet been published. Move ownership of 704 // the match callback to the caller. 705 return interfacesAddedMatch; 706 } 707 708 /** 709 * @brief Update the Bios Attribute Table 710 * 711 * If an instance of xyz.openbmc_project.Inventory.Decorator.Compatible is 712 * found, update the Bios Attribute Table with the appropriate host firmware 713 * data. 714 * 715 * @param[in] bus - D-Bus client connection. 716 * @param[in] extensionMap - Map of Compatible names and host firmware file 717 extensions. 718 * @param[in] elementsJsonFilePath - The Path to the json file 719 * @param[in] loop - Program event loop. 720 * @return nullptr 721 */ 722 std::vector<std::shared_ptr<void>> updateBiosAttrTable( 723 sdbusplus::bus_t& bus, 724 std::map<std::string, std::vector<std::string>> extensionMap, 725 std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop) 726 { 727 constexpr auto pldmPath = "/xyz/openbmc_project/pldm"; 728 constexpr auto entityManagerServiceName = 729 "xyz.openbmc_project.EntityManager"; 730 731 auto pExtensionMap = 732 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 733 auto pElementsJsonFilePath = 734 std::make_shared<decltype(elementsJsonFilePath)>( 735 std::move(elementsJsonFilePath)); 736 737 auto maybeSetAttrWithArgsBound = 738 std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap), 739 std::cref(*pElementsJsonFilePath), std::placeholders::_1); 740 741 std::vector<std::shared_ptr<void>> matches; 742 743 // Entity Manager is needed to get the list of supported extensions. Add a 744 // match to monitor interfaces added in case it's not running yet. 745 matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>( 746 bus, 747 sdbusplus::bus::match::rules::interfacesAdded() + 748 sdbusplus::bus::match::rules::sender( 749 "xyz.openbmc_project.EntityManager"), 750 [pldmPath, pExtensionMap, pElementsJsonFilePath, 751 maybeSetAttrWithArgsBound, &loop](auto& message) { 752 if (maybeCallMessage(message, maybeSetAttrWithArgsBound)) 753 { 754 loop.exit(0); 755 } 756 })); 757 758 // The BIOS attribute table can only be updated if PLDM is running because 759 // PLDM is the one that exposes this property. Add a match to monitor when 760 // the PLDM service starts. 761 matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>( 762 bus, 763 sdbusplus::bus::match::rules::nameOwnerChanged() + 764 sdbusplus::bus::match::rules::arg0namespace( 765 "xyz.openbmc_project.PLDM"), 766 [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound, 767 &loop](auto& message) { 768 std::string name; 769 std::string oldOwner; 770 std::string newOwner; 771 message.read(name, oldOwner, newOwner); 772 773 if (newOwner.empty()) 774 { 775 return; 776 } 777 778 auto bus = sdbusplus::bus::new_default(); 779 InterfacesPropertiesMap interfacesAndProperties; 780 auto objects = getManagedObjects(bus, entityManagerServiceName, 781 "/xyz/openbmc_project/inventory"); 782 for (const auto& pair : objects) 783 { 784 std::tie(std::ignore, interfacesAndProperties) = pair; 785 if (maybeCall(interfacesAndProperties, 786 maybeSetAttrWithArgsBound)) 787 { 788 loop.exit(0); 789 } 790 } 791 })); 792 793 InterfacesPropertiesMap interfacesAndProperties; 794 auto objects = getManagedObjects(bus, entityManagerServiceName, 795 "/xyz/openbmc_project/inventory"); 796 for (const auto& pair : objects) 797 { 798 std::tie(std::ignore, interfacesAndProperties) = pair; 799 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound)) 800 { 801 loop.exit(0); 802 return {}; 803 } 804 } 805 806 return matches; 807 } 808 809 } // namespace process_hostfirmware 810 } // namespace functions 811