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 getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 221 const std::string& pcieDevicePath, 222 const std::string& service) 223 { 224 sdbusplus::asio::getProperty<bool>( 225 *crow::connections::systemBus, service, pcieDevicePath, 226 "xyz.openbmc_project.Inventory.Item", "Present", 227 [aResp](const boost::system::error_code& ec, const bool value) { 228 if (ec) 229 { 230 if (ec.value() != EBADR) 231 { 232 BMCWEB_LOG_ERROR << "DBUS response error for State"; 233 messages::internalError(aResp->res); 234 } 235 return; 236 } 237 238 if (!value) 239 { 240 aResp->res.jsonValue["Status"]["State"] = "Absent"; 241 } 242 }); 243 } 244 245 inline void getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 246 const std::string& pcieDevicePath, 247 const std::string& service) 248 { 249 sdbusplus::asio::getAllProperties( 250 *crow::connections::systemBus, service, pcieDevicePath, 251 "xyz.openbmc_project.Inventory.Decorator.Asset", 252 [pcieDevicePath, 253 aResp{aResp}](const boost::system::error_code& ec, 254 const dbus::utility::DBusPropertiesMap& assetList) { 255 if (ec) 256 { 257 if (ec.value() != EBADR) 258 { 259 BMCWEB_LOG_ERROR << "DBUS response error for Properties" 260 << ec.value(); 261 messages::internalError(aResp->res); 262 } 263 return; 264 } 265 266 const std::string* manufacturer = nullptr; 267 const std::string* model = nullptr; 268 const std::string* partNumber = nullptr; 269 const std::string* serialNumber = nullptr; 270 const std::string* sparePartNumber = nullptr; 271 272 const bool success = sdbusplus::unpackPropertiesNoThrow( 273 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 274 manufacturer, "Model", model, "PartNumber", partNumber, 275 "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber); 276 277 if (!success) 278 { 279 messages::internalError(aResp->res); 280 return; 281 } 282 283 if (manufacturer != nullptr) 284 { 285 aResp->res.jsonValue["Manufacturer"] = *manufacturer; 286 } 287 if (model != nullptr) 288 { 289 aResp->res.jsonValue["Model"] = *model; 290 } 291 292 if (partNumber != nullptr) 293 { 294 aResp->res.jsonValue["PartNumber"] = *partNumber; 295 } 296 297 if (serialNumber != nullptr) 298 { 299 aResp->res.jsonValue["SerialNumber"] = *serialNumber; 300 } 301 302 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 303 { 304 aResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 305 } 306 }); 307 } 308 309 inline void addPCIeDeviceProperties( 310 crow::Response& resp, const std::string& pcieDeviceId, 311 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 312 { 313 const std::string* deviceType = nullptr; 314 const std::string* generationInUse = nullptr; 315 const int64_t* lanesInUse = nullptr; 316 317 const bool success = sdbusplus::unpackPropertiesNoThrow( 318 dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType", 319 deviceType, "GenerationInUse", generationInUse, "LanesInUse", 320 lanesInUse); 321 322 if (!success) 323 { 324 messages::internalError(resp); 325 return; 326 } 327 328 if (deviceType != nullptr && !deviceType->empty()) 329 { 330 resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType; 331 } 332 333 if (generationInUse != nullptr) 334 { 335 std::optional<pcie_device::PCIeTypes> redfishGenerationInUse = 336 redfishPcieGenerationFromDbus(*generationInUse); 337 338 if (!redfishGenerationInUse) 339 { 340 messages::internalError(resp); 341 return; 342 } 343 if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid) 344 { 345 resp.jsonValue["PCIeInterface"]["PCIeType"] = 346 *redfishGenerationInUse; 347 } 348 } 349 350 // The default value of LanesInUse is 0, and the field will be 351 // left as off if it is a default value. 352 if (lanesInUse != nullptr && *lanesInUse != 0) 353 { 354 resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse; 355 } 356 357 resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces( 358 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 359 "PCIeFunctions"); 360 } 361 362 inline void getPCIeDeviceProperties( 363 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 364 const std::string& pcieDevicePath, const std::string& service, 365 const std::function<void( 366 const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback) 367 { 368 sdbusplus::asio::getAllProperties( 369 *crow::connections::systemBus, service, pcieDevicePath, 370 "xyz.openbmc_project.Inventory.Item.PCIeDevice", 371 [aResp, 372 callback](const boost::system::error_code& ec, 373 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 374 if (ec) 375 { 376 if (ec.value() != EBADR) 377 { 378 BMCWEB_LOG_ERROR << "DBUS response error for Properties"; 379 messages::internalError(aResp->res); 380 } 381 return; 382 } 383 callback(pcieDevProperties); 384 }); 385 } 386 387 inline void addPCIeDeviceCommonProperties( 388 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 389 const std::string& pcieDeviceId) 390 { 391 aResp->res.addHeader( 392 boost::beast::http::field::link, 393 "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby"); 394 aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice"; 395 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 396 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId); 397 aResp->res.jsonValue["Name"] = "PCIe Device"; 398 aResp->res.jsonValue["Id"] = pcieDeviceId; 399 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 400 } 401 402 inline void handlePCIeDeviceGet(App& app, const crow::Request& req, 403 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 404 const std::string& systemName, 405 const std::string& pcieDeviceId) 406 { 407 if (!redfish::setUpRedfishRoute(app, req, aResp)) 408 { 409 return; 410 } 411 if (systemName != "system") 412 { 413 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 414 return; 415 } 416 417 getValidPCIeDevicePath( 418 pcieDeviceId, aResp, 419 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 420 const std::string& service) { 421 addPCIeDeviceCommonProperties(aResp, pcieDeviceId); 422 getPCIeDeviceAsset(aResp, pcieDevicePath, service); 423 getPCIeDeviceState(aResp, pcieDevicePath, service); 424 getPCIeDeviceProperties( 425 aResp, pcieDevicePath, service, 426 [aResp, pcieDeviceId]( 427 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 428 addPCIeDeviceProperties(aResp->res, pcieDeviceId, 429 pcieDevProperties); 430 }); 431 }); 432 } 433 434 inline void requestRoutesSystemPCIeDevice(App& app) 435 { 436 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/") 437 .privileges(redfish::privileges::getPCIeDevice) 438 .methods(boost::beast::http::verb::get)( 439 std::bind_front(handlePCIeDeviceGet, std::ref(app))); 440 } 441 442 inline void addPCIeFunctionList( 443 crow::Response& res, const std::string& pcieDeviceId, 444 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 445 { 446 nlohmann::json& pcieFunctionList = res.jsonValue["Members"]; 447 pcieFunctionList = nlohmann::json::array(); 448 static constexpr const int maxPciFunctionNum = 8; 449 450 for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++) 451 { 452 // Check if this function exists by 453 // looking for a device ID 454 std::string devIDProperty = "Function" + std::to_string(functionNum) + 455 "DeviceId"; 456 const std::string* property = nullptr; 457 for (const auto& propEntry : pcieDevProperties) 458 { 459 if (propEntry.first == devIDProperty) 460 { 461 property = std::get_if<std::string>(&propEntry.second); 462 break; 463 } 464 } 465 if (property == nullptr || property->empty()) 466 { 467 continue; 468 } 469 470 nlohmann::json::object_t pcieFunction; 471 pcieFunction["@odata.id"] = crow::utility::urlFromPieces( 472 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 473 "PCIeFunctions", std::to_string(functionNum)); 474 pcieFunctionList.emplace_back(std::move(pcieFunction)); 475 } 476 res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size(); 477 } 478 479 inline void handlePCIeFunctionCollectionGet( 480 App& app, const crow::Request& req, 481 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 482 const std::string& pcieDeviceId) 483 { 484 if (!redfish::setUpRedfishRoute(app, req, aResp)) 485 { 486 return; 487 } 488 489 getValidPCIeDevicePath( 490 pcieDeviceId, aResp, 491 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 492 const std::string& service) { 493 aResp->res.addHeader( 494 boost::beast::http::field::link, 495 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby"); 496 aResp->res.jsonValue["@odata.type"] = 497 "#PCIeFunctionCollection.PCIeFunctionCollection"; 498 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 499 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 500 "PCIeFunctions"); 501 aResp->res.jsonValue["Name"] = "PCIe Function Collection"; 502 aResp->res.jsonValue["Description"] = 503 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId; 504 getPCIeDeviceProperties( 505 aResp, pcieDevicePath, service, 506 [aResp, pcieDeviceId]( 507 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 508 addPCIeFunctionList(aResp->res, pcieDeviceId, pcieDevProperties); 509 }); 510 }); 511 } 512 513 inline void requestRoutesSystemPCIeFunctionCollection(App& app) 514 { 515 /** 516 * Functions triggers appropriate requests on DBus 517 */ 518 BMCWEB_ROUTE(app, 519 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/") 520 .privileges(redfish::privileges::getPCIeFunctionCollection) 521 .methods(boost::beast::http::verb::get)( 522 std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app))); 523 } 524 525 inline bool validatePCIeFunctionId( 526 const std::string& pcieFunctionId, 527 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 528 { 529 std::string functionName = "Function" + pcieFunctionId; 530 std::string devIDProperty = functionName + "DeviceId"; 531 532 const std::string* devIdProperty = nullptr; 533 for (const auto& property : pcieDevProperties) 534 { 535 if (property.first == devIDProperty) 536 { 537 devIdProperty = std::get_if<std::string>(&property.second); 538 break; 539 } 540 } 541 return (devIdProperty != nullptr && !devIdProperty->empty()); 542 } 543 544 inline void addPCIeFunctionProperties( 545 crow::Response& resp, const std::string& pcieFunctionId, 546 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 547 { 548 std::string functionName = "Function" + pcieFunctionId; 549 if (!validatePCIeFunctionId(pcieFunctionId, pcieDevProperties)) 550 { 551 messages::resourceNotFound(resp, "PCIeFunction", pcieFunctionId); 552 return; 553 } 554 for (const auto& property : pcieDevProperties) 555 { 556 const std::string* strProperty = 557 std::get_if<std::string>(&property.second); 558 559 if (property.first == functionName + "DeviceId") 560 { 561 resp.jsonValue["DeviceId"] = *strProperty; 562 } 563 if (property.first == functionName + "VendorId") 564 { 565 resp.jsonValue["VendorId"] = *strProperty; 566 } 567 // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus 568 // property strings should be mapped correctly to ensure these 569 // strings are Redfish enum values. For now just check for empty. 570 if (property.first == functionName + "FunctionType") 571 { 572 if (!strProperty->empty()) 573 { 574 resp.jsonValue["FunctionType"] = *strProperty; 575 } 576 } 577 if (property.first == functionName + "DeviceClass") 578 { 579 if (!strProperty->empty()) 580 { 581 resp.jsonValue["DeviceClass"] = *strProperty; 582 } 583 } 584 if (property.first == functionName + "ClassCode") 585 { 586 resp.jsonValue["ClassCode"] = *strProperty; 587 } 588 if (property.first == functionName + "RevisionId") 589 { 590 resp.jsonValue["RevisionId"] = *strProperty; 591 } 592 if (property.first == functionName + "SubsystemId") 593 { 594 resp.jsonValue["SubsystemId"] = *strProperty; 595 } 596 if (property.first == functionName + "SubsystemVendorId") 597 { 598 resp.jsonValue["SubsystemVendorId"] = *strProperty; 599 } 600 } 601 } 602 603 inline void addPCIeFunctionCommonProperties(crow::Response& resp, 604 const std::string& pcieDeviceId, 605 const std::string& pcieFunctionId) 606 { 607 resp.addHeader( 608 boost::beast::http::field::link, 609 "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby"); 610 resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction"; 611 resp.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 612 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 613 "PCIeFunctions", pcieFunctionId); 614 resp.jsonValue["Name"] = "PCIe Function"; 615 resp.jsonValue["Id"] = pcieFunctionId; 616 resp.jsonValue["FunctionId"] = std::stoi(pcieFunctionId); 617 resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = 618 crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", 619 "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