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