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