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 <gpiod.hpp> 12 #include <iomanip> 13 #include <nlohmann/json.hpp> 14 #include <phosphor-logging/elog-errors.hpp> 15 #include <phosphor-logging/log.hpp> 16 #include <regex> 17 #include <sdbusplus/server.hpp> 18 #include <sstream> 19 #include <vector> 20 #include <xyz/openbmc_project/Common/error.hpp> 21 22 using json = nlohmann::json; 23 24 namespace openpower 25 { 26 namespace vpd 27 { 28 using namespace openpower::vpd::constants; 29 using namespace inventory; 30 using namespace phosphor::logging; 31 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 32 using namespace record; 33 using namespace openpower::vpd::exceptions; 34 using namespace common::utility; 35 using Severity = openpower::vpd::constants::PelSeverity; 36 namespace fs = std::filesystem; 37 38 // mapping of severity enum to severity interface 39 static std::unordered_map<Severity, std::string> sevMap = { 40 {Severity::INFORMATIONAL, 41 "xyz.openbmc_project.Logging.Entry.Level.Informational"}, 42 {Severity::DEBUG, "xyz.openbmc_project.Logging.Entry.Level.Debug"}, 43 {Severity::NOTICE, "xyz.openbmc_project.Logging.Entry.Level.Notice"}, 44 {Severity::WARNING, "xyz.openbmc_project.Logging.Entry.Level.Warning"}, 45 {Severity::CRITICAL, "xyz.openbmc_project.Logging.Entry.Level.Critical"}, 46 {Severity::EMERGENCY, "xyz.openbmc_project.Logging.Entry.Level.Emergency"}, 47 {Severity::ERROR, "xyz.openbmc_project.Logging.Entry.Level.Error"}, 48 {Severity::ALERT, "xyz.openbmc_project.Logging.Entry.Level.Alert"}}; 49 50 namespace inventory 51 { 52 53 MapperResponse 54 getObjectSubtreeForInterfaces(const std::string& root, const int32_t depth, 55 const std::vector<std::string>& interfaces) 56 { 57 auto bus = sdbusplus::bus::new_default(); 58 auto mapperCall = bus.new_method_call(mapperDestination, mapperObjectPath, 59 mapperInterface, "GetSubTree"); 60 mapperCall.append(root); 61 mapperCall.append(depth); 62 mapperCall.append(interfaces); 63 64 MapperResponse result = {}; 65 66 try 67 { 68 auto response = bus.call(mapperCall); 69 70 response.read(result); 71 } 72 catch (const sdbusplus::exception::exception& e) 73 { 74 log<level::ERR>("Error in mapper GetSubTree", 75 entry("ERROR=%s", e.what())); 76 } 77 78 return result; 79 } 80 81 } // namespace inventory 82 83 LE2ByteData readUInt16LE(Binary::const_iterator iterator) 84 { 85 LE2ByteData lowByte = *iterator; 86 LE2ByteData highByte = *(iterator + 1); 87 lowByte |= (highByte << 8); 88 return lowByte; 89 } 90 91 /** @brief Encodes a keyword for D-Bus. 92 */ 93 string encodeKeyword(const string& kw, const string& encoding) 94 { 95 if (encoding == "MAC") 96 { 97 string res{}; 98 size_t first = kw[0]; 99 res += toHex(first >> 4); 100 res += toHex(first & 0x0f); 101 for (size_t i = 1; i < kw.size(); ++i) 102 { 103 res += ":"; 104 res += toHex(kw[i] >> 4); 105 res += toHex(kw[i] & 0x0f); 106 } 107 return res; 108 } 109 else if (encoding == "DATE") 110 { 111 // Date, represent as 112 // <year>-<month>-<day> <hour>:<min> 113 string res{}; 114 static constexpr uint8_t skipPrefix = 3; 115 116 auto strItr = kw.begin(); 117 advance(strItr, skipPrefix); 118 for_each(strItr, kw.end(), [&res](size_t c) { res += c; }); 119 120 res.insert(BD_YEAR_END, 1, '-'); 121 res.insert(BD_MONTH_END, 1, '-'); 122 res.insert(BD_DAY_END, 1, ' '); 123 res.insert(BD_HOUR_END, 1, ':'); 124 125 return res; 126 } 127 else // default to string encoding 128 { 129 return string(kw.begin(), kw.end()); 130 } 131 } 132 133 string readBusProperty(const string& obj, const string& inf, const string& prop) 134 { 135 std::string propVal{}; 136 std::string object = INVENTORY_PATH + obj; 137 auto bus = sdbusplus::bus::new_default(); 138 auto properties = bus.new_method_call( 139 "xyz.openbmc_project.Inventory.Manager", object.c_str(), 140 "org.freedesktop.DBus.Properties", "Get"); 141 properties.append(inf); 142 properties.append(prop); 143 auto result = bus.call(properties); 144 if (!result.is_method_error()) 145 { 146 variant<Binary, string> val; 147 result.read(val); 148 if (auto pVal = get_if<Binary>(&val)) 149 { 150 propVal.assign(reinterpret_cast<const char*>(pVal->data()), 151 pVal->size()); 152 } 153 else if (auto pVal = get_if<string>(&val)) 154 { 155 propVal.assign(pVal->data(), pVal->size()); 156 } 157 } 158 return propVal; 159 } 160 161 void createPEL(const std::map<std::string, std::string>& additionalData, 162 const Severity& sev, const std::string& errIntf) 163 { 164 try 165 { 166 std::string pelSeverity = 167 "xyz.openbmc_project.Logging.Entry.Level.Error"; 168 auto bus = sdbusplus::bus::new_default(); 169 auto service = getService(bus, loggerObjectPath, loggerCreateInterface); 170 auto method = bus.new_method_call(service.c_str(), loggerObjectPath, 171 loggerCreateInterface, "Create"); 172 173 auto itr = sevMap.find(sev); 174 if (itr != sevMap.end()) 175 { 176 pelSeverity = itr->second; 177 } 178 179 method.append(errIntf, pelSeverity, additionalData); 180 auto resp = bus.call(method); 181 } 182 catch (const sdbusplus::exception::exception& e) 183 { 184 throw std::runtime_error( 185 "Error in invoking D-Bus logging create interface to register PEL"); 186 } 187 } 188 189 inventory::VPDfilepath getVpdFilePath(const string& jsonFile, 190 const std::string& ObjPath) 191 { 192 ifstream inventoryJson(jsonFile); 193 const auto& jsonObject = json::parse(inventoryJson); 194 inventory::VPDfilepath filePath{}; 195 196 if (jsonObject.find("frus") == jsonObject.end()) 197 { 198 throw(VpdJsonException( 199 "Invalid JSON structure - frus{} object not found in ", jsonFile)); 200 } 201 202 const nlohmann::json& groupFRUS = 203 jsonObject["frus"].get_ref<const nlohmann::json::object_t&>(); 204 for (const auto& itemFRUS : groupFRUS.items()) 205 { 206 const std::vector<nlohmann::json>& groupEEPROM = 207 itemFRUS.value().get_ref<const nlohmann::json::array_t&>(); 208 for (const auto& itemEEPROM : groupEEPROM) 209 { 210 if (itemEEPROM["inventoryPath"] 211 .get_ref<const nlohmann::json::string_t&>() == ObjPath) 212 { 213 filePath = itemFRUS.key(); 214 return filePath; 215 } 216 } 217 } 218 219 return filePath; 220 } 221 222 bool isPathInJson(const std::string& eepromPath) 223 { 224 bool present = false; 225 ifstream inventoryJson(INVENTORY_JSON_SYM_LINK); 226 227 try 228 { 229 auto js = json::parse(inventoryJson); 230 if (js.find("frus") == js.end()) 231 { 232 throw(VpdJsonException( 233 "Invalid JSON structure - frus{} object not found in ", 234 INVENTORY_JSON_SYM_LINK)); 235 } 236 json fruJson = js["frus"]; 237 238 if (fruJson.find(eepromPath) != fruJson.end()) 239 { 240 present = true; 241 } 242 } 243 catch (const json::parse_error& ex) 244 { 245 throw(VpdJsonException("Json Parsing failed", INVENTORY_JSON_SYM_LINK)); 246 } 247 return present; 248 } 249 250 bool isRecKwInDbusJson(const std::string& recordName, 251 const std::string& keyword) 252 { 253 ifstream propertyJson(DBUS_PROP_JSON); 254 json dbusProperty; 255 bool present = false; 256 257 if (propertyJson.is_open()) 258 { 259 try 260 { 261 auto dbusPropertyJson = json::parse(propertyJson); 262 if (dbusPropertyJson.find("dbusProperties") == 263 dbusPropertyJson.end()) 264 { 265 throw(VpdJsonException("dbusProperties{} object not found in " 266 "DbusProperties json : ", 267 DBUS_PROP_JSON)); 268 } 269 270 dbusProperty = dbusPropertyJson["dbusProperties"]; 271 if (dbusProperty.contains(recordName)) 272 { 273 const vector<string>& kwdsToPublish = dbusProperty[recordName]; 274 if (find(kwdsToPublish.begin(), kwdsToPublish.end(), keyword) != 275 kwdsToPublish.end()) // present 276 { 277 present = true; 278 } 279 } 280 } 281 catch (const json::parse_error& ex) 282 { 283 throw(VpdJsonException("Json Parsing failed", DBUS_PROP_JSON)); 284 } 285 } 286 else 287 { 288 // If dbus properties json is not available, we assume the given 289 // record-keyword is part of dbus-properties json. So setting the bool 290 // variable to true. 291 present = true; 292 } 293 return present; 294 } 295 296 vpdType vpdTypeCheck(const Binary& vpdVector) 297 { 298 // Read first 3 Bytes to check the 11S bar code format 299 std::string is11SFormat = ""; 300 for (uint8_t i = 0; i < FORMAT_11S_LEN; i++) 301 { 302 is11SFormat += vpdVector[MEMORY_VPD_DATA_START + i]; 303 } 304 305 if (vpdVector[IPZ_DATA_START] == KW_VAL_PAIR_START_TAG) 306 { 307 // IPZ VPD FORMAT 308 return vpdType::IPZ_VPD; 309 } 310 else if (vpdVector[KW_VPD_DATA_START] == KW_VPD_START_TAG) 311 { 312 // KEYWORD VPD FORMAT 313 return vpdType::KEYWORD_VPD; 314 } 315 else if (is11SFormat.compare(MEMORY_VPD_START_TAG) == 0) 316 { 317 // Memory VPD format 318 return vpdType::MEMORY_VPD; 319 } 320 321 // INVALID VPD FORMAT 322 return vpdType::INVALID_VPD_FORMAT; 323 } 324 325 const string getIM(const Parsed& vpdMap) 326 { 327 Binary imVal; 328 auto property = vpdMap.find("VSBP"); 329 if (property != vpdMap.end()) 330 { 331 auto kw = (property->second).find("IM"); 332 if (kw != (property->second).end()) 333 { 334 copy(kw->second.begin(), kw->second.end(), back_inserter(imVal)); 335 } 336 } 337 338 ostringstream oss; 339 for (auto& i : imVal) 340 { 341 oss << setw(2) << setfill('0') << hex << static_cast<int>(i); 342 } 343 344 return oss.str(); 345 } 346 347 const string getHW(const Parsed& vpdMap) 348 { 349 Binary hwVal; 350 auto prop = vpdMap.find("VINI"); 351 if (prop != vpdMap.end()) 352 { 353 auto kw = (prop->second).find("HW"); 354 if (kw != (prop->second).end()) 355 { 356 copy(kw->second.begin(), kw->second.end(), back_inserter(hwVal)); 357 } 358 } 359 360 // The planar pass only comes from the LSB of the HW keyword, 361 // where as the MSB is used for other purposes such as signifying clock 362 // termination. 363 hwVal[0] = 0x00; 364 365 ostringstream hwString; 366 for (auto& i : hwVal) 367 { 368 hwString << setw(2) << setfill('0') << hex << static_cast<int>(i); 369 } 370 371 return hwString.str(); 372 } 373 374 string getSystemsJson(const Parsed& vpdMap) 375 { 376 string jsonPath = "/usr/share/vpd/"; 377 string jsonName{}; 378 379 ifstream systemJson(SYSTEM_JSON); 380 if (!systemJson) 381 { 382 throw((VpdJsonException("Failed to access Json path", SYSTEM_JSON))); 383 } 384 385 try 386 { 387 auto js = json::parse(systemJson); 388 389 const string hwKeyword = getHW(vpdMap); 390 const string imKeyword = getIM(vpdMap); 391 392 if (js.find("system") == js.end()) 393 { 394 throw runtime_error("Invalid systems Json"); 395 } 396 397 if (js["system"].find(imKeyword) == js["system"].end()) 398 { 399 throw runtime_error( 400 "Invalid system. This system type is not present " 401 "in the systemsJson. IM: " + 402 imKeyword); 403 } 404 405 if ((js["system"][imKeyword].find("constraint") != 406 js["system"][imKeyword].end()) && 407 (hwKeyword == js["system"][imKeyword]["constraint"]["HW"])) 408 { 409 jsonName = js["system"][imKeyword]["constraint"]["json"]; 410 } 411 else if (js["system"][imKeyword].find("default") != 412 js["system"][imKeyword].end()) 413 { 414 jsonName = js["system"][imKeyword]["default"]; 415 } 416 else 417 { 418 throw runtime_error( 419 "Bad System json. Neither constraint nor default found"); 420 } 421 422 jsonPath += jsonName; 423 } 424 425 catch (const json::parse_error& ex) 426 { 427 throw(VpdJsonException("Json Parsing failed", SYSTEM_JSON)); 428 } 429 return jsonPath; 430 } 431 432 void udevToGenericPath(string& file) 433 { 434 // Sample udevEvent i2c path : 435 // "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a480.i2c-bus/i2c-8/8-0051/8-00510/nvmem" 436 // find if the path contains the word i2c in it. 437 if (file.find("i2c") != string::npos) 438 { 439 string i2cBusAddr{}; 440 441 // Every udev i2c path should have the common pattern 442 // "i2c-bus_number/bus_number-vpd_address". Search for 443 // "bus_number-vpd_address". 444 regex i2cPattern("((i2c)-[0-9]+\\/)([0-9]+-[0-9]{4})"); 445 smatch match; 446 if (regex_search(file, match, i2cPattern)) 447 { 448 i2cBusAddr = match.str(3); 449 } 450 else 451 { 452 cerr << "The given udev path < " << file 453 << " > doesn't match the required pattern. Skipping VPD " 454 "collection." 455 << endl; 456 exit(EXIT_SUCCESS); 457 } 458 // Forming the generic file path 459 file = i2cPathPrefix + i2cBusAddr + "/eeprom"; 460 } 461 // Sample udevEvent spi path : 462 // "/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" 463 // find if the path contains the word spi in it. 464 else if (file.find("spi") != string::npos) 465 { 466 // Every udev spi path will have common pattern "spi<Digit>/", which 467 // describes the spi bus number at which the fru is connected; Followed 468 // by a slash following the vpd address of the fru. Taking the above 469 // input as a common key, we try to search for the pattern "spi<Digit>/" 470 // using regular expression. 471 regex spiPattern("((spi)[0-9]+)(\\/)"); 472 string spiBus{}; 473 smatch match; 474 if (regex_search(file, match, spiPattern)) 475 { 476 spiBus = match.str(1); 477 } 478 else 479 { 480 cerr << "The given udev path < " << file 481 << " > doesn't match the required pattern. Skipping VPD " 482 "collection." 483 << endl; 484 exit(EXIT_SUCCESS); 485 } 486 // Forming the generic path 487 file = spiPathPrefix + spiBus + ".0/eeprom"; 488 } 489 else 490 { 491 cerr << "\n The given EEPROM path < " << file 492 << " > is not valid. It's neither I2C nor " 493 "SPI path. Skipping VPD collection.." 494 << endl; 495 exit(EXIT_SUCCESS); 496 } 497 } 498 string getBadVpdName(const string& file) 499 { 500 string badVpd = BAD_VPD_DIR; 501 if (file.find("i2c") != string::npos) 502 { 503 badVpd += "i2c-"; 504 regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/"); 505 smatch match; 506 if (regex_search(file, match, i2cPattern)) 507 { 508 badVpd += match.str(2); 509 } 510 } 511 else if (file.find("spi") != string::npos) 512 { 513 regex spiPattern("((spi)[0-9]+)(.0)"); 514 smatch match; 515 if (regex_search(file, match, spiPattern)) 516 { 517 badVpd += match.str(1); 518 } 519 } 520 return badVpd; 521 } 522 523 void dumpBadVpd(const string& file, const Binary& vpdVector) 524 { 525 fs::path badVpdDir = BAD_VPD_DIR; 526 fs::create_directory(badVpdDir); 527 string badVpdPath = getBadVpdName(file); 528 if (fs::exists(badVpdPath)) 529 { 530 std::error_code ec; 531 fs::remove(badVpdPath, ec); 532 if (ec) // error code 533 { 534 string error = "Error removing the existing broken vpd in "; 535 error += badVpdPath; 536 error += ". Error code : "; 537 error += ec.value(); 538 error += ". Error message : "; 539 error += ec.message(); 540 throw runtime_error(error); 541 } 542 } 543 ofstream badVpdFileStream(badVpdPath, ofstream::binary); 544 if (!badVpdFileStream) 545 { 546 throw runtime_error("Failed to open bad vpd file path in /tmp/bad-vpd. " 547 "Unable to dump the broken/bad vpd file."); 548 } 549 badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()), 550 vpdVector.size()); 551 } 552 553 const string getKwVal(const Parsed& vpdMap, const string& rec, 554 const string& kwd) 555 { 556 string kwVal{}; 557 558 auto findRec = vpdMap.find(rec); 559 560 // check if record is found in map we got by parser 561 if (findRec != vpdMap.end()) 562 { 563 auto findKwd = findRec->second.find(kwd); 564 565 if (findKwd != findRec->second.end()) 566 { 567 kwVal = findKwd->second; 568 } 569 } 570 571 return kwVal; 572 } 573 574 string byteArrayToHexString(const Binary& vec) 575 { 576 stringstream ss; 577 string hexRep = "0x"; 578 ss << hexRep; 579 string str = ss.str(); 580 581 // convert Decimal to Hex string 582 for (auto& v : vec) 583 { 584 ss << setfill('0') << setw(2) << hex << (int)v; 585 str = ss.str(); 586 } 587 return str; 588 } 589 590 string getPrintableValue(const Binary& vec) 591 { 592 string str{}; 593 594 // find for a non printable value in the vector 595 const auto it = std::find_if(vec.begin(), vec.end(), 596 [](const auto& ele) { return !isprint(ele); }); 597 598 if (it != vec.end()) // if the given vector has any non printable value 599 { 600 for (auto itr = it; itr != vec.end(); itr++) 601 { 602 if (*itr != 0x00) 603 { 604 str = byteArrayToHexString(vec); 605 return str; 606 } 607 } 608 str = string(vec.begin(), it); 609 } 610 else 611 { 612 str = string(vec.begin(), vec.end()); 613 } 614 return str; 615 } 616 617 void executePostFailAction(const nlohmann::json& json, const string& file) 618 { 619 if ((json["frus"][file].at(0)).find("postActionFail") == 620 json["frus"][file].at(0).end()) 621 { 622 return; 623 } 624 625 uint8_t pinValue = 0; 626 string pinName; 627 628 for (const auto& postAction : 629 (json["frus"][file].at(0))["postActionFail"].items()) 630 { 631 if (postAction.key() == "pin") 632 { 633 pinName = postAction.value(); 634 } 635 else if (postAction.key() == "value") 636 { 637 // Get the value to set 638 pinValue = postAction.value(); 639 } 640 } 641 642 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl; 643 644 try 645 { 646 gpiod::line outputLine = gpiod::find_line(pinName); 647 648 if (!outputLine) 649 { 650 cout << "Couldn't find output line:" << pinName 651 << " on GPIO. Skipping...\n"; 652 653 return; 654 } 655 outputLine.request( 656 {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 657 pinValue); 658 } 659 catch (const system_error&) 660 { 661 cerr << "Failed to set post-action GPIO" << endl; 662 } 663 } 664 665 std::optional<bool> isPresent(const nlohmann::json& json, const string& file) 666 667 { 668 if ((json["frus"][file].at(0)).find("presence") != 669 json["frus"][file].at(0).end()) 670 { 671 if (((json["frus"][file].at(0)["presence"]).find("pin") != 672 json["frus"][file].at(0)["presence"].end()) && 673 ((json["frus"][file].at(0)["presence"]).find("value") != 674 json["frus"][file].at(0)["presence"].end())) 675 { 676 string presPinName = json["frus"][file].at(0)["presence"]["pin"]; 677 Byte presPinValue = json["frus"][file].at(0)["presence"]["value"]; 678 679 try 680 { 681 gpiod::line presenceLine = gpiod::find_line(presPinName); 682 683 if (!presenceLine) 684 { 685 cerr << "couldn't find presence line:" << presPinName 686 << "\n"; 687 return false; 688 } 689 690 presenceLine.request({"Read the presence line", 691 gpiod::line_request::DIRECTION_INPUT, 0}); 692 693 Byte gpioData = presenceLine.get_value(); 694 695 return (gpioData == presPinValue); 696 } 697 catch (system_error&) 698 { 699 cerr << "Failed to get the presence GPIO for - " << presPinName 700 << endl; 701 return false; 702 } 703 } 704 } 705 return std::optional<bool>{}; 706 } 707 708 bool executePreAction(const nlohmann::json& json, const string& file) 709 { 710 auto present = isPresent(json, file); 711 if (present && !present.value()) 712 { 713 executePostFailAction(json, file); 714 return false; 715 } 716 717 if ((json["frus"][file].at(0)).find("preAction") != 718 json["frus"][file].at(0).end()) 719 { 720 if (((json["frus"][file].at(0)["preAction"]).find("pin") != 721 json["frus"][file].at(0)["preAction"].end()) && 722 ((json["frus"][file].at(0)["preAction"]).find("value") != 723 json["frus"][file].at(0)["preAction"].end())) 724 { 725 string pinName = json["frus"][file].at(0)["preAction"]["pin"]; 726 // Get the value to set 727 Byte pinValue = json["frus"][file].at(0)["preAction"]["value"]; 728 729 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue 730 << endl; 731 try 732 { 733 gpiod::line outputLine = gpiod::find_line(pinName); 734 735 if (!outputLine) 736 { 737 cout << "Couldn't find output line:" << pinName 738 << " on GPIO. Skipping...\n"; 739 740 return false; 741 } 742 outputLine.request({"FRU pre-action", 743 ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 744 pinValue); 745 } 746 catch (system_error&) 747 { 748 cerr << "Failed to set pre-action for GPIO - " << pinName 749 << endl; 750 return false; 751 } 752 } 753 } 754 return true; 755 } 756 757 void insertOrMerge(inventory::InterfaceMap& map, 758 const inventory::Interface& interface, 759 inventory::PropertyMap&& property) 760 { 761 if (map.find(interface) != map.end()) 762 { 763 auto& prop = map.at(interface); 764 prop.insert(property.begin(), property.end()); 765 } 766 else 767 { 768 map.emplace(interface, property); 769 } 770 } 771 } // namespace vpd 772 } // namespace openpower 773