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