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 <phosphor-logging/log.hpp> 10 #include <sdbusplus/bus.hpp> 11 #include <sdbusplus/bus/match.hpp> 12 #include <sdbusplus/exception.hpp> 13 #include <sdbusplus/message.hpp> 14 #include <sdeventplus/event.hpp> 15 16 #include <filesystem> 17 #include <functional> 18 #include <iostream> 19 #include <map> 20 #include <memory> 21 #include <string> 22 #include <variant> 23 #include <vector> 24 25 namespace functions 26 { 27 namespace process_hostfirmware 28 { 29 30 using namespace phosphor::logging; 31 32 /** 33 * @brief Issue callbacks safely 34 * 35 * std::function can be empty, so this wrapper method checks for that prior to 36 * calling it to avoid std::bad_function_call 37 * 38 * @tparam Sig the types of the std::function arguments 39 * @tparam Args the deduced argument types 40 * @param[in] callback the callback being wrapped 41 * @param[in] args the callback arguments 42 */ 43 template <typename... Sig, typename... Args> 44 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args) 45 { 46 if (callback) 47 { 48 callback(std::forward<Args>(args)...); 49 } 50 } 51 52 /** 53 * @brief Get file extensions for IBMCompatibleSystem 54 * 55 * IBM host firmware can be deployed as blobs (files) in a filesystem. Host 56 * firmware blobs for different values of 57 * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with 58 * different filename extensions. getExtensionsForIbmCompatibleSystem 59 * maintains the mapping from a given value of 60 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of 61 * filename extensions. 62 * 63 * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and 64 * the extensions parameter is reset with the map entry. If no mapping is 65 * found getExtensionsForIbmCompatibleSystem returns false and extensions is 66 * unmodified. 67 * 68 * @param[in] extensionMap a map of 69 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 70 * file extensions. 71 * @param[in] ibmCompatibleSystem The names property of an instance of 72 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 73 * @param[out] extentions the host firmware blob file extensions 74 * @return true if an entry was found, otherwise false 75 */ 76 bool getExtensionsForIbmCompatibleSystem( 77 const std::map<std::string, std::vector<std::string>>& extensionMap, 78 const std::vector<std::string>& ibmCompatibleSystem, 79 std::vector<std::string>& extensions) 80 { 81 for (const auto& system : ibmCompatibleSystem) 82 { 83 auto extensionMapIterator = extensionMap.find(system); 84 if (extensionMapIterator != extensionMap.end()) 85 { 86 extensions = extensionMapIterator->second; 87 return true; 88 } 89 } 90 91 return false; 92 } 93 94 /** 95 * @brief Write host firmware well-known name 96 * 97 * A wrapper around std::filesystem::create_symlink that avoids EEXIST by 98 * deleting any pre-existing file. 99 * 100 * @param[in] linkTarget The link target argument to 101 * std::filesystem::create_symlink 102 * @param[in] linkPath The link path argument to std::filesystem::create_symlink 103 * @param[in] errorCallback A callback made in the event of filesystem errors. 104 */ 105 void writeLink(const std::filesystem::path& linkTarget, 106 const std::filesystem::path& linkPath, 107 const ErrorCallbackType& errorCallback) 108 { 109 std::error_code ec; 110 111 // remove files with the same name as the symlink to be created, 112 // otherwise symlink will fail with EEXIST. 113 if (!std::filesystem::remove(linkPath, ec)) 114 { 115 if (ec) 116 { 117 makeCallback(errorCallback, linkPath, ec); 118 return; 119 } 120 } 121 122 std::filesystem::create_symlink(linkTarget, linkPath, ec); 123 if (ec) 124 { 125 makeCallback(errorCallback, linkPath, ec); 126 return; 127 } 128 } 129 130 /** 131 * @brief Find host firmware blob files that need well-known names 132 * 133 * The IBM host firmware runtime looks for data and/or additional code while 134 * bootstraping in files with well-known names. findLinks uses the provided 135 * extensions argument to find host firmware blob files that require a 136 * well-known name. When a blob is found, issue the provided callback 137 * (typically a function that will write a symlink). 138 * 139 * @param[in] hostFirmwareDirectory The directory in which findLinks should 140 * look for host firmware blob files that need well-known names. 141 * @param[in] extentions The extensions of the firmware blob files denote a 142 * host firmware blob file requires a well-known name. 143 * @param[in] errorCallback A callback made in the event of filesystem errors. 144 * @param[in] linkCallback A callback made when host firmware blob files 145 * needing a well known name are found. 146 */ 147 void findLinks(const std::filesystem::path& hostFirmwareDirectory, 148 const std::vector<std::string>& extensions, 149 const ErrorCallbackType& errorCallback, 150 const LinkCallbackType& linkCallback) 151 { 152 std::error_code ec; 153 std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory, 154 ec); 155 if (ec) 156 { 157 makeCallback(errorCallback, hostFirmwareDirectory, ec); 158 return; 159 } 160 161 // Create a symlink from HBB to the corresponding LID file if it exists 162 static const auto hbbLid = "81e0065a.lid"; 163 auto hbbLidPath = hostFirmwareDirectory / hbbLid; 164 if (std::filesystem::exists(hbbLidPath)) 165 { 166 static const auto hbbName = "HBB"; 167 auto hbbLinkPath = hostFirmwareDirectory / hbbName; 168 makeCallback(linkCallback, hbbLid, hbbLinkPath, errorCallback); 169 } 170 171 for (; directoryIterator != std::filesystem::end(directoryIterator); 172 directoryIterator.increment(ec)) 173 { 174 const auto& file = directoryIterator->path(); 175 if (ec) 176 { 177 makeCallback(errorCallback, file, ec); 178 // quit here if the increment call failed otherwise the loop may 179 // never finish 180 break; 181 } 182 183 if (std::find(extensions.begin(), extensions.end(), file.extension()) == 184 extensions.end()) 185 { 186 // this file doesn't have an extension or doesn't match any of the 187 // provided extensions. 188 continue; 189 } 190 191 auto linkPath(file.parent_path().append( 192 static_cast<const std::string&>(file.stem()))); 193 194 makeCallback(linkCallback, file.filename(), linkPath, errorCallback); 195 } 196 } 197 198 /** 199 * @brief Set the bios attribute table with details of the host firmware data 200 * for this system. 201 */ 202 void setBiosAttr() 203 { 204 std::string biosAttrStr{}; 205 206 constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager"; 207 constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager"; 208 constexpr auto dbusAttrName = "hb_lid_ids"; 209 constexpr auto dbusAttrType = 210 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String"; 211 212 using PendingAttributesType = std::vector<std::pair< 213 std::string, std::tuple<std::string, std::variant<std::string>>>>; 214 PendingAttributesType pendingAttributes; 215 pendingAttributes.emplace_back(std::make_pair( 216 dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr))); 217 218 auto bus = sdbusplus::bus::new_default(); 219 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 220 MAPPER_INTERFACE, "GetObject"); 221 method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf})); 222 std::vector<std::pair<std::string, std::vector<std::string>>> response; 223 try 224 { 225 auto reply = bus.call(method); 226 reply.read(response); 227 if (response.empty()) 228 { 229 log<level::ERR>("Error reading mapper response", 230 entry("PATH=%s", biosConfigPath), 231 entry("INTERFACE=%s", biosConfigIntf)); 232 return; 233 } 234 auto method = bus.new_method_call((response.begin()->first).c_str(), 235 biosConfigPath, 236 SYSTEMD_PROPERTY_INTERFACE, "Set"); 237 method.append(biosConfigIntf, "PendingAttributes", 238 std::variant<PendingAttributesType>(pendingAttributes)); 239 bus.call(method); 240 } 241 catch (const sdbusplus::exception::SdBusError& e) 242 { 243 log<level::ERR>("Error setting the bios attribute", 244 entry("ERROR=%s", e.what()), 245 entry("ATTRIBUTE=%s", dbusAttrName)); 246 return; 247 } 248 } 249 250 /** 251 * @brief Make callbacks on 252 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances. 253 * 254 * Look for an instance of 255 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided 256 * argument and if found, issue the provided callback. 257 * 258 * @param[in] interfacesAndProperties the interfaces in which to look for an 259 * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem 260 * @param[in] callback the user callback to make if 261 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in 262 * interfacesAndProperties 263 * @return true if interfacesAndProperties contained an instance of 264 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise 265 */ 266 bool maybeCall(const std::map<std::string, 267 std::map<std::string, 268 std::variant<std::vector<std::string>>>>& 269 interfacesAndProperties, 270 const MaybeCallCallbackType& callback) 271 { 272 using namespace std::string_literals; 273 274 static const auto interfaceName = 275 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s; 276 auto interfaceIterator = interfacesAndProperties.find(interfaceName); 277 if (interfaceIterator == interfacesAndProperties.cend()) 278 { 279 // IBMCompatibleSystem interface not found, so instruct the caller to 280 // keep waiting or try again later. 281 return false; 282 } 283 auto propertyIterator = interfaceIterator->second.find("Names"s); 284 if (propertyIterator == interfaceIterator->second.cend()) 285 { 286 // The interface exists but the property doesn't. This is a bug in the 287 // IBMCompatibleSystem implementation. The caller should not try 288 // again. 289 std::cerr << "Names property not implemented on " << interfaceName 290 << "\n"; 291 return true; 292 } 293 294 const auto& ibmCompatibleSystem = 295 std::get<std::vector<std::string>>(propertyIterator->second); 296 if (callback) 297 { 298 callback(ibmCompatibleSystem); 299 } 300 301 // IBMCompatibleSystem found and callback issued. 302 return true; 303 } 304 305 /** 306 * @brief Make callbacks on 307 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances. 308 * 309 * Look for an instance of 310 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided 311 * argument and if found, issue the provided callback. 312 * 313 * @param[in] message the DBus message in which to look for an instance of 314 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 315 * @param[in] callback the user callback to make if 316 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in 317 * message 318 * @return true if message contained an instance of 319 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise 320 */ 321 bool maybeCallMessage(sdbusplus::message::message& message, 322 const MaybeCallCallbackType& callback) 323 { 324 std::map<std::string, 325 std::map<std::string, std::variant<std::vector<std::string>>>> 326 interfacesAndProperties; 327 sdbusplus::message::object_path _; 328 message.read(_, interfacesAndProperties); 329 return maybeCall(interfacesAndProperties, callback); 330 } 331 332 /** 333 * @brief Determine system support for host firmware well-known names. 334 * 335 * Using the provided extensionMap and 336 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if 337 * well-known names for host firmare blob files are necessary and if so, create 338 * them. 339 * 340 * @param[in] extensionMap a map of 341 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 342 * file extensions. 343 * @param[in] hostFirmwareDirectory The directory in which findLinks should 344 * look for host firmware blob files that need well-known names. 345 * @param[in] ibmCompatibleSystem The names property of an instance of 346 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 347 * @param[in] errorCallback A callback made in the event of filesystem errors. 348 */ 349 void maybeMakeLinks( 350 const std::map<std::string, std::vector<std::string>>& extensionMap, 351 const std::filesystem::path& hostFirmwareDirectory, 352 const std::vector<std::string>& ibmCompatibleSystem, 353 const ErrorCallbackType& errorCallback) 354 { 355 std::vector<std::string> extensions; 356 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 357 extensions)) 358 { 359 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink); 360 } 361 } 362 363 /** 364 * @brief Determine system support for updating the bios attribute table. 365 * 366 * Using the provided extensionMap and 367 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios 368 * attribute table needs to be updated. 369 * 370 * @param[in] extensionMap a map of 371 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 372 * file extensions. 373 * @param[in] ibmCompatibleSystem The names property of an instance of 374 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 375 */ 376 void maybeSetBiosAttr( 377 const std::map<std::string, std::vector<std::string>>& extensionMap, 378 const std::vector<std::string>& ibmCompatibleSystem) 379 { 380 std::vector<std::string> extensions; 381 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 382 extensions)) 383 { 384 setBiosAttr(); 385 } 386 } 387 388 /** 389 * @brief process host firmware 390 * 391 * Allocate a callback context and register for DBus.ObjectManager Interfaces 392 * added signals from entity manager. 393 * 394 * Check the current entity manager object tree for a 395 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity 396 * manager will be dbus activated if it is not running). If one is found, 397 * determine if symlinks need to be created and create them. Instruct the 398 * program event loop to exit. 399 * 400 * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is 401 * found return the callback context to main, where the program will sleep 402 * until the callback is invoked one or more times and instructs the program 403 * event loop to exit when 404 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added. 405 * 406 * @param[in] bus a DBus client connection 407 * @param[in] extensionMap a map of 408 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 409 * file extensions. 410 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware 411 * should look for blob files. 412 * @param[in] errorCallback A callback made in the event of filesystem errors. 413 * @param[in] loop a program event loop 414 * @return nullptr if an instance of 415 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a 416 * pointer to an sdbusplus match object. 417 */ 418 std::shared_ptr<void> processHostFirmware( 419 sdbusplus::bus::bus& bus, 420 std::map<std::string, std::vector<std::string>> extensionMap, 421 std::filesystem::path hostFirmwareDirectory, 422 ErrorCallbackType errorCallback, sdeventplus::Event& loop) 423 { 424 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't 425 // be transfered to the match callback because they are needed in the non 426 // async part of this function below, so they need to be moved to the heap. 427 auto pExtensionMap = 428 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 429 auto pHostFirmwareDirectory = 430 std::make_shared<decltype(hostFirmwareDirectory)>( 431 std::move(hostFirmwareDirectory)); 432 auto pErrorCallback = 433 std::make_shared<decltype(errorCallback)>(std::move(errorCallback)); 434 435 // register for a callback in case the IBMCompatibleSystem interface has 436 // not yet been published by entity manager. 437 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>( 438 bus, 439 sdbusplus::bus::match::rules::interfacesAdded() + 440 sdbusplus::bus::match::rules::sender( 441 "xyz.openbmc_project.EntityManager"), 442 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback, 443 &loop](auto& message) { 444 // bind the extension map, host firmware directory, and error 445 // callback to the maybeMakeLinks function. 446 auto maybeMakeLinksWithArgsBound = 447 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 448 std::cref(*pHostFirmwareDirectory), 449 std::placeholders::_1, std::cref(*pErrorCallback)); 450 451 // if the InterfacesAdded message contains an an instance of 452 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to 453 // see if links are necessary on this system and if so, create 454 // them. 455 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound)) 456 { 457 // The IBMCompatibleSystem interface was found and the links 458 // were created if applicable. Instruct the event loop / 459 // subcommand to exit. 460 loop.exit(0); 461 } 462 }); 463 464 // now that we'll get a callback in the event of an InterfacesAdded signal 465 // (potentially containing 466 // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity 467 // manager if it isn't running and enumerate its objects 468 auto getManagedObjects = bus.new_method_call( 469 "xyz.openbmc_project.EntityManager", "/", 470 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 471 std::map<std::string, 472 std::map<std::string, std::variant<std::vector<std::string>>>> 473 interfacesAndProperties; 474 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 475 objects; 476 try 477 { 478 auto reply = bus.call(getManagedObjects); 479 reply.read(objects); 480 } 481 catch (const sdbusplus::exception::SdBusError& e) 482 { 483 // Error querying the EntityManager interface. Return the match to have 484 // the callback run if/when the interface appears in D-Bus. 485 return interfacesAddedMatch; 486 } 487 488 // bind the extension map, host firmware directory, and error callback to 489 // the maybeMakeLinks function. 490 auto maybeMakeLinksWithArgsBound = 491 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 492 std::cref(*pHostFirmwareDirectory), std::placeholders::_1, 493 std::cref(*pErrorCallback)); 494 495 for (const auto& pair : objects) 496 { 497 std::tie(std::ignore, interfacesAndProperties) = pair; 498 // if interfacesAndProperties contains an an instance of 499 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see 500 // if links are necessary on this system and if so, create them 501 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound)) 502 { 503 // The IBMCompatibleSystem interface is already on the bus and the 504 // links were created if applicable. Instruct the event loop to 505 // exit. 506 loop.exit(0); 507 // The match object isn't needed anymore, so destroy it on return. 508 return nullptr; 509 } 510 } 511 512 // The IBMCompatibleSystem interface has not yet been published. Move 513 // ownership of the match callback to the caller. 514 return interfacesAddedMatch; 515 } 516 517 /** 518 * @brief Update the Bios Attribute Table 519 * 520 * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is 521 * found, update the Bios Attribute Table with the appropriate host firmware 522 * data. 523 * 524 * @param[in] bus - D-Bus client connection. 525 * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware 526 * file extensions. 527 * @param[in] loop - Program event loop. 528 * @return nullptr 529 */ 530 std::shared_ptr<void> updateBiosAttrTable( 531 sdbusplus::bus::bus& bus, 532 std::map<std::string, std::vector<std::string>> extensionMap, 533 sdeventplus::Event& loop) 534 { 535 auto pExtensionMap = 536 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 537 538 auto getManagedObjects = bus.new_method_call( 539 "xyz.openbmc_project.EntityManager", "/", 540 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 541 std::map<std::string, 542 std::map<std::string, std::variant<std::vector<std::string>>>> 543 interfacesAndProperties; 544 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 545 objects; 546 try 547 { 548 auto reply = bus.call(getManagedObjects); 549 reply.read(objects); 550 } 551 catch (const sdbusplus::exception::SdBusError& e) 552 {} 553 554 auto maybeSetAttrWithArgsBound = std::bind( 555 maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1); 556 557 for (const auto& pair : objects) 558 { 559 std::tie(std::ignore, interfacesAndProperties) = pair; 560 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound)) 561 { 562 break; 563 } 564 } 565 566 loop.exit(0); 567 return nullptr; 568 } 569 570 } // namespace process_hostfirmware 571 } // namespace functions 572