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