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