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