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