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(hostFirmwareDirectory, 188 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 = std::filesystem::path("/media/hostfw/running") / 352 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(const std::map<std::string, 449 std::map<std::string, 450 std::variant<std::vector<std::string>>>>& 451 interfacesAndProperties, 452 const MaybeCallCallbackType& callback) 453 { 454 using namespace std::string_literals; 455 456 static const auto interfaceName = 457 "xyz.openbmc_project.Inventory.Decorator.Compatible"s; 458 auto interfaceIterator = interfacesAndProperties.find(interfaceName); 459 if (interfaceIterator == interfacesAndProperties.cend()) 460 { 461 // Compatible interface not found, so instruct the caller to keep 462 // waiting or try again later. 463 return false; 464 } 465 auto propertyIterator = interfaceIterator->second.find("Names"s); 466 if (propertyIterator == interfaceIterator->second.cend()) 467 { 468 // The interface exists but the property doesn't. This is a bug in the 469 // Compatible implementation. The caller should not try again. 470 std::cerr << "Names property not implemented on " << interfaceName 471 << "\n"; 472 return true; 473 } 474 475 const auto& ibmCompatibleSystem = 476 std::get<std::vector<std::string>>(propertyIterator->second); 477 if (callback) 478 { 479 try 480 { 481 callback(ibmCompatibleSystem); 482 } 483 catch (const sdbusplus::exception_t& e) 484 { 485 return false; 486 } 487 } 488 489 // Compatible found and callback issued. 490 return true; 491 } 492 493 /** 494 * @brief Make callbacks on 495 * xyz.openbmc_project.Inventory.Decorator.Compatible instances. 496 * 497 * Look for an instance ofxyz.openbmc_project.Inventory.Decorator.Compatible in 498 * the provided argument and if found, issue the provided callback. 499 * 500 * @param[in] message the DBus message in which to look for an instance of 501 * xyz.openbmc_project.Inventory.Decorator.Compatible 502 * @param[in] callback the user callback to make if 503 * xyz.openbmc_project.Inventory.Decorator.Compatible is found in message 504 * @return true if message contained an instance of 505 * xyz.openbmc_project.Inventory.Decorator.Compatible, false otherwise 506 */ 507 bool maybeCallMessage(sdbusplus::message_t& message, 508 const MaybeCallCallbackType& callback) 509 { 510 std::map<std::string, 511 std::map<std::string, std::variant<std::vector<std::string>>>> 512 interfacesAndProperties; 513 sdbusplus::message::object_path _; 514 message.read(_, interfacesAndProperties); 515 return maybeCall(interfacesAndProperties, callback); 516 } 517 518 /** 519 * @brief Determine system support for host firmware well-known names. 520 * 521 * Using the provided extensionMap and 522 * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if well-known 523 * names for host firmware blob files are necessary and if so, create them. 524 * 525 * @param[in] extensionMap a map of 526 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 527 * extensions. 528 * @param[in] hostFirmwareDirectory The directory in which findLinks should look 529 * for host firmware blob files that need well-known names. 530 * @param[in] ibmCompatibleSystem The names property of an instance of 531 * xyz.openbmc_project.Inventory.Decorator.Compatible 532 * @param[in] errorCallback A callback made in the event of filesystem errors. 533 */ 534 void maybeMakeLinks( 535 const std::map<std::string, std::vector<std::string>>& extensionMap, 536 const std::filesystem::path& hostFirmwareDirectory, 537 const std::vector<std::string>& ibmCompatibleSystem, 538 const ErrorCallbackType& errorCallback) 539 { 540 std::vector<std::string> extensions; 541 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 542 extensions)) 543 { 544 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink); 545 } 546 } 547 548 /** 549 * @brief Determine system support for updating the bios attribute table. 550 * 551 * Using the provided extensionMap and 552 * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if the bios 553 * attribute table needs to be updated. 554 * 555 * @param[in] extensionMap a map of 556 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 557 * extensions. 558 * @param[in] elementsJsonFilePath The file path to the json file 559 * @param[in] ibmCompatibleSystem The names property of an instance of 560 * xyz.openbmc_project.Inventory.Decorator.Compatible 561 */ 562 void maybeSetBiosAttr( 563 const std::map<std::string, std::vector<std::string>>& extensionMap, 564 const std::filesystem::path& elementsJsonFilePath, 565 const std::vector<std::string>& ibmCompatibleSystem) 566 { 567 std::vector<std::string> extensions; 568 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 569 extensions)) 570 { 571 try 572 { 573 setBiosAttr(elementsJsonFilePath, extensions); 574 } 575 catch (const sdbusplus::exception_t& e) 576 { 577 throw; 578 } 579 } 580 } 581 582 /** 583 * @brief process host firmware 584 * 585 * Allocate a callback context and register for DBus.ObjectManager Interfaces 586 * added signals from entity manager. 587 * 588 * Check the current entity manager object tree for a 589 * xyz.openbmc_project.Inventory.Decorator.Compatible instance (entity manager 590 * will be dbus activated if it is not running). If one is found, determine if 591 * symlinks need to be created and create them. Instruct the program event loop 592 * to exit. 593 * 594 * If no instance of xyz.openbmc_project.Inventory.Decorator.Compatible is found 595 * return the callback context to main, where the program will sleep until the 596 * callback is invoked one or more times and instructs the program event loop to 597 * exit when xyz.openbmc_project.Inventory.Decorator.Compatible is added. 598 * 599 * @param[in] bus a DBus client connection 600 * @param[in] extensionMap a map of 601 * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file 602 * extensions. 603 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware 604 * should look for blob files. 605 * @param[in] errorCallback A callback made in the event of filesystem errors. 606 * @param[in] loop a program event loop 607 * @return nullptr if an instance of 608 * xyz.openbmc_project.Inventory.Decorator.Compatible is found, otherwise a 609 * pointer to an sdbusplus match object. 610 */ 611 std::shared_ptr<void> processHostFirmware( 612 sdbusplus::bus_t& bus, 613 std::map<std::string, std::vector<std::string>> extensionMap, 614 std::filesystem::path hostFirmwareDirectory, 615 ErrorCallbackType errorCallback, sdeventplus::Event& loop) 616 { 617 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't 618 // be transferred to the match callback because they are needed in the non 619 // async part of this function below, so they need to be moved to the heap. 620 auto pExtensionMap = 621 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 622 auto pHostFirmwareDirectory = 623 std::make_shared<decltype(hostFirmwareDirectory)>( 624 std::move(hostFirmwareDirectory)); 625 auto pErrorCallback = 626 std::make_shared<decltype(errorCallback)>(std::move(errorCallback)); 627 628 // register for a callback in case the Compatible interface has not yet been 629 // published by entity manager. 630 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match_t>( 631 bus, 632 sdbusplus::bus::match::rules::interfacesAdded() + 633 sdbusplus::bus::match::rules::sender( 634 "xyz.openbmc_project.EntityManager"), 635 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback, 636 &loop](auto& message) { 637 // bind the extension map, host firmware directory, and error 638 // callback to the maybeMakeLinks function. 639 auto maybeMakeLinksWithArgsBound = 640 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 641 std::cref(*pHostFirmwareDirectory), std::placeholders::_1, 642 std::cref(*pErrorCallback)); 643 644 // if the InterfacesAdded message contains an an instance of 645 // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see if 646 // links are necessary on this system and if so, create them. 647 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound)) 648 { 649 // The Compatible interface was found and the links were created if 650 // applicable. Instruct the event loop / subcommand to exit. 651 loop.exit(0); 652 } 653 }); 654 655 // now that we'll get a callback in the event of an InterfacesAdded signal 656 // (potentially containing 657 // xyz.openbmc_project.Inventory.Decorator.Compatible), activate entity 658 // manager if it isn't running and enumerate its objects 659 auto getManagedObjects = bus.new_method_call( 660 "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", 661 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 662 std::map<std::string, 663 std::map<std::string, std::variant<std::vector<std::string>>>> 664 interfacesAndProperties; 665 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 666 objects; 667 try 668 { 669 auto reply = bus.call(getManagedObjects); 670 reply.read(objects); 671 } 672 catch (const sdbusplus::exception_t& e) 673 { 674 // Error querying the EntityManager interface. Return the match to have 675 // the callback run if/when the interface appears in D-Bus. 676 return interfacesAddedMatch; 677 } 678 679 // bind the extension map, host firmware directory, and error callback to 680 // the maybeMakeLinks function. 681 auto maybeMakeLinksWithArgsBound = 682 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 683 std::cref(*pHostFirmwareDirectory), std::placeholders::_1, 684 std::cref(*pErrorCallback)); 685 686 for (const auto& pair : objects) 687 { 688 std::tie(std::ignore, interfacesAndProperties) = pair; 689 // if interfacesAndProperties contains an an instance of 690 // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see if 691 // links are necessary on this system and if so, create them 692 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound)) 693 { 694 // The Compatible interface is already on the bus and the links were 695 // created if applicable. Instruct the event loop to exit. 696 loop.exit(0); 697 // The match object isn't needed anymore, so destroy it on return. 698 return nullptr; 699 } 700 } 701 702 // The Compatible interface has not yet been published. Move ownership of 703 // the match callback to the caller. 704 return interfacesAddedMatch; 705 } 706 707 /** 708 * @brief Update the Bios Attribute Table 709 * 710 * If an instance of xyz.openbmc_project.Inventory.Decorator.Compatible is 711 * found, update the Bios Attribute Table with the appropriate host firmware 712 * data. 713 * 714 * @param[in] bus - D-Bus client connection. 715 * @param[in] extensionMap - Map of Compatible names and host firmware file 716 extensions. 717 * @param[in] elementsJsonFilePath - The Path to the json file 718 * @param[in] loop - Program event loop. 719 * @return nullptr 720 */ 721 std::vector<std::shared_ptr<void>> updateBiosAttrTable( 722 sdbusplus::bus_t& bus, 723 std::map<std::string, std::vector<std::string>> extensionMap, 724 std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop) 725 { 726 constexpr auto pldmPath = "/xyz/openbmc_project/pldm"; 727 constexpr auto entityManagerServiceName = 728 "xyz.openbmc_project.EntityManager"; 729 730 auto pExtensionMap = 731 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 732 auto pElementsJsonFilePath = 733 std::make_shared<decltype(elementsJsonFilePath)>( 734 std::move(elementsJsonFilePath)); 735 736 auto maybeSetAttrWithArgsBound = 737 std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap), 738 std::cref(*pElementsJsonFilePath), std::placeholders::_1); 739 740 std::vector<std::shared_ptr<void>> matches; 741 742 // Entity Manager is needed to get the list of supported extensions. Add a 743 // match to monitor interfaces added in case it's not running yet. 744 matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>( 745 bus, 746 sdbusplus::bus::match::rules::interfacesAdded() + 747 sdbusplus::bus::match::rules::sender( 748 "xyz.openbmc_project.EntityManager"), 749 [pldmPath, pExtensionMap, pElementsJsonFilePath, 750 maybeSetAttrWithArgsBound, &loop](auto& message) { 751 if (maybeCallMessage(message, maybeSetAttrWithArgsBound)) 752 { 753 loop.exit(0); 754 } 755 })); 756 757 // The BIOS attribute table can only be updated if PLDM is running because 758 // PLDM is the one that exposes this property. Add a match to monitor when 759 // the PLDM service starts. 760 matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>( 761 bus, 762 sdbusplus::bus::match::rules::nameOwnerChanged() + 763 sdbusplus::bus::match::rules::arg0namespace( 764 "xyz.openbmc_project.PLDM"), 765 [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound, 766 &loop](auto& message) { 767 std::string name; 768 std::string oldOwner; 769 std::string newOwner; 770 message.read(name, oldOwner, newOwner); 771 772 if (newOwner.empty()) 773 { 774 return; 775 } 776 777 auto bus = sdbusplus::bus::new_default(); 778 InterfacesPropertiesMap interfacesAndProperties; 779 auto objects = getManagedObjects(bus, entityManagerServiceName, 780 "/xyz/openbmc_project/inventory"); 781 for (const auto& pair : objects) 782 { 783 std::tie(std::ignore, interfacesAndProperties) = pair; 784 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound)) 785 { 786 loop.exit(0); 787 } 788 } 789 })); 790 791 InterfacesPropertiesMap interfacesAndProperties; 792 auto objects = getManagedObjects(bus, entityManagerServiceName, 793 "/xyz/openbmc_project/inventory"); 794 for (const auto& pair : objects) 795 { 796 std::tie(std::ignore, interfacesAndProperties) = pair; 797 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound)) 798 { 799 loop.exit(0); 800 return {}; 801 } 802 } 803 804 return matches; 805 } 806 807 } // namespace process_hostfirmware 808 } // namespace functions 809