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