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