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::exception& 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::exception& 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 (const 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 (const 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 // The planar pass only comes from the LSB of the HW keyword, 360 // where as the MSB is used for other purposes such as signifying clock 361 // termination. 362 hwVal[0] = 0x00; 363 364 ostringstream hwString; 365 for (auto& i : hwVal) 366 { 367 hwString << setw(2) << setfill('0') << hex << static_cast<int>(i); 368 } 369 370 return hwString.str(); 371 } 372 373 string getSystemsJson(const Parsed& vpdMap) 374 { 375 string jsonPath = "/usr/share/vpd/"; 376 string jsonName{}; 377 378 ifstream systemJson(SYSTEM_JSON); 379 if (!systemJson) 380 { 381 throw((VpdJsonException("Failed to access Json path", SYSTEM_JSON))); 382 } 383 384 try 385 { 386 auto js = json::parse(systemJson); 387 388 const string hwKeyword = getHW(vpdMap); 389 const string imKeyword = getIM(vpdMap); 390 391 if (js.find("system") == js.end()) 392 { 393 throw runtime_error("Invalid systems Json"); 394 } 395 396 if (js["system"].find(imKeyword) == js["system"].end()) 397 { 398 throw runtime_error( 399 "Invalid system. This system type is not present " 400 "in the systemsJson. IM: " + 401 imKeyword); 402 } 403 404 if ((js["system"][imKeyword].find("constraint") != 405 js["system"][imKeyword].end()) && 406 (hwKeyword == js["system"][imKeyword]["constraint"]["HW"])) 407 { 408 jsonName = js["system"][imKeyword]["constraint"]["json"]; 409 } 410 else if (js["system"][imKeyword].find("default") != 411 js["system"][imKeyword].end()) 412 { 413 jsonName = js["system"][imKeyword]["default"]; 414 } 415 else 416 { 417 throw runtime_error( 418 "Bad System json. Neither constraint nor default found"); 419 } 420 421 jsonPath += jsonName; 422 } 423 424 catch (const json::parse_error& ex) 425 { 426 throw(VpdJsonException("Json Parsing failed", SYSTEM_JSON)); 427 } 428 return jsonPath; 429 } 430 431 void udevToGenericPath(string& file) 432 { 433 // Sample udevEvent i2c path : 434 // "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a480.i2c-bus/i2c-8/8-0051/8-00510/nvmem" 435 // find if the path contains the word i2c in it. 436 if (file.find("i2c") != string::npos) 437 { 438 string i2cBusAddr{}; 439 440 // Every udev i2c path should have the common pattern 441 // "i2c-bus_number/bus_number-vpd_address". Search for 442 // "bus_number-vpd_address". 443 regex i2cPattern("((i2c)-[0-9]+\\/)([0-9]+-[0-9]{4})"); 444 smatch match; 445 if (regex_search(file, match, i2cPattern)) 446 { 447 i2cBusAddr = match.str(3); 448 } 449 else 450 { 451 cerr << "The given udev path < " << file 452 << " > doesn't match the required pattern. Skipping VPD " 453 "collection." 454 << endl; 455 exit(EXIT_SUCCESS); 456 } 457 // Forming the generic file path 458 file = i2cPathPrefix + i2cBusAddr + "/eeprom"; 459 } 460 // Sample udevEvent spi path : 461 // "/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" 462 // find if the path contains the word spi in it. 463 else if (file.find("spi") != string::npos) 464 { 465 // Every udev spi path will have common pattern "spi<Digit>/", which 466 // describes the spi bus number at which the fru is connected; Followed 467 // by a slash following the vpd address of the fru. Taking the above 468 // input as a common key, we try to search for the pattern "spi<Digit>/" 469 // using regular expression. 470 regex spiPattern("((spi)[0-9]+)(\\/)"); 471 string spiBus{}; 472 smatch match; 473 if (regex_search(file, match, spiPattern)) 474 { 475 spiBus = match.str(1); 476 } 477 else 478 { 479 cerr << "The given udev path < " << file 480 << " > doesn't match the required pattern. Skipping VPD " 481 "collection." 482 << endl; 483 exit(EXIT_SUCCESS); 484 } 485 // Forming the generic path 486 file = spiPathPrefix + spiBus + ".0/eeprom"; 487 } 488 else 489 { 490 cerr << "\n The given EEPROM path < " << file 491 << " > is not valid. It's neither I2C nor " 492 "SPI path. Skipping VPD collection.." 493 << endl; 494 exit(EXIT_SUCCESS); 495 } 496 } 497 string getBadVpdName(const string& file) 498 { 499 string badVpd = BAD_VPD_DIR; 500 if (file.find("i2c") != string::npos) 501 { 502 badVpd += "i2c-"; 503 regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/"); 504 smatch match; 505 if (regex_search(file, match, i2cPattern)) 506 { 507 badVpd += match.str(2); 508 } 509 } 510 else if (file.find("spi") != string::npos) 511 { 512 regex spiPattern("((spi)[0-9]+)(.0)"); 513 smatch match; 514 if (regex_search(file, match, spiPattern)) 515 { 516 badVpd += match.str(1); 517 } 518 } 519 return badVpd; 520 } 521 522 void dumpBadVpd(const string& file, const Binary& vpdVector) 523 { 524 fs::path badVpdDir = BAD_VPD_DIR; 525 fs::create_directory(badVpdDir); 526 string badVpdPath = getBadVpdName(file); 527 if (fs::exists(badVpdPath)) 528 { 529 std::error_code ec; 530 fs::remove(badVpdPath, ec); 531 if (ec) // error code 532 { 533 string error = "Error removing the existing broken vpd in "; 534 error += badVpdPath; 535 error += ". Error code : "; 536 error += ec.value(); 537 error += ". Error message : "; 538 error += ec.message(); 539 throw runtime_error(error); 540 } 541 } 542 ofstream badVpdFileStream(badVpdPath, ofstream::binary); 543 if (!badVpdFileStream) 544 { 545 throw runtime_error("Failed to open bad vpd file path in /tmp/bad-vpd. " 546 "Unable to dump the broken/bad vpd file."); 547 } 548 badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()), 549 vpdVector.size()); 550 } 551 552 const string getKwVal(const Parsed& vpdMap, const string& rec, 553 const string& kwd) 554 { 555 string kwVal{}; 556 557 auto findRec = vpdMap.find(rec); 558 559 // check if record is found in map we got by parser 560 if (findRec != vpdMap.end()) 561 { 562 auto findKwd = findRec->second.find(kwd); 563 564 if (findKwd != findRec->second.end()) 565 { 566 kwVal = findKwd->second; 567 } 568 } 569 570 return kwVal; 571 } 572 573 string byteArrayToHexString(const Binary& vec) 574 { 575 stringstream ss; 576 string hexRep = "0x"; 577 ss << hexRep; 578 string str = ss.str(); 579 580 // convert Decimal to Hex string 581 for (auto& v : vec) 582 { 583 ss << setfill('0') << setw(2) << hex << (int)v; 584 str = ss.str(); 585 } 586 return str; 587 } 588 589 string getPrintableValue(const Binary& vec) 590 { 591 string str{}; 592 593 // find for a non printable value in the vector 594 const auto it = std::find_if(vec.begin(), vec.end(), 595 [](const auto& ele) { return !isprint(ele); }); 596 597 if (it != vec.end()) // if the given vector has any non printable value 598 { 599 for (auto itr = it; itr != vec.end(); itr++) 600 { 601 if (*itr != 0x00) 602 { 603 str = byteArrayToHexString(vec); 604 return str; 605 } 606 } 607 str = string(vec.begin(), it); 608 } 609 else 610 { 611 str = string(vec.begin(), vec.end()); 612 } 613 return str; 614 } 615 616 } // namespace vpd 617 } // namespace openpower