blob: 65ff4f8027964340d02160825549450e2ff224c2 [file] [log] [blame]
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -05001#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
18namespace vpd
19{
20namespace 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 *
Souvik Roy8fc12522025-02-19 01:28:38 -060029 * @param[in] i_vpdFilePath - file path of the vpd.
30 *
31 * @return On success, returns generated file name, otherwise returns empty
32 * string.
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050033 */
Souvik Roy8fc12522025-02-19 01:28:38 -060034inline std::string generateBadVPDFileName(
35 const std::string& i_vpdFilePath) noexcept
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050036{
Souvik Roy8fc12522025-02-19 01:28:38 -060037 std::string l_badVpdFileName{BAD_VPD_DIR};
38 try
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050039 {
Souvik Roy8fc12522025-02-19 01:28:38 -060040 if (i_vpdFilePath.find("i2c") != std::string::npos)
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050041 {
Souvik Roy8fc12522025-02-19 01:28:38 -060042 l_badVpdFileName += "i2c-";
43 std::regex l_i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
44 std::smatch l_match;
45 if (std::regex_search(i_vpdFilePath, l_match, l_i2cPattern))
46 {
47 l_badVpdFileName += l_match.str(2);
48 }
49 }
50 else if (i_vpdFilePath.find("spi") != std::string::npos)
51 {
52 std::regex l_spiPattern("((spi)[0-9]+)(.0)");
53 std::smatch l_match;
54 if (std::regex_search(i_vpdFilePath, l_match, l_spiPattern))
55 {
56 l_badVpdFileName += l_match.str(1);
57 }
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050058 }
59 }
Souvik Roy8fc12522025-02-19 01:28:38 -060060 catch (const std::exception& l_ex)
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050061 {
Souvik Roy8fc12522025-02-19 01:28:38 -060062 l_badVpdFileName.clear();
63 logging::logMessage("Failed to generate bad VPD file name for [" +
64 i_vpdFilePath + "]. Error: " + l_ex.what());
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050065 }
Souvik Roy8fc12522025-02-19 01:28:38 -060066 return l_badVpdFileName;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050067}
68
69/**
70 * @brief API which dumps the broken/bad vpd in a directory.
71 * When the vpd is bad, this API places the bad vpd file inside
72 * "/tmp/bad-vpd" in BMC, in order to collect bad VPD data as a part of user
73 * initiated BMC dump.
74 *
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050075 *
Souvik Roy8fc12522025-02-19 01:28:38 -060076 * @param[in] i_vpdFilePath - vpd file path
77 * @param[in] i_vpdVector - vpd vector
78 *
79 * @return On success returns 0, otherwise returns -1.
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050080 */
Souvik Roy8fc12522025-02-19 01:28:38 -060081inline int dumpBadVpd(const std::string& i_vpdFilePath,
82 const types::BinaryVector& i_vpdVector) noexcept
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050083{
Souvik Roy8fc12522025-02-19 01:28:38 -060084 int l_rc{constants::FAILURE};
85 try
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050086 {
Souvik Roy8fc12522025-02-19 01:28:38 -060087 std::filesystem::create_directory(BAD_VPD_DIR);
88 auto l_badVpdPath = generateBadVPDFileName(i_vpdFilePath);
89
90 if (l_badVpdPath.empty())
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050091 {
Souvik Roy8fc12522025-02-19 01:28:38 -060092 throw std::runtime_error("Failed to generate bad VPD file name");
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050093 }
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050094
Souvik Roy8fc12522025-02-19 01:28:38 -060095 if (std::filesystem::exists(l_badVpdPath))
96 {
97 std::error_code l_ec;
98 std::filesystem::remove(l_badVpdPath, l_ec);
99 if (l_ec) // error code
100 {
101 const std::string l_errorMsg{
102 "Error removing the existing broken vpd in " +
103 l_badVpdPath +
104 ". Error code : " + std::to_string(l_ec.value()) +
105 ". Error message : " + l_ec.message()};
106
107 throw std::runtime_error(l_errorMsg);
108 }
109 }
110
111 std::ofstream l_badVpdFileStream(l_badVpdPath, std::ofstream::binary);
112 if (!l_badVpdFileStream.is_open())
113 {
114 throw std::runtime_error(
115 "Failed to open bad vpd file path in /tmp/bad-vpd. "
116 "Unable to dump the broken/bad vpd file.");
117 }
118
119 l_badVpdFileStream.write(
120 reinterpret_cast<const char*>(i_vpdVector.data()),
121 i_vpdVector.size());
122
123 l_rc = constants::SUCCESS;
124 }
125 catch (const std::exception& l_ex)
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500126 {
Souvik Roy8fc12522025-02-19 01:28:38 -0600127 logging::logMessage("Failed to dump bad VPD for [" + i_vpdFilePath +
128 "]. Error: " + l_ex.what());
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500129 }
Souvik Roy8fc12522025-02-19 01:28:38 -0600130 return l_rc;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500131}
132
133/**
134 * @brief An API to read value of a keyword.
135 *
136 * Note: Throws exception. Caller needs to handle.
137 *
138 * @param[in] kwdValueMap - A map having Kwd value pair.
139 * @param[in] kwd - keyword name.
140 * @param[out] kwdValue - Value of the keyword read from map.
141 */
142inline void getKwVal(const types::IPZKwdValueMap& kwdValueMap,
143 const std::string& kwd, std::string& kwdValue)
144{
145 if (kwd.empty())
146 {
147 logging::logMessage("Invalid parameters");
148 throw std::runtime_error("Invalid parameters");
149 }
150
151 auto itrToKwd = kwdValueMap.find(kwd);
152 if (itrToKwd != kwdValueMap.end())
153 {
154 kwdValue = itrToKwd->second;
155 return;
156 }
157
158 throw std::runtime_error("Keyword not found");
159}
160
161/**
162 * @brief An API to process encoding of a keyword.
163 *
164 * @param[in] keyword - Keyword to be processed.
165 * @param[in] encoding - Type of encoding.
166 * @return Value after being processed for encoded type.
167 */
168inline std::string encodeKeyword(const std::string& keyword,
169 const std::string& encoding)
170{
171 // Default value is keyword value
172 std::string result(keyword.begin(), keyword.end());
173
174 if (encoding == "MAC")
175 {
176 result.clear();
177 size_t firstByte = keyword[0];
178 result += commonUtility::toHex(firstByte >> 4);
179 result += commonUtility::toHex(firstByte & 0x0f);
180 for (size_t i = 1; i < keyword.size(); ++i)
181 {
182 result += ":";
183 result += commonUtility::toHex(keyword[i] >> 4);
184 result += commonUtility::toHex(keyword[i] & 0x0f);
185 }
186 }
187 else if (encoding == "DATE")
188 {
189 // Date, represent as
190 // <year>-<month>-<day> <hour>:<min>
191 result.clear();
192 static constexpr uint8_t skipPrefix = 3;
193
194 auto strItr = keyword.begin();
195 advance(strItr, skipPrefix);
196 for_each(strItr, keyword.end(), [&result](size_t c) { result += c; });
197
198 result.insert(constants::BD_YEAR_END, 1, '-');
199 result.insert(constants::BD_MONTH_END, 1, '-');
200 result.insert(constants::BD_DAY_END, 1, ' ');
201 result.insert(constants::BD_HOUR_END, 1, ':');
202 }
203
204 return result;
205}
206
207/**
208 * @brief Helper function to insert or merge in map.
209 *
210 * This method checks in an interface if the given interface exists. If the
211 * interface key already exists, property map is inserted corresponding to it.
212 * If the key does'nt exist then given interface and property map pair is newly
213 * created. If the property present in propertymap already exist in the
214 * InterfaceMap, then the new property value is ignored.
215 *
216 * @param[in,out] map - Interface map.
217 * @param[in] interface - Interface to be processed.
218 * @param[in] propertyMap - new property map that needs to be emplaced.
219 */
220inline void insertOrMerge(types::InterfaceMap& map,
221 const std::string& interface,
222 types::PropertyMap&& propertyMap)
223{
224 if (map.find(interface) != map.end())
225 {
226 try
227 {
228 auto& prop = map.at(interface);
229 std::for_each(propertyMap.begin(), propertyMap.end(),
230 [&prop](auto l_keyValue) {
231 prop[l_keyValue.first] = l_keyValue.second;
232 });
233 }
234 catch (const std::exception& l_ex)
235 {
236 // ToDo:: Log PEL
237 logging::logMessage(
238 "Inserting properties into interface[" + interface +
239 "] map is failed, reason: " + std::string(l_ex.what()));
240 }
241 }
242 else
243 {
244 map.emplace(interface, propertyMap);
245 }
246}
247
248/**
249 * @brief API to expand unpanded location code.
250 *
251 * Note: The API handles all the exception internally, in case of any error
252 * unexpanded location code will be returned as it is.
253 *
254 * @param[in] unexpandedLocationCode - Unexpanded location code.
255 * @param[in] parsedVpdMap - Parsed VPD map.
256 * @return Expanded location code. In case of any error, unexpanded is returned
257 * as it is.
258 */
Patrick Williams43fedab2025-02-03 14:28:05 -0500259inline std::string getExpandedLocationCode(
260 const std::string& unexpandedLocationCode,
261 const types::VPDMapVariant& parsedVpdMap)
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500262{
263 auto expanded{unexpandedLocationCode};
264
265 try
266 {
267 // Expanded location code is formed by combining two keywords
268 // depending on type in unexpanded one. Second one is always "SE".
269 std::string kwd1, kwd2{constants::kwdSE};
270
271 // interface to search for required keywords;
272 std::string kwdInterface;
273
274 // record which holds the required keywords.
275 std::string recordName;
276
277 auto pos = unexpandedLocationCode.find("fcs");
278 if (pos != std::string::npos)
279 {
280 kwd1 = constants::kwdFC;
281 kwdInterface = constants::vcenInf;
282 recordName = constants::recVCEN;
283 }
284 else
285 {
286 pos = unexpandedLocationCode.find("mts");
287 if (pos != std::string::npos)
288 {
289 kwd1 = constants::kwdTM;
290 kwdInterface = constants::vsysInf;
291 recordName = constants::recVSYS;
292 }
293 else
294 {
295 throw std::runtime_error(
296 "Error detecting type of unexpanded location code.");
297 }
298 }
299
300 std::string firstKwdValue, secondKwdValue;
301
302 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap);
303 ipzVpdMap && (*ipzVpdMap).find(recordName) != (*ipzVpdMap).end())
304 {
305 auto itrToVCEN = (*ipzVpdMap).find(recordName);
306 // The exceptions will be cautght at end.
307 getKwVal(itrToVCEN->second, kwd1, firstKwdValue);
308 getKwVal(itrToVCEN->second, kwd2, secondKwdValue);
309 }
310 else
311 {
312 std::array<const char*, 1> interfaceList = {kwdInterface.c_str()};
313
314 types::MapperGetObject mapperRetValue = dbusUtility::getObjectMap(
315 std::string(constants::systemVpdInvPath), interfaceList);
316
317 if (mapperRetValue.empty())
318 {
319 throw std::runtime_error("Mapper failed to get service");
320 }
321
322 const std::string& serviceName = std::get<0>(mapperRetValue.at(0));
323
324 auto retVal = dbusUtility::readDbusProperty(
325 serviceName, std::string(constants::systemVpdInvPath),
326 kwdInterface, kwd1);
327
328 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
329 {
330 firstKwdValue.assign(
331 reinterpret_cast<const char*>(kwdVal->data()),
332 kwdVal->size());
333 }
334 else
335 {
336 throw std::runtime_error(
337 "Failed to read value of " + kwd1 + " from Bus");
338 }
339
340 retVal = dbusUtility::readDbusProperty(
341 serviceName, std::string(constants::systemVpdInvPath),
342 kwdInterface, kwd2);
343
344 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
345 {
346 secondKwdValue.assign(
347 reinterpret_cast<const char*>(kwdVal->data()),
348 kwdVal->size());
349 }
350 else
351 {
352 throw std::runtime_error(
353 "Failed to read value of " + kwd2 + " from Bus");
354 }
355 }
356
357 if (unexpandedLocationCode.find("fcs") != std::string::npos)
358 {
359 // TODO: See if ND0 can be placed in the JSON
360 expanded.replace(
361 pos, 3, firstKwdValue.substr(0, 4) + ".ND0." + secondKwdValue);
362 }
363 else
364 {
365 replace(firstKwdValue.begin(), firstKwdValue.end(), '-', '.');
366 expanded.replace(pos, 3, firstKwdValue + "." + secondKwdValue);
367 }
368 }
369 catch (const std::exception& ex)
370 {
371 logging::logMessage("Failed to expand location code with exception: " +
372 std::string(ex.what()));
373 }
374
375 return expanded;
376}
377
378/**
379 * @brief An API to get VPD in a vector.
380 *
381 * The vector is required by the respective parser to fill the VPD map.
382 * Note: API throws exception in case of failure. Caller needs to handle.
383 *
384 * @param[in] vpdFilePath - EEPROM path of the FRU.
385 * @param[out] vpdVector - VPD in vector form.
386 * @param[in] vpdStartOffset - Offset of VPD data in EEPROM.
387 */
388inline void getVpdDataInVector(const std::string& vpdFilePath,
389 types::BinaryVector& vpdVector,
390 size_t& vpdStartOffset)
391{
392 try
393 {
394 std::fstream vpdFileStream;
395 vpdFileStream.exceptions(
396 std::ifstream::badbit | std::ifstream::failbit);
397 vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::binary);
398 auto vpdSizeToRead = std::min(std::filesystem::file_size(vpdFilePath),
399 static_cast<uintmax_t>(65504));
400 vpdVector.resize(vpdSizeToRead);
401
402 vpdFileStream.seekg(vpdStartOffset, std::ios_base::beg);
403 vpdFileStream.read(reinterpret_cast<char*>(&vpdVector[0]),
404 vpdSizeToRead);
405
406 vpdVector.resize(vpdFileStream.gcount());
407 vpdFileStream.clear(std::ios_base::eofbit);
408 }
409 catch (const std::ifstream::failure& fail)
410 {
411 std::cerr << "Exception in file handling [" << vpdFilePath
412 << "] error : " << fail.what();
413 throw;
414 }
415}
416
417/**
418 * @brief An API to get D-bus representation of given VPD keyword.
419 *
420 * @param[in] i_keywordName - VPD keyword name.
421 *
422 * @return D-bus representation of given keyword.
423 */
424inline std::string getDbusPropNameForGivenKw(const std::string& i_keywordName)
425{
426 // Check for "#" prefixed VPD keyword.
427 if ((i_keywordName.size() == vpd::constants::TWO_BYTES) &&
428 (i_keywordName.at(0) == constants::POUND_KW))
429 {
430 // D-bus doesn't support "#". Replace "#" with "PD_" for those "#"
431 // prefixed keywords.
432 return (std::string(constants::POUND_KW_PREFIX) +
433 i_keywordName.substr(1));
434 }
435
436 // Return the keyword name back, if D-bus representation is same as the VPD
437 // keyword name.
438 return i_keywordName;
439}
440
441/**
442 * @brief API to find CCIN in parsed VPD map.
443 *
444 * Few FRUs need some special handling. To identify those FRUs CCIN are used.
445 * The API will check from parsed VPD map if the FRU is the one with desired
446 * CCIN.
447 *
448 * @throw std::runtime_error
449 * @throw DataException
450 *
451 * @param[in] i_JsonObject - Any JSON which contains CCIN tag to match.
452 * @param[in] i_parsedVpdMap - Parsed VPD map.
453 * @return True if found, false otherwise.
454 */
455inline bool findCcinInVpd(const nlohmann::json& i_JsonObject,
456 const types::VPDMapVariant& i_parsedVpdMap)
457{
458 if (i_JsonObject.empty())
459 {
460 throw std::runtime_error("Json object is empty. Can't find CCIN");
461 }
462
463 if (auto l_ipzVPDMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap))
464 {
465 auto l_itrToRec = (*l_ipzVPDMap).find("VINI");
466 if (l_itrToRec == (*l_ipzVPDMap).end())
467 {
468 throw DataException(
469 "VINI record not found in parsed VPD. Can't find CCIN");
470 }
471
472 std::string l_ccinFromVpd;
473 vpdSpecificUtility::getKwVal(l_itrToRec->second, "CC", l_ccinFromVpd);
474 if (l_ccinFromVpd.empty())
475 {
476 throw DataException("Empty CCIN value in VPD map. Can't find CCIN");
477 }
478
479 transform(l_ccinFromVpd.begin(), l_ccinFromVpd.end(),
480 l_ccinFromVpd.begin(), ::toupper);
481
482 for (std::string l_ccinValue : i_JsonObject["ccin"])
483 {
484 transform(l_ccinValue.begin(), l_ccinValue.end(),
485 l_ccinValue.begin(), ::toupper);
486
487 if (l_ccinValue.compare(l_ccinFromVpd) ==
488 constants::STR_CMP_SUCCESS)
489 {
490 // CCIN found
491 return true;
492 }
493 }
494
495 logging::logMessage("No match found for CCIN");
496 return false;
497 }
498
499 logging::logMessage("VPD type not supported. Can't find CCIN");
500 return false;
501}
502
503/**
504 * @brief API to reset data of a FRU populated under PIM.
505 *
506 * This API resets the data for particular interfaces of a FRU under PIM.
507 *
508 * @param[in] i_objectPath - DBus object path of the FRU.
509 * @param[in] io_interfaceMap - Interface and its properties map.
510 */
511inline void resetDataUnderPIM(const std::string& i_objectPath,
512 types::InterfaceMap& io_interfaceMap)
513{
514 try
515 {
516 std::array<const char*, 0> l_interfaces;
517 const types::MapperGetObject& l_getObjectMap =
518 dbusUtility::getObjectMap(i_objectPath, l_interfaces);
519
520 const std::vector<std::string>& l_vpdRelatedInterfaces{
521 constants::operationalStatusInf, constants::inventoryItemInf,
522 constants::assetInf};
523
524 for (const auto& [l_service, l_interfaceList] : l_getObjectMap)
525 {
526 if (l_service.compare(constants::pimServiceName) !=
527 constants::STR_CMP_SUCCESS)
528 {
529 continue;
530 }
531
532 for (const auto& l_interface : l_interfaceList)
533 {
534 if ((l_interface.find(constants::ipzVpdInf) !=
535 std::string::npos) ||
536 ((std::find(l_vpdRelatedInterfaces.begin(),
537 l_vpdRelatedInterfaces.end(), l_interface)) !=
538 l_vpdRelatedInterfaces.end()))
539 {
540 const types::PropertyMap& l_propertyValueMap =
541 dbusUtility::getPropertyMap(l_service, i_objectPath,
542 l_interface);
543
544 types::PropertyMap l_propertyMap;
545
546 for (const auto& l_aProperty : l_propertyValueMap)
547 {
548 const std::string& l_propertyName = l_aProperty.first;
549 const auto& l_propertyValue = l_aProperty.second;
550
551 if (std::holds_alternative<types::BinaryVector>(
552 l_propertyValue))
553 {
554 l_propertyMap.emplace(l_propertyName,
555 types::BinaryVector{});
556 }
557 else if (std::holds_alternative<std::string>(
558 l_propertyValue))
559 {
560 l_propertyMap.emplace(l_propertyName,
561 std::string{});
562 }
563 else if (std::holds_alternative<bool>(l_propertyValue))
564 {
565 // ToDo -- Update the functional status property
566 // to true.
567 if (l_propertyName.compare("Present") ==
568 constants::STR_CMP_SUCCESS)
569 {
570 l_propertyMap.emplace(l_propertyName, false);
571 }
572 }
573 }
574 io_interfaceMap.emplace(l_interface,
575 std::move(l_propertyMap));
576 }
577 }
578 }
579 }
580 catch (const std::exception& l_ex)
581 {
582 logging::logMessage("Failed to remove VPD for FRU: " + i_objectPath +
583 " with error: " + std::string(l_ex.what()));
584 }
585}
Sunny Srivastava78c91072025-02-05 14:09:50 +0530586
587/**
588 * @brief API to detect pass1 planar type.
589 *
590 * Based on HW version and IM keyword, This API detects is it is a pass1 planar
591 * or not.
592 *
593 * @return True if pass 1 planar, false otherwise.
594 */
595inline bool isPass1Planar()
596{
597 auto l_retVal = dbusUtility::readDbusProperty(
598 constants::pimServiceName, constants::systemVpdInvPath,
599 constants::viniInf, constants::kwdHW);
600
601 auto l_hwVer = std::get_if<types::BinaryVector>(&l_retVal);
602
603 l_retVal = dbusUtility::readDbusProperty(
604 constants::pimServiceName, constants::systemInvPath, constants::vsbpInf,
605 constants::kwdIM);
606
607 auto l_imValue = std::get_if<types::BinaryVector>(&l_retVal);
608
609 if (l_hwVer && l_imValue)
610 {
611 types::BinaryVector everest{80, 00, 48, 00};
612 types::BinaryVector fuji{96, 00, 32, 00};
613
614 if (((*l_imValue) == everest) || ((*l_imValue) == fuji))
615 {
616 if ((*l_hwVer).at(1) < constants::VALUE_21)
617 {
618 return true;
619 }
620 }
621 else if ((*l_hwVer).at(1) < constants::VALUE_2)
622 {
623 return true;
624 }
625 }
626
627 return false;
628}
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500629} // namespace vpdSpecificUtility
630} // namespace vpd