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, const std::string& pcieDeviceId, 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 resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces( 281 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 282 "PCIeFunctions"); 283 } 284 285 inline void getPCIeDeviceProperties( 286 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 287 const std::string& pcieDevicePath, const std::string& service, 288 const std::function<void( 289 const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback) 290 { 291 sdbusplus::asio::getAllProperties( 292 *crow::connections::systemBus, service, pcieDevicePath, 293 "xyz.openbmc_project.Inventory.Item.PCIeDevice", 294 [aResp, 295 callback](const boost::system::error_code& ec, 296 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 297 if (ec) 298 { 299 if (ec.value() != EBADR) 300 { 301 BMCWEB_LOG_ERROR << "DBUS response error for Properties"; 302 messages::internalError(aResp->res); 303 } 304 return; 305 } 306 callback(pcieDevProperties); 307 }); 308 } 309 310 inline void addPCIeDeviceCommonProperties( 311 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 312 const std::string& pcieDeviceId) 313 { 314 aResp->res.addHeader( 315 boost::beast::http::field::link, 316 "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby"); 317 aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice"; 318 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 319 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId); 320 aResp->res.jsonValue["Name"] = "PCIe Device"; 321 aResp->res.jsonValue["Id"] = pcieDeviceId; 322 } 323 324 inline void handlePCIeDeviceGet(App& app, const crow::Request& req, 325 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 326 const std::string& systemName, 327 const std::string& pcieDeviceId) 328 { 329 if (!redfish::setUpRedfishRoute(app, req, aResp)) 330 { 331 return; 332 } 333 if (systemName != "system") 334 { 335 messages::resourceNotFound(aResp->res, "ComputerSystem", systemName); 336 return; 337 } 338 339 getValidPCIeDevicePath( 340 pcieDeviceId, aResp, 341 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 342 const std::string& service) { 343 addPCIeDeviceCommonProperties(aResp, pcieDeviceId); 344 getPCIeDeviceProperties( 345 aResp, pcieDevicePath, service, 346 [aResp, pcieDeviceId]( 347 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 348 addPCIeDeviceProperties(aResp->res, pcieDeviceId, 349 pcieDevProperties); 350 }); 351 }); 352 } 353 354 inline void requestRoutesSystemPCIeDevice(App& app) 355 { 356 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/") 357 .privileges(redfish::privileges::getPCIeDevice) 358 .methods(boost::beast::http::verb::get)( 359 std::bind_front(handlePCIeDeviceGet, std::ref(app))); 360 } 361 362 inline void addPCIeFunctionList( 363 crow::Response& res, const std::string& pcieDeviceId, 364 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 365 { 366 nlohmann::json& pcieFunctionList = res.jsonValue["Members"]; 367 pcieFunctionList = nlohmann::json::array(); 368 static constexpr const int maxPciFunctionNum = 8; 369 370 for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++) 371 { 372 // Check if this function exists by 373 // looking for a device ID 374 std::string devIDProperty = 375 "Function" + std::to_string(functionNum) + "DeviceId"; 376 const std::string* property = nullptr; 377 for (const auto& propEntry : pcieDevProperties) 378 { 379 if (propEntry.first == devIDProperty) 380 { 381 property = std::get_if<std::string>(&propEntry.second); 382 break; 383 } 384 } 385 if (property == nullptr || property->empty()) 386 { 387 continue; 388 } 389 390 nlohmann::json::object_t pcieFunction; 391 pcieFunction["@odata.id"] = crow::utility::urlFromPieces( 392 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 393 "PCIeFunctions", std::to_string(functionNum)); 394 pcieFunctionList.push_back(std::move(pcieFunction)); 395 } 396 res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size(); 397 } 398 399 inline void handlePCIeFunctionCollectionGet( 400 App& app, const crow::Request& req, 401 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 402 const std::string& pcieDeviceId) 403 { 404 if (!redfish::setUpRedfishRoute(app, req, aResp)) 405 { 406 return; 407 } 408 409 getValidPCIeDevicePath( 410 pcieDeviceId, aResp, 411 [aResp, pcieDeviceId](const std::string& pcieDevicePath, 412 const std::string& service) { 413 aResp->res.addHeader( 414 boost::beast::http::field::link, 415 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby"); 416 aResp->res.jsonValue["@odata.type"] = 417 "#PCIeFunctionCollection.PCIeFunctionCollection"; 418 aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 419 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 420 "PCIeFunctions"); 421 aResp->res.jsonValue["Name"] = "PCIe Function Collection"; 422 aResp->res.jsonValue["Description"] = 423 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId; 424 getPCIeDeviceProperties( 425 aResp, pcieDevicePath, service, 426 [aResp, pcieDeviceId]( 427 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 428 addPCIeFunctionList(aResp->res, pcieDeviceId, pcieDevProperties); 429 }); 430 }); 431 } 432 433 inline void requestRoutesSystemPCIeFunctionCollection(App& app) 434 { 435 /** 436 * Functions triggers appropriate requests on DBus 437 */ 438 BMCWEB_ROUTE(app, 439 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/") 440 .privileges(redfish::privileges::getPCIeFunctionCollection) 441 .methods(boost::beast::http::verb::get)( 442 std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app))); 443 } 444 445 inline void requestRoutesSystemPCIeFunction(App& app) 446 { 447 BMCWEB_ROUTE( 448 app, 449 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/") 450 .privileges(redfish::privileges::getPCIeFunction) 451 .methods(boost::beast::http::verb::get)( 452 [&app](const crow::Request& req, 453 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 454 const std::string& device, const std::string& function) { 455 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 456 { 457 return; 458 } 459 auto getPCIeDeviceCallback = 460 [asyncResp, device, function]( 461 const boost::system::error_code& ec, 462 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 463 if (ec) 464 { 465 BMCWEB_LOG_DEBUG 466 << "failed to get PCIe Device properties ec: " << ec.value() 467 << ": " << ec.message(); 468 if (ec.value() == 469 boost::system::linux_error::bad_request_descriptor) 470 { 471 messages::resourceNotFound(asyncResp->res, "PCIeDevice", 472 device); 473 } 474 else 475 { 476 messages::internalError(asyncResp->res); 477 } 478 return; 479 } 480 481 // Check if this function exists by looking for a device 482 // ID 483 std::string functionName = "Function" + function; 484 std::string devIDProperty = functionName + "DeviceId"; 485 486 const std::string* devIdProperty = nullptr; 487 for (const auto& property : pcieDevProperties) 488 { 489 if (property.first == devIDProperty) 490 { 491 devIdProperty = std::get_if<std::string>(&property.second); 492 continue; 493 } 494 } 495 if (devIdProperty == nullptr || devIdProperty->empty()) 496 { 497 messages::resourceNotFound(asyncResp->res, "PCIeFunction", 498 function); 499 return; 500 } 501 502 asyncResp->res.jsonValue["@odata.type"] = 503 "#PCIeFunction.v1_2_0.PCIeFunction"; 504 asyncResp->res.jsonValue["@odata.id"] = 505 crow::utility::urlFromPieces("redfish", "v1", "Systems", 506 "system", "PCIeDevices", device, 507 "PCIeFunctions", function); 508 asyncResp->res.jsonValue["Name"] = "PCIe Function"; 509 asyncResp->res.jsonValue["Id"] = function; 510 asyncResp->res.jsonValue["FunctionId"] = std::stoi(function); 511 asyncResp->res.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = 512 crow::utility::urlFromPieces("redfish", "v1", "Systems", 513 "system", "PCIeDevices", device); 514 515 for (const auto& property : pcieDevProperties) 516 { 517 const std::string* strProperty = 518 std::get_if<std::string>(&property.second); 519 if (property.first == functionName + "DeviceId") 520 { 521 asyncResp->res.jsonValue["DeviceId"] = *strProperty; 522 } 523 if (property.first == functionName + "VendorId") 524 { 525 asyncResp->res.jsonValue["VendorId"] = *strProperty; 526 } 527 if (property.first == functionName + "FunctionType") 528 { 529 asyncResp->res.jsonValue["FunctionType"] = *strProperty; 530 } 531 if (property.first == functionName + "DeviceClass") 532 { 533 asyncResp->res.jsonValue["DeviceClass"] = *strProperty; 534 } 535 if (property.first == functionName + "ClassCode") 536 { 537 asyncResp->res.jsonValue["ClassCode"] = *strProperty; 538 } 539 if (property.first == functionName + "RevisionId") 540 { 541 asyncResp->res.jsonValue["RevisionId"] = *strProperty; 542 } 543 if (property.first == functionName + "SubsystemId") 544 { 545 asyncResp->res.jsonValue["SubsystemId"] = *strProperty; 546 } 547 if (property.first == functionName + "SubsystemVendorId") 548 { 549 asyncResp->res.jsonValue["SubsystemVendorId"] = 550 *strProperty; 551 } 552 } 553 }; 554 std::string escapedPath = std::string(pciePath) + "/" + device; 555 dbus::utility::escapePathForDbus(escapedPath); 556 sdbusplus::asio::getAllProperties( 557 *crow::connections::systemBus, pcieService, escapedPath, 558 pcieDeviceInterface, std::move(getPCIeDeviceCallback)); 559 }); 560 } 561 562 } // namespace redfish 563