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