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