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