xref: /openbmc/openpower-vpd-parser/ibm_vpd_app.cpp (revision 3ab26a74c68da014af3860c7c070b617d6195bd7)
1 #include "config.h"
2 
3 #include "common_utility.hpp"
4 #include "defines.hpp"
5 #include "ibm_vpd_utils.hpp"
6 #include "ipz_parser.hpp"
7 #include "keyword_vpd_parser.hpp"
8 #include "memory_vpd_parser.hpp"
9 #include "parser_factory.hpp"
10 #include "vpd_exceptions.hpp"
11 
12 #include <assert.h>
13 #include <ctype.h>
14 
15 #include <CLI/CLI.hpp>
16 #include <algorithm>
17 #include <cstdarg>
18 #include <exception>
19 #include <filesystem>
20 #include <fstream>
21 #include <gpiod.hpp>
22 #include <iostream>
23 #include <iterator>
24 #include <nlohmann/json.hpp>
25 #include <phosphor-logging/log.hpp>
26 
27 using namespace std;
28 using namespace openpower::vpd;
29 using namespace CLI;
30 using namespace vpd::keyword::parser;
31 using namespace openpower::vpd::constants;
32 namespace fs = filesystem;
33 using json = nlohmann::json;
34 using namespace openpower::vpd::parser::factory;
35 using namespace openpower::vpd::inventory;
36 using namespace openpower::vpd::memory::parser;
37 using namespace openpower::vpd::parser::interface;
38 using namespace openpower::vpd::exceptions;
39 using namespace phosphor::logging;
40 
41 static const deviceTreeMap deviceTreeSystemTypeMap = {
42     {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"},
43     {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"},
44     {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"},
45     {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"},
46     {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"},
47     {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"}};
48 
49 /**
50  * @brief Returns the power state for chassis0
51  */
52 static auto getPowerState()
53 {
54     // TODO: How do we handle multiple chassis?
55     string powerState{};
56     auto bus = sdbusplus::bus::new_default();
57     auto properties =
58         bus.new_method_call("xyz.openbmc_project.State.Chassis",
59                             "/xyz/openbmc_project/state/chassis0",
60                             "org.freedesktop.DBus.Properties", "Get");
61     properties.append("xyz.openbmc_project.State.Chassis");
62     properties.append("CurrentPowerState");
63     auto result = bus.call(properties);
64     if (!result.is_method_error())
65     {
66         variant<string> val;
67         result.read(val);
68         if (auto pVal = get_if<string>(&val))
69         {
70             powerState = *pVal;
71         }
72     }
73     cout << "Power state is: " << powerState << endl;
74     return powerState;
75 }
76 
77 /**
78  * @brief Expands location codes
79  */
80 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap,
81                                bool isSystemVpd)
82 {
83     auto expanded{unexpanded};
84     static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard";
85     static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN";
86     static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS";
87     size_t idx = expanded.find("fcs");
88     try
89     {
90         if (idx != string::npos)
91         {
92             string fc{};
93             string se{};
94             if (isSystemVpd)
95             {
96                 const auto& fcData = vpdMap.at("VCEN").at("FC");
97                 const auto& seData = vpdMap.at("VCEN").at("SE");
98                 fc = string(fcData.data(), fcData.size());
99                 se = string(seData.data(), seData.size());
100             }
101             else
102             {
103                 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC");
104                 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE");
105             }
106 
107             // TODO: See if ND0 can be placed in the JSON
108             expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se);
109         }
110         else
111         {
112             idx = expanded.find("mts");
113             if (idx != string::npos)
114             {
115                 string mt{};
116                 string se{};
117                 if (isSystemVpd)
118                 {
119                     const auto& mtData = vpdMap.at("VSYS").at("TM");
120                     const auto& seData = vpdMap.at("VSYS").at("SE");
121                     mt = string(mtData.data(), mtData.size());
122                     se = string(seData.data(), seData.size());
123                 }
124                 else
125                 {
126                     mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM");
127                     se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE");
128                 }
129 
130                 replace(mt.begin(), mt.end(), '-', '.');
131                 expanded.replace(idx, 3, mt + "." + se);
132             }
133         }
134     }
135     catch (exception& e)
136     {
137         cerr << "Failed to expand location code with exception: " << e.what()
138              << "\n";
139     }
140     return expanded;
141 }
142 
143 /**
144  * @brief Populate FRU specific interfaces.
145  *
146  * This is a common method which handles both
147  * ipz and keyword specific interfaces thus,
148  * reducing the code redundancy.
149  * @param[in] map - Reference to the innermost keyword-value map.
150  * @param[in] preIntrStr - Reference to the interface string.
151  * @param[out] interfaces - Reference to interface map.
152  */
153 template <typename T>
154 static void populateFruSpecificInterfaces(const T& map,
155                                           const string& preIntrStr,
156                                           inventory::InterfaceMap& interfaces)
157 {
158     inventory::PropertyMap prop;
159 
160     for (const auto& kwVal : map)
161     {
162         auto kw = kwVal.first;
163 
164         if (kw[0] == '#')
165         {
166             kw = string("PD_") + kw[1];
167         }
168         else if (isdigit(kw[0]))
169         {
170             kw = string("N_") + kw;
171         }
172         if constexpr (is_same<T, KeywordVpdMap>::value)
173         {
174             if (get_if<Binary>(&kwVal.second))
175             {
176                 Binary vec(get_if<Binary>(&kwVal.second)->begin(),
177                            get_if<Binary>(&kwVal.second)->end());
178 
179                 prop.emplace(move(kw), move(vec));
180             }
181             else
182             {
183                 if (kw == "MemorySizeInKB")
184                 {
185                     inventory::PropertyMap memProp;
186                     auto memVal = get_if<size_t>(&kwVal.second);
187                     if (memVal)
188                     {
189                         memProp.emplace(move(kw),
190                                         ((*memVal) * CONVERT_MB_TO_KB));
191                         interfaces.emplace(
192                             "xyz.openbmc_project.Inventory.Item.Dimm",
193                             move(memProp));
194                     }
195                     else
196                     {
197                         cerr << "MemorySizeInKB value not found in vpd map\n";
198                     }
199                 }
200             }
201         }
202         else
203         {
204             Binary vec(kwVal.second.begin(), kwVal.second.end());
205             prop.emplace(move(kw), move(vec));
206         }
207     }
208 
209     interfaces.emplace(preIntrStr, move(prop));
210 }
211 
212 /**
213  * @brief Populate Interfaces.
214  *
215  * This method populates common and extra interfaces to dbus.
216  * @param[in] js - json object
217  * @param[out] interfaces - Reference to interface map
218  * @param[in] vpdMap - Reference to the parsed vpd map.
219  * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD.
220  */
221 template <typename T>
222 static void populateInterfaces(const nlohmann::json& js,
223                                inventory::InterfaceMap& interfaces,
224                                const T& vpdMap, bool isSystemVpd)
225 {
226     for (const auto& ifs : js.items())
227     {
228         string inf = ifs.key();
229         inventory::PropertyMap props;
230 
231         for (const auto& itr : ifs.value().items())
232         {
233             const string& busProp = itr.key();
234 
235             if (itr.value().is_boolean())
236             {
237                 props.emplace(busProp, itr.value().get<bool>());
238             }
239             else if (itr.value().is_string())
240             {
241                 if constexpr (is_same<T, Parsed>::value)
242                 {
243                     if (busProp == "LocationCode" &&
244                         inf == IBM_LOCATION_CODE_INF)
245                     {
246                         // TODO deprecate the com.ibm interface later
247                         auto prop = expandLocationCode(
248                             itr.value().get<string>(), vpdMap, isSystemVpd);
249                         props.emplace(busProp, prop);
250                         interfaces.emplace(XYZ_LOCATION_CODE_INF, props);
251                     }
252                     else
253                     {
254                         props.emplace(busProp, itr.value().get<string>());
255                     }
256                 }
257                 else
258                 {
259                     props.emplace(busProp, itr.value().get<string>());
260                 }
261             }
262             else if (itr.value().is_array())
263             {
264                 try
265                 {
266                     props.emplace(busProp, itr.value().get<Binary>());
267                 }
268                 catch (nlohmann::detail::type_error& e)
269                 {
270                     std::cerr << "Type exception: " << e.what() << "\n";
271                     // Ignore any type errors
272                 }
273             }
274             else if (itr.value().is_object())
275             {
276                 const string& rec = itr.value().value("recordName", "");
277                 const string& kw = itr.value().value("keywordName", "");
278                 const string& encoding = itr.value().value("encoding", "");
279 
280                 if constexpr (is_same<T, Parsed>::value)
281                 {
282                     if (!rec.empty() && !kw.empty() && vpdMap.count(rec) &&
283                         vpdMap.at(rec).count(kw))
284                     {
285                         auto encoded =
286                             encodeKeyword(vpdMap.at(rec).at(kw), encoding);
287                         props.emplace(busProp, encoded);
288                     }
289                 }
290                 else if constexpr (is_same<T, KeywordVpdMap>::value)
291                 {
292                     if (!kw.empty() && vpdMap.count(kw))
293                     {
294                         auto kwValue = get_if<Binary>(&vpdMap.at(kw));
295                         auto uintValue = get_if<size_t>(&vpdMap.at(kw));
296 
297                         if (kwValue)
298                         {
299                             auto prop =
300                                 string((*kwValue).begin(), (*kwValue).end());
301 
302                             auto encoded = encodeKeyword(prop, encoding);
303 
304                             props.emplace(busProp, encoded);
305                         }
306                         else if (uintValue)
307                         {
308                             props.emplace(busProp, *uintValue);
309                         }
310                     }
311                 }
312             }
313         }
314         interfaces.emplace(inf, move(props));
315     }
316 }
317 
318 static Binary getVpdDataInVector(const nlohmann::json& js, const string& file)
319 {
320     uint32_t offset = 0;
321     // check if offset present?
322     for (const auto& item : js["frus"][file])
323     {
324         if (item.find("offset") != item.end())
325         {
326             offset = item["offset"];
327         }
328     }
329 
330     // TODO: Figure out a better way to get max possible VPD size.
331     Binary vpdVector;
332     vpdVector.resize(65504);
333     ifstream vpdFile;
334     vpdFile.open(file, ios::binary);
335 
336     vpdFile.seekg(offset, ios_base::cur);
337     vpdFile.read(reinterpret_cast<char*>(&vpdVector[0]), 65504);
338     vpdVector.resize(vpdFile.gcount());
339 
340     return vpdVector;
341 }
342 
343 /** This API will be called at the end of VPD collection to perform any post
344  * actions.
345  *
346  * @param[in] json - json object
347  * @param[in] file - eeprom file path
348  */
349 static void postFailAction(const nlohmann::json& json, const string& file)
350 {
351     if ((json["frus"][file].at(0)).find("postActionFail") ==
352         json["frus"][file].at(0).end())
353     {
354         return;
355     }
356 
357     uint8_t pinValue = 0;
358     string pinName;
359 
360     for (const auto& postAction :
361          (json["frus"][file].at(0))["postActionFail"].items())
362     {
363         if (postAction.key() == "pin")
364         {
365             pinName = postAction.value();
366         }
367         else if (postAction.key() == "value")
368         {
369             // Get the value to set
370             pinValue = postAction.value();
371         }
372     }
373 
374     cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
375 
376     try
377     {
378         gpiod::line outputLine = gpiod::find_line(pinName);
379 
380         if (!outputLine)
381         {
382             cout << "Couldn't find output line:" << pinName
383                  << " on GPIO. Skipping...\n";
384 
385             return;
386         }
387         outputLine.request(
388             {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
389             pinValue);
390     }
391     catch (system_error&)
392     {
393         cerr << "Failed to set post-action GPIO" << endl;
394     }
395 }
396 
397 /** Performs any pre-action needed to get the FRU setup for collection.
398  *
399  * @param[in] json - json object
400  * @param[in] file - eeprom file path
401  */
402 static void preAction(const nlohmann::json& json, const string& file)
403 {
404     if ((json["frus"][file].at(0)).find("preAction") ==
405         json["frus"][file].at(0).end())
406     {
407         return;
408     }
409 
410     uint8_t pinValue = 0;
411     string pinName;
412 
413     for (const auto& postAction :
414          (json["frus"][file].at(0))["preAction"].items())
415     {
416         if (postAction.key() == "pin")
417         {
418             pinName = postAction.value();
419         }
420         else if (postAction.key() == "value")
421         {
422             // Get the value to set
423             pinValue = postAction.value();
424         }
425     }
426 
427     cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
428     try
429     {
430         gpiod::line outputLine = gpiod::find_line(pinName);
431 
432         if (!outputLine)
433         {
434             cout << "Couldn't find output line:" << pinName
435                  << " on GPIO. Skipping...\n";
436 
437             return;
438         }
439         outputLine.request(
440             {"FRU pre-action", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
441             pinValue);
442     }
443     catch (system_error&)
444     {
445         cerr << "Failed to set pre-action GPIO" << endl;
446         return;
447     }
448 
449     // Now bind the device
450     string bind = json["frus"][file].at(0).value("bind", "");
451     cout << "Binding device " << bind << endl;
452     string bindCmd = string("echo \"") + bind +
453                      string("\" > /sys/bus/i2c/drivers/at24/bind");
454     cout << bindCmd << endl;
455     executeCmd(bindCmd);
456 
457     // Check if device showed up (test for file)
458     if (!fs::exists(file))
459     {
460         cout << "EEPROM " << file << " does not exist. Take failure action"
461              << endl;
462         // If not, then take failure postAction
463         postFailAction(json, file);
464     }
465 }
466 
467 /**
468  * @brief Prime the Inventory
469  * Prime the inventory by populating only the location code,
470  * type interface and the inventory object for the frus
471  * which are not system vpd fru.
472  *
473  * @param[in] jsObject - Reference to vpd inventory json object
474  * @param[in] vpdMap -  Reference to the parsed vpd map
475  *
476  * @returns Map of items in extraInterface.
477  */
478 template <typename T>
479 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject,
480                                     const T& vpdMap)
481 {
482     inventory::ObjectMap objects;
483 
484     for (auto& itemFRUS : jsObject["frus"].items())
485     {
486         // Take pre actions
487         preAction(jsObject, itemFRUS.key());
488         for (auto& itemEEPROM : itemFRUS.value())
489         {
490             inventory::InterfaceMap interfaces;
491             inventory::Object object(itemEEPROM.at("inventoryPath"));
492 
493             if ((itemFRUS.key() != systemVpdFilePath) &&
494                 !itemEEPROM.value("noprime", false))
495             {
496                 inventory::PropertyMap presProp;
497                 presProp.emplace("Present", false);
498                 interfaces.emplace("xyz.openbmc_project.Inventory.Item",
499                                    move(presProp));
500                 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end())
501                 {
502                     for (const auto& eI : itemEEPROM["extraInterfaces"].items())
503                     {
504                         inventory::PropertyMap props;
505                         if (eI.key() == IBM_LOCATION_CODE_INF)
506                         {
507                             if constexpr (std::is_same<T, Parsed>::value)
508                             {
509                                 for (auto& lC : eI.value().items())
510                                 {
511                                     auto propVal = expandLocationCode(
512                                         lC.value().get<string>(), vpdMap, true);
513 
514                                     props.emplace(move(lC.key()),
515                                                   move(propVal));
516                                     interfaces.emplace(XYZ_LOCATION_CODE_INF,
517                                                        props);
518                                     interfaces.emplace(move(eI.key()),
519                                                        move(props));
520                                 }
521                             }
522                         }
523                         else if (eI.key().find("Inventory.Item.") !=
524                                  string::npos)
525                         {
526                             interfaces.emplace(move(eI.key()), move(props));
527                         }
528                     }
529                 }
530                 objects.emplace(move(object), move(interfaces));
531             }
532         }
533     }
534     return objects;
535 }
536 
537 /**
538  * @brief This API executes command to set environment variable
539  *        And then reboot the system
540  * @param[in] key   -env key to set new value
541  * @param[in] value -value to set.
542  */
543 void setEnvAndReboot(const string& key, const string& value)
544 {
545     // set env and reboot and break.
546     executeCmd("/sbin/fw_setenv", key, value);
547     log<level::INFO>("Rebooting BMC to pick up new device tree");
548     // make dbus call to reboot
549     auto bus = sdbusplus::bus::new_default_system();
550     auto method = bus.new_method_call(
551         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
552         "org.freedesktop.systemd1.Manager", "Reboot");
553     bus.call_noreply(method);
554 }
555 
556 /*
557  * @brief This API checks for env var fitconfig.
558  *        If not initialised OR updated as per the current system type,
559  *        update this env var and reboot the system.
560  *
561  * @param[in] systemType IM kwd in vpd tells about which system type it is.
562  * */
563 void setDevTreeEnv(const string& systemType)
564 {
565     string newDeviceTree;
566 
567     if (deviceTreeSystemTypeMap.find(systemType) !=
568         deviceTreeSystemTypeMap.end())
569     {
570         newDeviceTree = deviceTreeSystemTypeMap.at(systemType);
571     }
572 
573     string readVarValue;
574     bool envVarFound = false;
575 
576     vector<string> output = executeCmd("/sbin/fw_printenv");
577     for (const auto& entry : output)
578     {
579         size_t pos = entry.find("=");
580         string key = entry.substr(0, pos);
581         if (key != "fitconfig")
582         {
583             continue;
584         }
585 
586         envVarFound = true;
587         if (pos + 1 < entry.size())
588         {
589             readVarValue = entry.substr(pos + 1);
590             if (readVarValue.find(newDeviceTree) != string::npos)
591             {
592                 // fitconfig is Updated. No action needed
593                 break;
594             }
595         }
596         // set env and reboot and break.
597         setEnvAndReboot(key, newDeviceTree);
598         exit(0);
599     }
600 
601     // check If env var Not found
602     if (!envVarFound)
603     {
604         setEnvAndReboot("fitconfig", newDeviceTree);
605     }
606 }
607 
608 /**
609  * @brief API to call VPD manager to write VPD to EEPROM.
610  * @param[in] Object path.
611  * @param[in] record to be updated.
612  * @param[in] keyword to be updated.
613  * @param[in] keyword data to be updated
614  */
615 void updateHardware(const string& objectName, const string& recName,
616                     const string& kwdName, const Binary& data)
617 {
618     try
619     {
620         auto bus = sdbusplus::bus::new_default();
621         auto properties =
622             bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword");
623         properties.append(
624             static_cast<sdbusplus::message::object_path>(objectName));
625         properties.append(recName);
626         properties.append(kwdName);
627         properties.append(data);
628         bus.call(properties);
629     }
630     catch (const sdbusplus::exception::exception& e)
631     {
632         std::string what =
633             "VPDManager WriteKeyword api failed for inventory path " +
634             objectName;
635         what += " record " + recName;
636         what += " keyword " + kwdName;
637         what += " with bus error = " + std::string(e.what());
638 
639         // map to hold additional data in case of logging pel
640         PelAdditionalData additionalData{};
641         additionalData.emplace("CALLOUT_INVENTORY_PATH", objectName);
642         additionalData.emplace("DESCRIPTION", what);
643         createPEL(additionalData, PelSeverity::WARNING, errIntfForBusFailure);
644     }
645 }
646 
647 /**
648  * @brief API to check if we need to restore system VPD
649  * This functionality is only applicable for IPZ VPD data.
650  * @param[in] vpdMap - IPZ vpd map
651  * @param[in] objectPath - Object path for the FRU
652  * @return EEPROMs with records and keywords updated at standby
653  */
654 std::vector<RestoredEeproms> restoreSystemVPD(Parsed& vpdMap,
655                                               const string& objectPath)
656 {
657     // the list of keywords for VSYS record is as per the S0 system. Should be
658     // updated for another type of systems
659     static std::unordered_map<std::string, std::vector<std::string>> svpdKwdMap{
660         {"VSYS", {"BR", "TM", "SE", "SU", "RB"}},
661         {"VCEN", {"FC", "SE"}},
662         {"LXR0", {"LX"}}};
663 
664     // vector to hold all the EEPROMs updated at standby
665     std::vector<RestoredEeproms> updatedEeproms = {};
666 
667     for (const auto& systemRecKwdPair : svpdKwdMap)
668     {
669         auto it = vpdMap.find(systemRecKwdPair.first);
670 
671         // check if record is found in map we got by parser
672         if (it != vpdMap.end())
673         {
674             const auto& kwdListForRecord = systemRecKwdPair.second;
675             for (const auto& keyword : kwdListForRecord)
676             {
677                 DbusPropertyMap& kwdValMap = it->second;
678                 auto iterator = kwdValMap.find(keyword);
679 
680                 if (iterator != kwdValMap.end())
681                 {
682                     string& kwdValue = iterator->second;
683 
684                     // check bus data
685                     const string& recordName = systemRecKwdPair.first;
686                     const string& busValue = readBusProperty(
687                         objectPath, ipzVpdInf + recordName, keyword);
688 
689                     if (busValue.find_first_not_of(' ') != string::npos)
690                     {
691                         if (kwdValue.find_first_not_of(' ') != string::npos)
692                         {
693                             // both the data are present, check for mismatch
694                             if (busValue != kwdValue)
695                             {
696                                 string errMsg = "VPD data mismatch on cache "
697                                                 "and hardware for record: ";
698                                 errMsg += (*it).first;
699                                 errMsg += " and keyword: ";
700                                 errMsg += keyword;
701 
702                                 // data mismatch
703                                 PelAdditionalData additionalData;
704                                 additionalData.emplace("CALLOUT_INVENTORY_PATH",
705                                                        objectPath);
706 
707                                 additionalData.emplace("DESCRIPTION", errMsg);
708 
709                                 createPEL(additionalData, PelSeverity::WARNING,
710                                           errIntfForInvalidVPD);
711                             }
712                         }
713                         else
714                         {
715                             // implies hardware data is blank
716                             // update the map
717                             Binary busData(busValue.begin(), busValue.end());
718 
719                             updatedEeproms.push_back(std::make_tuple(
720                                 objectPath, recordName, keyword, busData));
721 
722                             // update the map as well, so that cache data is not
723                             // updated as blank while populating VPD map on Dbus
724                             // in populateDBus Api
725                             kwdValue = busValue;
726                         }
727                     }
728                     else if (kwdValue.find_first_not_of(' ') == string::npos)
729                     {
730                         string errMsg = "VPD is blank on both cache and "
731                                         "hardware for record: ";
732                         errMsg += (*it).first;
733                         errMsg += " and keyword: ";
734                         errMsg += keyword;
735                         errMsg += ". SSR need to update hardware VPD.";
736 
737                         // both the data are blanks, log PEL
738                         PelAdditionalData additionalData;
739                         additionalData.emplace("CALLOUT_INVENTORY_PATH",
740                                                objectPath);
741 
742                         additionalData.emplace("DESCRIPTION", errMsg);
743 
744                         // log PEL TODO: Block IPL
745                         createPEL(additionalData, PelSeverity::ERROR,
746                                   errIntfForBlankSystemVPD);
747                         continue;
748                     }
749                 }
750             }
751         }
752     }
753 
754     return updatedEeproms;
755 }
756 
757 /**
758  * @brief Populate Dbus.
759  * This method invokes all the populateInterface functions
760  * and notifies PIM about dbus object.
761  * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the
762  * input.
763  * @param[in] js - Inventory json object
764  * @param[in] filePath - Path of the vpd file
765  * @param[in] preIntrStr - Interface string
766  */
767 template <typename T>
768 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath)
769 {
770     inventory::InterfaceMap interfaces;
771     inventory::ObjectMap objects;
772     inventory::PropertyMap prop;
773 
774     // map to hold all the keywords whose value has been changed at standby
775     vector<RestoredEeproms> updatedEeproms = {};
776 
777     bool isSystemVpd = (filePath == systemVpdFilePath);
778     if constexpr (is_same<T, Parsed>::value)
779     {
780         if (isSystemVpd)
781         {
782             std::vector<std::string> interfaces = {motherBoardInterface};
783             // call mapper to check for object path creation
784             MapperResponse subTree =
785                 getObjectSubtreeForInterfaces(pimPath, 0, interfaces);
786             string mboardPath =
787                 js["frus"][filePath].at(0).value("inventoryPath", "");
788 
789             // Attempt system VPD restore if we have a motherboard
790             // object in the inventory.
791             if ((subTree.size() != 0) &&
792                 (subTree.find(pimPath + mboardPath) != subTree.end()))
793             {
794                 updatedEeproms = restoreSystemVPD(vpdMap, mboardPath);
795             }
796             else
797             {
798                 log<level::ERR>("No object path found");
799             }
800         }
801     }
802 
803     for (const auto& item : js["frus"][filePath])
804     {
805         const auto& objectPath = item["inventoryPath"];
806         sdbusplus::message::object_path object(objectPath);
807 
808         // Populate the VPD keywords and the common interfaces only if we
809         // are asked to inherit that data from the VPD, else only add the
810         // extraInterfaces.
811         if (item.value("inherit", true))
812         {
813             if constexpr (is_same<T, Parsed>::value)
814             {
815                 // Each record in the VPD becomes an interface and all
816                 // keyword within the record are properties under that
817                 // interface.
818                 for (const auto& record : vpdMap)
819                 {
820                     populateFruSpecificInterfaces(
821                         record.second, ipzVpdInf + record.first, interfaces);
822                 }
823             }
824             else if constexpr (is_same<T, KeywordVpdMap>::value)
825             {
826                 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces);
827             }
828             if (js.find("commonInterfaces") != js.end())
829             {
830                 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap,
831                                    isSystemVpd);
832             }
833         }
834         else
835         {
836             // Check if we have been asked to inherit specific record(s)
837             if constexpr (is_same<T, Parsed>::value)
838             {
839                 if (item.find("copyRecords") != item.end())
840                 {
841                     for (const auto& record : item["copyRecords"])
842                     {
843                         const string& recordName = record;
844                         if (vpdMap.find(recordName) != vpdMap.end())
845                         {
846                             populateFruSpecificInterfaces(
847                                 vpdMap.at(recordName), ipzVpdInf + recordName,
848                                 interfaces);
849                         }
850                     }
851                 }
852             }
853         }
854         if (item.value("inheritEI", true))
855         {
856             // Populate interfaces and properties that are common to every FRU
857             // and additional interface that might be defined on a per-FRU
858             // basis.
859             if (item.find("extraInterfaces") != item.end())
860             {
861                 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap,
862                                    isSystemVpd);
863             }
864         }
865         objects.emplace(move(object), move(interfaces));
866     }
867 
868     if (isSystemVpd)
869     {
870         string systemJsonName{};
871         if constexpr (is_same<T, Parsed>::value)
872         {
873             // pick the right system json
874             systemJsonName = getSystemsJson(vpdMap);
875         }
876 
877         fs::path target = systemJsonName;
878         fs::path link = INVENTORY_JSON_SYM_LINK;
879 
880         // Create the directory for hosting the symlink
881         fs::create_directories(VPD_FILES_PATH);
882         // unlink the symlink previously created (if any)
883         remove(INVENTORY_JSON_SYM_LINK);
884         // create a new symlink based on the system
885         fs::create_symlink(target, link);
886 
887         // Reloading the json
888         ifstream inventoryJson(link);
889         auto js = json::parse(inventoryJson);
890         inventoryJson.close();
891 
892         inventory::ObjectMap primeObject = primeInventory(js, vpdMap);
893         objects.insert(primeObject.begin(), primeObject.end());
894 
895         // if system VPD has been restored at standby, update the EEPROM
896         for (const auto& item : updatedEeproms)
897         {
898             updateHardware(get<0>(item), get<1>(item), get<2>(item),
899                            get<3>(item));
900         }
901 
902         // set the U-boot environment variable for device-tree
903         if constexpr (is_same<T, Parsed>::value)
904         {
905             const string imKeyword = getIM(vpdMap);
906             const string hwKeyword = getHW(vpdMap);
907             string systemType = imKeyword;
908 
909             // check If system has constraint then append HW version to it.
910             ifstream sysJson(SYSTEM_JSON);
911             if (!sysJson)
912             {
913                 throw((VpdJsonException("Failed to access Json path",
914                                         SYSTEM_JSON)));
915             }
916 
917             try
918             {
919                 auto systemJson = json::parse(sysJson);
920                 if (systemJson["system"].find(imKeyword) !=
921                     systemJson["system"].end())
922                 {
923                     if (systemJson["system"][imKeyword].find("constraint") !=
924                         systemJson["system"][imKeyword].end())
925                     {
926                         systemType += "_" + hwKeyword;
927                     }
928                 }
929             }
930             catch (json::parse_error& ex)
931             {
932                 throw((VpdJsonException("System Json parsing failed",
933                                         SYSTEM_JSON)));
934             }
935 
936             setDevTreeEnv(systemType);
937         }
938     }
939 
940     // Notify PIM
941     common::utility::callPIM(move(objects));
942 }
943 
944 int main(int argc, char** argv)
945 {
946     int rc = 0;
947     json js{};
948     Binary vpdVector{};
949     string file{};
950     // map to hold additional data in case of logging pel
951     PelAdditionalData additionalData{};
952 
953     // this is needed to hold base fru inventory path in case there is ECC or
954     // vpd exception while parsing the file
955     std::string baseFruInventoryPath = {};
956 
957     // severity for PEL
958     PelSeverity pelSeverity = PelSeverity::WARNING;
959 
960     try
961     {
962         App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store "
963                 "in DBUS"};
964 
965         app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)")
966             ->required();
967 
968         CLI11_PARSE(app, argc, argv);
969 
970         cout << "Parser launched with file: " << file << "\n";
971 
972         // PEL severity should be ERROR in case of any system VPD failure
973         if (file == systemVpdFilePath)
974         {
975             pelSeverity = PelSeverity::ERROR;
976         }
977 
978         auto jsonToParse = INVENTORY_JSON_DEFAULT;
979 
980         // If the symlink exists, it means it has been setup for us, switch the
981         // path
982         if (fs::exists(INVENTORY_JSON_SYM_LINK))
983         {
984             jsonToParse = INVENTORY_JSON_SYM_LINK;
985         }
986 
987         // Make sure that the file path we get is for a supported EEPROM
988         ifstream inventoryJson(jsonToParse);
989         if (!inventoryJson)
990         {
991             throw(VpdJsonException("Failed to access Json path", jsonToParse));
992         }
993 
994         try
995         {
996             js = json::parse(inventoryJson);
997         }
998         catch (json::parse_error& ex)
999         {
1000             throw(VpdJsonException("Json parsing failed", jsonToParse));
1001         }
1002 
1003         // Do we have the mandatory "frus" section?
1004         if (js.find("frus") == js.end())
1005         {
1006             throw(VpdJsonException("FRUs section not found in JSON",
1007                                    jsonToParse));
1008         }
1009 
1010         // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@)
1011         if (file.find("/ahb:apb") != string::npos)
1012         {
1013             // Translate udev path to a generic /sys/bus/.. file path.
1014             udevToGenericPath(file);
1015             cout << "Path after translation: " << file << "\n";
1016 
1017             if ((js["frus"].find(file) != js["frus"].end()) &&
1018                 (file == systemVpdFilePath))
1019             {
1020                 return 0;
1021             }
1022         }
1023 
1024         if (file.empty())
1025         {
1026             cerr << "The EEPROM path <" << file << "> is not valid.";
1027             return 0;
1028         }
1029         if (js["frus"].find(file) == js["frus"].end())
1030         {
1031             cout << "Device path not in JSON, ignoring" << endl;
1032             return 0;
1033         }
1034 
1035         if (!fs::exists(file))
1036         {
1037             cout << "Device path: " << file
1038                  << " does not exist. Spurious udev event? Exiting." << endl;
1039             return 0;
1040         }
1041 
1042         baseFruInventoryPath = js["frus"][file][0]["inventoryPath"];
1043         // Check if we can read the VPD file based on the power state
1044         if (js["frus"][file].at(0).value("powerOffOnly", false))
1045         {
1046             if ("xyz.openbmc_project.State.Chassis.PowerState.On" ==
1047                 getPowerState())
1048             {
1049                 cout << "This VPD cannot be read when power is ON" << endl;
1050                 return 0;
1051             }
1052         }
1053 
1054         try
1055         {
1056             vpdVector = getVpdDataInVector(js, file);
1057             ParserInterface* parser = ParserFactory::getParser(vpdVector);
1058             variant<KeywordVpdMap, Store> parseResult;
1059             parseResult = parser->parse();
1060 
1061             if (auto pVal = get_if<Store>(&parseResult))
1062             {
1063                 populateDbus(pVal->getVpdMap(), js, file);
1064             }
1065             else if (auto pVal = get_if<KeywordVpdMap>(&parseResult))
1066             {
1067                 populateDbus(*pVal, js, file);
1068             }
1069 
1070             // release the parser object
1071             ParserFactory::freeParser(parser);
1072         }
1073         catch (exception& e)
1074         {
1075             postFailAction(js, file);
1076             throw;
1077         }
1078     }
1079     catch (const VpdJsonException& ex)
1080     {
1081         additionalData.emplace("JSON_PATH", ex.getJsonPath());
1082         additionalData.emplace("DESCRIPTION", ex.what());
1083         createPEL(additionalData, pelSeverity, errIntfForJsonFailure);
1084 
1085         cerr << ex.what() << "\n";
1086         rc = -1;
1087     }
1088     catch (const VpdEccException& ex)
1089     {
1090         additionalData.emplace("DESCRIPTION", "ECC check failed");
1091         additionalData.emplace("CALLOUT_INVENTORY_PATH",
1092                                INVENTORY_PATH + baseFruInventoryPath);
1093         createPEL(additionalData, pelSeverity, errIntfForEccCheckFail);
1094         dumpBadVpd(file, vpdVector);
1095         cerr << ex.what() << "\n";
1096         rc = -1;
1097     }
1098     catch (const VpdDataException& ex)
1099     {
1100         additionalData.emplace("DESCRIPTION", "Invalid VPD data");
1101         additionalData.emplace("CALLOUT_INVENTORY_PATH",
1102                                INVENTORY_PATH + baseFruInventoryPath);
1103         createPEL(additionalData, pelSeverity, errIntfForInvalidVPD);
1104         dumpBadVpd(file, vpdVector);
1105         cerr << ex.what() << "\n";
1106         rc = -1;
1107     }
1108     catch (exception& e)
1109     {
1110         dumpBadVpd(file, vpdVector);
1111         cerr << e.what() << "\n";
1112         rc = -1;
1113     }
1114 
1115     return rc;
1116 }