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 #pragma once 17 18 #include "app.hpp" 19 #include "dbus_singleton.hpp" 20 #include "dbus_utility.hpp" 21 #include "error_messages.hpp" 22 #include "health.hpp" 23 #include "query.hpp" 24 #include "registries/privilege_registry.hpp" 25 #include "utils/collection.hpp" 26 #include "utils/dbus_utils.hpp" 27 #include "utils/json_utils.hpp" 28 29 #include <boost/container/flat_map.hpp> 30 #include <boost/system/error_code.hpp> 31 #include <sdbusplus/asio/property.hpp> 32 #include <sdbusplus/message/native_types.hpp> 33 #include <sdbusplus/unpack_properties.hpp> 34 #include <sdbusplus/utility/dedup_variant.hpp> 35 36 #include <array> 37 #include <limits> 38 #include <string_view> 39 40 namespace redfish 41 { 42 43 // Interfaces which imply a D-Bus object represents a Processor 44 constexpr std::array<std::string_view, 2> processorInterfaces = { 45 "xyz.openbmc_project.Inventory.Item.Cpu", 46 "xyz.openbmc_project.Inventory.Item.Accelerator"}; 47 48 /** 49 * @brief Fill out uuid info of a processor by 50 * requesting data from the given D-Bus object. 51 * 52 * @param[in,out] aResp Async HTTP response. 53 * @param[in] service D-Bus service to query. 54 * @param[in] objPath D-Bus object to query. 55 */ 56 inline void getProcessorUUID(std::shared_ptr<bmcweb::AsyncResp> aResp, 57 const std::string& service, 58 const std::string& objPath) 59 { 60 BMCWEB_LOG_DEBUG << "Get Processor UUID"; 61 sdbusplus::asio::getProperty<std::string>( 62 *crow::connections::systemBus, service, objPath, 63 "xyz.openbmc_project.Common.UUID", "UUID", 64 [objPath, aResp{std::move(aResp)}](const boost::system::error_code& ec, 65 const std::string& property) { 66 if (ec) 67 { 68 BMCWEB_LOG_DEBUG << "DBUS response error"; 69 messages::internalError(aResp->res); 70 return; 71 } 72 aResp->res.jsonValue["UUID"] = property; 73 }); 74 } 75 76 inline void getCpuDataByInterface( 77 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 78 const dbus::utility::DBusInteracesMap& cpuInterfacesProperties) 79 { 80 BMCWEB_LOG_DEBUG << "Get CPU resources by interface."; 81 82 // Set the default value of state 83 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 84 aResp->res.jsonValue["Status"]["Health"] = "OK"; 85 86 for (const auto& interface : cpuInterfacesProperties) 87 { 88 for (const auto& property : interface.second) 89 { 90 if (property.first == "Present") 91 { 92 const bool* cpuPresent = std::get_if<bool>(&property.second); 93 if (cpuPresent == nullptr) 94 { 95 // Important property not in desired type 96 messages::internalError(aResp->res); 97 return; 98 } 99 if (!*cpuPresent) 100 { 101 // Slot is not populated 102 aResp->res.jsonValue["Status"]["State"] = "Absent"; 103 } 104 } 105 else if (property.first == "Functional") 106 { 107 const bool* cpuFunctional = std::get_if<bool>(&property.second); 108 if (cpuFunctional == nullptr) 109 { 110 messages::internalError(aResp->res); 111 return; 112 } 113 if (!*cpuFunctional) 114 { 115 aResp->res.jsonValue["Status"]["Health"] = "Critical"; 116 } 117 } 118 else if (property.first == "CoreCount") 119 { 120 const uint16_t* coresCount = 121 std::get_if<uint16_t>(&property.second); 122 if (coresCount == nullptr) 123 { 124 messages::internalError(aResp->res); 125 return; 126 } 127 aResp->res.jsonValue["TotalCores"] = *coresCount; 128 } 129 else if (property.first == "MaxSpeedInMhz") 130 { 131 const uint32_t* value = std::get_if<uint32_t>(&property.second); 132 if (value != nullptr) 133 { 134 aResp->res.jsonValue["MaxSpeedMHz"] = *value; 135 } 136 } 137 else if (property.first == "Socket") 138 { 139 const std::string* value = 140 std::get_if<std::string>(&property.second); 141 if (value != nullptr) 142 { 143 aResp->res.jsonValue["Socket"] = *value; 144 } 145 } 146 else if (property.first == "ThreadCount") 147 { 148 const uint16_t* value = std::get_if<uint16_t>(&property.second); 149 if (value != nullptr) 150 { 151 aResp->res.jsonValue["TotalThreads"] = *value; 152 } 153 } 154 else if (property.first == "EffectiveFamily") 155 { 156 const uint16_t* value = std::get_if<uint16_t>(&property.second); 157 if (value != nullptr && *value != 2) 158 { 159 aResp->res.jsonValue["ProcessorId"]["EffectiveFamily"] = 160 "0x" + intToHexString(*value, 4); 161 } 162 } 163 else if (property.first == "EffectiveModel") 164 { 165 const uint16_t* value = std::get_if<uint16_t>(&property.second); 166 if (value == nullptr) 167 { 168 messages::internalError(aResp->res); 169 return; 170 } 171 if (*value != 0) 172 { 173 aResp->res.jsonValue["ProcessorId"]["EffectiveModel"] = 174 "0x" + intToHexString(*value, 4); 175 } 176 } 177 else if (property.first == "Id") 178 { 179 const uint64_t* value = std::get_if<uint64_t>(&property.second); 180 if (value != nullptr && *value != 0) 181 { 182 aResp->res 183 .jsonValue["ProcessorId"]["IdentificationRegisters"] = 184 "0x" + intToHexString(*value, 16); 185 } 186 } 187 else if (property.first == "Microcode") 188 { 189 const uint32_t* value = std::get_if<uint32_t>(&property.second); 190 if (value == nullptr) 191 { 192 messages::internalError(aResp->res); 193 return; 194 } 195 if (*value != 0) 196 { 197 aResp->res.jsonValue["ProcessorId"]["MicrocodeInfo"] = 198 "0x" + intToHexString(*value, 8); 199 } 200 } 201 else if (property.first == "Step") 202 { 203 const uint16_t* value = std::get_if<uint16_t>(&property.second); 204 if (value == nullptr) 205 { 206 messages::internalError(aResp->res); 207 return; 208 } 209 if (*value != std::numeric_limits<uint16_t>::max()) 210 { 211 aResp->res.jsonValue["ProcessorId"]["Step"] = 212 "0x" + intToHexString(*value, 4); 213 } 214 } 215 } 216 } 217 } 218 219 inline void getCpuDataByService(std::shared_ptr<bmcweb::AsyncResp> aResp, 220 const std::string& cpuId, 221 const std::string& service, 222 const std::string& objPath) 223 { 224 BMCWEB_LOG_DEBUG << "Get available system cpu resources by service."; 225 226 crow::connections::systemBus->async_method_call( 227 [cpuId, service, objPath, aResp{std::move(aResp)}]( 228 const boost::system::error_code& ec, 229 const dbus::utility::ManagedObjectType& dbusData) { 230 if (ec) 231 { 232 BMCWEB_LOG_DEBUG << "DBUS response error"; 233 messages::internalError(aResp->res); 234 return; 235 } 236 aResp->res.jsonValue["Id"] = cpuId; 237 aResp->res.jsonValue["Name"] = "Processor"; 238 aResp->res.jsonValue["ProcessorType"] = "CPU"; 239 240 bool slotPresent = false; 241 std::string corePath = objPath + "/core"; 242 size_t totalCores = 0; 243 for (const auto& object : dbusData) 244 { 245 if (object.first.str == objPath) 246 { 247 getCpuDataByInterface(aResp, object.second); 248 } 249 else if (object.first.str.starts_with(corePath)) 250 { 251 for (const auto& interface : object.second) 252 { 253 if (interface.first == "xyz.openbmc_project.Inventory.Item") 254 { 255 for (const auto& property : interface.second) 256 { 257 if (property.first == "Present") 258 { 259 const bool* present = 260 std::get_if<bool>(&property.second); 261 if (present != nullptr) 262 { 263 if (*present) 264 { 265 slotPresent = true; 266 totalCores++; 267 } 268 } 269 } 270 } 271 } 272 } 273 } 274 } 275 // In getCpuDataByInterface(), state and health are set 276 // based on the present and functional status. If core 277 // count is zero, then it has a higher precedence. 278 if (slotPresent) 279 { 280 if (totalCores == 0) 281 { 282 // Slot is not populated, set status end return 283 aResp->res.jsonValue["Status"]["State"] = "Absent"; 284 aResp->res.jsonValue["Status"]["Health"] = "OK"; 285 } 286 aResp->res.jsonValue["TotalCores"] = totalCores; 287 } 288 return; 289 }, 290 service, "/xyz/openbmc_project/inventory", 291 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 292 } 293 294 inline void getCpuAssetData(std::shared_ptr<bmcweb::AsyncResp> aResp, 295 const std::string& service, 296 const std::string& objPath) 297 { 298 BMCWEB_LOG_DEBUG << "Get Cpu Asset Data"; 299 sdbusplus::asio::getAllProperties( 300 *crow::connections::systemBus, service, objPath, 301 "xyz.openbmc_project.Inventory.Decorator.Asset", 302 [objPath, aResp{std::move(aResp)}]( 303 const boost::system::error_code& ec, 304 const dbus::utility::DBusPropertiesMap& properties) { 305 if (ec) 306 { 307 BMCWEB_LOG_DEBUG << "DBUS response error"; 308 messages::internalError(aResp->res); 309 return; 310 } 311 312 const std::string* serialNumber = nullptr; 313 const std::string* model = nullptr; 314 const std::string* manufacturer = nullptr; 315 const std::string* partNumber = nullptr; 316 const std::string* sparePartNumber = nullptr; 317 318 const bool success = sdbusplus::unpackPropertiesNoThrow( 319 dbus_utils::UnpackErrorPrinter(), properties, "SerialNumber", 320 serialNumber, "Model", model, "Manufacturer", manufacturer, 321 "PartNumber", partNumber, "SparePartNumber", sparePartNumber); 322 323 if (!success) 324 { 325 messages::internalError(aResp->res); 326 return; 327 } 328 329 if (serialNumber != nullptr && !serialNumber->empty()) 330 { 331 aResp->res.jsonValue["SerialNumber"] = *serialNumber; 332 } 333 334 if ((model != nullptr) && !model->empty()) 335 { 336 aResp->res.jsonValue["Model"] = *model; 337 } 338 339 if (manufacturer != nullptr) 340 { 341 aResp->res.jsonValue["Manufacturer"] = *manufacturer; 342 343 // Otherwise would be unexpected. 344 if (manufacturer->find("Intel") != std::string::npos) 345 { 346 aResp->res.jsonValue["ProcessorArchitecture"] = "x86"; 347 aResp->res.jsonValue["InstructionSet"] = "x86-64"; 348 } 349 else if (manufacturer->find("IBM") != std::string::npos) 350 { 351 aResp->res.jsonValue["ProcessorArchitecture"] = "Power"; 352 aResp->res.jsonValue["InstructionSet"] = "PowerISA"; 353 } 354 } 355 356 if (partNumber != nullptr) 357 { 358 aResp->res.jsonValue["PartNumber"] = *partNumber; 359 } 360 361 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 362 { 363 aResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 364 } 365 }); 366 } 367 368 inline void getCpuRevisionData(std::shared_ptr<bmcweb::AsyncResp> aResp, 369 const std::string& service, 370 const std::string& objPath) 371 { 372 BMCWEB_LOG_DEBUG << "Get Cpu Revision Data"; 373 sdbusplus::asio::getAllProperties( 374 *crow::connections::systemBus, service, objPath, 375 "xyz.openbmc_project.Inventory.Decorator.Revision", 376 [objPath, aResp{std::move(aResp)}]( 377 const boost::system::error_code& ec, 378 const dbus::utility::DBusPropertiesMap& properties) { 379 if (ec) 380 { 381 BMCWEB_LOG_DEBUG << "DBUS response error"; 382 messages::internalError(aResp->res); 383 return; 384 } 385 386 const std::string* version = nullptr; 387 388 const bool success = sdbusplus::unpackPropertiesNoThrow( 389 dbus_utils::UnpackErrorPrinter(), properties, "Version", version); 390 391 if (!success) 392 { 393 messages::internalError(aResp->res); 394 return; 395 } 396 397 if (version != nullptr) 398 { 399 aResp->res.jsonValue["Version"] = *version; 400 } 401 }); 402 } 403 404 inline void getAcceleratorDataByService( 405 std::shared_ptr<bmcweb::AsyncResp> aResp, const std::string& acclrtrId, 406 const std::string& service, const std::string& objPath) 407 { 408 BMCWEB_LOG_DEBUG 409 << "Get available system Accelerator resources by service."; 410 sdbusplus::asio::getAllProperties( 411 *crow::connections::systemBus, service, objPath, "", 412 [acclrtrId, aResp{std::move(aResp)}]( 413 const boost::system::error_code& ec, 414 const dbus::utility::DBusPropertiesMap& properties) { 415 if (ec) 416 { 417 BMCWEB_LOG_DEBUG << "DBUS response error"; 418 messages::internalError(aResp->res); 419 return; 420 } 421 422 const bool* functional = nullptr; 423 const bool* present = nullptr; 424 425 const bool success = sdbusplus::unpackPropertiesNoThrow( 426 dbus_utils::UnpackErrorPrinter(), properties, "Functional", 427 functional, "Present", present); 428 429 if (!success) 430 { 431 messages::internalError(aResp->res); 432 return; 433 } 434 435 std::string state = "Enabled"; 436 std::string health = "OK"; 437 438 if (present != nullptr && !*present) 439 { 440 state = "Absent"; 441 } 442 443 if (functional != nullptr && !*functional) 444 { 445 if (state == "Enabled") 446 { 447 health = "Critical"; 448 } 449 } 450 451 aResp->res.jsonValue["Id"] = acclrtrId; 452 aResp->res.jsonValue["Name"] = "Processor"; 453 aResp->res.jsonValue["Status"]["State"] = state; 454 aResp->res.jsonValue["Status"]["Health"] = health; 455 aResp->res.jsonValue["ProcessorType"] = "Accelerator"; 456 }); 457 } 458 459 // OperatingConfig D-Bus Types 460 using TurboProfileProperty = std::vector<std::tuple<uint32_t, size_t>>; 461 using BaseSpeedPrioritySettingsProperty = 462 std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>; 463 // uint32_t and size_t may or may not be the same type, requiring a dedup'd 464 // variant 465 466 /** 467 * Fill out the HighSpeedCoreIDs in a Processor resource from the given 468 * OperatingConfig D-Bus property. 469 * 470 * @param[in,out] aResp Async HTTP response. 471 * @param[in] baseSpeedSettings Full list of base speed priority groups, 472 * to use to determine the list of high 473 * speed cores. 474 */ 475 inline void highSpeedCoreIdsHandler( 476 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 477 const BaseSpeedPrioritySettingsProperty& baseSpeedSettings) 478 { 479 // The D-Bus property does not indicate which bucket is the "high 480 // priority" group, so let's discern that by looking for the one with 481 // highest base frequency. 482 auto highPriorityGroup = baseSpeedSettings.cend(); 483 uint32_t highestBaseSpeed = 0; 484 for (auto it = baseSpeedSettings.cbegin(); it != baseSpeedSettings.cend(); 485 ++it) 486 { 487 const uint32_t baseFreq = std::get<uint32_t>(*it); 488 if (baseFreq > highestBaseSpeed) 489 { 490 highestBaseSpeed = baseFreq; 491 highPriorityGroup = it; 492 } 493 } 494 495 nlohmann::json& jsonCoreIds = aResp->res.jsonValue["HighSpeedCoreIDs"]; 496 jsonCoreIds = nlohmann::json::array(); 497 498 // There may not be any entries in the D-Bus property, so only populate 499 // if there was actually something there. 500 if (highPriorityGroup != baseSpeedSettings.cend()) 501 { 502 jsonCoreIds = std::get<std::vector<uint32_t>>(*highPriorityGroup); 503 } 504 } 505 506 /** 507 * Fill out OperatingConfig related items in a Processor resource by requesting 508 * data from the given D-Bus object. 509 * 510 * @param[in,out] aResp Async HTTP response. 511 * @param[in] cpuId CPU D-Bus name. 512 * @param[in] service D-Bus service to query. 513 * @param[in] objPath D-Bus object to query. 514 */ 515 inline void getCpuConfigData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 516 const std::string& cpuId, 517 const std::string& service, 518 const std::string& objPath) 519 { 520 BMCWEB_LOG_INFO << "Getting CPU operating configs for " << cpuId; 521 522 // First, GetAll CurrentOperatingConfig properties on the object 523 sdbusplus::asio::getAllProperties( 524 *crow::connections::systemBus, service, objPath, 525 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 526 [aResp, cpuId, 527 service](const boost::system::error_code& ec, 528 const dbus::utility::DBusPropertiesMap& properties) { 529 if (ec) 530 { 531 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 532 messages::internalError(aResp->res); 533 return; 534 } 535 536 nlohmann::json& json = aResp->res.jsonValue; 537 538 const sdbusplus::message::object_path* appliedConfig = nullptr; 539 const bool* baseSpeedPriorityEnabled = nullptr; 540 541 const bool success = sdbusplus::unpackPropertiesNoThrow( 542 dbus_utils::UnpackErrorPrinter(), properties, "AppliedConfig", 543 appliedConfig, "BaseSpeedPriorityEnabled", 544 baseSpeedPriorityEnabled); 545 546 if (!success) 547 { 548 messages::internalError(aResp->res); 549 return; 550 } 551 552 if (appliedConfig != nullptr) 553 { 554 const std::string& dbusPath = appliedConfig->str; 555 nlohmann::json::object_t operatingConfig; 556 operatingConfig["@odata.id"] = crow::utility::urlFromPieces( 557 "redfish", "v1", "Systems", "system", "Processors", cpuId, 558 "OperatingConfigs"); 559 json["OperatingConfigs"] = std::move(operatingConfig); 560 561 // Reuse the D-Bus config object name for the Redfish 562 // URI 563 size_t baseNamePos = dbusPath.rfind('/'); 564 if (baseNamePos == std::string::npos || 565 baseNamePos == (dbusPath.size() - 1)) 566 { 567 // If the AppliedConfig was somehow not a valid path, 568 // skip adding any more properties, since everything 569 // else is tied to this applied config. 570 messages::internalError(aResp->res); 571 return; 572 } 573 nlohmann::json::object_t appliedOperatingConfig; 574 appliedOperatingConfig["@odata.id"] = crow::utility::urlFromPieces( 575 "redfish", "v1", "Systems", "system", "Processors", cpuId, 576 "OperatingConfigs", dbusPath.substr(baseNamePos + 1)); 577 json["AppliedOperatingConfig"] = std::move(appliedOperatingConfig); 578 579 // Once we found the current applied config, queue another 580 // request to read the base freq core ids out of that 581 // config. 582 sdbusplus::asio::getProperty<BaseSpeedPrioritySettingsProperty>( 583 *crow::connections::systemBus, service, dbusPath, 584 "xyz.openbmc_project.Inventory.Item.Cpu." 585 "OperatingConfig", 586 "BaseSpeedPrioritySettings", 587 [aResp]( 588 const boost::system::error_code& ec2, 589 const BaseSpeedPrioritySettingsProperty& baseSpeedList) { 590 if (ec2) 591 { 592 BMCWEB_LOG_WARNING << "D-Bus Property Get error: " << ec2; 593 messages::internalError(aResp->res); 594 return; 595 } 596 597 highSpeedCoreIdsHandler(aResp, baseSpeedList); 598 }); 599 } 600 601 if (baseSpeedPriorityEnabled != nullptr) 602 { 603 json["BaseSpeedPriorityState"] = 604 *baseSpeedPriorityEnabled ? "Enabled" : "Disabled"; 605 } 606 }); 607 } 608 609 /** 610 * @brief Fill out location info of a processor by 611 * requesting data from the given D-Bus object. 612 * 613 * @param[in,out] aResp Async HTTP response. 614 * @param[in] service D-Bus service to query. 615 * @param[in] objPath D-Bus object to query. 616 */ 617 inline void getCpuLocationCode(std::shared_ptr<bmcweb::AsyncResp> aResp, 618 const std::string& service, 619 const std::string& objPath) 620 { 621 BMCWEB_LOG_DEBUG << "Get Cpu Location Data"; 622 sdbusplus::asio::getProperty<std::string>( 623 *crow::connections::systemBus, service, objPath, 624 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 625 [objPath, aResp{std::move(aResp)}](const boost::system::error_code& ec, 626 const std::string& property) { 627 if (ec) 628 { 629 BMCWEB_LOG_DEBUG << "DBUS response error"; 630 messages::internalError(aResp->res); 631 return; 632 } 633 634 aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 635 property; 636 }); 637 } 638 639 /** 640 * Populate the unique identifier in a Processor resource by requesting data 641 * from the given D-Bus object. 642 * 643 * @param[in,out] aResp Async HTTP response. 644 * @param[in] service D-Bus service to query. 645 * @param[in] objPath D-Bus object to query. 646 */ 647 inline void getCpuUniqueId(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 648 const std::string& service, 649 const std::string& objectPath) 650 { 651 BMCWEB_LOG_DEBUG << "Get CPU UniqueIdentifier"; 652 sdbusplus::asio::getProperty<std::string>( 653 *crow::connections::systemBus, service, objectPath, 654 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier", 655 "UniqueIdentifier", 656 [aResp](const boost::system::error_code& ec, const std::string& id) { 657 if (ec) 658 { 659 BMCWEB_LOG_ERROR << "Failed to read cpu unique id: " << ec; 660 messages::internalError(aResp->res); 661 return; 662 } 663 aResp->res.jsonValue["ProcessorId"]["ProtectedIdentificationNumber"] = 664 id; 665 }); 666 } 667 668 /** 669 * Find the D-Bus object representing the requested Processor, and call the 670 * handler with the results. If matching object is not found, add 404 error to 671 * response and don't call the handler. 672 * 673 * @param[in,out] resp Async HTTP response. 674 * @param[in] processorId Redfish Processor Id. 675 * @param[in] handler Callback to continue processing request upon 676 * successfully finding object. 677 */ 678 template <typename Handler> 679 inline void getProcessorObject(const std::shared_ptr<bmcweb::AsyncResp>& resp, 680 const std::string& processorId, 681 Handler&& handler) 682 { 683 BMCWEB_LOG_DEBUG << "Get available system processor resources."; 684 685 // GetSubTree on all interfaces which provide info about a Processor 686 constexpr std::array<std::string_view, 8> interfaces = { 687 "xyz.openbmc_project.Common.UUID", 688 "xyz.openbmc_project.Inventory.Decorator.Asset", 689 "xyz.openbmc_project.Inventory.Decorator.Revision", 690 "xyz.openbmc_project.Inventory.Item.Cpu", 691 "xyz.openbmc_project.Inventory.Decorator.LocationCode", 692 "xyz.openbmc_project.Inventory.Item.Accelerator", 693 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 694 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier"}; 695 dbus::utility::getSubTree( 696 "/xyz/openbmc_project/inventory", 0, interfaces, 697 [resp, processorId, handler = std::forward<Handler>(handler)]( 698 const boost::system::error_code& ec, 699 const dbus::utility::MapperGetSubTreeResponse& subtree) { 700 if (ec) 701 { 702 BMCWEB_LOG_DEBUG << "DBUS response error: " << ec; 703 messages::internalError(resp->res); 704 return; 705 } 706 for (const auto& [objectPath, serviceMap] : subtree) 707 { 708 // Ignore any objects which don't end with our desired cpu name 709 if (!objectPath.ends_with(processorId)) 710 { 711 continue; 712 } 713 714 bool found = false; 715 // Filter out objects that don't have the CPU-specific 716 // interfaces to make sure we can return 404 on non-CPUs 717 // (e.g. /redfish/../Processors/dimm0) 718 for (const auto& [serviceName, interfaceList] : serviceMap) 719 { 720 if (std::find_first_of( 721 interfaceList.begin(), interfaceList.end(), 722 processorInterfaces.begin(), 723 processorInterfaces.end()) != interfaceList.end()) 724 { 725 found = true; 726 break; 727 } 728 } 729 730 if (!found) 731 { 732 continue; 733 } 734 735 // Process the first object which does match our cpu name and 736 // required interfaces, and potentially ignore any other 737 // matching objects. Assume all interfaces we want to process 738 // must be on the same object path. 739 740 handler(objectPath, serviceMap); 741 return; 742 } 743 messages::resourceNotFound(resp->res, "Processor", processorId); 744 }); 745 } 746 747 inline void getProcessorData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 748 const std::string& processorId, 749 const std::string& objectPath, 750 const dbus::utility::MapperServiceMap& serviceMap) 751 { 752 for (const auto& [serviceName, interfaceList] : serviceMap) 753 { 754 for (const auto& interface : interfaceList) 755 { 756 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 757 { 758 getCpuAssetData(aResp, serviceName, objectPath); 759 } 760 else if (interface == 761 "xyz.openbmc_project.Inventory.Decorator.Revision") 762 { 763 getCpuRevisionData(aResp, serviceName, objectPath); 764 } 765 else if (interface == "xyz.openbmc_project.Inventory.Item.Cpu") 766 { 767 getCpuDataByService(aResp, processorId, serviceName, 768 objectPath); 769 } 770 else if (interface == 771 "xyz.openbmc_project.Inventory.Item.Accelerator") 772 { 773 getAcceleratorDataByService(aResp, processorId, serviceName, 774 objectPath); 775 } 776 else if ( 777 interface == 778 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig") 779 { 780 getCpuConfigData(aResp, processorId, serviceName, objectPath); 781 } 782 else if (interface == 783 "xyz.openbmc_project.Inventory.Decorator.LocationCode") 784 { 785 getCpuLocationCode(aResp, serviceName, objectPath); 786 } 787 else if (interface == "xyz.openbmc_project.Common.UUID") 788 { 789 getProcessorUUID(aResp, serviceName, objectPath); 790 } 791 else if (interface == 792 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier") 793 { 794 getCpuUniqueId(aResp, serviceName, objectPath); 795 } 796 } 797 } 798 } 799 800 /** 801 * Request all the properties for the given D-Bus object and fill out the 802 * related entries in the Redfish OperatingConfig response. 803 * 804 * @param[in,out] aResp Async HTTP response. 805 * @param[in] service D-Bus service name to query. 806 * @param[in] objPath D-Bus object to query. 807 */ 808 inline void 809 getOperatingConfigData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 810 const std::string& service, 811 const std::string& objPath) 812 { 813 sdbusplus::asio::getAllProperties( 814 *crow::connections::systemBus, service, objPath, 815 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig", 816 [aResp](const boost::system::error_code& ec, 817 const dbus::utility::DBusPropertiesMap& properties) { 818 if (ec) 819 { 820 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 821 messages::internalError(aResp->res); 822 return; 823 } 824 825 const size_t* availableCoreCount = nullptr; 826 const uint32_t* baseSpeed = nullptr; 827 const uint32_t* maxJunctionTemperature = nullptr; 828 const uint32_t* maxSpeed = nullptr; 829 const uint32_t* powerLimit = nullptr; 830 const TurboProfileProperty* turboProfile = nullptr; 831 const BaseSpeedPrioritySettingsProperty* baseSpeedPrioritySettings = 832 nullptr; 833 834 const bool success = sdbusplus::unpackPropertiesNoThrow( 835 dbus_utils::UnpackErrorPrinter(), properties, "AvailableCoreCount", 836 availableCoreCount, "BaseSpeed", baseSpeed, 837 "MaxJunctionTemperature", maxJunctionTemperature, "MaxSpeed", 838 maxSpeed, "PowerLimit", powerLimit, "TurboProfile", turboProfile, 839 "BaseSpeedPrioritySettings", baseSpeedPrioritySettings); 840 841 if (!success) 842 { 843 messages::internalError(aResp->res); 844 return; 845 } 846 847 nlohmann::json& json = aResp->res.jsonValue; 848 849 if (availableCoreCount != nullptr) 850 { 851 json["TotalAvailableCoreCount"] = *availableCoreCount; 852 } 853 854 if (baseSpeed != nullptr) 855 { 856 json["BaseSpeedMHz"] = *baseSpeed; 857 } 858 859 if (maxJunctionTemperature != nullptr) 860 { 861 json["MaxJunctionTemperatureCelsius"] = *maxJunctionTemperature; 862 } 863 864 if (maxSpeed != nullptr) 865 { 866 json["MaxSpeedMHz"] = *maxSpeed; 867 } 868 869 if (powerLimit != nullptr) 870 { 871 json["TDPWatts"] = *powerLimit; 872 } 873 874 if (turboProfile != nullptr) 875 { 876 nlohmann::json& turboArray = json["TurboProfile"]; 877 turboArray = nlohmann::json::array(); 878 for (const auto& [turboSpeed, coreCount] : *turboProfile) 879 { 880 nlohmann::json::object_t turbo; 881 turbo["ActiveCoreCount"] = coreCount; 882 turbo["MaxSpeedMHz"] = turboSpeed; 883 turboArray.emplace_back(std::move(turbo)); 884 } 885 } 886 887 if (baseSpeedPrioritySettings != nullptr) 888 { 889 nlohmann::json& baseSpeedArray = json["BaseSpeedPrioritySettings"]; 890 baseSpeedArray = nlohmann::json::array(); 891 for (const auto& [baseSpeedMhz, coreList] : 892 *baseSpeedPrioritySettings) 893 { 894 nlohmann::json::object_t speed; 895 speed["CoreCount"] = coreList.size(); 896 speed["CoreIDs"] = coreList; 897 speed["BaseSpeedMHz"] = baseSpeedMhz; 898 baseSpeedArray.emplace_back(std::move(speed)); 899 } 900 } 901 }); 902 } 903 904 /** 905 * Handle the D-Bus response from attempting to set the CPU's AppliedConfig 906 * property. Main task is to translate error messages into Redfish errors. 907 * 908 * @param[in,out] resp HTTP response. 909 * @param[in] setPropVal Value which we attempted to set. 910 * @param[in] ec D-Bus response error code. 911 * @param[in] msg D-Bus response message. 912 */ 913 inline void 914 handleAppliedConfigResponse(const std::shared_ptr<bmcweb::AsyncResp>& resp, 915 const std::string& setPropVal, 916 const boost::system::error_code& ec, 917 const sdbusplus::message_t& msg) 918 { 919 if (!ec) 920 { 921 BMCWEB_LOG_DEBUG << "Set Property succeeded"; 922 return; 923 } 924 925 BMCWEB_LOG_DEBUG << "Set Property failed: " << ec; 926 927 const sd_bus_error* dbusError = msg.get_error(); 928 if (dbusError == nullptr) 929 { 930 messages::internalError(resp->res); 931 return; 932 } 933 934 // The asio error code doesn't know about our custom errors, so we have to 935 // parse the error string. Some of these D-Bus -> Redfish translations are a 936 // stretch, but it's good to try to communicate something vaguely useful. 937 if (strcmp(dbusError->name, 938 "xyz.openbmc_project.Common.Error.InvalidArgument") == 0) 939 { 940 // Service did not like the object_path we tried to set. 941 messages::propertyValueIncorrect( 942 resp->res, "AppliedOperatingConfig/@odata.id", setPropVal); 943 } 944 else if (strcmp(dbusError->name, 945 "xyz.openbmc_project.Common.Error.NotAllowed") == 0) 946 { 947 // Service indicates we can never change the config for this processor. 948 messages::propertyNotWritable(resp->res, "AppliedOperatingConfig"); 949 } 950 else if (strcmp(dbusError->name, 951 "xyz.openbmc_project.Common.Error.Unavailable") == 0) 952 { 953 // Service indicates the config cannot be changed right now, but maybe 954 // in a different system state. 955 messages::resourceInStandby(resp->res); 956 } 957 else 958 { 959 messages::internalError(resp->res); 960 } 961 } 962 963 /** 964 * Handle the PATCH operation of the AppliedOperatingConfig property. Do basic 965 * validation of the input data, and then set the D-Bus property. 966 * 967 * @param[in,out] resp Async HTTP response. 968 * @param[in] processorId Processor's Id. 969 * @param[in] appliedConfigUri New property value to apply. 970 * @param[in] cpuObjectPath Path of CPU object to modify. 971 * @param[in] serviceMap Service map for CPU object. 972 */ 973 inline void patchAppliedOperatingConfig( 974 const std::shared_ptr<bmcweb::AsyncResp>& resp, 975 const std::string& processorId, const std::string& appliedConfigUri, 976 const std::string& cpuObjectPath, 977 const dbus::utility::MapperServiceMap& serviceMap) 978 { 979 // Check that the property even exists by checking for the interface 980 const std::string* controlService = nullptr; 981 for (const auto& [serviceName, interfaceList] : serviceMap) 982 { 983 if (std::find(interfaceList.begin(), interfaceList.end(), 984 "xyz.openbmc_project.Control.Processor." 985 "CurrentOperatingConfig") != interfaceList.end()) 986 { 987 controlService = &serviceName; 988 break; 989 } 990 } 991 992 if (controlService == nullptr) 993 { 994 messages::internalError(resp->res); 995 return; 996 } 997 998 // Check that the config URI is a child of the cpu URI being patched. 999 std::string expectedPrefix("/redfish/v1/Systems/system/Processors/"); 1000 expectedPrefix += processorId; 1001 expectedPrefix += "/OperatingConfigs/"; 1002 if (!appliedConfigUri.starts_with(expectedPrefix) || 1003 expectedPrefix.size() == appliedConfigUri.size()) 1004 { 1005 messages::propertyValueIncorrect( 1006 resp->res, "AppliedOperatingConfig/@odata.id", appliedConfigUri); 1007 return; 1008 } 1009 1010 // Generate the D-Bus path of the OperatingConfig object, by assuming it's a 1011 // direct child of the CPU object. 1012 // Strip the expectedPrefix from the config URI to get the "filename", and 1013 // append to the CPU's path. 1014 std::string configBaseName = appliedConfigUri.substr(expectedPrefix.size()); 1015 sdbusplus::message::object_path configPath(cpuObjectPath); 1016 configPath /= configBaseName; 1017 1018 BMCWEB_LOG_INFO << "Setting config to " << configPath.str; 1019 1020 // Set the property, with handler to check error responses 1021 crow::connections::systemBus->async_method_call( 1022 [resp, appliedConfigUri](const boost::system::error_code& ec, 1023 const sdbusplus::message_t& msg) { 1024 handleAppliedConfigResponse(resp, appliedConfigUri, ec, msg); 1025 }, 1026 *controlService, cpuObjectPath, "org.freedesktop.DBus.Properties", 1027 "Set", "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 1028 "AppliedConfig", dbus::utility::DbusVariantType(std::move(configPath))); 1029 } 1030 1031 inline void handleProcessorHead(crow::App& app, const crow::Request& req, 1032 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 1033 const std::string& /* systemName */, 1034 const std::string& /* processorId */) 1035 { 1036 if (!redfish::setUpRedfishRoute(app, req, aResp)) 1037 { 1038 return; 1039 } 1040 aResp->res.addHeader( 1041 boost::beast::http::field::link, 1042 "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby"); 1043 } 1044 1045 inline void handleProcessorCollectionHead( 1046 crow::App& app, const crow::Request& req, 1047 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 1048 const std::string& /* systemName */) 1049 { 1050 if (!redfish::setUpRedfishRoute(app, req, aResp)) 1051 { 1052 return; 1053 } 1054 aResp->res.addHeader( 1055 boost::beast::http::field::link, 1056 "</redfish/v1/JsonSchemas/ProcessorCollection/ProcessorCollection.json>; rel=describedby"); 1057 } 1058 1059 inline void requestRoutesOperatingConfigCollection(App& app) 1060 { 1061 BMCWEB_ROUTE( 1062 app, "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/") 1063 .privileges(redfish::privileges::getOperatingConfigCollection) 1064 .methods(boost::beast::http::verb::get)( 1065 [&app](const crow::Request& req, 1066 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1067 const std::string& cpuName) { 1068 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1069 { 1070 return; 1071 } 1072 asyncResp->res.jsonValue["@odata.type"] = 1073 "#OperatingConfigCollection.OperatingConfigCollection"; 1074 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 1075 "redfish", "v1", "Systems", "system", "Processors", cpuName, 1076 "OperatingConfigs"); 1077 asyncResp->res.jsonValue["Name"] = "Operating Config Collection"; 1078 1079 // First find the matching CPU object so we know how to 1080 // constrain our search for related Config objects. 1081 const std::array<std::string_view, 1> interfaces = { 1082 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig"}; 1083 dbus::utility::getSubTreePaths( 1084 "/xyz/openbmc_project/inventory", 0, interfaces, 1085 [asyncResp, cpuName]( 1086 const boost::system::error_code& ec, 1087 const dbus::utility::MapperGetSubTreePathsResponse& objects) { 1088 if (ec) 1089 { 1090 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " 1091 << ec.message(); 1092 messages::internalError(asyncResp->res); 1093 return; 1094 } 1095 1096 for (const std::string& object : objects) 1097 { 1098 if (!object.ends_with(cpuName)) 1099 { 1100 continue; 1101 } 1102 1103 // Not expected that there will be multiple matching 1104 // CPU objects, but if there are just use the first 1105 // one. 1106 1107 // Use the common search routine to construct the 1108 // Collection of all Config objects under this CPU. 1109 constexpr std::array<std::string_view, 1> interface { 1110 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig" 1111 }; 1112 collection_util::getCollectionMembers( 1113 asyncResp, 1114 crow::utility::urlFromPieces("redfish", "v1", "Systems", 1115 "system", "Processors", 1116 cpuName, "OperatingConfigs"), 1117 interface, object.c_str()); 1118 return; 1119 } 1120 }); 1121 }); 1122 } 1123 1124 inline void requestRoutesOperatingConfig(App& app) 1125 { 1126 BMCWEB_ROUTE( 1127 app, 1128 "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/<str>/") 1129 .privileges(redfish::privileges::getOperatingConfig) 1130 .methods(boost::beast::http::verb::get)( 1131 [&app](const crow::Request& req, 1132 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1133 const std::string& cpuName, const std::string& configName) { 1134 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1135 { 1136 return; 1137 } 1138 // Ask for all objects implementing OperatingConfig so we can search 1139 // for one with a matching name 1140 constexpr std::array<std::string_view, 1> interfaces = { 1141 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig"}; 1142 dbus::utility::getSubTree( 1143 "/xyz/openbmc_project/inventory", 0, interfaces, 1144 [asyncResp, cpuName, configName]( 1145 const boost::system::error_code& ec, 1146 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1147 if (ec) 1148 { 1149 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " 1150 << ec.message(); 1151 messages::internalError(asyncResp->res); 1152 return; 1153 } 1154 const std::string expectedEnding = cpuName + '/' + configName; 1155 for (const auto& [objectPath, serviceMap] : subtree) 1156 { 1157 // Ignore any configs without matching cpuX/configY 1158 if (!objectPath.ends_with(expectedEnding) || serviceMap.empty()) 1159 { 1160 continue; 1161 } 1162 1163 nlohmann::json& json = asyncResp->res.jsonValue; 1164 json["@odata.type"] = "#OperatingConfig.v1_0_0.OperatingConfig"; 1165 json["@odata.id"] = crow::utility::urlFromPieces( 1166 "redfish", "v1", "Systems", "system", "Processors", cpuName, 1167 "OperatingConfigs", configName); 1168 json["Name"] = "Processor Profile"; 1169 json["Id"] = configName; 1170 1171 // Just use the first implementation of the object - not 1172 // expected that there would be multiple matching 1173 // services 1174 getOperatingConfigData(asyncResp, serviceMap.begin()->first, 1175 objectPath); 1176 return; 1177 } 1178 messages::resourceNotFound(asyncResp->res, "OperatingConfig", 1179 configName); 1180 }); 1181 }); 1182 } 1183 1184 inline void requestRoutesProcessorCollection(App& app) 1185 { 1186 /** 1187 * Functions triggers appropriate requests on DBus 1188 */ 1189 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/") 1190 .privileges(redfish::privileges::headProcessorCollection) 1191 .methods(boost::beast::http::verb::head)( 1192 std::bind_front(handleProcessorCollectionHead, std::ref(app))); 1193 1194 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/") 1195 .privileges(redfish::privileges::getProcessorCollection) 1196 .methods(boost::beast::http::verb::get)( 1197 [&app](const crow::Request& req, 1198 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1199 const std::string& systemName) { 1200 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1201 { 1202 return; 1203 } 1204 if (systemName != "system") 1205 { 1206 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1207 systemName); 1208 return; 1209 } 1210 1211 asyncResp->res.addHeader( 1212 boost::beast::http::field::link, 1213 "</redfish/v1/JsonSchemas/ProcessorCollection/ProcessorCollection.json>; rel=describedby"); 1214 1215 asyncResp->res.jsonValue["@odata.type"] = 1216 "#ProcessorCollection.ProcessorCollection"; 1217 asyncResp->res.jsonValue["Name"] = "Processor Collection"; 1218 1219 asyncResp->res.jsonValue["@odata.id"] = 1220 "/redfish/v1/Systems/system/Processors"; 1221 1222 collection_util::getCollectionMembers( 1223 asyncResp, 1224 boost::urls::url("/redfish/v1/Systems/system/Processors"), 1225 processorInterfaces); 1226 }); 1227 } 1228 1229 inline void requestRoutesProcessor(App& app) 1230 { 1231 /** 1232 * Functions triggers appropriate requests on DBus 1233 */ 1234 1235 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1236 .privileges(redfish::privileges::headProcessor) 1237 .methods(boost::beast::http::verb::head)( 1238 std::bind_front(handleProcessorHead, std::ref(app))); 1239 1240 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1241 .privileges(redfish::privileges::getProcessor) 1242 .methods(boost::beast::http::verb::get)( 1243 [&app](const crow::Request& req, 1244 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1245 const std::string& systemName, 1246 const std::string& processorId) { 1247 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1248 { 1249 return; 1250 } 1251 if (systemName != "system") 1252 { 1253 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1254 systemName); 1255 return; 1256 } 1257 1258 asyncResp->res.addHeader( 1259 boost::beast::http::field::link, 1260 "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby"); 1261 asyncResp->res.jsonValue["@odata.type"] = 1262 "#Processor.v1_11_0.Processor"; 1263 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 1264 "redfish", "v1", "Systems", "system", "Processors", processorId); 1265 1266 getProcessorObject( 1267 asyncResp, processorId, 1268 std::bind_front(getProcessorData, asyncResp, processorId)); 1269 }); 1270 1271 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1272 .privileges(redfish::privileges::patchProcessor) 1273 .methods(boost::beast::http::verb::patch)( 1274 [&app](const crow::Request& req, 1275 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1276 const std::string& systemName, 1277 const std::string& processorId) { 1278 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1279 { 1280 return; 1281 } 1282 if (systemName != "system") 1283 { 1284 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1285 systemName); 1286 return; 1287 } 1288 1289 std::optional<nlohmann::json> appliedConfigJson; 1290 if (!json_util::readJsonPatch(req, asyncResp->res, 1291 "AppliedOperatingConfig", 1292 appliedConfigJson)) 1293 { 1294 return; 1295 } 1296 1297 if (appliedConfigJson) 1298 { 1299 std::string appliedConfigUri; 1300 if (!json_util::readJson(*appliedConfigJson, asyncResp->res, 1301 "@odata.id", appliedConfigUri)) 1302 { 1303 return; 1304 } 1305 // Check for 404 and find matching D-Bus object, then run 1306 // property patch handlers if that all succeeds. 1307 getProcessorObject(asyncResp, processorId, 1308 std::bind_front(patchAppliedOperatingConfig, 1309 asyncResp, processorId, 1310 appliedConfigUri)); 1311 } 1312 }); 1313 } 1314 1315 } // namespace redfish 1316