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