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 "query.hpp" 22 #include "registries/privilege_registry.hpp" 23 #include "utils/collection.hpp" 24 #include "utils/dbus_utils.hpp" 25 #include "utils/pcie_util.hpp" 26 27 #include <boost/system/linux_error.hpp> 28 #include <boost/url/format.hpp> 29 #include <sdbusplus/asio/property.hpp> 30 #include <sdbusplus/unpack_properties.hpp> 31 32 namespace redfish 33 { 34 35 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory"; 36 static constexpr std::array<std::string_view, 1> pcieDeviceInterface = { 37 "xyz.openbmc_project.Inventory.Item.PCIeDevice"}; 38 39 static inline void handlePCIeDevicePath( 40 const std::string& pcieDeviceId, 41 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 42 const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths, 43 const std::function<void(const std::string& pcieDevicePath, 44 const std::string& service)>& callback) 45 46 { 47 for (const std::string& pcieDevicePath : pcieDevicePaths) 48 { 49 std::string pciecDeviceName = 50 sdbusplus::message::object_path(pcieDevicePath).filename(); 51 if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId) 52 { 53 continue; 54 } 55 56 dbus::utility::getDbusObject( 57 pcieDevicePath, {}, 58 [pcieDevicePath, asyncResp, 59 callback](const boost::system::error_code& ec, 60 const dbus::utility::MapperGetObject& object) { 61 if (ec || object.empty()) 62 { 63 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 64 messages::internalError(asyncResp->res); 65 return; 66 } 67 callback(pcieDevicePath, object.begin()->first); 68 }); 69 return; 70 } 71 72 BMCWEB_LOG_WARNING << "PCIe Device not found"; 73 messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId); 74 } 75 76 static inline void getValidPCIeDevicePath( 77 const std::string& pcieDeviceId, 78 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 79 const std::function<void(const std::string& pcieDevicePath, 80 const std::string& service)>& callback) 81 { 82 dbus::utility::getSubTreePaths( 83 inventoryPath, 0, pcieDeviceInterface, 84 [pcieDeviceId, asyncResp, 85 callback](const boost::system::error_code& ec, 86 const dbus::utility::MapperGetSubTreePathsResponse& 87 pcieDevicePaths) { 88 if (ec) 89 { 90 BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec; 91 messages::internalError(asyncResp->res); 92 return; 93 } 94 handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths, 95 callback); 96 return; 97 }); 98 } 99 100 static inline void handlePCIeDeviceCollectionGet( 101 crow::App& app, const crow::Request& req, 102 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 103 const std::string& systemName) 104 { 105 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 106 { 107 return; 108 } 109 if constexpr (bmcwebEnableMultiHost) 110 { 111 // Option currently returns no systems. TBD 112 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 113 systemName); 114 return; 115 } 116 if (systemName != "system") 117 { 118 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 119 systemName); 120 return; 121 } 122 123 asyncResp->res.addHeader(boost::beast::http::field::link, 124 "</redfish/v1/JsonSchemas/PCIeDeviceCollection/" 125 "PCIeDeviceCollection.json>; rel=describedby"); 126 asyncResp->res.jsonValue["@odata.type"] = 127 "#PCIeDeviceCollection.PCIeDeviceCollection"; 128 asyncResp->res.jsonValue["@odata.id"] = 129 "/redfish/v1/Systems/system/PCIeDevices"; 130 asyncResp->res.jsonValue["Name"] = "PCIe Device Collection"; 131 asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices"; 132 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 133 asyncResp->res.jsonValue["Members@odata.count"] = 0; 134 135 pcie_util::getPCIeDeviceList(asyncResp, "Members"); 136 } 137 138 inline void requestRoutesSystemPCIeDeviceCollection(App& app) 139 { 140 /** 141 * Functions triggers appropriate requests on DBus 142 */ 143 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/") 144 .privileges(redfish::privileges::getPCIeDeviceCollection) 145 .methods(boost::beast::http::verb::get)( 146 std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app))); 147 } 148 149 inline void 150 getPCIeDeviceHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 151 const std::string& pcieDevicePath, 152 const std::string& service) 153 { 154 sdbusplus::asio::getProperty<bool>( 155 *crow::connections::systemBus, service, pcieDevicePath, 156 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 157 [asyncResp](const boost::system::error_code& ec, const bool value) { 158 if (ec) 159 { 160 if (ec.value() != EBADR) 161 { 162 BMCWEB_LOG_ERROR << "DBUS response error for Health " 163 << ec.value(); 164 messages::internalError(asyncResp->res); 165 } 166 return; 167 } 168 169 if (!value) 170 { 171 asyncResp->res.jsonValue["Status"]["Health"] = "Critical"; 172 } 173 }); 174 } 175 176 inline void 177 getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 178 const std::string& pcieDevicePath, 179 const std::string& service) 180 { 181 sdbusplus::asio::getProperty<bool>( 182 *crow::connections::systemBus, service, pcieDevicePath, 183 "xyz.openbmc_project.Inventory.Item", "Present", 184 [asyncResp](const boost::system::error_code& ec, const bool value) { 185 if (ec) 186 { 187 if (ec.value() != EBADR) 188 { 189 BMCWEB_LOG_ERROR << "DBUS response error for State"; 190 messages::internalError(asyncResp->res); 191 } 192 return; 193 } 194 195 if (!value) 196 { 197 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 198 } 199 }); 200 } 201 202 inline void 203 getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 204 const std::string& pcieDevicePath, 205 const std::string& service) 206 { 207 sdbusplus::asio::getAllProperties( 208 *crow::connections::systemBus, service, pcieDevicePath, 209 "xyz.openbmc_project.Inventory.Decorator.Asset", 210 [pcieDevicePath, asyncResp{asyncResp}]( 211 const boost::system::error_code& ec, 212 const dbus::utility::DBusPropertiesMap& assetList) { 213 if (ec) 214 { 215 if (ec.value() != EBADR) 216 { 217 BMCWEB_LOG_ERROR << "DBUS response error for Properties" 218 << ec.value(); 219 messages::internalError(asyncResp->res); 220 } 221 return; 222 } 223 224 const std::string* manufacturer = nullptr; 225 const std::string* model = nullptr; 226 const std::string* partNumber = nullptr; 227 const std::string* serialNumber = nullptr; 228 const std::string* sparePartNumber = nullptr; 229 230 const bool success = sdbusplus::unpackPropertiesNoThrow( 231 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 232 manufacturer, "Model", model, "PartNumber", partNumber, 233 "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber); 234 235 if (!success) 236 { 237 messages::internalError(asyncResp->res); 238 return; 239 } 240 241 if (manufacturer != nullptr) 242 { 243 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 244 } 245 if (model != nullptr) 246 { 247 asyncResp->res.jsonValue["Model"] = *model; 248 } 249 250 if (partNumber != nullptr) 251 { 252 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 253 } 254 255 if (serialNumber != nullptr) 256 { 257 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 258 } 259 260 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 261 { 262 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 263 } 264 }); 265 } 266 267 inline void addPCIeDeviceProperties( 268 crow::Response& resp, const std::string& pcieDeviceId, 269 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 270 { 271 const std::string* deviceType = nullptr; 272 const std::string* generationInUse = nullptr; 273 const int64_t* lanesInUse = nullptr; 274 275 const bool success = sdbusplus::unpackPropertiesNoThrow( 276 dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType", 277 deviceType, "GenerationInUse", generationInUse, "LanesInUse", 278 lanesInUse); 279 280 if (!success) 281 { 282 messages::internalError(resp); 283 return; 284 } 285 286 if (deviceType != nullptr && !deviceType->empty()) 287 { 288 resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType; 289 } 290 291 if (generationInUse != nullptr) 292 { 293 std::optional<pcie_device::PCIeTypes> redfishGenerationInUse = 294 pcie_util::redfishPcieGenerationFromDbus(*generationInUse); 295 296 if (!redfishGenerationInUse) 297 { 298 BMCWEB_LOG_WARNING << "Unknown PCIe Device Generation: " 299 << *generationInUse; 300 } 301 else 302 { 303 if (*redfishGenerationInUse == pcie_device::PCIeTypes::Invalid) 304 { 305 BMCWEB_LOG_ERROR << "Invalid PCIe Device Generation: " 306 << *generationInUse; 307 messages::internalError(resp); 308 return; 309 } 310 resp.jsonValue["PCIeInterface"]["PCIeType"] = 311 *redfishGenerationInUse; 312 } 313 } 314 315 // The default value of LanesInUse is 0, and the field will be 316 // left as off if it is a default value. 317 if (lanesInUse != nullptr && *lanesInUse != 0) 318 { 319 resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse; 320 } 321 322 resp.jsonValue["PCIeFunctions"]["@odata.id"] = boost::urls::format( 323 "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions", 324 pcieDeviceId); 325 } 326 327 inline void getPCIeDeviceProperties( 328 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 329 const std::string& pcieDevicePath, const std::string& service, 330 const std::function<void( 331 const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback) 332 { 333 sdbusplus::asio::getAllProperties( 334 *crow::connections::systemBus, service, pcieDevicePath, 335 "xyz.openbmc_project.Inventory.Item.PCIeDevice", 336 [asyncResp, 337 callback](const boost::system::error_code& ec, 338 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 339 if (ec) 340 { 341 if (ec.value() != EBADR) 342 { 343 BMCWEB_LOG_ERROR << "DBUS response error for Properties"; 344 messages::internalError(asyncResp->res); 345 } 346 return; 347 } 348 callback(pcieDevProperties); 349 }); 350 } 351 352 inline void addPCIeDeviceCommonProperties( 353 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 354 const std::string& pcieDeviceId) 355 { 356 asyncResp->res.addHeader( 357 boost::beast::http::field::link, 358 "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby"); 359 asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice"; 360 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 361 "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId); 362 asyncResp->res.jsonValue["Name"] = "PCIe Device"; 363 asyncResp->res.jsonValue["Id"] = pcieDeviceId; 364 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 365 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 366 } 367 368 inline void 369 handlePCIeDeviceGet(App& app, const crow::Request& req, 370 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 371 const std::string& systemName, 372 const std::string& pcieDeviceId) 373 { 374 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 375 { 376 return; 377 } 378 if constexpr (bmcwebEnableMultiHost) 379 { 380 // Option currently returns no systems. TBD 381 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 382 systemName); 383 return; 384 } 385 if (systemName != "system") 386 { 387 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 388 systemName); 389 return; 390 } 391 392 getValidPCIeDevicePath( 393 pcieDeviceId, asyncResp, 394 [asyncResp, pcieDeviceId](const std::string& pcieDevicePath, 395 const std::string& service) { 396 addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId); 397 getPCIeDeviceAsset(asyncResp, pcieDevicePath, service); 398 getPCIeDeviceState(asyncResp, pcieDevicePath, service); 399 getPCIeDeviceHealth(asyncResp, pcieDevicePath, service); 400 getPCIeDeviceProperties( 401 asyncResp, pcieDevicePath, service, 402 [asyncResp, pcieDeviceId]( 403 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 404 addPCIeDeviceProperties(asyncResp->res, pcieDeviceId, 405 pcieDevProperties); 406 }); 407 }); 408 } 409 410 inline void requestRoutesSystemPCIeDevice(App& app) 411 { 412 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/") 413 .privileges(redfish::privileges::getPCIeDevice) 414 .methods(boost::beast::http::verb::get)( 415 std::bind_front(handlePCIeDeviceGet, std::ref(app))); 416 } 417 418 inline void addPCIeFunctionList( 419 crow::Response& res, const std::string& pcieDeviceId, 420 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 421 { 422 nlohmann::json& pcieFunctionList = res.jsonValue["Members"]; 423 pcieFunctionList = nlohmann::json::array(); 424 static constexpr const int maxPciFunctionNum = 8; 425 426 for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++) 427 { 428 // Check if this function exists by 429 // looking for a device ID 430 std::string devIDProperty = "Function" + std::to_string(functionNum) + 431 "DeviceId"; 432 const std::string* property = nullptr; 433 for (const auto& propEntry : pcieDevProperties) 434 { 435 if (propEntry.first == devIDProperty) 436 { 437 property = std::get_if<std::string>(&propEntry.second); 438 break; 439 } 440 } 441 if (property == nullptr || property->empty()) 442 { 443 continue; 444 } 445 446 nlohmann::json::object_t pcieFunction; 447 pcieFunction["@odata.id"] = boost::urls::format( 448 "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}", 449 pcieDeviceId, std::to_string(functionNum)); 450 pcieFunctionList.emplace_back(std::move(pcieFunction)); 451 } 452 res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size(); 453 } 454 455 inline void handlePCIeFunctionCollectionGet( 456 App& app, const crow::Request& req, 457 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 458 const std::string& systemName, const std::string& pcieDeviceId) 459 { 460 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 461 { 462 return; 463 } 464 if constexpr (bmcwebEnableMultiHost) 465 { 466 // Option currently returns no systems. TBD 467 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 468 systemName); 469 return; 470 } 471 472 getValidPCIeDevicePath( 473 pcieDeviceId, asyncResp, 474 [asyncResp, pcieDeviceId](const std::string& pcieDevicePath, 475 const std::string& service) { 476 asyncResp->res.addHeader( 477 boost::beast::http::field::link, 478 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby"); 479 asyncResp->res.jsonValue["@odata.type"] = 480 "#PCIeFunctionCollection.PCIeFunctionCollection"; 481 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 482 "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions", 483 pcieDeviceId); 484 asyncResp->res.jsonValue["Name"] = "PCIe Function Collection"; 485 asyncResp->res.jsonValue["Description"] = 486 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId; 487 getPCIeDeviceProperties( 488 asyncResp, pcieDevicePath, service, 489 [asyncResp, pcieDeviceId]( 490 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 491 addPCIeFunctionList(asyncResp->res, pcieDeviceId, 492 pcieDevProperties); 493 }); 494 }); 495 } 496 497 inline void requestRoutesSystemPCIeFunctionCollection(App& app) 498 { 499 /** 500 * Functions triggers appropriate requests on DBus 501 */ 502 BMCWEB_ROUTE(app, 503 "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/") 504 .privileges(redfish::privileges::getPCIeFunctionCollection) 505 .methods(boost::beast::http::verb::get)( 506 std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app))); 507 } 508 509 inline bool validatePCIeFunctionId( 510 uint64_t pcieFunctionId, 511 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 512 { 513 std::string functionName = "Function" + std::to_string(pcieFunctionId); 514 std::string devIDProperty = functionName + "DeviceId"; 515 516 const std::string* devIdProperty = nullptr; 517 for (const auto& property : pcieDevProperties) 518 { 519 if (property.first == devIDProperty) 520 { 521 devIdProperty = std::get_if<std::string>(&property.second); 522 break; 523 } 524 } 525 return (devIdProperty != nullptr && !devIdProperty->empty()); 526 } 527 528 inline void addPCIeFunctionProperties( 529 crow::Response& resp, uint64_t pcieFunctionId, 530 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 531 { 532 std::string functionName = "Function" + std::to_string(pcieFunctionId); 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 uint64_t 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"] = boost::urls::format( 591 "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}", 592 pcieDeviceId, std::to_string(pcieFunctionId)); 593 resp.jsonValue["Name"] = "PCIe Function"; 594 resp.jsonValue["Id"] = std::to_string(pcieFunctionId); 595 resp.jsonValue["FunctionId"] = pcieFunctionId; 596 resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = boost::urls::format( 597 "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId); 598 } 599 600 inline void 601 handlePCIeFunctionGet(App& app, const crow::Request& req, 602 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 603 const std::string& systemName, 604 const std::string& pcieDeviceId, 605 const std::string& pcieFunctionIdStr) 606 { 607 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 608 { 609 return; 610 } 611 if constexpr (bmcwebEnableMultiHost) 612 { 613 // Option currently returns no systems. TBD 614 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 615 systemName); 616 return; 617 } 618 if (systemName != "system") 619 { 620 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 621 systemName); 622 return; 623 } 624 625 uint64_t pcieFunctionId = 0; 626 std::from_chars_result result = std::from_chars( 627 &*pcieFunctionIdStr.begin(), &*pcieFunctionIdStr.end(), pcieFunctionId); 628 if (result.ec != std::errc{} || result.ptr != &*pcieFunctionIdStr.end()) 629 { 630 messages::resourceNotFound(asyncResp->res, "PCIeFunction", 631 pcieFunctionIdStr); 632 return; 633 } 634 635 getValidPCIeDevicePath(pcieDeviceId, asyncResp, 636 [asyncResp, pcieDeviceId, 637 pcieFunctionId](const std::string& pcieDevicePath, 638 const std::string& service) { 639 getPCIeDeviceProperties( 640 asyncResp, pcieDevicePath, service, 641 [asyncResp, pcieDeviceId, pcieFunctionId]( 642 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 643 addPCIeFunctionCommonProperties(asyncResp->res, pcieDeviceId, 644 pcieFunctionId); 645 addPCIeFunctionProperties(asyncResp->res, pcieFunctionId, 646 pcieDevProperties); 647 }); 648 }); 649 } 650 651 inline void requestRoutesSystemPCIeFunction(App& app) 652 { 653 BMCWEB_ROUTE( 654 app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/") 655 .privileges(redfish::privileges::getPCIeFunction) 656 .methods(boost::beast::http::verb::get)( 657 std::bind_front(handlePCIeFunctionGet, std::ref(app))); 658 } 659 660 } // namespace redfish 661