xref: /openbmc/openpower-vpd-parser/ibm_vpd_utils.cpp (revision f39537634cd08d39272c80b27fa7bda667da4033)
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 <iomanip>
12 #include <nlohmann/json.hpp>
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/log.hpp>
15 #include <regex>
16 #include <sdbusplus/server.hpp>
17 #include <sstream>
18 #include <vector>
19 #include <xyz/openbmc_project/Common/error.hpp>
20 
21 using json = nlohmann::json;
22 
23 namespace openpower
24 {
25 namespace vpd
26 {
27 using namespace openpower::vpd::constants;
28 using namespace inventory;
29 using namespace phosphor::logging;
30 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
31 using namespace record;
32 using namespace openpower::vpd::exceptions;
33 using namespace common::utility;
34 using Severity = openpower::vpd::constants::PelSeverity;
35 namespace fs = std::filesystem;
36 
37 // mapping of severity enum to severity interface
38 static std::unordered_map<Severity, std::string> sevMap = {
39     {Severity::INFORMATIONAL,
40      "xyz.openbmc_project.Logging.Entry.Level.Informational"},
41     {Severity::DEBUG, "xyz.openbmc_project.Logging.Entry.Level.Debug"},
42     {Severity::NOTICE, "xyz.openbmc_project.Logging.Entry.Level.Notice"},
43     {Severity::WARNING, "xyz.openbmc_project.Logging.Entry.Level.Warning"},
44     {Severity::CRITICAL, "xyz.openbmc_project.Logging.Entry.Level.Critical"},
45     {Severity::EMERGENCY, "xyz.openbmc_project.Logging.Entry.Level.Emergency"},
46     {Severity::ERROR, "xyz.openbmc_project.Logging.Entry.Level.Error"},
47     {Severity::ALERT, "xyz.openbmc_project.Logging.Entry.Level.Alert"}};
48 
49 namespace inventory
50 {
51 
52 MapperResponse
53     getObjectSubtreeForInterfaces(const std::string& root, const int32_t depth,
54                                   const std::vector<std::string>& interfaces)
55 {
56     auto bus = sdbusplus::bus::new_default();
57     auto mapperCall = bus.new_method_call(mapperDestination, mapperObjectPath,
58                                           mapperInterface, "GetSubTree");
59     mapperCall.append(root);
60     mapperCall.append(depth);
61     mapperCall.append(interfaces);
62 
63     MapperResponse result = {};
64 
65     try
66     {
67         auto response = bus.call(mapperCall);
68 
69         response.read(result);
70     }
71     catch (const sdbusplus::exception::SdBusError& e)
72     {
73         log<level::ERR>("Error in mapper GetSubTree",
74                         entry("ERROR=%s", e.what()));
75     }
76 
77     return result;
78 }
79 
80 } // namespace inventory
81 
82 LE2ByteData readUInt16LE(Binary::const_iterator iterator)
83 {
84     LE2ByteData lowByte = *iterator;
85     LE2ByteData highByte = *(iterator + 1);
86     lowByte |= (highByte << 8);
87     return lowByte;
88 }
89 
90 /** @brief Encodes a keyword for D-Bus.
91  */
92 string encodeKeyword(const string& kw, const string& encoding)
93 {
94     if (encoding == "MAC")
95     {
96         string res{};
97         size_t first = kw[0];
98         res += toHex(first >> 4);
99         res += toHex(first & 0x0f);
100         for (size_t i = 1; i < kw.size(); ++i)
101         {
102             res += ":";
103             res += toHex(kw[i] >> 4);
104             res += toHex(kw[i] & 0x0f);
105         }
106         return res;
107     }
108     else if (encoding == "DATE")
109     {
110         // Date, represent as
111         // <year>-<month>-<day> <hour>:<min>
112         string res{};
113         static constexpr uint8_t skipPrefix = 3;
114 
115         auto strItr = kw.begin();
116         advance(strItr, skipPrefix);
117         for_each(strItr, kw.end(), [&res](size_t c) { res += c; });
118 
119         res.insert(BD_YEAR_END, 1, '-');
120         res.insert(BD_MONTH_END, 1, '-');
121         res.insert(BD_DAY_END, 1, ' ');
122         res.insert(BD_HOUR_END, 1, ':');
123 
124         return res;
125     }
126     else // default to string encoding
127     {
128         return string(kw.begin(), kw.end());
129     }
130 }
131 
132 string readBusProperty(const string& obj, const string& inf, const string& prop)
133 {
134     std::string propVal{};
135     std::string object = INVENTORY_PATH + obj;
136     auto bus = sdbusplus::bus::new_default();
137     auto properties = bus.new_method_call(
138         "xyz.openbmc_project.Inventory.Manager", object.c_str(),
139         "org.freedesktop.DBus.Properties", "Get");
140     properties.append(inf);
141     properties.append(prop);
142     auto result = bus.call(properties);
143     if (!result.is_method_error())
144     {
145         variant<Binary, string> val;
146         result.read(val);
147         if (auto pVal = get_if<Binary>(&val))
148         {
149             propVal.assign(reinterpret_cast<const char*>(pVal->data()),
150                            pVal->size());
151         }
152         else if (auto pVal = get_if<string>(&val))
153         {
154             propVal.assign(pVal->data(), pVal->size());
155         }
156     }
157     return propVal;
158 }
159 
160 void createPEL(const std::map<std::string, std::string>& additionalData,
161                const Severity& sev, const std::string& errIntf)
162 {
163     try
164     {
165         std::string pelSeverity =
166             "xyz.openbmc_project.Logging.Entry.Level.Error";
167         auto bus = sdbusplus::bus::new_default();
168         auto service = getService(bus, loggerObjectPath, loggerCreateInterface);
169         auto method = bus.new_method_call(service.c_str(), loggerObjectPath,
170                                           loggerCreateInterface, "Create");
171 
172         auto itr = sevMap.find(sev);
173         if (itr != sevMap.end())
174         {
175             pelSeverity = itr->second;
176         }
177 
178         method.append(errIntf, pelSeverity, additionalData);
179         auto resp = bus.call(method);
180     }
181     catch (const sdbusplus::exception::SdBusError& e)
182     {
183         throw std::runtime_error(
184             "Error in invoking D-Bus logging create interface to register PEL");
185     }
186 }
187 
188 inventory::VPDfilepath getVpdFilePath(const string& jsonFile,
189                                       const std::string& ObjPath)
190 {
191     ifstream inventoryJson(jsonFile);
192     const auto& jsonObject = json::parse(inventoryJson);
193     inventory::VPDfilepath filePath{};
194 
195     if (jsonObject.find("frus") == jsonObject.end())
196     {
197         throw(VpdJsonException(
198             "Invalid JSON structure - frus{} object not found in ", jsonFile));
199     }
200 
201     const nlohmann::json& groupFRUS =
202         jsonObject["frus"].get_ref<const nlohmann::json::object_t&>();
203     for (const auto& itemFRUS : groupFRUS.items())
204     {
205         const std::vector<nlohmann::json>& groupEEPROM =
206             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
207         for (const auto& itemEEPROM : groupEEPROM)
208         {
209             if (itemEEPROM["inventoryPath"]
210                     .get_ref<const nlohmann::json::string_t&>() == ObjPath)
211             {
212                 filePath = itemFRUS.key();
213                 return filePath;
214             }
215         }
216     }
217 
218     return filePath;
219 }
220 
221 bool isPathInJson(const std::string& eepromPath)
222 {
223     bool present = false;
224     ifstream inventoryJson(INVENTORY_JSON_SYM_LINK);
225 
226     try
227     {
228         auto js = json::parse(inventoryJson);
229         if (js.find("frus") == js.end())
230         {
231             throw(VpdJsonException(
232                 "Invalid JSON structure - frus{} object not found in ",
233                 INVENTORY_JSON_SYM_LINK));
234         }
235         json fruJson = js["frus"];
236 
237         if (fruJson.find(eepromPath) != fruJson.end())
238         {
239             present = true;
240         }
241     }
242     catch (json::parse_error& ex)
243     {
244         throw(VpdJsonException("Json Parsing failed", INVENTORY_JSON_SYM_LINK));
245     }
246     return present;
247 }
248 
249 bool isRecKwInDbusJson(const std::string& recordName,
250                        const std::string& keyword)
251 {
252     ifstream propertyJson(DBUS_PROP_JSON);
253     json dbusProperty;
254     bool present = false;
255 
256     if (propertyJson.is_open())
257     {
258         try
259         {
260             auto dbusPropertyJson = json::parse(propertyJson);
261             if (dbusPropertyJson.find("dbusProperties") ==
262                 dbusPropertyJson.end())
263             {
264                 throw(VpdJsonException("dbusProperties{} object not found in "
265                                        "DbusProperties json : ",
266                                        DBUS_PROP_JSON));
267             }
268 
269             dbusProperty = dbusPropertyJson["dbusProperties"];
270             if (dbusProperty.contains(recordName))
271             {
272                 const vector<string>& kwdsToPublish = dbusProperty[recordName];
273                 if (find(kwdsToPublish.begin(), kwdsToPublish.end(), keyword) !=
274                     kwdsToPublish.end()) // present
275                 {
276                     present = true;
277                 }
278             }
279         }
280         catch (json::parse_error& ex)
281         {
282             throw(VpdJsonException("Json Parsing failed", DBUS_PROP_JSON));
283         }
284     }
285     else
286     {
287         // If dbus properties json is not available, we assume the given
288         // record-keyword is part of dbus-properties json. So setting the bool
289         // variable to true.
290         present = true;
291     }
292     return present;
293 }
294 
295 vpdType vpdTypeCheck(const Binary& vpdVector)
296 {
297     // Read first 3 Bytes to check the 11S bar code format
298     std::string is11SFormat = "";
299     for (uint8_t i = 0; i < FORMAT_11S_LEN; i++)
300     {
301         is11SFormat += vpdVector[MEMORY_VPD_DATA_START + i];
302     }
303 
304     if (vpdVector[IPZ_DATA_START] == KW_VAL_PAIR_START_TAG)
305     {
306         // IPZ VPD FORMAT
307         return vpdType::IPZ_VPD;
308     }
309     else if (vpdVector[KW_VPD_DATA_START] == KW_VPD_START_TAG)
310     {
311         // KEYWORD VPD FORMAT
312         return vpdType::KEYWORD_VPD;
313     }
314     else if (is11SFormat.compare(MEMORY_VPD_START_TAG) == 0)
315     {
316         // Memory VPD format
317         return vpdType::MEMORY_VPD;
318     }
319 
320     // INVALID VPD FORMAT
321     return vpdType::INVALID_VPD_FORMAT;
322 }
323 
324 const string getIM(const Parsed& vpdMap)
325 {
326     Binary imVal;
327     auto property = vpdMap.find("VSBP");
328     if (property != vpdMap.end())
329     {
330         auto kw = (property->second).find("IM");
331         if (kw != (property->second).end())
332         {
333             copy(kw->second.begin(), kw->second.end(), back_inserter(imVal));
334         }
335     }
336 
337     ostringstream oss;
338     for (auto& i : imVal)
339     {
340         oss << setw(2) << setfill('0') << hex << static_cast<int>(i);
341     }
342 
343     return oss.str();
344 }
345 
346 const string getHW(const Parsed& vpdMap)
347 {
348     Binary hwVal;
349     auto prop = vpdMap.find("VINI");
350     if (prop != vpdMap.end())
351     {
352         auto kw = (prop->second).find("HW");
353         if (kw != (prop->second).end())
354         {
355             copy(kw->second.begin(), kw->second.end(), back_inserter(hwVal));
356         }
357     }
358 
359     ostringstream hwString;
360     for (auto& i : hwVal)
361     {
362         hwString << setw(2) << setfill('0') << hex << static_cast<int>(i);
363     }
364 
365     return hwString.str();
366 }
367 
368 string getSystemsJson(const Parsed& vpdMap)
369 {
370     string jsonPath = "/usr/share/vpd/";
371     string jsonName{};
372 
373     ifstream systemJson(SYSTEM_JSON);
374     if (!systemJson)
375     {
376         throw((VpdJsonException("Failed to access Json path", SYSTEM_JSON)));
377     }
378 
379     try
380     {
381         auto js = json::parse(systemJson);
382 
383         const string hwKeyword = getHW(vpdMap);
384         const string imKeyword = getIM(vpdMap);
385 
386         if (js.find("system") == js.end())
387         {
388             throw runtime_error("Invalid systems Json");
389         }
390 
391         if (js["system"].find(imKeyword) == js["system"].end())
392         {
393             throw runtime_error(
394                 "Invalid system. This system type is not present "
395                 "in the systemsJson. IM: " +
396                 imKeyword);
397         }
398 
399         if ((js["system"][imKeyword].find("constraint") !=
400              js["system"][imKeyword].end()) &&
401             (hwKeyword == js["system"][imKeyword]["constraint"]["HW"]))
402         {
403             jsonName = js["system"][imKeyword]["constraint"]["json"];
404         }
405         else if (js["system"][imKeyword].find("default") !=
406                  js["system"][imKeyword].end())
407         {
408             jsonName = js["system"][imKeyword]["default"];
409         }
410         else
411         {
412             throw runtime_error(
413                 "Bad System json. Neither constraint nor default found");
414         }
415 
416         jsonPath += jsonName;
417     }
418 
419     catch (json::parse_error& ex)
420     {
421         throw(VpdJsonException("Json Parsing failed", SYSTEM_JSON));
422     }
423     return jsonPath;
424 }
425 
426 void udevToGenericPath(string& file)
427 {
428     // Sample udevEvent i2c path :
429     // "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a480.i2c-bus/i2c-8/8-0051/8-00510/nvmem"
430     // find if the path contains the word i2c in it.
431     if (file.find("i2c") != string::npos)
432     {
433         string i2cBusAddr{};
434 
435         // Every udev i2c path should have the common pattern
436         // "i2c-bus_number/bus_number-vpd_address". Search for
437         // "bus_number-vpd_address".
438         regex i2cPattern("((i2c)-[0-9]+\\/)([0-9]+-[0-9]{4})");
439         smatch match;
440         if (regex_search(file, match, i2cPattern))
441         {
442             i2cBusAddr = match.str(3);
443         }
444         else
445         {
446             cerr << "The given udev path < " << file
447                  << " > doesn't match the required pattern. Skipping VPD "
448                     "collection."
449                  << endl;
450             exit(EXIT_SUCCESS);
451         }
452         // Forming the generic file path
453         file = i2cPathPrefix + i2cBusAddr + "/eeprom";
454     }
455     // Sample udevEvent spi path :
456     // "/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"
457     // find if the path contains the word spi in it.
458     else if (file.find("spi") != string::npos)
459     {
460         // Every udev spi path will have common pattern "spi<Digit>/", which
461         // describes the spi bus number at which the fru is connected; Followed
462         // by a slash following the vpd address of the fru. Taking the above
463         // input as a common key, we try to search for the pattern "spi<Digit>/"
464         // using regular expression.
465         regex spiPattern("((spi)[0-9]+)(\\/)");
466         string spiBus{};
467         smatch match;
468         if (regex_search(file, match, spiPattern))
469         {
470             spiBus = match.str(1);
471         }
472         else
473         {
474             cerr << "The given udev path < " << file
475                  << " > doesn't match the required pattern. Skipping VPD "
476                     "collection."
477                  << endl;
478             exit(EXIT_SUCCESS);
479         }
480         // Forming the generic path
481         file = spiPathPrefix + spiBus + ".0/eeprom";
482     }
483     else
484     {
485         cerr << "\n The given EEPROM path < " << file
486              << " > is not valid. It's neither I2C nor "
487                 "SPI path. Skipping VPD collection.."
488              << endl;
489         exit(EXIT_SUCCESS);
490     }
491 }
492 string getBadVpdName(const string& file)
493 {
494     string badVpd = BAD_VPD_DIR;
495     if (file.find("i2c") != string::npos)
496     {
497         badVpd += "i2c-";
498         regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
499         smatch match;
500         if (regex_search(file, match, i2cPattern))
501         {
502             badVpd += match.str(2);
503         }
504     }
505     else if (file.find("spi") != string::npos)
506     {
507         regex spiPattern("((spi)[0-9]+)(.0)");
508         smatch match;
509         if (regex_search(file, match, spiPattern))
510         {
511             badVpd += match.str(1);
512         }
513     }
514     return badVpd;
515 }
516 
517 void dumpBadVpd(const string& file, const Binary& vpdVector)
518 {
519     fs::path badVpdDir = BAD_VPD_DIR;
520     fs::create_directory(badVpdDir);
521     string badVpdPath = getBadVpdName(file);
522     if (fs::exists(badVpdPath))
523     {
524         std::error_code ec;
525         fs::remove(badVpdPath, ec);
526         if (ec) // error code
527         {
528             string error = "Error removing the existing broken vpd in ";
529             error += badVpdPath;
530             error += ". Error code : ";
531             error += ec.value();
532             error += ". Error message : ";
533             error += ec.message();
534             throw runtime_error(error);
535         }
536     }
537     ofstream badVpdFileStream(badVpdPath, ofstream::binary);
538     if (!badVpdFileStream)
539     {
540         throw runtime_error("Failed to open bad vpd file path in /tmp/bad-vpd. "
541                             "Unable to dump the broken/bad vpd file.");
542     }
543     badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
544                            vpdVector.size());
545 }
546 } // namespace vpd
547 } // namespace openpower