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