1 #include "config.h"
2 
3 #include "common_utility.hpp"
4 #include "defines.hpp"
5 #include "editor_impl.hpp"
6 #include "ibm_vpd_utils.hpp"
7 #include "ipz_parser.hpp"
8 #include "keyword_vpd_parser.hpp"
9 #include "memory_vpd_parser.hpp"
10 #include "parser_factory.hpp"
11 #include "vpd_exceptions.hpp"
12 
13 #include <assert.h>
14 #include <ctype.h>
15 
16 #include <CLI/CLI.hpp>
17 #include <boost/algorithm/string.hpp>
18 #include <gpiod.hpp>
19 #include <phosphor-logging/log.hpp>
20 
21 #include <algorithm>
22 #include <cstdarg>
23 #include <exception>
24 #include <filesystem>
25 #include <fstream>
26 #include <iostream>
27 #include <iterator>
28 #include <regex>
29 #include <thread>
30 
31 using namespace std;
32 using namespace openpower::vpd;
33 using namespace CLI;
34 using namespace vpd::keyword::parser;
35 using namespace openpower::vpd::constants;
36 namespace fs = filesystem;
37 using json = nlohmann::json;
38 using namespace openpower::vpd::parser::factory;
39 using namespace openpower::vpd::inventory;
40 using namespace openpower::vpd::memory::parser;
41 using namespace openpower::vpd::parser::interface;
42 using namespace openpower::vpd::exceptions;
43 using namespace phosphor::logging;
44 using namespace openpower::vpd::manager::editor;
45 
46 /**
47  * @brief API declaration, Populate Dbus.
48  *
49  * This method invokes all the populateInterface functions
50  * and notifies PIM about dbus object.
51  *
52  * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the
53  * input.
54  * @param[in] js - Inventory json object
55  * @param[in] filePath - Path of the vpd file
56  * @param[in] preIntrStr - Interface string
57  */
58 template <typename T>
59 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath);
60 
61 /**
62  * @brief Returns the BMC state
63  */
getBMCState()64 static auto getBMCState()
65 {
66     std::string bmcState;
67     try
68     {
69         auto bus = sdbusplus::bus::new_default();
70         auto properties = bus.new_method_call(
71             "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0",
72             "org.freedesktop.DBus.Properties", "Get");
73         properties.append("xyz.openbmc_project.State.BMC");
74         properties.append("CurrentBMCState");
75         auto result = bus.call(properties);
76         std::variant<std::string> val;
77         result.read(val);
78         if (auto pVal = std::get_if<std::string>(&val))
79         {
80             bmcState = *pVal;
81         }
82     }
83     catch (const sdbusplus::exception::SdBusError& e)
84     {
85         // Ignore any error
86         std::cerr << "Failed to get BMC state: " << e.what() << "\n";
87         // Since we failed set to not ready state
88         bmcState = "xyz.openbmc_project.State.BMC.BMCState.NotReady";
89     }
90     return bmcState;
91 }
92 
93 /**
94  * @brief Check if the FRU is in the cache
95  *
96  * Checks if the FRU associated with the supplied D-Bus object path is already
97  * on D-Bus. This can be used to test if a VPD collection is required for this
98  * FRU. It uses the "xyz.openbmc_project.Inventory.Item, Present" property to
99  * determine the presence of a FRU in the cache.
100  *
101  * @param objectPath - The D-Bus object path without the PIM prefix.
102  * @return true if the object exists on D-Bus, false otherwise.
103  */
isFruInVpdCache(const std::string & objectPath)104 static auto isFruInVpdCache(const std::string& objectPath)
105 {
106     try
107     {
108         auto bus = sdbusplus::bus::new_default();
109         auto invPath = std::string{pimPath} + objectPath;
110         auto props = bus.new_method_call(
111             "xyz.openbmc_project.Inventory.Manager", invPath.c_str(),
112             "org.freedesktop.DBus.Properties", "Get");
113         props.append("xyz.openbmc_project.Inventory.Item");
114         props.append("Present");
115         auto result = bus.call(props);
116         std::variant<bool> present;
117         result.read(present);
118         if (auto pVal = std::get_if<bool>(&present))
119         {
120             return *pVal;
121         }
122         return false;
123     }
124     catch (const sdbusplus::exception::SdBusError& e)
125     {
126         std::cout << "FRU: " << objectPath << " not in D-Bus\n";
127         // Assume not present in case of an error
128         return false;
129     }
130 }
131 
132 /**
133  * @brief Check if VPD recollection is needed for the given EEPROM
134  *
135  * Not all FRUs can be swapped at BMC ready state. This function does the
136  * following:
137  * -- Check if the FRU is marked as "pluggableAtStandby" OR
138  *    "concurrentlyMaintainable". If so, return true.
139  * -- Check if we are at BMC NotReady state. If we are, then return true.
140  * -- Else check if the FRU is not present in the VPD cache (to cover for VPD
141  *    force collection). If not found in the cache, return true.
142  * -- Else return false.
143  *
144  * @param js - JSON Object.
145  * @param filePath - The EEPROM file.
146  * @return true if collection should be attempted, false otherwise.
147  */
needsRecollection(const nlohmann::json & js,const string & filePath)148 static auto needsRecollection(const nlohmann::json& js, const string& filePath)
149 {
150     if (js["frus"][filePath].at(0).value("pluggableAtStandby", false) ||
151         js["frus"][filePath].at(0).value("concurrentlyMaintainable", false))
152     {
153         return true;
154     }
155     if (getBMCState() == "xyz.openbmc_project.State.BMC.BMCState.NotReady")
156     {
157         return true;
158     }
159     if (!isFruInVpdCache(js["frus"][filePath].at(0).value("inventoryPath", "")))
160     {
161         return true;
162     }
163     return false;
164 }
165 
166 /**
167  * @brief Expands location codes
168  */
expandLocationCode(const string & unexpanded,const Parsed & vpdMap,bool isSystemVpd)169 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap,
170                                bool isSystemVpd)
171 {
172     auto expanded{unexpanded};
173     static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard";
174     static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN";
175     static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS";
176     size_t idx = expanded.find("fcs");
177     try
178     {
179         if (idx != string::npos)
180         {
181             string fc{};
182             string se{};
183             if (isSystemVpd)
184             {
185                 const auto& fcData = vpdMap.at("VCEN").at("FC");
186                 const auto& seData = vpdMap.at("VCEN").at("SE");
187                 fc = string(fcData.data(), fcData.size());
188                 se = string(seData.data(), seData.size());
189             }
190             else
191             {
192                 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC");
193                 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE");
194             }
195 
196             // TODO: See if ND0 can be placed in the JSON
197             expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se);
198         }
199         else
200         {
201             idx = expanded.find("mts");
202             if (idx != string::npos)
203             {
204                 string mt{};
205                 string se{};
206                 if (isSystemVpd)
207                 {
208                     const auto& mtData = vpdMap.at("VSYS").at("TM");
209                     const auto& seData = vpdMap.at("VSYS").at("SE");
210                     mt = string(mtData.data(), mtData.size());
211                     se = string(seData.data(), seData.size());
212                 }
213                 else
214                 {
215                     mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM");
216                     se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE");
217                 }
218 
219                 replace(mt.begin(), mt.end(), '-', '.');
220                 expanded.replace(idx, 3, mt + "." + se);
221             }
222         }
223     }
224     catch (const exception& e)
225     {
226         std::cerr << "Failed to expand location code with exception: "
227                   << e.what() << "\n";
228     }
229     return expanded;
230 }
231 
232 /**
233  * @brief Populate FRU specific interfaces.
234  *
235  * This is a common method which handles both
236  * ipz and keyword specific interfaces thus,
237  * reducing the code redundancy.
238  * @param[in] map - Reference to the innermost keyword-value map.
239  * @param[in] preIntrStr - Reference to the interface string.
240  * @param[out] interfaces - Reference to interface map.
241  */
242 template <typename T>
populateFruSpecificInterfaces(const T & map,const string & preIntrStr,inventory::InterfaceMap & interfaces)243 static void populateFruSpecificInterfaces(const T& map,
244                                           const string& preIntrStr,
245                                           inventory::InterfaceMap& interfaces)
246 {
247     inventory::PropertyMap prop;
248 
249     for (const auto& kwVal : map)
250     {
251         auto kw = kwVal.first;
252 
253         if (kw[0] == '#')
254         {
255             kw = string("PD_") + kw[1];
256         }
257         else if (isdigit(kw[0]))
258         {
259             kw = string("N_") + kw;
260         }
261         if constexpr (is_same<T, KeywordVpdMap>::value)
262         {
263             if (auto keywordValue = get_if<Binary>(&kwVal.second))
264             {
265                 Binary vec((*keywordValue).begin(), (*keywordValue).end());
266                 prop.emplace(move(kw), move(vec));
267             }
268             else if (auto keywordValue = get_if<std::string>(&kwVal.second))
269             {
270                 Binary vec((*keywordValue).begin(), (*keywordValue).end());
271                 prop.emplace(move(kw), move(vec));
272             }
273             else if (auto keywordValue = get_if<size_t>(&kwVal.second))
274             {
275                 if (kw == "MemorySizeInKB")
276                 {
277                     inventory::PropertyMap memProp;
278                     memProp.emplace(move(kw), ((*keywordValue)));
279                     interfaces.emplace(
280                         "xyz.openbmc_project.Inventory.Item.Dimm",
281                         move(memProp));
282                 }
283                 else
284                 {
285                     std::cerr << "Unknown Keyword[" << kw << "] found ";
286                 }
287             }
288             else
289             {
290                 std::cerr << "Unknown Variant found ";
291             }
292         }
293         else
294         {
295             Binary vec(kwVal.second.begin(), kwVal.second.end());
296             prop.emplace(move(kw), move(vec));
297         }
298     }
299 
300     interfaces.emplace(preIntrStr, move(prop));
301 }
302 
303 /**
304  * @brief Populate Interfaces.
305  *
306  * This method populates common and extra interfaces to dbus.
307  * @param[in] js - json object
308  * @param[out] interfaces - Reference to interface map
309  * @param[in] vpdMap - Reference to the parsed vpd map.
310  * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD.
311  */
312 template <typename T>
populateInterfaces(const nlohmann::json & js,inventory::InterfaceMap & interfaces,const T & vpdMap,bool isSystemVpd)313 static void populateInterfaces(const nlohmann::json& js,
314                                inventory::InterfaceMap& interfaces,
315                                const T& vpdMap, bool isSystemVpd)
316 {
317     for (const auto& ifs : js.items())
318     {
319         string inf = ifs.key();
320         inventory::PropertyMap props;
321 
322         for (const auto& itr : ifs.value().items())
323         {
324             const string& busProp = itr.key();
325 
326             if (itr.value().is_boolean())
327             {
328                 props.emplace(busProp, itr.value().get<bool>());
329             }
330             else if (itr.value().is_string())
331             {
332                 if (busProp == "LocationCode" && inf == IBM_LOCATION_CODE_INF)
333                 {
334                     std::string prop;
335                     if constexpr (is_same<T, Parsed>::value)
336                     {
337                         // TODO deprecate the com.ibm interface later
338                         prop = expandLocationCode(itr.value().get<string>(),
339                                                   vpdMap, isSystemVpd);
340                     }
341                     else if constexpr (is_same<T, KeywordVpdMap>::value)
342                     {
343                         // Send empty Parsed object to expandLocationCode api.
344                         prop = expandLocationCode(itr.value().get<string>(),
345                                                   Parsed{}, false);
346                     }
347                     props.emplace(busProp, prop);
348                     interfaces.emplace(XYZ_LOCATION_CODE_INF, props);
349                     interfaces.emplace(IBM_LOCATION_CODE_INF, props);
350                 }
351                 else
352                 {
353                     props.emplace(busProp, itr.value().get<string>());
354                 }
355             }
356             else if (itr.value().is_array())
357             {
358                 try
359                 {
360                     props.emplace(busProp, itr.value().get<Binary>());
361                 }
362                 catch (const nlohmann::detail::type_error& e)
363                 {
364                     std::cerr << "Type exception: " << e.what() << "\n";
365                     // Ignore any type errors
366                 }
367             }
368             else if (itr.value().is_object())
369             {
370                 const string& rec = itr.value().value("recordName", "");
371                 const string& kw = itr.value().value("keywordName", "");
372                 const string& encoding = itr.value().value("encoding", "");
373 
374                 if constexpr (is_same<T, Parsed>::value)
375                 {
376                     if (!rec.empty() && !kw.empty() && vpdMap.count(rec) &&
377                         vpdMap.at(rec).count(kw))
378                     {
379                         auto encoded = encodeKeyword(vpdMap.at(rec).at(kw),
380                                                      encoding);
381                         props.emplace(busProp, encoded);
382                     }
383                 }
384                 else if constexpr (is_same<T, KeywordVpdMap>::value)
385                 {
386                     if (!kw.empty() && vpdMap.count(kw))
387                     {
388                         if (auto kwValue = get_if<Binary>(&vpdMap.at(kw)))
389                         {
390                             auto prop = string((*kwValue).begin(),
391                                                (*kwValue).end());
392 
393                             auto encoded = encodeKeyword(prop, encoding);
394 
395                             props.emplace(busProp, encoded);
396                         }
397                         else if (auto kwValue =
398                                      get_if<std::string>(&vpdMap.at(kw)))
399                         {
400                             auto prop = string((*kwValue).begin(),
401                                                (*kwValue).end());
402 
403                             auto encoded = encodeKeyword(prop, encoding);
404 
405                             props.emplace(busProp, encoded);
406                         }
407                         else if (auto uintValue =
408                                      get_if<size_t>(&vpdMap.at(kw)))
409                         {
410                             props.emplace(busProp, *uintValue);
411                         }
412                         else
413                         {
414                             std::cerr << " Unknown Keyword [" << kw
415                                       << "] Encountered";
416                         }
417                     }
418                 }
419             }
420             else if (itr.value().is_number())
421             {
422                 // For now assume the value is a size_t.  In the future it would
423                 // be nice to come up with a way to get the type from the JSON.
424                 props.emplace(busProp, itr.value().get<size_t>());
425             }
426         }
427         insertOrMerge(interfaces, inf, move(props));
428     }
429 }
430 
431 /**
432  * @brief This API checks if this FRU is pcie_devices. If yes then it further
433  *        checks whether it is PASS1 planar.
434  */
isThisPcieOnPass1planar(const nlohmann::json & js,const string & file)435 static bool isThisPcieOnPass1planar(const nlohmann::json& js,
436                                     const string& file)
437 {
438     auto isThisPCIeDev = false;
439     auto isPASS1 = false;
440 
441     // Check if it is a PCIE device
442     if (js["frus"].find(file) != js["frus"].end())
443     {
444         if ((js["frus"][file].at(0).find("extraInterfaces") !=
445              js["frus"][file].at(0).end()))
446         {
447             if (js["frus"][file].at(0)["extraInterfaces"].find(
448                     "xyz.openbmc_project.Inventory.Item.PCIeDevice") !=
449                 js["frus"][file].at(0)["extraInterfaces"].end())
450             {
451                 isThisPCIeDev = true;
452             }
453         }
454     }
455 
456     if (isThisPCIeDev)
457     {
458         // Collect HW version and SystemType to know if it is PASS1 planar.
459         auto bus = sdbusplus::bus::new_default();
460         auto property1 = bus.new_method_call(
461             INVENTORY_MANAGER_SERVICE,
462             "/xyz/openbmc_project/inventory/system/chassis/motherboard",
463             "org.freedesktop.DBus.Properties", "Get");
464         property1.append("com.ibm.ipzvpd.VINI");
465         property1.append("HW");
466         auto result1 = bus.call(property1);
467         inventory::Value hwVal;
468         result1.read(hwVal);
469 
470         // SystemType
471         auto property2 = bus.new_method_call(
472             INVENTORY_MANAGER_SERVICE,
473             "/xyz/openbmc_project/inventory/system/chassis/motherboard",
474             "org.freedesktop.DBus.Properties", "Get");
475         property2.append("com.ibm.ipzvpd.VSBP");
476         property2.append("IM");
477         auto result2 = bus.call(property2);
478         inventory::Value imVal;
479         result2.read(imVal);
480 
481         auto pVal1 = get_if<Binary>(&hwVal);
482         auto pVal2 = get_if<Binary>(&imVal);
483 
484         if (pVal1 && pVal2)
485         {
486             auto hwVersion = *pVal1;
487             auto systemType = *pVal2;
488 
489             // IM kw for Everest
490             Binary everestSystem{80, 00, 48, 00};
491 
492             if (systemType == everestSystem)
493             {
494                 if (hwVersion[1] < 21)
495                 {
496                     isPASS1 = true;
497                 }
498             }
499             else if (hwVersion[1] < 2)
500             {
501                 isPASS1 = true;
502             }
503         }
504     }
505 
506     return (isThisPCIeDev && isPASS1);
507 }
508 
509 /** Performs any pre-action needed to get the FRU setup for collection.
510  *
511  * @param[in] json - json object
512  * @param[in] file - eeprom file path
513  */
preAction(const nlohmann::json & json,const string & file)514 static void preAction(const nlohmann::json& json, const string& file)
515 {
516     if ((json["frus"][file].at(0)).find("preAction") ==
517         json["frus"][file].at(0).end())
518     {
519         return;
520     }
521 
522     try
523     {
524         if (executePreAction(json, file))
525         {
526             if (json["frus"][file].at(0).find("devAddress") !=
527                 json["frus"][file].at(0).end())
528             {
529                 // Now bind the device
530                 string bind = json["frus"][file].at(0).value("devAddress", "");
531                 std::cout << "Binding device " << bind << std::endl;
532                 string bindCmd = string("echo \"") + bind +
533                                  string("\" > /sys/bus/i2c/drivers/at24/bind");
534                 std::cout << bindCmd << std::endl;
535                 executeCmd(bindCmd);
536 
537                 // Check if device showed up (test for file)
538                 if (!fs::exists(file))
539                 {
540                     std::cerr << "EEPROM " << file
541                               << " does not exist. Take failure action"
542                               << std::endl;
543                     // If not, then take failure postAction
544                     executePostFailAction(json, file);
545                 }
546             }
547             else
548             {
549                 // missing required information
550                 std::cerr << "VPD inventory JSON missing basic information of "
551                              "preAction "
552                              "for this FRU : ["
553                           << file << "]. Executing executePostFailAction."
554                           << std::endl;
555 
556                 // Take failure postAction
557                 executePostFailAction(json, file);
558                 return;
559             }
560         }
561         else
562         {
563             // If the FRU is not there, clear the VINI/CCIN data.
564             // Entity manager probes for this keyword to look for this
565             // FRU, now if the data is persistent on BMC and FRU is
566             // removed this can lead to ambiguity. Hence clearing this
567             // Keyword if FRU is absent.
568             const auto& invPath =
569                 json["frus"][file].at(0).value("inventoryPath", "");
570 
571             if (!invPath.empty())
572             {
573                 inventory::ObjectMap pimObjMap{
574                     {invPath, {{"com.ibm.ipzvpd.VINI", {{"CC", Binary{}}}}}}};
575 
576                 common::utility::callPIM(move(pimObjMap));
577             }
578             else
579             {
580                 throw std::runtime_error("Path empty in Json");
581             }
582         }
583     }
584     catch (const GpioException& e)
585     {
586         PelAdditionalData additionalData{};
587         additionalData.emplace("DESCRIPTION", e.what());
588         createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError,
589                   nullptr);
590     }
591 }
592 
593 /**
594  * @brief Fills the Decorator.AssetTag property into the interfaces map
595  *
596  * This function should only be called in cases where we did not find a JSON
597  * symlink. A missing symlink in /var/lib will be considered as a factory reset
598  * and this function will be used to default the AssetTag property.
599  *
600  * @param interfaces A possibly pre-populated map of inetrfaces to properties.
601  * @param vpdMap A VPD map of the system VPD data.
602  */
fillAssetTag(inventory::InterfaceMap & interfaces,const Parsed & vpdMap)603 static void fillAssetTag(inventory::InterfaceMap& interfaces,
604                          const Parsed& vpdMap)
605 {
606     // Read the system serial number and MTM
607     // Default asset tag is Server-MTM-System Serial
608     inventory::Interface assetIntf{
609         "xyz.openbmc_project.Inventory.Decorator.AssetTag"};
610     inventory::PropertyMap assetTagProps;
611     std::string defaultAssetTag =
612         std::string{"Server-"} + getKwVal(vpdMap, "VSYS", "TM") +
613         std::string{"-"} + getKwVal(vpdMap, "VSYS", "SE");
614     assetTagProps.emplace("AssetTag", defaultAssetTag);
615     insertOrMerge(interfaces, assetIntf, std::move(assetTagProps));
616 }
617 
618 /**
619  * @brief Set certain one time properties in the inventory
620  * Use this function to insert the Functional and Enabled properties into the
621  * inventory map. This function first checks if the object in question already
622  * has these properties hosted on D-Bus, if the property is already there, it is
623  * not modified, hence the name "one time". If the property is not already
624  * present, it will be added to the map with a suitable default value (true for
625  * Functional and Enabled)
626  *
627  * @param[in] object - The inventory D-Bus object without the inventory prefix.
628  * @param[in,out] interfaces - Reference to a map of inventory interfaces to
629  * which the properties will be attached.
630  */
setOneTimeProperties(const std::string & object,inventory::InterfaceMap & interfaces)631 static void setOneTimeProperties(const std::string& object,
632                                  inventory::InterfaceMap& interfaces)
633 {
634     auto bus = sdbusplus::bus::new_default();
635     auto objectPath = INVENTORY_PATH + object;
636     auto prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager",
637                                     objectPath.c_str(),
638                                     "org.freedesktop.DBus.Properties", "Get");
639     prop.append("xyz.openbmc_project.State.Decorator.OperationalStatus");
640     prop.append("Functional");
641     try
642     {
643         auto result = bus.call(prop);
644     }
645     catch (const sdbusplus::exception::SdBusError& e)
646     {
647         // Treat as property unavailable
648         inventory::PropertyMap prop;
649         prop.emplace("Functional", true);
650         interfaces.emplace(
651             "xyz.openbmc_project.State.Decorator.OperationalStatus",
652             move(prop));
653     }
654     prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager",
655                                objectPath.c_str(),
656                                "org.freedesktop.DBus.Properties", "Get");
657     prop.append("xyz.openbmc_project.Object.Enable");
658     prop.append("Enabled");
659     try
660     {
661         auto result = bus.call(prop);
662     }
663     catch (const sdbusplus::exception::SdBusError& e)
664     {
665         // Treat as property unavailable
666         inventory::PropertyMap prop;
667         prop.emplace("Enabled", true);
668         interfaces.emplace("xyz.openbmc_project.Object.Enable", move(prop));
669     }
670 }
671 
672 /**
673  * @brief Prime the Inventory
674  * Prime the inventory by populating only the location code,
675  * type interface and the inventory object for the frus
676  * which are not system vpd fru.
677  *
678  * @param[in] jsObject - Reference to vpd inventory json object
679  * @param[in] vpdMap -  Reference to the parsed vpd map
680  *
681  * @returns Map of items in extraInterface.
682  */
683 template <typename T>
primeInventory(const nlohmann::json & jsObject,const T & vpdMap)684 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject,
685                                     const T& vpdMap)
686 {
687     inventory::ObjectMap objects;
688 
689     for (auto& itemFRUS : jsObject["frus"].items())
690     {
691         for (auto& itemEEPROM : itemFRUS.value())
692         {
693             // Take pre actions if needed
694             if (itemEEPROM.find("preAction") != itemEEPROM.end())
695             {
696                 preAction(jsObject, itemFRUS.key());
697             }
698 
699             inventory::InterfaceMap interfaces;
700             inventory::Object object(itemEEPROM.at("inventoryPath"));
701 
702             if ((itemFRUS.key() != systemVpdFilePath) &&
703                 !itemEEPROM.value("noprime", false))
704             {
705                 inventory::PropertyMap presProp;
706 
707                 // Do not populate Present property for frus whose
708                 // synthesized=true. synthesized=true says the fru VPD is
709                 // synthesized and owned by a separate component.
710                 // In some cases, the FRU has its own VPD, but still a separate
711                 // application handles the FRU's presence. So VPD parser skips
712                 // populating Present property by checking the JSON flag,
713                 // "handlePresence".
714                 if (!itemEEPROM.value("synthesized", false))
715                 {
716                     if (itemEEPROM.value("handlePresence", true))
717                     {
718                         presProp.emplace("Present", false);
719                         interfaces.emplace("xyz.openbmc_project.Inventory.Item",
720                                            presProp);
721                     }
722                 }
723 
724                 setOneTimeProperties(object, interfaces);
725                 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end())
726                 {
727                     for (const auto& eI : itemEEPROM["extraInterfaces"].items())
728                     {
729                         inventory::PropertyMap props;
730                         if (eI.key() == IBM_LOCATION_CODE_INF)
731                         {
732                             if constexpr (std::is_same<T, Parsed>::value)
733                             {
734                                 for (auto& lC : eI.value().items())
735                                 {
736                                     auto propVal = expandLocationCode(
737                                         lC.value().get<string>(), vpdMap, true);
738 
739                                     props.emplace(move(lC.key()),
740                                                   move(propVal));
741                                     interfaces.emplace(XYZ_LOCATION_CODE_INF,
742                                                        props);
743                                     interfaces.emplace(move(eI.key()),
744                                                        move(props));
745                                 }
746                             }
747                         }
748                         else if (eI.key() ==
749                                  "xyz.openbmc_project.Inventory.Item")
750                         {
751                             for (auto& val : eI.value().items())
752                             {
753                                 if (val.key() == "PrettyName")
754                                 {
755                                     presProp.emplace(val.key(),
756                                                      val.value().get<string>());
757                                 }
758                             }
759                             // Use insert_or_assign here as we may already have
760                             // inserted the present property only earlier in
761                             // this function under this same interface.
762                             interfaces.insert_or_assign(eI.key(),
763                                                         move(presProp));
764                         }
765                         else
766                         {
767                             interfaces.emplace(move(eI.key()), move(props));
768                         }
769                     }
770                 }
771                 objects.emplace(move(object), move(interfaces));
772             }
773         }
774     }
775     return objects;
776 }
777 
778 /**
779  * @brief This API executes command to set environment variable
780  *        And then reboot the system
781  * @param[in] key   -env key to set new value
782  * @param[in] value -value to set.
783  */
setEnvAndReboot(const string & key,const string & value)784 void setEnvAndReboot(const string& key, const string& value)
785 {
786     // set env and reboot and break.
787     executeCmd("/sbin/fw_setenv", key, value);
788     log<level::INFO>("Rebooting BMC to pick up new device tree");
789     // make dbus call to reboot
790     auto bus = sdbusplus::bus::new_default_system();
791     auto method = bus.new_method_call(
792         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
793         "org.freedesktop.systemd1.Manager", "Reboot");
794     bus.call_noreply(method);
795 }
796 
797 /*
798  * @brief This API checks for env var fitconfig.
799  *        If not initialised OR updated as per the current system type,
800  *        update this env var and reboot the system.
801  *
802  * @param[in] systemType IM kwd in vpd tells about which system type it is.
803  * */
setDevTreeEnv(const string & systemType)804 void setDevTreeEnv(const string& systemType)
805 {
806     // Init with default dtb
807     string newDeviceTree = "conf-aspeed-bmc-ibm-rainier-p1.dtb";
808     static const deviceTreeMap deviceTreeSystemTypeMap = {
809         {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"},
810         {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"},
811         {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"},
812         {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"},
813         {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"},
814         {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"},
815         {EVEREST_V2, "conf-aspeed-bmc-ibm-everest.dtb"},
816         {BONNELL, "conf-aspeed-bmc-ibm-bonnell.dtb"}};
817 
818     if (deviceTreeSystemTypeMap.find(systemType) !=
819         deviceTreeSystemTypeMap.end())
820     {
821         newDeviceTree = deviceTreeSystemTypeMap.at(systemType);
822     }
823     else
824     {
825         // System type not supported
826         string err = "This System type not found/supported in dtb table " +
827                      systemType +
828                      ".Please check the HW and IM keywords in the system "
829                      "VPD.Breaking...";
830 
831         // map to hold additional data in case of logging pel
832         PelAdditionalData additionalData{};
833         additionalData.emplace("DESCRIPTION", err);
834         createPEL(additionalData, PelSeverity::WARNING,
835                   errIntfForInvalidSystemType, nullptr);
836         exit(-1);
837     }
838 
839     string readVarValue;
840     bool envVarFound = false;
841 
842     vector<string> output = executeCmd("/sbin/fw_printenv");
843     for (const auto& entry : output)
844     {
845         size_t pos = entry.find("=");
846         string key = entry.substr(0, pos);
847         if (key != "fitconfig")
848         {
849             continue;
850         }
851 
852         envVarFound = true;
853         if (pos + 1 < entry.size())
854         {
855             readVarValue = entry.substr(pos + 1);
856             if (readVarValue.find(newDeviceTree) != string::npos)
857             {
858                 // fitconfig is Updated. No action needed
859                 break;
860             }
861         }
862         // set env and reboot and break.
863         setEnvAndReboot(key, newDeviceTree);
864         exit(0);
865     }
866 
867     // check If env var Not found
868     if (!envVarFound)
869     {
870         setEnvAndReboot("fitconfig", newDeviceTree);
871     }
872 }
873 
874 /**
875  * @brief Parse the given EEPROM file.
876  *
877  * @param[in] vpdFilePath - Path of EEPROM file
878  * @param[in] js- Reference to vpd inventory json object
879  * @return Parsed VPD map
880  */
881 std::variant<KeywordVpdMap, openpower::vpd::Store>
parseVpdFile(const std::string & vpdFilePath,const nlohmann::json & js)882     parseVpdFile(const std::string& vpdFilePath, const nlohmann::json& js)
883 {
884     uint32_t vpdStartOffset = 0;
885     for (const auto& item : js["frus"][vpdFilePath])
886     {
887         if (item.find("offset") != item.end())
888         {
889             vpdStartOffset = item["offset"];
890             break;
891         }
892     }
893 
894     Binary vpdVector = getVpdDataInVector(js, vpdFilePath);
895 
896     ParserInterface* parser = ParserFactory::getParser(
897         vpdVector,
898         (pimPath + js["frus"][vpdFilePath][0]["inventoryPath"]
899                        .get_ref<const nlohmann::json::string_t&>()),
900         vpdFilePath, vpdStartOffset);
901 
902     auto parseResult = parser->parse();
903 
904     // release the parser object
905     ParserFactory::freeParser(parser);
906 
907     return parseResult;
908 }
909 
910 /*
911  * @brief This API retrieves the hardware backup in map
912  *
913  * @param[in] systemVpdBackupPath - The path that backs up the system VPD.
914  * @param[in] backupVpdInvPath - FRU inventory path.
915  * @param[in] js - JSON object.
916  * @param[out] backupVpdMap - An IPZ VPD map containing the parsed backup VPD.
917  *
918  * */
getBackupVpdInMap(const string & systemVpdBackupPath,const string & backupVpdInvPath,const nlohmann::json & js,Parsed & backupVpdMap)919 void getBackupVpdInMap(const string& systemVpdBackupPath,
920                        const string& backupVpdInvPath, const nlohmann::json& js,
921                        Parsed& backupVpdMap)
922 {
923     PelAdditionalData additionalData{};
924 
925     if (!fs::exists(systemVpdBackupPath))
926     {
927         string errorMsg = "Device path ";
928         errorMsg += systemVpdBackupPath;
929         errorMsg += " does not exist";
930 
931         additionalData.emplace("DESCRIPTION", errorMsg);
932 
933         additionalData.emplace("CALLOUT_INVENTORY_PATH",
934                                INVENTORY_PATH + backupVpdInvPath);
935 
936         createPEL(additionalData, PelSeverity::ERROR, errIntfForStreamFail,
937                   nullptr);
938     }
939     else
940     {
941         auto backupVpdParsedResult = parseVpdFile(systemVpdBackupPath, js);
942 
943         if (auto pVal = get_if<Store>(&backupVpdParsedResult))
944         {
945             backupVpdMap = pVal->getVpdMap();
946         }
947         else
948         {
949             std::cerr << "Invalid format of VPD in back up. Restore aborted."
950                       << std::endl;
951         }
952     }
953 }
954 
updateVpdDataOnHw(const std::string & vpdFilePath,nlohmann::json & js,const std::string & recName,const std::string & kwName,const Binary & kwdData)955 void updateVpdDataOnHw(const std::string& vpdFilePath, nlohmann::json& js,
956                        const std::string& recName, const std::string& kwName,
957                        const Binary& kwdData)
958 {
959     const std::string& fruInvPath =
960         js["frus"][vpdFilePath][0]["inventoryPath"]
961             .get_ref<const nlohmann::json::string_t&>();
962 
963     EditorImpl edit(vpdFilePath, js, recName, kwName, fruInvPath);
964 
965     uint32_t offset = 0;
966     // Setup offset, if any
967     for (const auto& item : js["frus"][vpdFilePath])
968     {
969         if (item.find("offset") != item.end())
970         {
971             offset = item["offset"];
972             break;
973         }
974     }
975 
976     // update keyword data on to EEPROM file
977     // Note: Updating keyword data on cache is
978     // handled via PIM Notify call hence passing
979     // the updCache flag value as false here.
980     edit.updateKeyword(kwdData, offset, false);
981 }
982 
983 /**
984  * @brief API to check if we need to restore system VPD
985  * This functionality is only applicable for IPZ VPD data.
986 
987  * @param[in] vpdMap - IPZ vpd map
988  * @param[in] objectPath - Object path for the FRU
989  * @param[in] js - JSON Object
990  * @param[in] isBackupOnCache - Denotes whether the backup is on cache/hardware
991  */
restoreSystemVPD(Parsed & vpdMap,const string & objectPath,nlohmann::json & js,bool isBackupOnCache=true)992 void restoreSystemVPD(Parsed& vpdMap, const string& objectPath,
993                       nlohmann::json& js, bool isBackupOnCache = true)
994 {
995     std::string systemVpdBackupPath{};
996     std::string backupVpdInvPath{};
997     Parsed backupVpdMap{};
998 
999     if (!isBackupOnCache)
1000     {
1001         // Get the value of systemvpdBackupPath field from json
1002         systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value(
1003             "systemVpdBackupPath", "");
1004 
1005         backupVpdInvPath = js["frus"][systemVpdBackupPath][0]["inventoryPath"]
1006                                .get_ref<const nlohmann::json::string_t&>();
1007 
1008         getBackupVpdInMap(systemVpdBackupPath, backupVpdInvPath, js,
1009                           backupVpdMap);
1010 
1011         if (backupVpdMap.empty())
1012         {
1013             std::cerr << "Backup VPD map is empty" << std::endl;
1014             return;
1015         }
1016     }
1017 
1018     for (const auto& systemRecKwdPair : svpdKwdMap)
1019     {
1020         const string& recordName = systemRecKwdPair.first;
1021         auto it = vpdMap.find(recordName);
1022 
1023         // check if record is found in map we got by parser
1024         if (it != vpdMap.end())
1025         {
1026             const auto& kwdListForRecord = systemRecKwdPair.second;
1027             for (const auto& keywordInfo : kwdListForRecord)
1028             {
1029                 const auto keywordName = get<0>(keywordInfo);
1030 
1031                 DbusPropertyMap& kwdValMap = it->second;
1032                 auto iterator = kwdValMap.find(keywordName);
1033 
1034                 if (iterator != kwdValMap.end())
1035                 {
1036                     string& kwdValue = iterator->second;
1037 
1038                     std::string backupValue{};
1039                     const auto& defaultValue = get<1>(keywordInfo);
1040                     const auto& backupVpdRecName = get<4>(keywordInfo);
1041                     const auto& backupVpdKwName = get<5>(keywordInfo);
1042 
1043                     // If the 'isBackupOnCache' flag is false, we need
1044                     // to backup the systemVPD on the specified fru's eeprom
1045                     // path or restore it from the specified fru's eeprom path.
1046                     if (isBackupOnCache)
1047                     {
1048                         // check bus data
1049                         backupValue = readBusProperty(
1050                             objectPath, ipzVpdInf + recordName, keywordName);
1051                     }
1052                     else
1053                     {
1054                         backupValue = getKwVal(backupVpdMap, backupVpdRecName,
1055                                                backupVpdKwName);
1056 
1057                         if (backupValue.empty())
1058                         {
1059                             string errorMsg{};
1060                             if (backupVpdMap.find(backupVpdRecName) ==
1061                                 backupVpdMap.end())
1062                             {
1063                                 errorMsg = backupVpdRecName +
1064                                            " Record does not exist in "
1065                                            "the EEPROM file ";
1066                             }
1067                             else
1068                             {
1069                                 errorMsg = backupVpdKwName +
1070                                            " Keyword not found or empty.";
1071                             }
1072 
1073                             errorMsg += systemVpdBackupPath;
1074 
1075                             PelAdditionalData additionalData;
1076                             additionalData.emplace("DESCRIPTION", errorMsg);
1077 
1078                             createPEL(additionalData, PelSeverity::ERROR,
1079                                       errIntfForInvalidVPD, nullptr);
1080 
1081                             continue;
1082                         }
1083                     }
1084 
1085                     Binary backupDataInBinary(backupValue.begin(),
1086                                               backupValue.end());
1087 
1088                     Binary kwdDataInBinary(kwdValue.begin(), kwdValue.end());
1089 
1090                     if (backupDataInBinary != defaultValue)
1091                     {
1092                         if (kwdDataInBinary != defaultValue)
1093                         {
1094                             // both the data are present, check for mismatch
1095                             if (backupValue != kwdValue)
1096                             {
1097                                 string errMsg = "Mismatch found between backup "
1098                                                 "and primary VPD for record: ";
1099                                 errMsg += (*it).first;
1100                                 errMsg += " and keyword: ";
1101                                 errMsg += keywordName;
1102 
1103                                 std::ostringstream busStream;
1104                                 for (uint16_t byte : backupValue)
1105                                 {
1106                                     busStream << std::setfill('0')
1107                                               << std::setw(2) << std::hex
1108                                               << "0x" << byte << " ";
1109                                 }
1110 
1111                                 std::ostringstream vpdStream;
1112                                 for (uint16_t byte : kwdValue)
1113                                 {
1114                                     vpdStream << std::setfill('0')
1115                                               << std::setw(2) << std::hex
1116                                               << "0x" << byte << " ";
1117                                 }
1118 
1119                                 // data mismatch
1120                                 PelAdditionalData additionalData;
1121 
1122                                 additionalData.emplace("DESCRIPTION", errMsg);
1123                                 additionalData.emplace(
1124                                     "Value read from Backup: ",
1125                                     busStream.str());
1126                                 additionalData.emplace(
1127                                     "Value read from Primary: ",
1128                                     vpdStream.str());
1129 
1130                                 createPEL(additionalData, PelSeverity::WARNING,
1131                                           errIntfForVPDMismatch, nullptr);
1132 
1133                                 if (!isBackupOnCache)
1134                                 {
1135                                     // Backing up or restoring from a hardware
1136                                     // path does not requires copying the backup
1137                                     // data to the VPD map, as this will result
1138                                     // in a mismatch between the primary VPD and
1139                                     // its cache.
1140                                     continue;
1141                                 }
1142                             }
1143                             else
1144                             {
1145                                 // both the backup and primary data is
1146                                 // non-default and same. Nothing needs to be
1147                                 // done.
1148                                 continue;
1149                             }
1150                         }
1151 
1152                         // If the backup is on the cache we need to copy the
1153                         // backup data to the VPD map to ensure there is no
1154                         // mismatch b/n them. So if backup data is not default,
1155                         // then irrespective of primary data(default or other
1156                         // than backup), copy the backup data to vpd map as we
1157                         // don't need to change the backup data in either case
1158                         // in the process of restoring system vpd.
1159                         kwdValue = backupValue;
1160 
1161                         // If the backup data is on the base panel the restoring
1162                         // of Backup VPD on to the system backplane VPD
1163                         // file is done here not through the VPD manager code
1164                         // path. This is to have the logic of restoring data on
1165                         // to the cache & hardware in the same code path.
1166                         if (!isBackupOnCache)
1167                         {
1168                             // copy backup VPD on to system backplane
1169                             // EEPROM  file.
1170                             updateVpdDataOnHw(systemVpdFilePath, js, recordName,
1171                                               keywordName, backupDataInBinary);
1172                         }
1173                     }
1174                     else if (kwdDataInBinary == defaultValue &&
1175                              get<2>(keywordInfo)) // Check isPELRequired is true
1176                     {
1177                         string errMsg = "Found default value on both backup "
1178                                         "and primary VPD for record: ";
1179                         errMsg += (*it).first;
1180                         errMsg += " and keyword: ";
1181                         errMsg += keywordName;
1182                         errMsg += ". Update primary VPD.";
1183 
1184                         // mfg default on both backup and primary, log PEL
1185                         PelAdditionalData additionalData;
1186                         additionalData.emplace("DESCRIPTION", errMsg);
1187 
1188                         createPEL(additionalData, PelSeverity::ERROR,
1189                                   errIntfForVPDDefault, nullptr);
1190 
1191                         continue;
1192                     }
1193                     else if ((kwdDataInBinary != defaultValue) &&
1194                              (!isBackupOnCache))
1195                     {
1196                         // update primary VPD on to backup VPD file
1197                         updateVpdDataOnHw(systemVpdBackupPath, js,
1198                                           backupVpdRecName, backupVpdKwName,
1199                                           kwdDataInBinary);
1200 
1201                         // copy primary VPD to backup VPD to publish on
1202                         // DBus
1203                         backupVpdMap.find(backupVpdRecName)
1204                             ->second.find(backupVpdKwName)
1205                             ->second = kwdValue;
1206                     }
1207                 }
1208             }
1209         }
1210     }
1211 }
1212 
1213 /**
1214  * @brief This checks for is this FRU a processor
1215  *        And if yes, then checks for is this primary
1216  *
1217  * @param[in] js- vpd json to get the information about this FRU
1218  * @param[in] filePath- FRU vpd
1219  *
1220  * @return true/false
1221  */
isThisPrimaryProcessor(nlohmann::json & js,const string & filePath)1222 bool isThisPrimaryProcessor(nlohmann::json& js, const string& filePath)
1223 {
1224     bool isProcessor = false;
1225     bool isPrimary = false;
1226 
1227     for (const auto& item : js["frus"][filePath])
1228     {
1229         if (item.find("extraInterfaces") != item.end())
1230         {
1231             for (const auto& eI : item["extraInterfaces"].items())
1232             {
1233                 if (eI.key().find("Inventory.Item.Cpu") != string::npos)
1234                 {
1235                     isProcessor = true;
1236                 }
1237             }
1238         }
1239 
1240         if (isProcessor)
1241         {
1242             string cpuType = item.value("cpuType", "");
1243             if (cpuType == "primary")
1244             {
1245                 isPrimary = true;
1246             }
1247         }
1248     }
1249 
1250     return (isProcessor && isPrimary);
1251 }
1252 
1253 /**
1254  * @brief This finds DIMM vpd in vpd json and enables them by binding the device
1255  *        driver
1256  * @param[in] js- vpd json to iterate through and take action if it is DIMM
1257  */
doEnableAllDimms(nlohmann::json & js)1258 void doEnableAllDimms(nlohmann::json& js)
1259 {
1260     // iterate over each fru
1261     for (const auto& eachFru : js["frus"].items())
1262     {
1263         // skip the driver binding if eeprom already exists
1264         if (fs::exists(eachFru.key()))
1265         {
1266             continue;
1267         }
1268 
1269         for (const auto& eachInventory : eachFru.value())
1270         {
1271             if (eachInventory.find("extraInterfaces") != eachInventory.end())
1272             {
1273                 for (const auto& eI : eachInventory["extraInterfaces"].items())
1274                 {
1275                     if (eI.key().find("Inventory.Item.Dimm") != string::npos)
1276                     {
1277                         string dimmVpd = eachFru.key();
1278                         // fetch it from
1279                         // "/sys/bus/i2c/drivers/at24/414-0050/eeprom"
1280 
1281                         regex matchPatern("([0-9]+-[0-9]{4})");
1282                         smatch matchFound;
1283                         if (regex_search(dimmVpd, matchFound, matchPatern))
1284                         {
1285                             vector<string> i2cReg;
1286                             boost::split(i2cReg, matchFound.str(0),
1287                                          boost::is_any_of("-"));
1288 
1289                             // remove 0s from beginning
1290                             const regex pattern("^0+(?!$)");
1291                             for (auto& i : i2cReg)
1292                             {
1293                                 i = regex_replace(i, pattern, "");
1294                             }
1295 
1296                             // For ISDIMM which uses ee1004 driver
1297                             // the below is done
1298                             size_t stringFound = dimmVpd.find("ee1004");
1299                             if (stringFound != string::npos)
1300                             {
1301                                 // echo ee1004 0x50 >
1302                                 // /sys/bus/i2c/devices/i2c-110/new_device
1303                                 string cmnd = "echo ee1004 0x" + i2cReg[1] +
1304                                               " > /sys/bus/i2c/devices/i2c-" +
1305                                               i2cReg[0] + "/new_device";
1306                                 executeCmd(cmnd);
1307                             }
1308                             else if (i2cReg.size() == 2)
1309                             {
1310                                 // echo 24c32 0x50 >
1311                                 // /sys/bus/i2c/devices/i2c-16/new_device
1312                                 string cmnd = "echo 24c32 0x" + i2cReg[1] +
1313                                               " > /sys/bus/i2c/devices/i2c-" +
1314                                               i2cReg[0] + "/new_device";
1315                                 executeCmd(cmnd);
1316                             }
1317                         }
1318                     }
1319                 }
1320             }
1321         }
1322     }
1323 }
1324 
1325 /**
1326  * @brief Check if the given CPU is an IO only chip.
1327  * The CPU is termed as IO, whose all of the cores are bad and can never be
1328  * used. Those CPU chips can be used for IO purpose like connecting PCIe devices
1329  * etc., The CPU whose every cores are bad, can be identified from the CP00
1330  * record's PG keyword, only if all of the 8 EQs' value equals 0xE7F9FF. (1EQ
1331  * has 4 cores grouped together by sharing its cache memory.)
1332  * @param [in] pgKeyword - PG Keyword of CPU.
1333  * @return true if the given cpu is an IO, false otherwise.
1334  */
isCPUIOGoodOnly(const string & pgKeyword)1335 static bool isCPUIOGoodOnly(const string& pgKeyword)
1336 {
1337     const unsigned char io[] = {0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9,
1338                                 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7,
1339                                 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF};
1340     // EQ0 index (in PG keyword) starts at 97 (with offset starting from 0).
1341     // Each EQ carries 3 bytes of data. Totally there are 8 EQs. If all EQs'
1342     // value equals 0xE7F9FF, then the cpu has no good cores and its treated as
1343     // IO.
1344     if (memcmp(io, pgKeyword.data() + 97, 24) == 0)
1345     {
1346         return true;
1347     }
1348 
1349     // The CPU is not an IO
1350     return false;
1351 }
1352 
1353 /**
1354  * @brief Function to bring MUX out of idle state
1355  *
1356  *        This finds All the MUX defined in the system json and enables
1357  *        them by setting the holdidle parameter to 0.
1358  * @param[in] js- system json to iterate through and take action
1359  */
doEnableAllMuxChips(const nlohmann::json & js)1360 void doEnableAllMuxChips(const nlohmann::json& js)
1361 {
1362     // Do we have the mandatory "muxes" section?
1363     if (js.find("muxes") != js.end())
1364     {
1365         std::cout << "Enabling all the MUX on the system " << std::endl;
1366         // iterate over each MUX detail and enable them
1367         for (const auto& item : js["muxes"])
1368         {
1369             if (item.find("holdidlepath") != item.end())
1370             {
1371                 const std::string& holdidle = item["holdidlepath"];
1372                 std::cout << "Setting holdidle state for " << holdidle
1373                           << "to 0 " << std::endl;
1374                 string cmd = "echo 0 > " + holdidle;
1375                 executeCmd(cmd);
1376             }
1377         }
1378         std::cout << "Completed enabling all the MUX on the system "
1379                   << std::endl;
1380     }
1381     else
1382     {
1383         std::cout << "No MUX was defined for the system" << std::endl;
1384     }
1385 }
1386 
1387 /**
1388  * @brief Populate Dbus.
1389  * This method invokes all the populateInterface functions
1390  * and notifies PIM about dbus object.
1391  * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the
1392  * input.
1393  * @param[in] js - Inventory json object
1394  * @param[in] filePath - Path of the vpd file
1395  * @param[in] preIntrStr - Interface string
1396  */
1397 template <typename T>
populateDbus(T & vpdMap,nlohmann::json & js,const string & filePath)1398 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath)
1399 {
1400     inventory::InterfaceMap interfaces;
1401     inventory::ObjectMap objects;
1402     inventory::PropertyMap prop;
1403     string ccinFromVpd;
1404 
1405     bool isSystemVpd = (filePath == systemVpdFilePath);
1406     if constexpr (is_same<T, Parsed>::value)
1407     {
1408         ccinFromVpd = getKwVal(vpdMap, "VINI", "CC");
1409         transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(),
1410                   ::toupper);
1411 
1412         if (isSystemVpd)
1413         {
1414             string mboardPath =
1415                 js["frus"][filePath].at(0).value("inventoryPath", "");
1416 
1417             // Get the value of systemvpdBackupPath field from json
1418             const std::string& systemVpdBackupPath =
1419                 js["frus"][filePath].at(0).value("systemVpdBackupPath", "");
1420 
1421             if (systemVpdBackupPath.empty())
1422             {
1423                 std::vector<std::string> interfaces = {motherBoardInterface};
1424                 // call mapper to check for object path creation
1425                 MapperResponse subTree =
1426                     getObjectSubtreeForInterfaces(pimPath, 0, interfaces);
1427 
1428                 // Attempt system VPD restore if we have a motherboard
1429                 // object in the inventory.
1430                 if ((subTree.size() != 0) &&
1431                     (subTree.find(pimPath + mboardPath) != subTree.end()))
1432                 {
1433                     restoreSystemVPD(vpdMap, mboardPath, js);
1434                 }
1435                 else
1436                 {
1437                     log<level::ERR>("No object path found");
1438                 }
1439             }
1440             else
1441             {
1442                 restoreSystemVPD(vpdMap, mboardPath, js, false);
1443             }
1444         }
1445         else
1446         {
1447             // check if it is processor vpd.
1448             auto isPrimaryCpu = isThisPrimaryProcessor(js, filePath);
1449 
1450             if (isPrimaryCpu)
1451             {
1452                 auto ddVersion = getKwVal(vpdMap, "CRP0", "DD");
1453 
1454                 auto chipVersion = atoi(ddVersion.substr(1, 2).c_str());
1455 
1456                 if (chipVersion >= 2)
1457                 {
1458                     doEnableAllDimms(js);
1459                     // Sleep for a few seconds to let the DIMM parses start
1460                     using namespace std::chrono_literals;
1461                     std::this_thread::sleep_for(5s);
1462                 }
1463             }
1464         }
1465     }
1466 
1467     auto processFactoryReset = false;
1468 
1469     if (isSystemVpd)
1470     {
1471         string systemJsonName{};
1472         if constexpr (is_same<T, Parsed>::value)
1473         {
1474             // pick the right system json
1475             systemJsonName = getSystemsJson(vpdMap);
1476         }
1477 
1478         fs::path target = systemJsonName;
1479         fs::path link = INVENTORY_JSON_SYM_LINK;
1480 
1481         // If the symlink does not exist, we treat that as a factory reset
1482         processFactoryReset = !fs::exists(INVENTORY_JSON_SYM_LINK);
1483 
1484         // Create the directory for hosting the symlink
1485         fs::create_directories(VPD_FILES_PATH);
1486         // unlink the symlink previously created (if any)
1487         remove(INVENTORY_JSON_SYM_LINK);
1488         // create a new symlink based on the system
1489         fs::create_symlink(target, link);
1490 
1491         // Reloading the json
1492         ifstream inventoryJson(link);
1493         js = json::parse(inventoryJson);
1494         inventoryJson.close();
1495 
1496         // enable the muxes again here to cover the case where during first boot
1497         // after reset, system would have come up with default JSON
1498         // configuration and have skipped enabling mux at the beginning.
1499         // Default config JSON does not have mux entries.
1500         doEnableAllMuxChips(js);
1501     }
1502 
1503     for (const auto& item : js["frus"][filePath])
1504     {
1505         const auto& objectPath = item["inventoryPath"];
1506         sdbusplus::message::object_path object(objectPath);
1507 
1508         vector<string> ccinList;
1509         if (item.find("ccin") != item.end())
1510         {
1511             for (const auto& cc : item["ccin"])
1512             {
1513                 string ccin = cc;
1514                 transform(ccin.begin(), ccin.end(), ccin.begin(), ::toupper);
1515                 ccinList.push_back(ccin);
1516             }
1517         }
1518 
1519         if (!ccinFromVpd.empty() && !ccinList.empty() &&
1520             (find(ccinList.begin(), ccinList.end(), ccinFromVpd) ==
1521              ccinList.end()))
1522         {
1523             continue;
1524         }
1525 
1526         if ((isSystemVpd) || (item.value("noprime", false)))
1527         {
1528             // Populate one time properties for the system VPD and its sub-frus
1529             // and for other non-primeable frus.
1530             // For the remaining FRUs, this will get handled as a part of
1531             // priming the inventory.
1532             setOneTimeProperties(objectPath, interfaces);
1533         }
1534 
1535         // Populate the VPD keywords and the common interfaces only if we
1536         // are asked to inherit that data from the VPD, else only add the
1537         // extraInterfaces.
1538         if (item.value("inherit", true))
1539         {
1540             if constexpr (is_same<T, Parsed>::value)
1541             {
1542                 // Each record in the VPD becomes an interface and all
1543                 // keyword within the record are properties under that
1544                 // interface.
1545                 for (const auto& record : vpdMap)
1546                 {
1547                     populateFruSpecificInterfaces(
1548                         record.second, ipzVpdInf + record.first, interfaces);
1549                 }
1550             }
1551             else if constexpr (is_same<T, KeywordVpdMap>::value)
1552             {
1553                 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces);
1554             }
1555             if (js.find("commonInterfaces") != js.end())
1556             {
1557                 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap,
1558                                    isSystemVpd);
1559             }
1560         }
1561         else
1562         {
1563             // Check if we have been asked to inherit specific record(s)
1564             if constexpr (is_same<T, Parsed>::value)
1565             {
1566                 if (item.find("copyRecords") != item.end())
1567                 {
1568                     for (const auto& record : item["copyRecords"])
1569                     {
1570                         const string& recordName = record;
1571                         if (vpdMap.find(recordName) != vpdMap.end())
1572                         {
1573                             populateFruSpecificInterfaces(
1574                                 vpdMap.at(recordName), ipzVpdInf + recordName,
1575                                 interfaces);
1576                         }
1577                     }
1578                 }
1579             }
1580         }
1581         // Populate interfaces and properties that are common to every FRU
1582         // and additional interface that might be defined on a per-FRU
1583         // basis.
1584         if (item.find("extraInterfaces") != item.end())
1585         {
1586             populateInterfaces(item["extraInterfaces"], interfaces, vpdMap,
1587                                isSystemVpd);
1588             if constexpr (is_same<T, Parsed>::value)
1589             {
1590                 if (item["extraInterfaces"].find(
1591                         "xyz.openbmc_project.Inventory.Item.Cpu") !=
1592                     item["extraInterfaces"].end())
1593                 {
1594                     if (isCPUIOGoodOnly(getKwVal(vpdMap, "CP00", "PG")))
1595                     {
1596                         interfaces[invItemIntf]["PrettyName"] = "IO Module";
1597                     }
1598                 }
1599             }
1600         }
1601 
1602         // embedded property(true or false) says whether the subfru is embedded
1603         // into the parent fru (or) not. VPD sets Present property only for
1604         // embedded frus. If the subfru is not an embedded FRU, the subfru may
1605         // or may not be physically present. Those non embedded frus will always
1606         // have Present=false irrespective of its physical presence or absence.
1607         // Eg: nvme drive in nvme slot is not an embedded FRU. So don't set
1608         // Present to true for such sub frus.
1609         // Eg: ethernet port is embedded into bmc card. So set Present to true
1610         // for such sub frus. Also do not populate present property for embedded
1611         // subfru which is synthesized. Currently there is no subfru which are
1612         // both embedded and synthesized. But still the case is handled here.
1613         if ((item.value("embedded", true)) &&
1614             (!item.value("synthesized", false)))
1615         {
1616             // Check if its required to handle presence for this FRU.
1617             if (item.value("handlePresence", true))
1618             {
1619                 inventory::PropertyMap presProp;
1620                 presProp.emplace("Present", true);
1621                 insertOrMerge(interfaces, invItemIntf, move(presProp));
1622             }
1623         }
1624 
1625         if constexpr (is_same<T, Parsed>::value)
1626         {
1627             // Restore asset tag, if needed
1628             if (processFactoryReset && objectPath == "/system")
1629             {
1630                 fillAssetTag(interfaces, vpdMap);
1631             }
1632         }
1633 
1634         objects.emplace(move(object), move(interfaces));
1635     }
1636 
1637     if (isSystemVpd)
1638     {
1639         inventory::ObjectMap primeObject = primeInventory(js, vpdMap);
1640         objects.insert(primeObject.begin(), primeObject.end());
1641 
1642         // set the U-boot environment variable for device-tree
1643         if constexpr (is_same<T, Parsed>::value)
1644         {
1645             setDevTreeEnv(fs::path(getSystemsJson(vpdMap)).filename());
1646         }
1647     }
1648 
1649     // Notify PIM
1650     common::utility::callPIM(move(objects));
1651 }
1652 
main(int argc,char ** argv)1653 int main(int argc, char** argv)
1654 {
1655     int rc = 0;
1656     json js{};
1657     Binary vpdVector{};
1658     string file{};
1659     string driver{};
1660     // map to hold additional data in case of logging pel
1661     PelAdditionalData additionalData{};
1662 
1663     // this is needed to hold base fru inventory path in case there is ECC or
1664     // vpd exception while parsing the file
1665     std::string baseFruInventoryPath = {};
1666 
1667     // It holds the backup EEPROM file path for the system backplane's critical
1668     // data
1669     std::string systemVpdBackupPath{};
1670 
1671     // It holds the inventory path of backup EEPROM file
1672     std::string backupVpdInvPath{};
1673 
1674     bool isSystemVpd = false;
1675 
1676     // severity for PEL
1677     PelSeverity pelSeverity = PelSeverity::WARNING;
1678 
1679     try
1680     {
1681         App app{"ibm-read-vpd - App to read IPZ/Jedec format VPD, parse it and "
1682                 "store it in DBUS"};
1683 
1684         app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)")
1685             ->required();
1686 
1687         app.add_option("--driver", driver,
1688                        "Driver used by kernel (at24,at25,ee1004)")
1689             ->required();
1690 
1691         CLI11_PARSE(app, argc, argv);
1692 
1693         // PEL severity should be ERROR in case of any system VPD failure
1694         if (file == systemVpdFilePath)
1695         {
1696             pelSeverity = PelSeverity::ERROR;
1697             isSystemVpd = true;
1698         }
1699 
1700         // Check if input file is not empty.
1701         if ((file.empty()) || (driver.empty()))
1702         {
1703             std::cerr << "Encountered empty input parameter file [" << file
1704                       << "] driver [" << driver << "]" << std::endl;
1705             return 0;
1706         }
1707 
1708         // Check if currently supported driver or not
1709         if ((driver != at24driver) && (driver != at25driver) &&
1710             (driver != ee1004driver))
1711         {
1712             std::cerr << "The driver [" << driver << "] is not supported."
1713                       << std::endl;
1714             return 0;
1715         }
1716 
1717         auto jsonToParse = INVENTORY_JSON_DEFAULT;
1718 
1719         // If the symlink exists, it means it has been setup for us, switch the
1720         // path
1721         if (fs::exists(INVENTORY_JSON_SYM_LINK))
1722         {
1723             jsonToParse = INVENTORY_JSON_SYM_LINK;
1724         }
1725 
1726         // Make sure that the file path we get is for a supported EEPROM
1727         ifstream inventoryJson(jsonToParse);
1728         if (!inventoryJson)
1729         {
1730             throw(VpdJsonException("Failed to access Json path", jsonToParse));
1731         }
1732 
1733         try
1734         {
1735             js = json::parse(inventoryJson);
1736         }
1737         catch (const json::parse_error& ex)
1738         {
1739             throw(VpdJsonException("Json parsing failed", jsonToParse));
1740         }
1741 
1742         // Do we have the mandatory "frus" section?
1743         if (js.find("frus") == js.end())
1744         {
1745             throw(VpdJsonException("FRUs section not found in JSON",
1746                                    jsonToParse));
1747         }
1748 
1749         // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@)
1750         if (file.find("/ahb:apb") != string::npos)
1751         {
1752             // Translate udev path to a generic /sys/bus/.. file path.
1753             udevToGenericPath(file, driver);
1754 
1755             if ((js["frus"].find(file) != js["frus"].end()) &&
1756                 (file == systemVpdFilePath))
1757             {
1758                 std::cout << "We have already collected system VPD, skipping."
1759                           << std::endl;
1760                 return 0;
1761             }
1762         }
1763 
1764         // Enable all mux which are used for connecting to the i2c on the pcie
1765         // slots for pcie cards. These are not enabled by kernel due to an issue
1766         // seen with Castello cards, where the i2c line hangs on a probe.
1767         // To run it only once have kept it under System vpd check.
1768         // we need to run this on all BMC reboots so kept here
1769         if (file == systemVpdFilePath)
1770         {
1771             doEnableAllMuxChips(js);
1772         }
1773 
1774         if (file.empty())
1775         {
1776             std::cerr << "The EEPROM path <" << file << "> is not valid.";
1777             return 0;
1778         }
1779         if (js["frus"].find(file) == js["frus"].end())
1780         {
1781             std::cerr << "The EEPROM path [" << file
1782                       << "] is not found in the json." << std::endl;
1783             return 0;
1784         }
1785 
1786         if (!fs::exists(file))
1787         {
1788             std::cout << "Device path: " << file
1789                       << " does not exist. Spurious udev event? Exiting."
1790                       << std::endl;
1791             return 0;
1792         }
1793 
1794         // In case of system VPD it will already be filled, Don't have to
1795         // overwrite that.
1796         if (baseFruInventoryPath.empty())
1797         {
1798             baseFruInventoryPath = js["frus"][file][0]["inventoryPath"];
1799         }
1800 
1801         // Check if we can read the VPD file based on the power state
1802         // We skip reading VPD when the power is ON in two scenarios:
1803         // 1) The eeprom we are trying to read is that of the system VPD and the
1804         // JSON symlink is already setup (the symlink's existence tells us we
1805         // are not coming out of a factory reset)
1806         // 2) The JSON tells us that the FRU EEPROM cannot be
1807         // read when we are powered ON.
1808         if (js["frus"][file].at(0).value("powerOffOnly", false) ||
1809             (file == systemVpdFilePath && fs::exists(INVENTORY_JSON_SYM_LINK)))
1810         {
1811             if ("xyz.openbmc_project.State.Chassis.PowerState.On" ==
1812                 getPowerState())
1813             {
1814                 std::cout << "This VPD cannot be read when power is ON"
1815                           << std::endl;
1816                 return 0;
1817             }
1818         }
1819 
1820         // Check if this VPD should be recollected at all
1821         if (!needsRecollection(js, file))
1822         {
1823             std::cout << "Skip VPD recollection for: " << file << std::endl;
1824             return 0;
1825         }
1826 
1827         try
1828         {
1829             variant<KeywordVpdMap, Store> parseResult;
1830             parseResult = parseVpdFile(file, js);
1831 
1832             if (isSystemVpd)
1833             {
1834                 // Get the value of systemVpdBackupPath field from json
1835                 systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value(
1836                     "systemVpdBackupPath", "");
1837 
1838                 if (!systemVpdBackupPath.empty())
1839                 {
1840                     backupVpdInvPath =
1841                         js["frus"][systemVpdBackupPath][0]["inventoryPath"]
1842                             .get_ref<const nlohmann::json::string_t&>();
1843                 }
1844             }
1845 
1846             if (auto pVal = get_if<Store>(&parseResult))
1847             {
1848                 populateDbus(pVal->getVpdMap(), js, file);
1849             }
1850             else if (auto pVal = get_if<KeywordVpdMap>(&parseResult))
1851             {
1852                 populateDbus(*pVal, js, file);
1853             }
1854         }
1855         catch (const exception& e)
1856         {
1857             if (!systemVpdBackupPath.empty())
1858             {
1859                 file = systemVpdBackupPath;
1860                 baseFruInventoryPath = backupVpdInvPath;
1861             }
1862 
1863             executePostFailAction(js, file);
1864             throw;
1865         }
1866     }
1867     catch (const VpdJsonException& ex)
1868     {
1869         additionalData.emplace("JSON_PATH", ex.getJsonPath());
1870         additionalData.emplace("DESCRIPTION", ex.what());
1871         createPEL(additionalData, pelSeverity, errIntfForJsonFailure, nullptr);
1872 
1873         std::cerr << ex.what() << "\n";
1874         rc = -1;
1875     }
1876     catch (const VpdEccException& ex)
1877     {
1878         additionalData.emplace("DESCRIPTION", "ECC check failed");
1879         additionalData.emplace("CALLOUT_INVENTORY_PATH",
1880                                INVENTORY_PATH + baseFruInventoryPath);
1881         createPEL(additionalData, pelSeverity, errIntfForEccCheckFail, nullptr);
1882 
1883         if (systemVpdBackupPath.empty())
1884         {
1885             dumpBadVpd(file, vpdVector);
1886         }
1887 
1888         std::cerr << ex.what() << "\n";
1889         rc = -1;
1890     }
1891     catch (const VpdDataException& ex)
1892     {
1893         if (isThisPcieOnPass1planar(js, file))
1894         {
1895             std::cout << "Pcie_device  [" << file
1896                       << "]'s VPD is not valid on PASS1 planar.Ignoring.\n";
1897             rc = 0;
1898         }
1899         else if (!(isPresent(js, file).value_or(true)))
1900         {
1901             std::cout << "FRU at: " << file
1902                       << " is not detected present. Ignore parser error.\n";
1903             rc = 0;
1904         }
1905         else
1906         {
1907             string errorMsg =
1908                 "VPD file is either empty or invalid. Parser failed for [";
1909             errorMsg += file;
1910             errorMsg += "], with error = " + std::string(ex.what());
1911 
1912             additionalData.emplace("DESCRIPTION", errorMsg);
1913             additionalData.emplace("CALLOUT_INVENTORY_PATH",
1914                                    INVENTORY_PATH + baseFruInventoryPath);
1915             createPEL(additionalData, pelSeverity, errIntfForInvalidVPD,
1916                       nullptr);
1917 
1918             rc = -1;
1919         }
1920     }
1921     catch (const exception& e)
1922     {
1923         dumpBadVpd(file, vpdVector);
1924         std::cerr << e.what() << "\n";
1925         rc = -1;
1926     }
1927 
1928     return rc;
1929 }
1930