1 #include "config.h" 2 3 #include "defines.hpp" 4 #include "ipz_parser.hpp" 5 #include "keyword_vpd_parser.hpp" 6 #include "memory_vpd_parser.hpp" 7 #include "parser_factory.hpp" 8 #include "utils.hpp" 9 10 #include <ctype.h> 11 12 #include <CLI/CLI.hpp> 13 #include <algorithm> 14 #include <cstdarg> 15 #include <exception> 16 #include <filesystem> 17 #include <fstream> 18 #include <iostream> 19 #include <iterator> 20 #include <nlohmann/json.hpp> 21 22 using namespace std; 23 using namespace openpower::vpd; 24 using namespace CLI; 25 using namespace vpd::keyword::parser; 26 using namespace openpower::vpd::constants; 27 namespace fs = filesystem; 28 using json = nlohmann::json; 29 using namespace openpower::vpd::parser::factory; 30 using namespace openpower::vpd::inventory; 31 using namespace openpower::vpd::memory::parser; 32 using namespace openpower::vpd::parser::interface; 33 34 static const deviceTreeMap deviceTreeSystemTypeMap = { 35 {RAINIER_2U, "conf@aspeed-bmc-ibm-rainier-2u.dtb"}, 36 {RAINIER_4U, "conf@aspeed-bmc-ibm-rainier-4u.dtb"}}; 37 38 /** 39 * @brief Expands location codes 40 */ 41 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap, 42 bool isSystemVpd) 43 { 44 auto expanded{unexpanded}; 45 static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard"; 46 static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN"; 47 static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS"; 48 size_t idx = expanded.find("fcs"); 49 try 50 { 51 if (idx != string::npos) 52 { 53 string fc{}; 54 string se{}; 55 if (isSystemVpd) 56 { 57 const auto& fcData = vpdMap.at("VCEN").at("FC"); 58 const auto& seData = vpdMap.at("VCEN").at("SE"); 59 fc = string(fcData.data(), fcData.size()); 60 se = string(seData.data(), seData.size()); 61 } 62 else 63 { 64 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC"); 65 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE"); 66 } 67 68 // TODO: See if ND1 can be placed in the JSON 69 expanded.replace(idx, 3, fc.substr(0, 4) + ".ND1." + se); 70 } 71 else 72 { 73 idx = expanded.find("mts"); 74 if (idx != string::npos) 75 { 76 string mt{}; 77 string se{}; 78 if (isSystemVpd) 79 { 80 const auto& mtData = vpdMap.at("VSYS").at("TM"); 81 const auto& seData = vpdMap.at("VSYS").at("SE"); 82 mt = string(mtData.data(), mtData.size()); 83 se = string(seData.data(), seData.size()); 84 } 85 else 86 { 87 mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM"); 88 se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE"); 89 } 90 91 replace(mt.begin(), mt.end(), '-', '.'); 92 expanded.replace(idx, 3, mt + "." + se); 93 } 94 } 95 } 96 catch (exception& e) 97 { 98 cerr << "Failed to expand location code with exception: " << e.what() 99 << "\n"; 100 } 101 return expanded; 102 } 103 /** 104 * @brief Populate FRU specific interfaces. 105 * 106 * This is a common method which handles both 107 * ipz and keyword specific interfaces thus, 108 * reducing the code redundancy. 109 * @param[in] map - Reference to the innermost keyword-value map. 110 * @param[in] preIntrStr - Reference to the interface string. 111 * @param[out] interfaces - Reference to interface map. 112 */ 113 template <typename T> 114 static void populateFruSpecificInterfaces(const T& map, 115 const string& preIntrStr, 116 inventory::InterfaceMap& interfaces) 117 { 118 inventory::PropertyMap prop; 119 120 for (const auto& kwVal : map) 121 { 122 vector<uint8_t> vec(kwVal.second.begin(), kwVal.second.end()); 123 124 auto kw = kwVal.first; 125 126 if (kw[0] == '#') 127 { 128 kw = string("PD_") + kw[1]; 129 } 130 else if (isdigit(kw[0])) 131 { 132 kw = string("N_") + kw; 133 } 134 prop.emplace(move(kw), move(vec)); 135 } 136 137 interfaces.emplace(preIntrStr, move(prop)); 138 } 139 140 /** 141 * @brief Populate Interfaces. 142 * 143 * This method populates common and extra interfaces to dbus. 144 * @param[in] js - json object 145 * @param[out] interfaces - Reference to interface map 146 * @param[in] vpdMap - Reference to the parsed vpd map. 147 * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD. 148 */ 149 template <typename T> 150 static void populateInterfaces(const nlohmann::json& js, 151 inventory::InterfaceMap& interfaces, 152 const T& vpdMap, bool isSystemVpd) 153 { 154 for (const auto& ifs : js.items()) 155 { 156 string inf = ifs.key(); 157 inventory::PropertyMap props; 158 159 for (const auto& itr : ifs.value().items()) 160 { 161 const string& busProp = itr.key(); 162 163 if (itr.value().is_boolean()) 164 { 165 props.emplace(busProp, itr.value().get<bool>()); 166 } 167 else if (itr.value().is_string()) 168 { 169 if constexpr (is_same<T, Parsed>::value) 170 { 171 if (busProp == "LocationCode" && 172 inf == "com.ibm.ipzvpd.Location") 173 { 174 auto prop = expandLocationCode( 175 itr.value().get<string>(), vpdMap, isSystemVpd); 176 props.emplace(busProp, prop); 177 } 178 else 179 { 180 props.emplace(busProp, itr.value().get<string>()); 181 } 182 } 183 else 184 { 185 props.emplace(busProp, itr.value().get<string>()); 186 } 187 } 188 else if (itr.value().is_object()) 189 { 190 const string& rec = itr.value().value("recordName", ""); 191 const string& kw = itr.value().value("keywordName", ""); 192 const string& encoding = itr.value().value("encoding", ""); 193 194 if constexpr (is_same<T, Parsed>::value) 195 { 196 if (!rec.empty() && !kw.empty() && vpdMap.count(rec) && 197 vpdMap.at(rec).count(kw)) 198 { 199 auto encoded = 200 encodeKeyword(vpdMap.at(rec).at(kw), encoding); 201 props.emplace(busProp, encoded); 202 } 203 } 204 else if constexpr (is_same<T, KeywordVpdMap>::value) 205 { 206 if (!kw.empty() && vpdMap.count(kw)) 207 { 208 auto prop = 209 string(vpdMap.at(kw).begin(), vpdMap.at(kw).end()); 210 auto encoded = encodeKeyword(prop, encoding); 211 props.emplace(busProp, encoded); 212 } 213 } 214 } 215 } 216 interfaces.emplace(inf, move(props)); 217 } 218 } 219 220 Binary getVpdDataInVector(nlohmann::json& js, const string& file) 221 { 222 uint32_t offset = 0; 223 // check if offset present? 224 for (const auto& item : js["frus"][file]) 225 { 226 if (item.find("offset") != item.end()) 227 { 228 offset = item["offset"]; 229 } 230 } 231 232 // TODO: Figure out a better way to get max possible VPD size. 233 Binary vpdVector; 234 vpdVector.resize(65504); 235 ifstream vpdFile; 236 vpdFile.open(file, ios::binary); 237 238 vpdFile.seekg(offset, ios_base::cur); 239 vpdFile.read(reinterpret_cast<char*>(&vpdVector[0]), 65504); 240 vpdVector.resize(vpdFile.gcount()); 241 242 return vpdVector; 243 } 244 245 /** 246 * @brief Prime the Inventory 247 * Prime the inventory by populating only the location code, 248 * type interface and the inventory object for the frus 249 * which are not system vpd fru. 250 * 251 * @param[in] jsObject - Reference to vpd inventory json object 252 * @param[in] vpdMap - Reference to the parsed vpd map 253 * 254 * @returns Map of items in extraInterface. 255 */ 256 template <typename T> 257 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject, 258 const T& vpdMap) 259 { 260 inventory::ObjectMap objects; 261 262 for (auto& itemFRUS : jsObject["frus"].items()) 263 { 264 for (auto& itemEEPROM : itemFRUS.value()) 265 { 266 inventory::InterfaceMap interfaces; 267 auto isSystemVpd = itemEEPROM.value("isSystemVpd", false); 268 inventory::Object object(itemEEPROM.at("inventoryPath")); 269 270 if (!isSystemVpd && !itemEEPROM.value("noprime", false)) 271 { 272 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end()) 273 { 274 for (const auto& eI : itemEEPROM["extraInterfaces"].items()) 275 { 276 inventory::PropertyMap props; 277 if (eI.key() == 278 openpower::vpd::constants::LOCATION_CODE_INF) 279 { 280 if constexpr (std::is_same<T, Parsed>::value) 281 { 282 for (auto& lC : eI.value().items()) 283 { 284 auto propVal = expandLocationCode( 285 lC.value().get<string>(), vpdMap, true); 286 287 props.emplace(move(lC.key()), 288 move(propVal)); 289 interfaces.emplace(move(eI.key()), 290 move(props)); 291 } 292 } 293 } 294 else if (eI.key().find("Inventory.Item.") != 295 string::npos) 296 { 297 interfaces.emplace(move(eI.key()), move(props)); 298 } 299 } 300 } 301 objects.emplace(move(object), move(interfaces)); 302 } 303 } 304 } 305 return objects; 306 } 307 308 /* It does nothing. Just an empty function to return null 309 * at the end of variadic template args 310 */ 311 string getCommand() 312 { 313 return ""; 314 } 315 316 /* This function to arrange all arguments to make command 317 */ 318 template <typename T, typename... Types> 319 string getCommand(T arg1, Types... args) 320 { 321 string cmd = " " + arg1 + getCommand(args...); 322 323 return cmd; 324 } 325 326 /* This API takes arguments and run that command 327 * returns output of that command 328 */ 329 template <typename T, typename... Types> 330 static vector<string> executeCmd(T& path, Types... args) 331 { 332 vector<string> stdOutput; 333 array<char, 128> buffer; 334 335 string cmd = path + getCommand(args...); 336 337 unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose); 338 if (!pipe) 339 { 340 throw runtime_error("popen() failed!"); 341 } 342 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) 343 { 344 stdOutput.emplace_back(buffer.data()); 345 } 346 347 return stdOutput; 348 } 349 350 /** 351 * @brief This API executes command to set environment variable 352 * And then reboot the system 353 * @param[in] key -env key to set new value 354 * @param[in] value -value to set. 355 */ 356 void setEnvAndReboot(const string& key, const string& value) 357 { 358 // set env and reboot and break. 359 executeCmd("/sbin/fw_setenv", key, value); 360 // make dbus call to reboot 361 auto bus = sdbusplus::bus::new_default_system(); 362 auto method = bus.new_method_call( 363 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 364 "org.freedesktop.systemd1.Manager", "Reboot"); 365 bus.call_noreply(method); 366 } 367 368 /* 369 * @brief This API checks for env var fitconfig. 370 * If not initialised OR updated as per the current system type, 371 * update this env var and reboot the system. 372 * 373 * @param[in] systemType IM kwd in vpd tells about which system type it is. 374 * */ 375 void setDevTreeEnv(const string& systemType) 376 { 377 string newDeviceTree; 378 379 if (deviceTreeSystemTypeMap.find(systemType) != 380 deviceTreeSystemTypeMap.end()) 381 { 382 newDeviceTree = deviceTreeSystemTypeMap.at(systemType); 383 } 384 385 string readVarValue; 386 bool envVarFound = false; 387 388 vector<string> output = executeCmd("/sbin/fw_printenv"); 389 for (const auto& entry : output) 390 { 391 size_t pos = entry.find("="); 392 string key = entry.substr(0, pos); 393 if (key != "fitconfig") 394 { 395 continue; 396 } 397 398 envVarFound = true; 399 if (pos + 1 < entry.size()) 400 { 401 readVarValue = entry.substr(pos + 1); 402 if (readVarValue.find(newDeviceTree) != string::npos) 403 { 404 // fitconfig is Updated. No action needed 405 break; 406 } 407 } 408 // set env and reboot and break. 409 setEnvAndReboot(key, newDeviceTree); 410 exit(0); 411 } 412 413 // check If env var Not found 414 if (!envVarFound) 415 { 416 setEnvAndReboot("fitconfig", newDeviceTree); 417 } 418 } 419 420 /** 421 * @brief Populate Dbus. 422 * This method invokes all the populateInterface functions 423 * and notifies PIM about dbus object. 424 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 425 * input. 426 * @param[in] js - Inventory json object 427 * @param[in] filePath - Path of the vpd file 428 * @param[in] preIntrStr - Interface string 429 */ 430 template <typename T> 431 static void populateDbus(const T& vpdMap, nlohmann::json& js, 432 const string& filePath) 433 { 434 inventory::InterfaceMap interfaces; 435 inventory::ObjectMap objects; 436 inventory::PropertyMap prop; 437 438 bool isSystemVpd = false; 439 for (const auto& item : js["frus"][filePath]) 440 { 441 const auto& objectPath = item["inventoryPath"]; 442 sdbusplus::message::object_path object(objectPath); 443 isSystemVpd = item.value("isSystemVpd", false); 444 // Populate the VPD keywords and the common interfaces only if we 445 // are asked to inherit that data from the VPD, else only add the 446 // extraInterfaces. 447 if (item.value("inherit", true)) 448 { 449 if constexpr (is_same<T, Parsed>::value) 450 { 451 // Each record in the VPD becomes an interface and all 452 // keyword within the record are properties under that 453 // interface. 454 for (const auto& record : vpdMap) 455 { 456 populateFruSpecificInterfaces( 457 record.second, ipzVpdInf + record.first, interfaces); 458 } 459 } 460 else if constexpr (is_same<T, KeywordVpdMap>::value) 461 { 462 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); 463 } 464 if (js.find("commonInterfaces") != js.end()) 465 { 466 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, 467 isSystemVpd); 468 } 469 } 470 else 471 { 472 // Check if we have been asked to inherit specific record(s) 473 if constexpr (is_same<T, Parsed>::value) 474 { 475 if (item.find("copyRecords") != item.end()) 476 { 477 for (const auto& record : item["copyRecords"]) 478 { 479 const string& recordName = record; 480 if (vpdMap.find(recordName) != vpdMap.end()) 481 { 482 populateFruSpecificInterfaces( 483 vpdMap.at(recordName), ipzVpdInf + recordName, 484 interfaces); 485 } 486 } 487 } 488 } 489 } 490 491 if (item.value("inheritEI", true)) 492 { 493 // Populate interfaces and properties that are common to every FRU 494 // and additional interface that might be defined on a per-FRU 495 // basis. 496 if (item.find("extraInterfaces") != item.end()) 497 { 498 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, 499 isSystemVpd); 500 } 501 } 502 objects.emplace(move(object), move(interfaces)); 503 } 504 505 if (isSystemVpd) 506 { 507 vector<uint8_t> imVal; 508 if constexpr (is_same<T, Parsed>::value) 509 { 510 auto property = vpdMap.find("VSBP"); 511 if (property != vpdMap.end()) 512 { 513 auto value = (property->second).find("IM"); 514 if (value != (property->second).end()) 515 { 516 copy(value->second.begin(), value->second.end(), 517 back_inserter(imVal)); 518 } 519 } 520 } 521 522 fs::path target; 523 fs::path link = INVENTORY_JSON_SYM_LINK; 524 525 ostringstream oss; 526 for (auto& i : imVal) 527 { 528 oss << setw(2) << setfill('0') << hex << static_cast<int>(i); 529 } 530 string imValStr = oss.str(); 531 532 if (imValStr == RAINIER_4U) // 4U 533 { 534 target = INVENTORY_JSON_4U; 535 } 536 537 else if (imValStr == RAINIER_2U) // 2U 538 { 539 target = INVENTORY_JSON_2U; 540 } 541 542 // unlink the symlink which is created at build time 543 remove(INVENTORY_JSON_SYM_LINK); 544 // create a new symlink based on the system 545 fs::create_symlink(target, link); 546 547 // Reloading the json 548 ifstream inventoryJson(link); 549 auto js = json::parse(inventoryJson); 550 inventoryJson.close(); 551 552 inventory::ObjectMap primeObject = primeInventory(js, vpdMap); 553 objects.insert(primeObject.begin(), primeObject.end()); 554 555 // set the U-boot environment variable for device-tree 556 setDevTreeEnv(imValStr); 557 } 558 559 // Notify PIM 560 inventory::callPIM(move(objects)); 561 } 562 563 int main(int argc, char** argv) 564 { 565 int rc = 0; 566 567 try 568 { 569 App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store " 570 "in DBUS"}; 571 string file{}; 572 573 app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") 574 ->required() 575 ->check(ExistingFile); 576 577 CLI11_PARSE(app, argc, argv); 578 579 // Make sure that the file path we get is for a supported EEPROM 580 ifstream inventoryJson(INVENTORY_JSON); 581 auto js = json::parse(inventoryJson); 582 583 if ((js.find("frus") == js.end()) || 584 (js["frus"].find(file) == js["frus"].end())) 585 { 586 cout << "Device path not in JSON, ignoring" << endl; 587 return 0; 588 } 589 590 Binary vpdVector = getVpdDataInVector(js, file); 591 ParserInterface* parser = ParserFactory::getParser(move(vpdVector)); 592 593 variant<KeywordVpdMap, Store> parseResult; 594 parseResult = parser->parse(); 595 596 if (auto pVal = get_if<Store>(&parseResult)) 597 { 598 populateDbus(pVal->getVpdMap(), js, file); 599 } 600 else if (auto pVal = get_if<KeywordVpdMap>(&parseResult)) 601 { 602 populateDbus(*pVal, js, file); 603 } 604 605 // release the parser object 606 ParserFactory::freeParser(parser); 607 } 608 catch (exception& e) 609 { 610 cerr << e.what() << "\n"; 611 rc = -1; 612 } 613 614 return rc; 615 } 616