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