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