xref: /openbmc/openpower-vpd-parser/vpd-manager/include/utility/vpd_specific_utility.hpp (revision fa5e4d325ef9cea3c841fe89d202c340f92bd8c6)
1 #pragma once
2 
3 #include "config.h"
4 
5 #include "constants.hpp"
6 #include "exceptions.hpp"
7 #include "logger.hpp"
8 #include "types.hpp"
9 
10 #include <nlohmann/json.hpp>
11 #include <utility/common_utility.hpp>
12 #include <utility/dbus_utility.hpp>
13 
14 #include <filesystem>
15 #include <fstream>
16 #include <regex>
17 
18 namespace vpd
19 {
20 namespace vpdSpecificUtility
21 {
22 /**
23  * @brief API to generate file name for bad VPD.
24  *
25  * For i2c eeproms - the pattern of the vpd-name will be
26  * i2c-<bus-number>-<eeprom-address>.
27  * For spi eeproms - the pattern of the vpd-name will be spi-<spi-number>.
28  *
29  * @param[in] vpdFilePath - file path of the vpd.
30  * @return Generated file name.
31  */
generateBadVPDFileName(const std::string & vpdFilePath)32 inline std::string generateBadVPDFileName(const std::string& vpdFilePath)
33 {
34     std::string badVpdFileName = BAD_VPD_DIR;
35     if (vpdFilePath.find("i2c") != std::string::npos)
36     {
37         badVpdFileName += "i2c-";
38         std::regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
39         std::smatch match;
40         if (std::regex_search(vpdFilePath, match, i2cPattern))
41         {
42             badVpdFileName += match.str(2);
43         }
44     }
45     else if (vpdFilePath.find("spi") != std::string::npos)
46     {
47         std::regex spiPattern("((spi)[0-9]+)(.0)");
48         std::smatch match;
49         if (std::regex_search(vpdFilePath, match, spiPattern))
50         {
51             badVpdFileName += match.str(1);
52         }
53     }
54     return badVpdFileName;
55 }
56 
57 /**
58  * @brief API which dumps the broken/bad vpd in a directory.
59  * When the vpd is bad, this API places  the bad vpd file inside
60  * "/tmp/bad-vpd" in BMC, in order to collect bad VPD data as a part of user
61  * initiated BMC dump.
62  *
63  * Note: Throws exception in case of any failure.
64  *
65  * @param[in] vpdFilePath - vpd file path
66  * @param[in] vpdVector - vpd vector
67  */
dumpBadVpd(const std::string & vpdFilePath,const types::BinaryVector & vpdVector)68 inline void dumpBadVpd(const std::string& vpdFilePath,
69                        const types::BinaryVector& vpdVector)
70 {
71     std::filesystem::create_directory(BAD_VPD_DIR);
72     auto badVpdPath = generateBadVPDFileName(vpdFilePath);
73 
74     if (std::filesystem::exists(badVpdPath))
75     {
76         std::error_code ec;
77         std::filesystem::remove(badVpdPath, ec);
78         if (ec) // error code
79         {
80             std::string error = "Error removing the existing broken vpd in ";
81             error += badVpdPath;
82             error += ". Error code : ";
83             error += ec.value();
84             error += ". Error message : ";
85             error += ec.message();
86             throw std::runtime_error(error);
87         }
88     }
89 
90     std::ofstream badVpdFileStream(badVpdPath, std::ofstream::binary);
91     if (badVpdFileStream.is_open())
92     {
93         throw std::runtime_error(
94             "Failed to open bad vpd file path in /tmp/bad-vpd. "
95             "Unable to dump the broken/bad vpd file.");
96     }
97 
98     badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
99                            vpdVector.size());
100 }
101 
102 /**
103  * @brief An API to read value of a keyword.
104  *
105  * Note: Throws exception. Caller needs to handle.
106  *
107  * @param[in] kwdValueMap - A map having Kwd value pair.
108  * @param[in] kwd - keyword name.
109  * @param[out] kwdValue - Value of the keyword read from map.
110  */
getKwVal(const types::IPZKwdValueMap & kwdValueMap,const std::string & kwd,std::string & kwdValue)111 inline void getKwVal(const types::IPZKwdValueMap& kwdValueMap,
112                      const std::string& kwd, std::string& kwdValue)
113 {
114     if (kwd.empty())
115     {
116         logging::logMessage("Invalid parameters");
117         throw std::runtime_error("Invalid parameters");
118     }
119 
120     auto itrToKwd = kwdValueMap.find(kwd);
121     if (itrToKwd != kwdValueMap.end())
122     {
123         kwdValue = itrToKwd->second;
124         return;
125     }
126 
127     throw std::runtime_error("Keyword not found");
128 }
129 
130 /**
131  * @brief An API to process encoding of a keyword.
132  *
133  * @param[in] keyword - Keyword to be processed.
134  * @param[in] encoding - Type of encoding.
135  * @return Value after being processed for encoded type.
136  */
encodeKeyword(const std::string & keyword,const std::string & encoding)137 inline std::string encodeKeyword(const std::string& keyword,
138                                  const std::string& encoding)
139 {
140     // Default value is keyword value
141     std::string result(keyword.begin(), keyword.end());
142 
143     if (encoding == "MAC")
144     {
145         result.clear();
146         size_t firstByte = keyword[0];
147         result += commonUtility::toHex(firstByte >> 4);
148         result += commonUtility::toHex(firstByte & 0x0f);
149         for (size_t i = 1; i < keyword.size(); ++i)
150         {
151             result += ":";
152             result += commonUtility::toHex(keyword[i] >> 4);
153             result += commonUtility::toHex(keyword[i] & 0x0f);
154         }
155     }
156     else if (encoding == "DATE")
157     {
158         // Date, represent as
159         // <year>-<month>-<day> <hour>:<min>
160         result.clear();
161         static constexpr uint8_t skipPrefix = 3;
162 
163         auto strItr = keyword.begin();
164         advance(strItr, skipPrefix);
165         for_each(strItr, keyword.end(), [&result](size_t c) { result += c; });
166 
167         result.insert(constants::BD_YEAR_END, 1, '-');
168         result.insert(constants::BD_MONTH_END, 1, '-');
169         result.insert(constants::BD_DAY_END, 1, ' ');
170         result.insert(constants::BD_HOUR_END, 1, ':');
171     }
172 
173     return result;
174 }
175 
176 /**
177  * @brief Helper function to insert or merge in map.
178  *
179  * This method checks in an interface if the given interface exists. If the
180  * interface key already exists, property map is inserted corresponding to it.
181  * If the key does'nt exist then given interface and property map pair is newly
182  * created. If the property present in propertymap already exist in the
183  * InterfaceMap, then the new property value is ignored.
184  *
185  * @param[in,out] map - Interface map.
186  * @param[in] interface - Interface to be processed.
187  * @param[in] propertyMap - new property map that needs to be emplaced.
188  */
insertOrMerge(types::InterfaceMap & map,const std::string & interface,types::PropertyMap && propertyMap)189 inline void insertOrMerge(types::InterfaceMap& map,
190                           const std::string& interface,
191                           types::PropertyMap&& propertyMap)
192 {
193     if (map.find(interface) != map.end())
194     {
195         try
196         {
197             auto& prop = map.at(interface);
198             std::for_each(propertyMap.begin(), propertyMap.end(),
199                           [&prop](auto l_keyValue) {
200                               prop[l_keyValue.first] = l_keyValue.second;
201                           });
202         }
203         catch (const std::exception& l_ex)
204         {
205             // ToDo:: Log PEL
206             logging::logMessage(
207                 "Inserting properties into interface[" + interface +
208                 "] map is failed, reason: " + std::string(l_ex.what()));
209         }
210     }
211     else
212     {
213         map.emplace(interface, propertyMap);
214     }
215 }
216 
217 /**
218  * @brief API to expand unpanded location code.
219  *
220  * Note: The API handles all the exception internally, in case of any error
221  * unexpanded location code will be returned as it is.
222  *
223  * @param[in] unexpandedLocationCode - Unexpanded location code.
224  * @param[in] parsedVpdMap - Parsed VPD map.
225  * @return Expanded location code. In case of any error, unexpanded is returned
226  * as it is.
227  */
228 inline std::string
getExpandedLocationCode(const std::string & unexpandedLocationCode,const types::VPDMapVariant & parsedVpdMap)229     getExpandedLocationCode(const std::string& unexpandedLocationCode,
230                             const types::VPDMapVariant& parsedVpdMap)
231 {
232     auto expanded{unexpandedLocationCode};
233 
234     try
235     {
236         // Expanded location code is formed by combining two keywords
237         // depending on type in unexpanded one. Second one is always "SE".
238         std::string kwd1, kwd2{constants::kwdSE};
239 
240         // interface to search for required keywords;
241         std::string kwdInterface;
242 
243         // record which holds the required keywords.
244         std::string recordName;
245 
246         auto pos = unexpandedLocationCode.find("fcs");
247         if (pos != std::string::npos)
248         {
249             kwd1 = constants::kwdFC;
250             kwdInterface = constants::vcenInf;
251             recordName = constants::recVCEN;
252         }
253         else
254         {
255             pos = unexpandedLocationCode.find("mts");
256             if (pos != std::string::npos)
257             {
258                 kwd1 = constants::kwdTM;
259                 kwdInterface = constants::vsysInf;
260                 recordName = constants::recVSYS;
261             }
262             else
263             {
264                 throw std::runtime_error(
265                     "Error detecting type of unexpanded location code.");
266             }
267         }
268 
269         std::string firstKwdValue, secondKwdValue;
270 
271         if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap);
272             ipzVpdMap && (*ipzVpdMap).find(recordName) != (*ipzVpdMap).end())
273         {
274             auto itrToVCEN = (*ipzVpdMap).find(recordName);
275             // The exceptions will be cautght at end.
276             getKwVal(itrToVCEN->second, kwd1, firstKwdValue);
277             getKwVal(itrToVCEN->second, kwd2, secondKwdValue);
278         }
279         else
280         {
281             std::array<const char*, 1> interfaceList = {kwdInterface.c_str()};
282 
283             types::MapperGetObject mapperRetValue = dbusUtility::getObjectMap(
284                 std::string(constants::systemVpdInvPath), interfaceList);
285 
286             if (mapperRetValue.empty())
287             {
288                 throw std::runtime_error("Mapper failed to get service");
289             }
290 
291             const std::string& serviceName = std::get<0>(mapperRetValue.at(0));
292 
293             auto retVal = dbusUtility::readDbusProperty(
294                 serviceName, std::string(constants::systemVpdInvPath),
295                 kwdInterface, kwd1);
296 
297             if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
298             {
299                 firstKwdValue.assign(
300                     reinterpret_cast<const char*>(kwdVal->data()),
301                     kwdVal->size());
302             }
303             else
304             {
305                 throw std::runtime_error(
306                     "Failed to read value of " + kwd1 + " from Bus");
307             }
308 
309             retVal = dbusUtility::readDbusProperty(
310                 serviceName, std::string(constants::systemVpdInvPath),
311                 kwdInterface, kwd2);
312 
313             if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
314             {
315                 secondKwdValue.assign(
316                     reinterpret_cast<const char*>(kwdVal->data()),
317                     kwdVal->size());
318             }
319             else
320             {
321                 throw std::runtime_error(
322                     "Failed to read value of " + kwd2 + " from Bus");
323             }
324         }
325 
326         if (unexpandedLocationCode.find("fcs") != std::string::npos)
327         {
328             // TODO: See if ND0 can be placed in the JSON
329             expanded.replace(
330                 pos, 3, firstKwdValue.substr(0, 4) + ".ND0." + secondKwdValue);
331         }
332         else
333         {
334             replace(firstKwdValue.begin(), firstKwdValue.end(), '-', '.');
335             expanded.replace(pos, 3, firstKwdValue + "." + secondKwdValue);
336         }
337     }
338     catch (const std::exception& ex)
339     {
340         logging::logMessage("Failed to expand location code with exception: " +
341                             std::string(ex.what()));
342     }
343 
344     return expanded;
345 }
346 
347 /**
348  * @brief An API to get VPD in a vector.
349  *
350  * The vector is required by the respective parser to fill the VPD map.
351  * Note: API throws exception in case of failure. Caller needs to handle.
352  *
353  * @param[in] vpdFilePath - EEPROM path of the FRU.
354  * @param[out] vpdVector - VPD in vector form.
355  * @param[in] vpdStartOffset - Offset of VPD data in EEPROM.
356  */
getVpdDataInVector(const std::string & vpdFilePath,types::BinaryVector & vpdVector,size_t & vpdStartOffset)357 inline void getVpdDataInVector(const std::string& vpdFilePath,
358                                types::BinaryVector& vpdVector,
359                                size_t& vpdStartOffset)
360 {
361     try
362     {
363         std::fstream vpdFileStream;
364         vpdFileStream.exceptions(
365             std::ifstream::badbit | std::ifstream::failbit);
366         vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::binary);
367         auto vpdSizeToRead = std::min(std::filesystem::file_size(vpdFilePath),
368                                       static_cast<uintmax_t>(65504));
369         vpdVector.resize(vpdSizeToRead);
370 
371         vpdFileStream.seekg(vpdStartOffset, std::ios_base::beg);
372         vpdFileStream.read(reinterpret_cast<char*>(&vpdVector[0]),
373                            vpdSizeToRead);
374 
375         vpdVector.resize(vpdFileStream.gcount());
376         vpdFileStream.clear(std::ios_base::eofbit);
377     }
378     catch (const std::ifstream::failure& fail)
379     {
380         std::cerr << "Exception in file handling [" << vpdFilePath
381                   << "] error : " << fail.what();
382         throw;
383     }
384 }
385 
386 /**
387  * @brief An API to get D-bus representation of given VPD keyword.
388  *
389  * @param[in] i_keywordName - VPD keyword name.
390  *
391  * @return D-bus representation of given keyword.
392  */
getDbusPropNameForGivenKw(const std::string & i_keywordName)393 inline std::string getDbusPropNameForGivenKw(const std::string& i_keywordName)
394 {
395     // Check for "#" prefixed VPD keyword.
396     if ((i_keywordName.size() == vpd::constants::TWO_BYTES) &&
397         (i_keywordName.at(0) == constants::POUND_KW))
398     {
399         // D-bus doesn't support "#". Replace "#" with "PD_" for those "#"
400         // prefixed keywords.
401         return (std::string(constants::POUND_KW_PREFIX) +
402                 i_keywordName.substr(1));
403     }
404 
405     // Return the keyword name back, if D-bus representation is same as the VPD
406     // keyword name.
407     return i_keywordName;
408 }
409 
410 /**
411  * @brief API to find CCIN in parsed VPD map.
412  *
413  * Few FRUs need some special handling. To identify those FRUs CCIN are used.
414  * The API will check from parsed VPD map if the FRU is the one with desired
415  * CCIN.
416  *
417  * @throw std::runtime_error
418  * @throw DataException
419  *
420  * @param[in] i_JsonObject - Any JSON which contains CCIN tag to match.
421  * @param[in] i_parsedVpdMap - Parsed VPD map.
422  * @return True if found, false otherwise.
423  */
findCcinInVpd(const nlohmann::json & i_JsonObject,const types::VPDMapVariant & i_parsedVpdMap)424 inline bool findCcinInVpd(const nlohmann::json& i_JsonObject,
425                           const types::VPDMapVariant& i_parsedVpdMap)
426 {
427     if (i_JsonObject.empty())
428     {
429         throw std::runtime_error("Json object is empty. Can't find CCIN");
430     }
431 
432     if (auto l_ipzVPDMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap))
433     {
434         auto l_itrToRec = (*l_ipzVPDMap).find("VINI");
435         if (l_itrToRec == (*l_ipzVPDMap).end())
436         {
437             throw DataException(
438                 "VINI record not found in parsed VPD. Can't find CCIN");
439         }
440 
441         std::string l_ccinFromVpd;
442         vpdSpecificUtility::getKwVal(l_itrToRec->second, "CC", l_ccinFromVpd);
443         if (l_ccinFromVpd.empty())
444         {
445             throw DataException("Empty CCIN value in VPD map. Can't find CCIN");
446         }
447 
448         transform(l_ccinFromVpd.begin(), l_ccinFromVpd.end(),
449                   l_ccinFromVpd.begin(), ::toupper);
450 
451         for (std::string l_ccinValue : i_JsonObject["ccin"])
452         {
453             transform(l_ccinValue.begin(), l_ccinValue.end(),
454                       l_ccinValue.begin(), ::toupper);
455 
456             if (l_ccinValue.compare(l_ccinFromVpd) ==
457                 constants::STR_CMP_SUCCESS)
458             {
459                 // CCIN found
460                 return true;
461             }
462         }
463 
464         logging::logMessage("No match found for CCIN");
465         return false;
466     }
467 
468     logging::logMessage("VPD type not supported. Can't find CCIN");
469     return false;
470 }
471 
472 /**
473  * @brief API to reset data of a FRU populated under PIM.
474  *
475  * This API resets the data for particular interfaces of a FRU under PIM.
476  *
477  * @param[in] i_objectPath - DBus object path of the FRU.
478  * @param[in] io_interfaceMap - Interface and its properties map.
479  */
resetDataUnderPIM(const std::string & i_objectPath,types::InterfaceMap & io_interfaceMap)480 inline void resetDataUnderPIM(const std::string& i_objectPath,
481                               types::InterfaceMap& io_interfaceMap)
482 {
483     try
484     {
485         std::array<const char*, 0> l_interfaces;
486         const types::MapperGetObject& l_getObjectMap =
487             dbusUtility::getObjectMap(i_objectPath, l_interfaces);
488 
489         const std::vector<std::string>& l_vpdRelatedInterfaces{
490             constants::operationalStatusInf, constants::inventoryItemInf,
491             constants::assetInf};
492 
493         for (const auto& [l_service, l_interfaceList] : l_getObjectMap)
494         {
495             if (l_service.compare(constants::pimServiceName) !=
496                 constants::STR_CMP_SUCCESS)
497             {
498                 continue;
499             }
500 
501             for (const auto& l_interface : l_interfaceList)
502             {
503                 if ((l_interface.find(constants::ipzVpdInf) !=
504                      std::string::npos) ||
505                     ((std::find(l_vpdRelatedInterfaces.begin(),
506                                 l_vpdRelatedInterfaces.end(), l_interface)) !=
507                      l_vpdRelatedInterfaces.end()))
508                 {
509                     const types::PropertyMap& l_propertyValueMap =
510                         dbusUtility::getPropertyMap(l_service, i_objectPath,
511                                                     l_interface);
512 
513                     types::PropertyMap l_propertyMap;
514 
515                     for (const auto& l_aProperty : l_propertyValueMap)
516                     {
517                         const std::string& l_propertyName = l_aProperty.first;
518                         const auto& l_propertyValue = l_aProperty.second;
519 
520                         if (std::holds_alternative<types::BinaryVector>(
521                                 l_propertyValue))
522                         {
523                             l_propertyMap.emplace(l_propertyName,
524                                                   types::BinaryVector{});
525                         }
526                         else if (std::holds_alternative<std::string>(
527                                      l_propertyValue))
528                         {
529                             l_propertyMap.emplace(l_propertyName,
530                                                   std::string{});
531                         }
532                         else if (std::holds_alternative<bool>(l_propertyValue))
533                         {
534                             // ToDo -- Update the functional status property
535                             // to true.
536                             if (l_propertyName.compare("Present") ==
537                                 constants::STR_CMP_SUCCESS)
538                             {
539                                 l_propertyMap.emplace(l_propertyName, false);
540                             }
541                         }
542                     }
543                     io_interfaceMap.emplace(l_interface,
544                                             std::move(l_propertyMap));
545                 }
546             }
547         }
548     }
549     catch (const std::exception& l_ex)
550     {
551         logging::logMessage("Failed to remove VPD for FRU: " + i_objectPath +
552                             " with error: " + std::string(l_ex.what()));
553     }
554 }
555 } // namespace vpdSpecificUtility
556 } // namespace vpd
557