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