xref: /openbmc/openpower-vpd-parser/ibm_vpd_utils.cpp (revision a4a2adfa96d2c491b1baf776013a6442bc88c78b)
1 #include "config.h"
2 
3 #include "ibm_vpd_utils.hpp"
4 
5 #include "common_utility.hpp"
6 #include "defines.hpp"
7 #include "vpd_exceptions.hpp"
8 
9 #include <filesystem>
10 #include <fstream>
11 #include <gpiod.hpp>
12 #include <iomanip>
13 #include <nlohmann/json.hpp>
14 #include <phosphor-logging/elog-errors.hpp>
15 #include <phosphor-logging/log.hpp>
16 #include <regex>
17 #include <sdbusplus/server.hpp>
18 #include <sstream>
19 #include <vector>
20 #include <xyz/openbmc_project/Common/error.hpp>
21 
22 using json = nlohmann::json;
23 
24 namespace openpower
25 {
26 namespace vpd
27 {
28 using namespace openpower::vpd::constants;
29 using namespace inventory;
30 using namespace phosphor::logging;
31 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
32 using namespace record;
33 using namespace openpower::vpd::exceptions;
34 using namespace common::utility;
35 using Severity = openpower::vpd::constants::PelSeverity;
36 namespace fs = std::filesystem;
37 
38 // mapping of severity enum to severity interface
39 static std::unordered_map<Severity, std::string> sevMap = {
40     {Severity::INFORMATIONAL,
41      "xyz.openbmc_project.Logging.Entry.Level.Informational"},
42     {Severity::DEBUG, "xyz.openbmc_project.Logging.Entry.Level.Debug"},
43     {Severity::NOTICE, "xyz.openbmc_project.Logging.Entry.Level.Notice"},
44     {Severity::WARNING, "xyz.openbmc_project.Logging.Entry.Level.Warning"},
45     {Severity::CRITICAL, "xyz.openbmc_project.Logging.Entry.Level.Critical"},
46     {Severity::EMERGENCY, "xyz.openbmc_project.Logging.Entry.Level.Emergency"},
47     {Severity::ERROR, "xyz.openbmc_project.Logging.Entry.Level.Error"},
48     {Severity::ALERT, "xyz.openbmc_project.Logging.Entry.Level.Alert"}};
49 
50 namespace inventory
51 {
52 
53 MapperResponse
54     getObjectSubtreeForInterfaces(const std::string& root, const int32_t depth,
55                                   const std::vector<std::string>& interfaces)
56 {
57     auto bus = sdbusplus::bus::new_default();
58     auto mapperCall = bus.new_method_call(mapperDestination, mapperObjectPath,
59                                           mapperInterface, "GetSubTree");
60     mapperCall.append(root);
61     mapperCall.append(depth);
62     mapperCall.append(interfaces);
63 
64     MapperResponse result = {};
65 
66     try
67     {
68         auto response = bus.call(mapperCall);
69 
70         response.read(result);
71     }
72     catch (const sdbusplus::exception::exception& e)
73     {
74         log<level::ERR>("Error in mapper GetSubTree",
75                         entry("ERROR=%s", e.what()));
76     }
77 
78     return result;
79 }
80 
81 } // namespace inventory
82 
83 LE2ByteData readUInt16LE(Binary::const_iterator iterator)
84 {
85     LE2ByteData lowByte = *iterator;
86     LE2ByteData highByte = *(iterator + 1);
87     lowByte |= (highByte << 8);
88     return lowByte;
89 }
90 
91 /** @brief Encodes a keyword for D-Bus.
92  */
93 string encodeKeyword(const string& kw, const string& encoding)
94 {
95     if (encoding == "MAC")
96     {
97         string res{};
98         size_t first = kw[0];
99         res += toHex(first >> 4);
100         res += toHex(first & 0x0f);
101         for (size_t i = 1; i < kw.size(); ++i)
102         {
103             res += ":";
104             res += toHex(kw[i] >> 4);
105             res += toHex(kw[i] & 0x0f);
106         }
107         return res;
108     }
109     else if (encoding == "DATE")
110     {
111         // Date, represent as
112         // <year>-<month>-<day> <hour>:<min>
113         string res{};
114         static constexpr uint8_t skipPrefix = 3;
115 
116         auto strItr = kw.begin();
117         advance(strItr, skipPrefix);
118         for_each(strItr, kw.end(), [&res](size_t c) { res += c; });
119 
120         res.insert(BD_YEAR_END, 1, '-');
121         res.insert(BD_MONTH_END, 1, '-');
122         res.insert(BD_DAY_END, 1, ' ');
123         res.insert(BD_HOUR_END, 1, ':');
124 
125         return res;
126     }
127     else // default to string encoding
128     {
129         return string(kw.begin(), kw.end());
130     }
131 }
132 
133 string readBusProperty(const string& obj, const string& inf, const string& prop)
134 {
135     std::string propVal{};
136     std::string object = INVENTORY_PATH + obj;
137     auto bus = sdbusplus::bus::new_default();
138     auto properties = bus.new_method_call(
139         "xyz.openbmc_project.Inventory.Manager", object.c_str(),
140         "org.freedesktop.DBus.Properties", "Get");
141     properties.append(inf);
142     properties.append(prop);
143     auto result = bus.call(properties);
144     if (!result.is_method_error())
145     {
146         variant<Binary, string> val;
147         result.read(val);
148         if (auto pVal = get_if<Binary>(&val))
149         {
150             propVal.assign(reinterpret_cast<const char*>(pVal->data()),
151                            pVal->size());
152         }
153         else if (auto pVal = get_if<string>(&val))
154         {
155             propVal.assign(pVal->data(), pVal->size());
156         }
157     }
158     return propVal;
159 }
160 
161 void createPEL(const std::map<std::string, std::string>& additionalData,
162                const Severity& sev, const std::string& errIntf)
163 {
164     try
165     {
166         std::string pelSeverity =
167             "xyz.openbmc_project.Logging.Entry.Level.Error";
168         auto bus = sdbusplus::bus::new_default();
169         auto service = getService(bus, loggerObjectPath, loggerCreateInterface);
170         auto method = bus.new_method_call(service.c_str(), loggerObjectPath,
171                                           loggerCreateInterface, "Create");
172 
173         auto itr = sevMap.find(sev);
174         if (itr != sevMap.end())
175         {
176             pelSeverity = itr->second;
177         }
178 
179         method.append(errIntf, pelSeverity, additionalData);
180         auto resp = bus.call(method);
181     }
182     catch (const sdbusplus::exception::exception& e)
183     {
184         throw std::runtime_error(
185             "Error in invoking D-Bus logging create interface to register PEL");
186     }
187 }
188 
189 inventory::VPDfilepath getVpdFilePath(const string& jsonFile,
190                                       const std::string& ObjPath)
191 {
192     ifstream inventoryJson(jsonFile);
193     const auto& jsonObject = json::parse(inventoryJson);
194     inventory::VPDfilepath filePath{};
195 
196     if (jsonObject.find("frus") == jsonObject.end())
197     {
198         throw(VpdJsonException(
199             "Invalid JSON structure - frus{} object not found in ", jsonFile));
200     }
201 
202     const nlohmann::json& groupFRUS =
203         jsonObject["frus"].get_ref<const nlohmann::json::object_t&>();
204     for (const auto& itemFRUS : groupFRUS.items())
205     {
206         const std::vector<nlohmann::json>& groupEEPROM =
207             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
208         for (const auto& itemEEPROM : groupEEPROM)
209         {
210             if (itemEEPROM["inventoryPath"]
211                     .get_ref<const nlohmann::json::string_t&>() == ObjPath)
212             {
213                 filePath = itemFRUS.key();
214                 return filePath;
215             }
216         }
217     }
218 
219     return filePath;
220 }
221 
222 bool isPathInJson(const std::string& eepromPath)
223 {
224     bool present = false;
225     ifstream inventoryJson(INVENTORY_JSON_SYM_LINK);
226 
227     try
228     {
229         auto js = json::parse(inventoryJson);
230         if (js.find("frus") == js.end())
231         {
232             throw(VpdJsonException(
233                 "Invalid JSON structure - frus{} object not found in ",
234                 INVENTORY_JSON_SYM_LINK));
235         }
236         json fruJson = js["frus"];
237 
238         if (fruJson.find(eepromPath) != fruJson.end())
239         {
240             present = true;
241         }
242     }
243     catch (const json::parse_error& ex)
244     {
245         throw(VpdJsonException("Json Parsing failed", INVENTORY_JSON_SYM_LINK));
246     }
247     return present;
248 }
249 
250 bool isRecKwInDbusJson(const std::string& recordName,
251                        const std::string& keyword)
252 {
253     ifstream propertyJson(DBUS_PROP_JSON);
254     json dbusProperty;
255     bool present = false;
256 
257     if (propertyJson.is_open())
258     {
259         try
260         {
261             auto dbusPropertyJson = json::parse(propertyJson);
262             if (dbusPropertyJson.find("dbusProperties") ==
263                 dbusPropertyJson.end())
264             {
265                 throw(VpdJsonException("dbusProperties{} object not found in "
266                                        "DbusProperties json : ",
267                                        DBUS_PROP_JSON));
268             }
269 
270             dbusProperty = dbusPropertyJson["dbusProperties"];
271             if (dbusProperty.contains(recordName))
272             {
273                 const vector<string>& kwdsToPublish = dbusProperty[recordName];
274                 if (find(kwdsToPublish.begin(), kwdsToPublish.end(), keyword) !=
275                     kwdsToPublish.end()) // present
276                 {
277                     present = true;
278                 }
279             }
280         }
281         catch (const json::parse_error& ex)
282         {
283             throw(VpdJsonException("Json Parsing failed", DBUS_PROP_JSON));
284         }
285     }
286     else
287     {
288         // If dbus properties json is not available, we assume the given
289         // record-keyword is part of dbus-properties json. So setting the bool
290         // variable to true.
291         present = true;
292     }
293     return present;
294 }
295 
296 vpdType vpdTypeCheck(const Binary& vpdVector)
297 {
298     // Read first 3 Bytes to check the 11S bar code format
299     std::string is11SFormat = "";
300     for (uint8_t i = 0; i < FORMAT_11S_LEN; i++)
301     {
302         is11SFormat += vpdVector[MEMORY_VPD_DATA_START + i];
303     }
304 
305     if (vpdVector[IPZ_DATA_START] == KW_VAL_PAIR_START_TAG)
306     {
307         // IPZ VPD FORMAT
308         return vpdType::IPZ_VPD;
309     }
310     else if (vpdVector[KW_VPD_DATA_START] == KW_VPD_START_TAG)
311     {
312         // KEYWORD VPD FORMAT
313         return vpdType::KEYWORD_VPD;
314     }
315     else if (is11SFormat.compare(MEMORY_VPD_START_TAG) == 0)
316     {
317         // Memory VPD format
318         return vpdType::MEMORY_VPD;
319     }
320 
321     // INVALID VPD FORMAT
322     return vpdType::INVALID_VPD_FORMAT;
323 }
324 
325 const string getIM(const Parsed& vpdMap)
326 {
327     Binary imVal;
328     auto property = vpdMap.find("VSBP");
329     if (property != vpdMap.end())
330     {
331         auto kw = (property->second).find("IM");
332         if (kw != (property->second).end())
333         {
334             copy(kw->second.begin(), kw->second.end(), back_inserter(imVal));
335         }
336     }
337 
338     ostringstream oss;
339     for (auto& i : imVal)
340     {
341         oss << setw(2) << setfill('0') << hex << static_cast<int>(i);
342     }
343 
344     return oss.str();
345 }
346 
347 const string getHW(const Parsed& vpdMap)
348 {
349     Binary hwVal;
350     auto prop = vpdMap.find("VINI");
351     if (prop != vpdMap.end())
352     {
353         auto kw = (prop->second).find("HW");
354         if (kw != (prop->second).end())
355         {
356             copy(kw->second.begin(), kw->second.end(), back_inserter(hwVal));
357         }
358     }
359 
360     // The planar pass only comes from the LSB of the HW keyword,
361     // where as the MSB is used for other purposes such as signifying clock
362     // termination.
363     hwVal[0] = 0x00;
364 
365     ostringstream hwString;
366     for (auto& i : hwVal)
367     {
368         hwString << setw(2) << setfill('0') << hex << static_cast<int>(i);
369     }
370 
371     return hwString.str();
372 }
373 
374 string getSystemsJson(const Parsed& vpdMap)
375 {
376     string jsonPath = "/usr/share/vpd/";
377     string jsonName{};
378 
379     ifstream systemJson(SYSTEM_JSON);
380     if (!systemJson)
381     {
382         throw((VpdJsonException("Failed to access Json path", SYSTEM_JSON)));
383     }
384 
385     try
386     {
387         auto js = json::parse(systemJson);
388 
389         const string hwKeyword = getHW(vpdMap);
390         const string imKeyword = getIM(vpdMap);
391 
392         if (js.find("system") == js.end())
393         {
394             throw runtime_error("Invalid systems Json");
395         }
396 
397         if (js["system"].find(imKeyword) == js["system"].end())
398         {
399             throw runtime_error(
400                 "Invalid system. This system type is not present "
401                 "in the systemsJson. IM: " +
402                 imKeyword);
403         }
404 
405         if ((js["system"][imKeyword].find("constraint") !=
406              js["system"][imKeyword].end()) &&
407             (hwKeyword == js["system"][imKeyword]["constraint"]["HW"]))
408         {
409             jsonName = js["system"][imKeyword]["constraint"]["json"];
410         }
411         else if (js["system"][imKeyword].find("default") !=
412                  js["system"][imKeyword].end())
413         {
414             jsonName = js["system"][imKeyword]["default"];
415         }
416         else
417         {
418             throw runtime_error(
419                 "Bad System json. Neither constraint nor default found");
420         }
421 
422         jsonPath += jsonName;
423     }
424 
425     catch (const json::parse_error& ex)
426     {
427         throw(VpdJsonException("Json Parsing failed", SYSTEM_JSON));
428     }
429     return jsonPath;
430 }
431 
432 void udevToGenericPath(string& file)
433 {
434     // Sample udevEvent i2c path :
435     // "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a480.i2c-bus/i2c-8/8-0051/8-00510/nvmem"
436     // find if the path contains the word i2c in it.
437     if (file.find("i2c") != string::npos)
438     {
439         string i2cBusAddr{};
440 
441         // Every udev i2c path should have the common pattern
442         // "i2c-bus_number/bus_number-vpd_address". Search for
443         // "bus_number-vpd_address".
444         regex i2cPattern("((i2c)-[0-9]+\\/)([0-9]+-[0-9]{4})");
445         smatch match;
446         if (regex_search(file, match, i2cPattern))
447         {
448             i2cBusAddr = match.str(3);
449         }
450         else
451         {
452             cerr << "The given udev path < " << file
453                  << " > doesn't match the required pattern. Skipping VPD "
454                     "collection."
455                  << endl;
456             exit(EXIT_SUCCESS);
457         }
458         // Forming the generic file path
459         file = i2cPathPrefix + i2cBusAddr + "/eeprom";
460     }
461     // Sample udevEvent spi path :
462     // "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/fsi0/slave@00:00/00:00:00:04/spi_master/spi2/spi2.0/spi2.00/nvmem"
463     // find if the path contains the word spi in it.
464     else if (file.find("spi") != string::npos)
465     {
466         // Every udev spi path will have common pattern "spi<Digit>/", which
467         // describes the spi bus number at which the fru is connected; Followed
468         // by a slash following the vpd address of the fru. Taking the above
469         // input as a common key, we try to search for the pattern "spi<Digit>/"
470         // using regular expression.
471         regex spiPattern("((spi)[0-9]+)(\\/)");
472         string spiBus{};
473         smatch match;
474         if (regex_search(file, match, spiPattern))
475         {
476             spiBus = match.str(1);
477         }
478         else
479         {
480             cerr << "The given udev path < " << file
481                  << " > doesn't match the required pattern. Skipping VPD "
482                     "collection."
483                  << endl;
484             exit(EXIT_SUCCESS);
485         }
486         // Forming the generic path
487         file = spiPathPrefix + spiBus + ".0/eeprom";
488     }
489     else
490     {
491         cerr << "\n The given EEPROM path < " << file
492              << " > is not valid. It's neither I2C nor "
493                 "SPI path. Skipping VPD collection.."
494              << endl;
495         exit(EXIT_SUCCESS);
496     }
497 }
498 string getBadVpdName(const string& file)
499 {
500     string badVpd = BAD_VPD_DIR;
501     if (file.find("i2c") != string::npos)
502     {
503         badVpd += "i2c-";
504         regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
505         smatch match;
506         if (regex_search(file, match, i2cPattern))
507         {
508             badVpd += match.str(2);
509         }
510     }
511     else if (file.find("spi") != string::npos)
512     {
513         regex spiPattern("((spi)[0-9]+)(.0)");
514         smatch match;
515         if (regex_search(file, match, spiPattern))
516         {
517             badVpd += match.str(1);
518         }
519     }
520     return badVpd;
521 }
522 
523 void dumpBadVpd(const string& file, const Binary& vpdVector)
524 {
525     fs::path badVpdDir = BAD_VPD_DIR;
526     fs::create_directory(badVpdDir);
527     string badVpdPath = getBadVpdName(file);
528     if (fs::exists(badVpdPath))
529     {
530         std::error_code ec;
531         fs::remove(badVpdPath, ec);
532         if (ec) // error code
533         {
534             string error = "Error removing the existing broken vpd in ";
535             error += badVpdPath;
536             error += ". Error code : ";
537             error += ec.value();
538             error += ". Error message : ";
539             error += ec.message();
540             throw runtime_error(error);
541         }
542     }
543     ofstream badVpdFileStream(badVpdPath, ofstream::binary);
544     if (!badVpdFileStream)
545     {
546         throw runtime_error("Failed to open bad vpd file path in /tmp/bad-vpd. "
547                             "Unable to dump the broken/bad vpd file.");
548     }
549     badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
550                            vpdVector.size());
551 }
552 
553 const string getKwVal(const Parsed& vpdMap, const string& rec,
554                       const string& kwd)
555 {
556     string kwVal{};
557 
558     auto findRec = vpdMap.find(rec);
559 
560     // check if record is found in map we got by parser
561     if (findRec != vpdMap.end())
562     {
563         auto findKwd = findRec->second.find(kwd);
564 
565         if (findKwd != findRec->second.end())
566         {
567             kwVal = findKwd->second;
568         }
569     }
570 
571     return kwVal;
572 }
573 
574 string byteArrayToHexString(const Binary& vec)
575 {
576     stringstream ss;
577     string hexRep = "0x";
578     ss << hexRep;
579     string str = ss.str();
580 
581     // convert Decimal to Hex string
582     for (auto& v : vec)
583     {
584         ss << setfill('0') << setw(2) << hex << (int)v;
585         str = ss.str();
586     }
587     return str;
588 }
589 
590 string getPrintableValue(const Binary& vec)
591 {
592     string str{};
593 
594     // find for a non printable value in the vector
595     const auto it = std::find_if(vec.begin(), vec.end(),
596                                  [](const auto& ele) { return !isprint(ele); });
597 
598     if (it != vec.end()) // if the given vector has any non printable value
599     {
600         for (auto itr = it; itr != vec.end(); itr++)
601         {
602             if (*itr != 0x00)
603             {
604                 str = byteArrayToHexString(vec);
605                 return str;
606             }
607         }
608         str = string(vec.begin(), it);
609     }
610     else
611     {
612         str = string(vec.begin(), vec.end());
613     }
614     return str;
615 }
616 
617 void executePostFailAction(const nlohmann::json& json, const string& file)
618 {
619     if ((json["frus"][file].at(0)).find("postActionFail") ==
620         json["frus"][file].at(0).end())
621     {
622         return;
623     }
624 
625     uint8_t pinValue = 0;
626     string pinName;
627 
628     for (const auto& postAction :
629          (json["frus"][file].at(0))["postActionFail"].items())
630     {
631         if (postAction.key() == "pin")
632         {
633             pinName = postAction.value();
634         }
635         else if (postAction.key() == "value")
636         {
637             // Get the value to set
638             pinValue = postAction.value();
639         }
640     }
641 
642     cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
643 
644     try
645     {
646         gpiod::line outputLine = gpiod::find_line(pinName);
647 
648         if (!outputLine)
649         {
650             cout << "Couldn't find output line:" << pinName
651                  << " on GPIO. Skipping...\n";
652 
653             return;
654         }
655         outputLine.request(
656             {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
657             pinValue);
658     }
659     catch (const system_error&)
660     {
661         cerr << "Failed to set post-action GPIO" << endl;
662     }
663 }
664 
665 std::optional<bool> isPresent(const nlohmann::json& json, const string& file)
666 
667 {
668     if ((json["frus"][file].at(0)).find("presence") !=
669         json["frus"][file].at(0).end())
670     {
671         if (((json["frus"][file].at(0)["presence"]).find("pin") !=
672              json["frus"][file].at(0)["presence"].end()) &&
673             ((json["frus"][file].at(0)["presence"]).find("value") !=
674              json["frus"][file].at(0)["presence"].end()))
675         {
676             string presPinName = json["frus"][file].at(0)["presence"]["pin"];
677             Byte presPinValue = json["frus"][file].at(0)["presence"]["value"];
678 
679             try
680             {
681                 gpiod::line presenceLine = gpiod::find_line(presPinName);
682 
683                 if (!presenceLine)
684                 {
685                     cerr << "couldn't find presence line:" << presPinName
686                          << "\n";
687                     return false;
688                 }
689 
690                 presenceLine.request({"Read the presence line",
691                                       gpiod::line_request::DIRECTION_INPUT, 0});
692 
693                 Byte gpioData = presenceLine.get_value();
694 
695                 return (gpioData == presPinValue);
696             }
697             catch (system_error&)
698             {
699                 cerr << "Failed to get the presence GPIO for - " << presPinName
700                      << endl;
701                 return false;
702             }
703         }
704     }
705     return std::optional<bool>{};
706 }
707 
708 bool executePreAction(const nlohmann::json& json, const string& file)
709 {
710     auto present = isPresent(json, file);
711     if (present && !present.value())
712     {
713         executePostFailAction(json, file);
714         return false;
715     }
716 
717     if ((json["frus"][file].at(0)).find("preAction") !=
718         json["frus"][file].at(0).end())
719     {
720         if (((json["frus"][file].at(0)["preAction"]).find("pin") !=
721              json["frus"][file].at(0)["preAction"].end()) &&
722             ((json["frus"][file].at(0)["preAction"]).find("value") !=
723              json["frus"][file].at(0)["preAction"].end()))
724         {
725             string pinName = json["frus"][file].at(0)["preAction"]["pin"];
726             // Get the value to set
727             Byte pinValue = json["frus"][file].at(0)["preAction"]["value"];
728 
729             cout << "Setting GPIO: " << pinName << " to " << (int)pinValue
730                  << endl;
731             try
732             {
733                 gpiod::line outputLine = gpiod::find_line(pinName);
734 
735                 if (!outputLine)
736                 {
737                     cout << "Couldn't find output line:" << pinName
738                          << " on GPIO. Skipping...\n";
739 
740                     return false;
741                 }
742                 outputLine.request({"FRU pre-action",
743                                     ::gpiod::line_request::DIRECTION_OUTPUT, 0},
744                                    pinValue);
745             }
746             catch (system_error&)
747             {
748                 cerr << "Failed to set pre-action for GPIO - " << pinName
749                      << endl;
750                 return false;
751             }
752         }
753     }
754     return true;
755 }
756 
757 void insertOrMerge(inventory::InterfaceMap& map,
758                    const inventory::Interface& interface,
759                    inventory::PropertyMap&& property)
760 {
761     if (map.find(interface) != map.end())
762     {
763         auto& prop = map.at(interface);
764         prop.insert(property.begin(), property.end());
765     }
766     else
767     {
768         map.emplace(interface, property);
769     }
770 }
771 } // namespace vpd
772 } // namespace openpower
773