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 char const* pcieService = "xyz.openbmc_project.PCIe"; 35 static constexpr char const* pciePath = "/xyz/openbmc_project/PCIe"; 36 static constexpr char const* 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 constexpr std::array<std::string_view, 1> interfaces{ 83 "xyz.openbmc_project.Inventory.Item.PCIeDevice"}; 84 85 dbus::utility::getSubTreePaths( 86 "/xyz/openbmc_project/inventory", 0, interfaces, 87 [pcieDeviceId, aResp, 88 callback](const boost::system::error_code& ec, 89 const dbus::utility::MapperGetSubTreePathsResponse& 90 pcieDevicePaths) { 91 if (ec) 92 { 93 BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec; 94 messages::internalError(aResp->res); 95 return; 96 } 97 handlePCIeDevicePath(pcieDeviceId, aResp, pcieDevicePaths, callback); 98 return; 99 }); 100 } 101 102 static inline void 103 getPCIeDeviceList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 104 const std::string& name) 105 { 106 dbus::utility::getSubTreePaths( 107 pciePath, 1, {}, 108 [asyncResp, name](const boost::system::error_code& ec, 109 const dbus::utility::MapperGetSubTreePathsResponse& 110 pcieDevicePaths) { 111 if (ec) 112 { 113 BMCWEB_LOG_DEBUG << "no PCIe device paths found ec: " 114 << ec.message(); 115 // Not an error, system just doesn't have PCIe info 116 return; 117 } 118 nlohmann::json& pcieDeviceList = asyncResp->res.jsonValue[name]; 119 pcieDeviceList = nlohmann::json::array(); 120 for (const std::string& pcieDevicePath : pcieDevicePaths) 121 { 122 size_t devStart = pcieDevicePath.rfind('/'); 123 if (devStart == std::string::npos) 124 { 125 continue; 126 } 127 128 std::string devName = pcieDevicePath.substr(devStart + 1); 129 if (devName.empty()) 130 { 131 continue; 132 } 133 nlohmann::json::object_t pcieDevice; 134 pcieDevice["@odata.id"] = crow::utility::urlFromPieces( 135 "redfish", "v1", "Systems", "system", "PCIeDevices", devName); 136 pcieDeviceList.push_back(std::move(pcieDevice)); 137 } 138 asyncResp->res.jsonValue[name + "@odata.count"] = pcieDeviceList.size(); 139 }); 140 } 141 142 static inline void handlePCIeDeviceCollectionGet( 143 crow::App& app, const crow::Request& req, 144 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 145 const std::string& systemName) 146 { 147 if (!redfish::setUpRedfishRoute(app, req, aResp)) 148 { 149 return; 150 } 151 if (systemName != "system") 152 { 153 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 154 return; 155 } 156 157 aResp->res.addHeader(boost::beast::http::field::link, 158 "</redfish/v1/JsonSchemas/PCIeDeviceCollection/" 159 "PCIeDeviceCollection.json>; rel=describedby"); 160 aResp->res.jsonValue["@odata.type"] = 161 "#PCIeDeviceCollection.PCIeDeviceCollection"; 162 aResp->res.jsonValue["@odata.id"] = 163 "/redfish/v1/Systems/system/PCIeDevices"; 164 aResp->res.jsonValue["Name"] = "PCIe Device Collection"; 165 aResp->res.jsonValue["Description"] = "Collection of PCIe Devices"; 166 aResp->res.jsonValue["Members"] = nlohmann::json::array(); 167 aResp->res.jsonValue["Members@odata.count"] = 0; 168 169 constexpr std::array<std::string_view, 1> interfaces{ 170 "xyz.openbmc_project.Inventory.Item.PCIeDevice"}; 171 collection_util::getCollectionMembers( 172 aResp, boost::urls::url("/redfish/v1/Systems/system/PCIeDevices"), 173 interfaces); 174 } 175 176 inline void requestRoutesSystemPCIeDeviceCollection(App& app) 177 { 178 /** 179 * Functions triggers appropriate requests on DBus 180 */ 181 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/") 182 .privileges(redfish::privileges::getPCIeDeviceCollection) 183 .methods(boost::beast::http::verb::get)( 184 std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app))); 185 } 186 187 inline std::optional<pcie_device::PCIeTypes> 188 redfishPcieGenerationFromDbus(const std::string& generationInUse) 189 { 190 if (generationInUse == 191 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen1") 192 { 193 return pcie_device::PCIeTypes::Gen1; 194 } 195 if (generationInUse == 196 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen2") 197 { 198 return pcie_device::PCIeTypes::Gen2; 199 } 200 if (generationInUse == 201 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen3") 202 { 203 return pcie_device::PCIeTypes::Gen3; 204 } 205 if (generationInUse == 206 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen4") 207 { 208 return pcie_device::PCIeTypes::Gen4; 209 } 210 if (generationInUse == 211 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5") 212 { 213 return pcie_device::PCIeTypes::Gen5; 214 } 215 if (generationInUse.empty() || 216 generationInUse == 217 "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown") 218 { 219 return pcie_device::PCIeTypes::Invalid; 220 } 221 222 // The value is not unknown or Gen1-5, need return an internal error. 223 return std::nullopt; 224 } 225 226 inline void addPCIeDeviceProperties( 227 crow::Response& resp, 228 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 229 { 230 const std::string* manufacturer = nullptr; 231 const std::string* deviceType = nullptr; 232 const std::string* generationInUse = nullptr; 233 const int64_t* lanesInUse = nullptr; 234 235 const bool success = sdbusplus::unpackPropertiesNoThrow( 236 dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType", 237 deviceType, "GenerationInUse", generationInUse, "LanesInUse", 238 lanesInUse, "Manufacturer", manufacturer); 239 240 if (!success) 241 { 242 messages::internalError(resp); 243 return; 244 } 245 246 if (deviceType != nullptr && !deviceType->empty()) 247 { 248 resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType; 249 } 250 251 if (generationInUse != nullptr) 252 { 253 std::optional<pcie_device::PCIeTypes> redfishGenerationInUse = 254 redfishPcieGenerationFromDbus(*generationInUse); 255 256 if (!redfishGenerationInUse) 257 { 258 messages::internalError(resp); 259 return; 260 } 261 if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid) 262 { 263 resp.jsonValue["PCIeInterface"]["PCIeType"] = 264 *redfishGenerationInUse; 265 } 266 } 267 268 // The default value of LanesInUse is 0, and the field will be 269 // left as off if it is a default value. 270 if (lanesInUse != nullptr && *lanesInUse != 0) 271 { 272 resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse; 273 } 274 275 if (manufacturer != nullptr) 276 { 277 resp.jsonValue["PCIeInterface"]["Manufacturer"] = *manufacturer; 278 } 279 } 280 281 inline void getPCIeDeviceProperties( 282 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 283 const std::string& pcieDevicePath, const std::string& service, 284 const std::function<void( 285 const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback) 286 { 287 sdbusplus::asio::getAllProperties( 288 *crow::connections::systemBus, service, pcieDevicePath, 289 "xyz.openbmc_project.Inventory.Item.PCIeDevice", 290 [aResp, 291 callback](const boost::system::error_code& ec, 292 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 293 if (ec) 294 { 295 if (ec.value() != EBADR) 296 { 297 BMCWEB_LOG_ERROR << "DBUS response error for Properties"; 298 messages::internalError(aResp->res); 299 } 300 return; 301 } 302 callback(pcieDevProperties); 303 }); 304 } 305 306 inline void addPCIeDeviceCommonProperties( 307 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 308 const std::string& pcieDeviceId) 309 { 310 aResp->res.addHeader( 311 boost::beast::http::field::link, 312 "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby"); 313 aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice"; 314 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 315 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId); 316 aResp->res.jsonValue["Name"] = "PCIe Device"; 317 aResp->res.jsonValue["Id"] = pcieDeviceId; 318 } 319 320 inline void handlePCIeDeviceGet(App& app, const crow::Request& req, 321 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 322 const std::string& systemName, 323 const std::string& pcieDeviceId) 324 { 325 if (!redfish::setUpRedfishRoute(app, req, aResp)) 326 { 327 return; 328 } 329 if (systemName != "system") 330 { 331 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 332 return; 333 } 334 335 getValidPCIeDevicePath( 336 pcieDeviceId, aResp, 337 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 338 const std::string& service) { 339 addPCIeDeviceCommonProperties(aResp, pcieDeviceId); 340 getPCIeDeviceProperties( 341 aResp, pcieDevicePath, service, 342 [aResp](const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 343 addPCIeDeviceProperties(aResp->res, pcieDevProperties); 344 }); 345 }); 346 } 347 348 inline void requestRoutesSystemPCIeDevice(App& app) 349 { 350 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/") 351 .privileges(redfish::privileges::getPCIeDevice) 352 .methods(boost::beast::http::verb::get)( 353 std::bind_front(handlePCIeDeviceGet, std::ref(app))); 354 } 355 356 inline void requestRoutesSystemPCIeFunctionCollection(App& app) 357 { 358 /** 359 * Functions triggers appropriate requests on DBus 360 */ 361 BMCWEB_ROUTE(app, 362 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/") 363 .privileges(redfish::privileges::getPCIeFunctionCollection) 364 .methods(boost::beast::http::verb::get)( 365 [&app](const crow::Request& req, 366 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 367 const std::string& device) { 368 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 369 { 370 return; 371 } 372 373 asyncResp->res.jsonValue["@odata.type"] = 374 "#PCIeFunctionCollection.PCIeFunctionCollection"; 375 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 376 "redfish", "v1", "Systems", "system", "PCIeDevices", device, 377 "PCIeFunctions"); 378 asyncResp->res.jsonValue["Name"] = "PCIe Function Collection"; 379 asyncResp->res.jsonValue["Description"] = 380 "Collection of PCIe Functions for PCIe Device " + device; 381 382 auto getPCIeDeviceCallback = 383 [asyncResp, device]( 384 const boost::system::error_code& ec, 385 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 386 if (ec) 387 { 388 BMCWEB_LOG_DEBUG 389 << "failed to get PCIe Device properties ec: " << ec.value() 390 << ": " << ec.message(); 391 if (ec.value() == 392 boost::system::linux_error::bad_request_descriptor) 393 { 394 messages::resourceNotFound(asyncResp->res, "PCIeDevice", 395 device); 396 } 397 else 398 { 399 messages::internalError(asyncResp->res); 400 } 401 return; 402 } 403 404 nlohmann::json& pcieFunctionList = 405 asyncResp->res.jsonValue["Members"]; 406 pcieFunctionList = nlohmann::json::array(); 407 static constexpr const int maxPciFunctionNum = 8; 408 for (int functionNum = 0; functionNum < maxPciFunctionNum; 409 functionNum++) 410 { 411 // Check if this function exists by looking for a 412 // device ID 413 std::string devIDProperty = 414 "Function" + std::to_string(functionNum) + "DeviceId"; 415 const std::string* property = nullptr; 416 for (const auto& propEntry : pcieDevProperties) 417 { 418 if (propEntry.first == devIDProperty) 419 { 420 property = std::get_if<std::string>(&propEntry.second); 421 } 422 } 423 if (property == nullptr || property->empty()) 424 { 425 continue; 426 } 427 nlohmann::json::object_t pcieFunction; 428 pcieFunction["@odata.id"] = crow::utility::urlFromPieces( 429 "redfish", "v1", "Systems", "system", "PCIeDevices", device, 430 "PCIeFunctions", std::to_string(functionNum)); 431 pcieFunctionList.push_back(std::move(pcieFunction)); 432 } 433 asyncResp->res.jsonValue["Members@odata.count"] = 434 pcieFunctionList.size(); 435 }; 436 std::string escapedPath = std::string(pciePath) + "/" + device; 437 dbus::utility::escapePathForDbus(escapedPath); 438 sdbusplus::asio::getAllProperties( 439 *crow::connections::systemBus, pcieService, escapedPath, 440 pcieDeviceInterface, std::move(getPCIeDeviceCallback)); 441 }); 442 } 443 444 inline void requestRoutesSystemPCIeFunction(App& app) 445 { 446 BMCWEB_ROUTE( 447 app, 448 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/") 449 .privileges(redfish::privileges::getPCIeFunction) 450 .methods(boost::beast::http::verb::get)( 451 [&app](const crow::Request& req, 452 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 453 const std::string& device, const std::string& function) { 454 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 455 { 456 return; 457 } 458 auto getPCIeDeviceCallback = 459 [asyncResp, device, function]( 460 const boost::system::error_code& ec, 461 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 462 if (ec) 463 { 464 BMCWEB_LOG_DEBUG 465 << "failed to get PCIe Device properties ec: " << ec.value() 466 << ": " << ec.message(); 467 if (ec.value() == 468 boost::system::linux_error::bad_request_descriptor) 469 { 470 messages::resourceNotFound(asyncResp->res, "PCIeDevice", 471 device); 472 } 473 else 474 { 475 messages::internalError(asyncResp->res); 476 } 477 return; 478 } 479 480 // Check if this function exists by looking for a device 481 // ID 482 std::string functionName = "Function" + function; 483 std::string devIDProperty = functionName + "DeviceId"; 484 485 const std::string* devIdProperty = nullptr; 486 for (const auto& property : pcieDevProperties) 487 { 488 if (property.first == devIDProperty) 489 { 490 devIdProperty = std::get_if<std::string>(&property.second); 491 continue; 492 } 493 } 494 if (devIdProperty == nullptr || devIdProperty->empty()) 495 { 496 messages::resourceNotFound(asyncResp->res, "PCIeFunction", 497 function); 498 return; 499 } 500 501 asyncResp->res.jsonValue["@odata.type"] = 502 "#PCIeFunction.v1_2_0.PCIeFunction"; 503 asyncResp->res.jsonValue["@odata.id"] = 504 crow::utility::urlFromPieces("redfish", "v1", "Systems", 505 "system", "PCIeDevices", device, 506 "PCIeFunctions", function); 507 asyncResp->res.jsonValue["Name"] = "PCIe Function"; 508 asyncResp->res.jsonValue["Id"] = function; 509 asyncResp->res.jsonValue["FunctionId"] = std::stoi(function); 510 asyncResp->res.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = 511 crow::utility::urlFromPieces("redfish", "v1", "Systems", 512 "system", "PCIeDevices", device); 513 514 for (const auto& property : pcieDevProperties) 515 { 516 const std::string* strProperty = 517 std::get_if<std::string>(&property.second); 518 if (property.first == functionName + "DeviceId") 519 { 520 asyncResp->res.jsonValue["DeviceId"] = *strProperty; 521 } 522 if (property.first == functionName + "VendorId") 523 { 524 asyncResp->res.jsonValue["VendorId"] = *strProperty; 525 } 526 if (property.first == functionName + "FunctionType") 527 { 528 asyncResp->res.jsonValue["FunctionType"] = *strProperty; 529 } 530 if (property.first == functionName + "DeviceClass") 531 { 532 asyncResp->res.jsonValue["DeviceClass"] = *strProperty; 533 } 534 if (property.first == functionName + "ClassCode") 535 { 536 asyncResp->res.jsonValue["ClassCode"] = *strProperty; 537 } 538 if (property.first == functionName + "RevisionId") 539 { 540 asyncResp->res.jsonValue["RevisionId"] = *strProperty; 541 } 542 if (property.first == functionName + "SubsystemId") 543 { 544 asyncResp->res.jsonValue["SubsystemId"] = *strProperty; 545 } 546 if (property.first == functionName + "SubsystemVendorId") 547 { 548 asyncResp->res.jsonValue["SubsystemVendorId"] = 549 *strProperty; 550 } 551 } 552 }; 553 std::string escapedPath = std::string(pciePath) + "/" + device; 554 dbus::utility::escapePathForDbus(escapedPath); 555 sdbusplus::asio::getAllProperties( 556 *crow::connections::systemBus, pcieService, escapedPath, 557 pcieDeviceInterface, std::move(getPCIeDeviceCallback)); 558 }); 559 } 560 561 } // namespace redfish 562