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 bool validatePCIeFunctionId( 446 const std::string& pcieFunctionId, 447 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 448 { 449 std::string functionName = "Function" + pcieFunctionId; 450 std::string devIDProperty = functionName + "DeviceId"; 451 452 const std::string* devIdProperty = nullptr; 453 for (const auto& property : pcieDevProperties) 454 { 455 if (property.first == devIDProperty) 456 { 457 devIdProperty = std::get_if<std::string>(&property.second); 458 break; 459 } 460 } 461 return (devIdProperty != nullptr && !devIdProperty->empty()); 462 } 463 464 inline void addPCIeFunctionProperties( 465 crow::Response& resp, const std::string& pcieFunctionId, 466 const dbus::utility::DBusPropertiesMap& pcieDevProperties) 467 { 468 std::string functionName = "Function" + pcieFunctionId; 469 if (!validatePCIeFunctionId(pcieFunctionId, pcieDevProperties)) 470 { 471 messages::resourceNotFound(resp, "PCIeFunction", pcieFunctionId); 472 return; 473 } 474 for (const auto& property : pcieDevProperties) 475 { 476 const std::string* strProperty = 477 std::get_if<std::string>(&property.second); 478 479 if (property.first == functionName + "DeviceId") 480 { 481 resp.jsonValue["DeviceId"] = *strProperty; 482 } 483 if (property.first == functionName + "VendorId") 484 { 485 resp.jsonValue["VendorId"] = *strProperty; 486 } 487 // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus 488 // property strings should be mapped correctly to ensure these 489 // strings are Redfish enum values. For now just check for empty. 490 if (property.first == functionName + "FunctionType") 491 { 492 if (!strProperty->empty()) 493 { 494 resp.jsonValue["FunctionType"] = *strProperty; 495 } 496 } 497 if (property.first == functionName + "DeviceClass") 498 { 499 if (!strProperty->empty()) 500 { 501 resp.jsonValue["DeviceClass"] = *strProperty; 502 } 503 } 504 if (property.first == functionName + "ClassCode") 505 { 506 resp.jsonValue["ClassCode"] = *strProperty; 507 } 508 if (property.first == functionName + "RevisionId") 509 { 510 resp.jsonValue["RevisionId"] = *strProperty; 511 } 512 if (property.first == functionName + "SubsystemId") 513 { 514 resp.jsonValue["SubsystemId"] = *strProperty; 515 } 516 if (property.first == functionName + "SubsystemVendorId") 517 { 518 resp.jsonValue["SubsystemVendorId"] = *strProperty; 519 } 520 } 521 } 522 523 inline void addPCIeFunctionCommonProperties(crow::Response& resp, 524 const std::string& pcieDeviceId, 525 const std::string& pcieFunctionId) 526 { 527 resp.addHeader( 528 boost::beast::http::field::link, 529 "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby"); 530 resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction"; 531 resp.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 532 "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId, 533 "PCIeFunctions", pcieFunctionId); 534 resp.jsonValue["Name"] = "PCIe Function"; 535 resp.jsonValue["Id"] = pcieFunctionId; 536 resp.jsonValue["FunctionId"] = std::stoi(pcieFunctionId); 537 resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = 538 crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", 539 "PCIeDevices", pcieDeviceId); 540 } 541 542 inline void 543 handlePCIeFunctionGet(App& app, const crow::Request& req, 544 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 545 const std::string& pcieDeviceId, 546 const std::string& pcieFunctionId) 547 { 548 if (!redfish::setUpRedfishRoute(app, req, aResp)) 549 { 550 return; 551 } 552 553 getValidPCIeDevicePath( 554 pcieDeviceId, aResp, 555 [aResp, pcieDeviceId, pcieFunctionId](const std::string& pcieDevicePath, 556 const std::string& service) { 557 getPCIeDeviceProperties( 558 aResp, pcieDevicePath, service, 559 [aResp, pcieDeviceId, pcieFunctionId]( 560 const dbus::utility::DBusPropertiesMap& pcieDevProperties) { 561 addPCIeFunctionCommonProperties(aResp->res, pcieDeviceId, 562 pcieFunctionId); 563 addPCIeFunctionProperties(aResp->res, pcieFunctionId, 564 pcieDevProperties); 565 }); 566 }); 567 } 568 569 inline void requestRoutesSystemPCIeFunction(App& app) 570 { 571 BMCWEB_ROUTE( 572 app, 573 "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/") 574 .privileges(redfish::privileges::getPCIeFunction) 575 .methods(boost::beast::http::verb::get)( 576 std::bind_front(handlePCIeFunctionGet, std::ref(app))); 577 } 578 579 } // namespace redfish 580