1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #pragma once 18 19 #include "app.hpp" 20 #include "dbus_utility.hpp" 21 #include "generated/enums/pcie_device.hpp" 22 #include "query.hpp" 23 #include "registries/privilege_registry.hpp" 24 #include "utils/collection.hpp" 25 #include "utils/dbus_utils.hpp" 26 27 #include <boost/system/linux_error.hpp> 28 #include <sdbusplus/asio/property.hpp> 29 #include <sdbusplus/unpack_properties.hpp> 30 31 namespace redfish 32 { 33 34 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory"; 35 static constexpr std::array<std::string_view, 1> pcieDeviceInterface = { 36 "xyz.openbmc_project.Inventory.Item.PCIeDevice"}; 37 38 static inline void handlePCIeDevicePath( 39 const std::string& pcieDeviceId, 40 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 41 const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths, 42 const std::function<void(const std::string& pcieDevicePath, 43 const std::string& service)>& callback) 44 45 { 46 for (const std::string& pcieDevicePath : pcieDevicePaths) 47 { 48 std::string pciecDeviceName = 49 sdbusplus::message::object_path(pcieDevicePath).filename(); 50 if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId) 51 { 52 continue; 53 } 54 55 dbus::utility::getDbusObject( 56 pcieDevicePath, {}, 57 [pcieDevicePath, aResp, 58 callback](const boost::system::error_code& ec, 59 const dbus::utility::MapperGetObject& object) { 60 if (ec || object.empty()) 61 { 62 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 63 messages::internalError(aResp->res); 64 return; 65 } 66 callback(pcieDevicePath, object.begin()->first); 67 }); 68 return; 69 } 70 71 BMCWEB_LOG_WARNING << "PCIe Device not found"; 72 messages::resourceNotFound(aResp->res, "PCIeDevice", pcieDeviceId); 73 } 74 75 static inline void getValidPCIeDevicePath( 76 const std::string& pcieDeviceId, 77 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 78 const std::function<void(const std::string& pcieDevicePath, 79 const std::string& service)>& callback) 80 { 81 dbus::utility::getSubTreePaths( 82 inventoryPath, 0, pcieDeviceInterface, 83 [pcieDeviceId, aResp, 84 callback](const boost::system::error_code& ec, 85 const dbus::utility::MapperGetSubTreePathsResponse& 86 pcieDevicePaths) { 87 if (ec) 88 { 89 BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec; 90 messages::internalError(aResp->res); 91 return; 92 } 93 handlePCIeDevicePath(pcieDeviceId, aResp, pcieDevicePaths, callback); 94 return; 95 }); 96 } 97 98 static inline void 99 getPCIeDeviceList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 100 const std::string& name) 101 { 102 dbus::utility::getSubTreePaths( 103 inventoryPath, 0, pcieDeviceInterface, 104 [asyncResp, name](const boost::system::error_code& ec, 105 const dbus::utility::MapperGetSubTreePathsResponse& 106 pcieDevicePaths) { 107 if (ec) 108 { 109 BMCWEB_LOG_DEBUG << "no PCIe device paths found ec: " 110 << ec.message(); 111 // Not an error, system just doesn't have PCIe info 112 return; 113 } 114 nlohmann::json& pcieDeviceList = asyncResp->res.jsonValue[name]; 115 pcieDeviceList = nlohmann::json::array(); 116 for (const std::string& pcieDevicePath : pcieDevicePaths) 117 { 118 size_t devStart = pcieDevicePath.rfind('/'); 119 if (devStart == std::string::npos) 120 { 121 continue; 122 } 123 124 std::string devName = pcieDevicePath.substr(devStart + 1); 125 if (devName.empty()) 126 { 127 continue; 128 } 129 nlohmann::json::object_t pcieDevice; 130 pcieDevice["@odata.id"] = crow::utility::urlFromPieces( 131 "redfish", "v1", "Systems", "system", "PCIeDevices", devName); 132 pcieDeviceList.emplace_back(std::move(pcieDevice)); 133 } 134 asyncResp->res.jsonValue[name + "@odata.count"] = pcieDeviceList.size(); 135 }); 136 } 137 138 static inline void handlePCIeDeviceCollectionGet( 139 crow::App& app, const crow::Request& req, 140 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 141 const std::string& systemName) 142 { 143 if (!redfish::setUpRedfishRoute(app, req, aResp)) 144 { 145 return; 146 } 147 if (systemName != "system") 148 { 149 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 150 return; 151 } 152 153 aResp->res.addHeader(boost::beast::http::field::link, 154 "</redfish/v1/JsonSchemas/PCIeDeviceCollection/" 155 "PCIeDeviceCollection.json>; rel=describedby"); 156 aResp->res.jsonValue["@odata.type"] = 157 "#PCIeDeviceCollection.PCIeDeviceCollection"; 158 aResp->res.jsonValue["@odata.id"] = 159 "/redfish/v1/Systems/system/PCIeDevices"; 160 aResp->res.jsonValue["Name"] = "PCIe Device Collection"; 161 aResp->res.jsonValue["Description"] = "Collection of PCIe Devices"; 162 aResp->res.jsonValue["Members"] = nlohmann::json::array(); 163 aResp->res.jsonValue["Members@odata.count"] = 0; 164 165 collection_util::getCollectionMembers( 166 aResp, boost::urls::url("/redfish/v1/Systems/system/PCIeDevices"), 167 pcieDeviceInterface); 168 } 169 170 inline void requestRoutesSystemPCIeDeviceCollection(App& app) 171 { 172 /** 173 * Functions triggers appropriate requests on DBus 174 */ 175 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/") 176 .privileges(redfish::privileges::getPCIeDeviceCollection) 177 .methods(boost::beast::http::verb::get)( 178 std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app))); 179 } 180 181 inline std::optional<pcie_device::PCIeTypes> 182 redfishPcieGenerationFromDbus(const std::string& generationInUse) 183 { 184 if (generationInUse == 185 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen1") 186 { 187 return pcie_device::PCIeTypes::Gen1; 188 } 189 if (generationInUse == 190 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen2") 191 { 192 return pcie_device::PCIeTypes::Gen2; 193 } 194 if (generationInUse == 195 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen3") 196 { 197 return pcie_device::PCIeTypes::Gen3; 198 } 199 if (generationInUse == 200 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen4") 201 { 202 return pcie_device::PCIeTypes::Gen4; 203 } 204 if (generationInUse == 205 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5") 206 { 207 return pcie_device::PCIeTypes::Gen5; 208 } 209 if (generationInUse.empty() || 210 generationInUse == 211 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown") 212 { 213 return pcie_device::PCIeTypes::Invalid; 214 } 215 216 // The value is not unknown or Gen1-5, need return an internal error. 217 return std::nullopt; 218 } 219 220 inline void getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 221 const std::string& pcieDevicePath, 222 const std::string& service) 223 { 224 sdbusplus::asio::getAllProperties( 225 *crow::connections::systemBus, service, pcieDevicePath, 226 "xyz.openbmc_project.Inventory.Decorator.Asset", 227 [pcieDevicePath, 228 aResp{aResp}](const boost::system::error_code& ec, 229 const dbus::utility::DBusPropertiesMap& assetList) { 230 if (ec) 231 { 232 if (ec.value() != EBADR) 233 { 234 BMCWEB_LOG_ERROR << "DBUS response error for Properties" 235 << ec.value(); 236 messages::internalError(aResp->res); 237 } 238 return; 239 } 240 241 const std::string* manufacturer = nullptr; 242 const std::string* model = nullptr; 243 const std::string* partNumber = nullptr; 244 const std::string* serialNumber = nullptr; 245 const std::string* sparePartNumber = nullptr; 246 247 const bool success = sdbusplus::unpackPropertiesNoThrow( 248 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 249 manufacturer, "Model", model, "PartNumber", partNumber, 250 "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber); 251 252 if (!success) 253 { 254 messages::internalError(aResp->res); 255 return; 256 } 257 258 if (manufacturer != nullptr) 259 { 260 aResp->res.jsonValue["Manufacturer"] = *manufacturer; 261 } 262 if (model != nullptr) 263 { 264 aResp->res.jsonValue["Model"] = *model; 265 } 266 267 if (partNumber != nullptr) 268 { 269 aResp->res.jsonValue["PartNumber"] = *partNumber; 270 } 271 272 if (serialNumber != nullptr) 273 { 274 aResp->res.jsonValue["SerialNumber"] = *serialNumber; 275 } 276 277 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 278 { 279 aResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 280 } 281 }); 282 } 283 284 inline void addPCIeDeviceProperties( 285 crow::Response& resp, const std::string& pcieDeviceId, 286 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 287 { 288 const std::string* manufacturer = nullptr; 289 const std::string* deviceType = nullptr; 290 const std::string* generationInUse = nullptr; 291 const int64_t* lanesInUse = nullptr; 292 293 const bool success = sdbusplus::unpackPropertiesNoThrow( 294 dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType", 295 deviceType, "GenerationInUse", generationInUse, "LanesInUse", 296 lanesInUse, "Manufacturer", manufacturer); 297 298 if (!success) 299 { 300 messages::internalError(resp); 301 return; 302 } 303 304 if (deviceType != nullptr && !deviceType->empty()) 305 { 306 resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType; 307 } 308 309 if (generationInUse != nullptr) 310 { 311 std::optional<pcie_device::PCIeTypes> redfishGenerationInUse = 312 redfishPcieGenerationFromDbus(*generationInUse); 313 314 if (!redfishGenerationInUse) 315 { 316 messages::internalError(resp); 317 return; 318 } 319 if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid) 320 { 321 resp.jsonValue["PCIeInterface"]["PCIeType"] = 322 *redfishGenerationInUse; 323 } 324 } 325 326 // The default value of LanesInUse is 0, and the field will be 327 // left as off if it is a default value. 328 if (lanesInUse != nullptr && *lanesInUse != 0) 329 { 330 resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse; 331 } 332 333 if (manufacturer != nullptr) 334 { 335 resp.jsonValue["PCIeInterface"]["Manufacturer"] = *manufacturer; 336 } 337 338 resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces( 339 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 340 "PCIeFunctions"); 341 } 342 343 inline void getPCIeDeviceProperties( 344 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 345 const std::string& pcieDevicePath, const std::string& service, 346 const std::function<void( 347 const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback) 348 { 349 sdbusplus::asio::getAllProperties( 350 *crow::connections::systemBus, service, pcieDevicePath, 351 "xyz.openbmc_project.Inventory.Item.PCIeDevice", 352 [aResp, 353 callback](const boost::system::error_code& ec, 354 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 355 if (ec) 356 { 357 if (ec.value() != EBADR) 358 { 359 BMCWEB_LOG_ERROR << "DBUS response error for Properties"; 360 messages::internalError(aResp->res); 361 } 362 return; 363 } 364 callback(pcieDevProperties); 365 }); 366 } 367 368 inline void addPCIeDeviceCommonProperties( 369 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 370 const std::string& pcieDeviceId) 371 { 372 aResp->res.addHeader( 373 boost::beast::http::field::link, 374 "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby"); 375 aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice"; 376 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 377 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId); 378 aResp->res.jsonValue["Name"] = "PCIe Device"; 379 aResp->res.jsonValue["Id"] = pcieDeviceId; 380 } 381 382 inline void handlePCIeDeviceGet(App& app, const crow::Request& req, 383 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 384 const std::string& systemName, 385 const std::string& pcieDeviceId) 386 { 387 if (!redfish::setUpRedfishRoute(app, req, aResp)) 388 { 389 return; 390 } 391 if (systemName != "system") 392 { 393 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 394 return; 395 } 396 397 getValidPCIeDevicePath( 398 pcieDeviceId, aResp, 399 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 400 const std::string& service) { 401 addPCIeDeviceCommonProperties(aResp, pcieDeviceId); 402 getPCIeDeviceAsset(aResp, pcieDevicePath, service); 403 getPCIeDeviceProperties( 404 aResp, pcieDevicePath, service, 405 [aResp, pcieDeviceId]( 406 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 407 addPCIeDeviceProperties(aResp->res, pcieDeviceId, 408 pcieDevProperties); 409 }); 410 }); 411 } 412 413 inline void requestRoutesSystemPCIeDevice(App& app) 414 { 415 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/") 416 .privileges(redfish::privileges::getPCIeDevice) 417 .methods(boost::beast::http::verb::get)( 418 std::bind_front(handlePCIeDeviceGet, std::ref(app))); 419 } 420 421 inline void addPCIeFunctionList( 422 crow::Response& res, const std::string& pcieDeviceId, 423 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 424 { 425 nlohmann::json& pcieFunctionList = res.jsonValue["Members"]; 426 pcieFunctionList = nlohmann::json::array(); 427 static constexpr const int maxPciFunctionNum = 8; 428 429 for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++) 430 { 431 // Check if this function exists by 432 // looking for a device ID 433 std::string devIDProperty = "Function" + std::to_string(functionNum) + 434 "DeviceId"; 435 const std::string* property = nullptr; 436 for (const auto& propEntry : pcieDevProperties) 437 { 438 if (propEntry.first == devIDProperty) 439 { 440 property = std::get_if<std::string>(&propEntry.second); 441 break; 442 } 443 } 444 if (property == nullptr || property->empty()) 445 { 446 continue; 447 } 448 449 nlohmann::json::object_t pcieFunction; 450 pcieFunction["@odata.id"] = crow::utility::urlFromPieces( 451 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 452 "PCIeFunctions", std::to_string(functionNum)); 453 pcieFunctionList.emplace_back(std::move(pcieFunction)); 454 } 455 res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size(); 456 } 457 458 inline void handlePCIeFunctionCollectionGet( 459 App& app, const crow::Request& req, 460 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 461 const std::string& pcieDeviceId) 462 { 463 if (!redfish::setUpRedfishRoute(app, req, aResp)) 464 { 465 return; 466 } 467 468 getValidPCIeDevicePath( 469 pcieDeviceId, aResp, 470 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 471 const std::string& service) { 472 aResp->res.addHeader( 473 boost::beast::http::field::link, 474 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby"); 475 aResp->res.jsonValue["@odata.type"] = 476 "#PCIeFunctionCollection.PCIeFunctionCollection"; 477 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 478 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 479 "PCIeFunctions"); 480 aResp->res.jsonValue["Name"] = "PCIe Function Collection"; 481 aResp->res.jsonValue["Description"] = 482 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId; 483 getPCIeDeviceProperties( 484 aResp, pcieDevicePath, service, 485 [aResp, pcieDeviceId]( 486 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 487 addPCIeFunctionList(aResp->res, pcieDeviceId, pcieDevProperties); 488 }); 489 }); 490 } 491 492 inline void requestRoutesSystemPCIeFunctionCollection(App& app) 493 { 494 /** 495 * Functions triggers appropriate requests on DBus 496 */ 497 BMCWEB_ROUTE(app, 498 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/") 499 .privileges(redfish::privileges::getPCIeFunctionCollection) 500 .methods(boost::beast::http::verb::get)( 501 std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app))); 502 } 503 504 inline bool validatePCIeFunctionId( 505 const std::string& pcieFunctionId, 506 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 507 { 508 std::string functionName = "Function" + pcieFunctionId; 509 std::string devIDProperty = functionName + "DeviceId"; 510 511 const std::string* devIdProperty = nullptr; 512 for (const auto& property : pcieDevProperties) 513 { 514 if (property.first == devIDProperty) 515 { 516 devIdProperty = std::get_if<std::string>(&property.second); 517 break; 518 } 519 } 520 return (devIdProperty != nullptr && !devIdProperty->empty()); 521 } 522 523 inline void addPCIeFunctionProperties( 524 crow::Response& resp, const std::string& pcieFunctionId, 525 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 526 { 527 std::string functionName = "Function" + pcieFunctionId; 528 if (!validatePCIeFunctionId(pcieFunctionId, pcieDevProperties)) 529 { 530 messages::resourceNotFound(resp, "PCIeFunction", pcieFunctionId); 531 return; 532 } 533 for (const auto& property : pcieDevProperties) 534 { 535 const std::string* strProperty = 536 std::get_if<std::string>(&property.second); 537 538 if (property.first == functionName + "DeviceId") 539 { 540 resp.jsonValue["DeviceId"] = *strProperty; 541 } 542 if (property.first == functionName + "VendorId") 543 { 544 resp.jsonValue["VendorId"] = *strProperty; 545 } 546 // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus 547 // property strings should be mapped correctly to ensure these 548 // strings are Redfish enum values. For now just check for empty. 549 if (property.first == functionName + "FunctionType") 550 { 551 if (!strProperty->empty()) 552 { 553 resp.jsonValue["FunctionType"] = *strProperty; 554 } 555 } 556 if (property.first == functionName + "DeviceClass") 557 { 558 if (!strProperty->empty()) 559 { 560 resp.jsonValue["DeviceClass"] = *strProperty; 561 } 562 } 563 if (property.first == functionName + "ClassCode") 564 { 565 resp.jsonValue["ClassCode"] = *strProperty; 566 } 567 if (property.first == functionName + "RevisionId") 568 { 569 resp.jsonValue["RevisionId"] = *strProperty; 570 } 571 if (property.first == functionName + "SubsystemId") 572 { 573 resp.jsonValue["SubsystemId"] = *strProperty; 574 } 575 if (property.first == functionName + "SubsystemVendorId") 576 { 577 resp.jsonValue["SubsystemVendorId"] = *strProperty; 578 } 579 } 580 } 581 582 inline void addPCIeFunctionCommonProperties(crow::Response& resp, 583 const std::string& pcieDeviceId, 584 const std::string& pcieFunctionId) 585 { 586 resp.addHeader( 587 boost::beast::http::field::link, 588 "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby"); 589 resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction"; 590 resp.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 591 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 592 "PCIeFunctions", pcieFunctionId); 593 resp.jsonValue["Name"] = "PCIe Function"; 594 resp.jsonValue["Id"] = pcieFunctionId; 595 resp.jsonValue["FunctionId"] = std::stoi(pcieFunctionId); 596 resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = 597 crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", 598 "PCIeDevices", pcieDeviceId); 599 } 600 601 inline void 602 handlePCIeFunctionGet(App& app, const crow::Request& req, 603 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 604 const std::string& pcieDeviceId, 605 const std::string& pcieFunctionId) 606 { 607 if (!redfish::setUpRedfishRoute(app, req, aResp)) 608 { 609 return; 610 } 611 612 getValidPCIeDevicePath( 613 pcieDeviceId, aResp, 614 [aResp, pcieDeviceId, pcieFunctionId](const std::string& pcieDevicePath, 615 const std::string& service) { 616 getPCIeDeviceProperties( 617 aResp, pcieDevicePath, service, 618 [aResp, pcieDeviceId, pcieFunctionId]( 619 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 620 addPCIeFunctionCommonProperties(aResp->res, pcieDeviceId, 621 pcieFunctionId); 622 addPCIeFunctionProperties(aResp->res, pcieFunctionId, 623 pcieDevProperties); 624 }); 625 }); 626 } 627 628 inline void requestRoutesSystemPCIeFunction(App& app) 629 { 630 BMCWEB_ROUTE( 631 app, 632 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/") 633 .privileges(redfish::privileges::getPCIeFunction) 634 .methods(boost::beast::http::verb::get)( 635 std::bind_front(handlePCIeFunctionGet, std::ref(app))); 636 } 637 638 } // namespace redfish 639