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