1 #include "config.h" 2 3 #include "editor_impl.hpp" 4 5 #include "ipz_parser.hpp" 6 #include "parser_factory.hpp" 7 #include "utils.hpp" 8 9 #include "vpdecc/vpdecc.h" 10 11 using namespace openpower::vpd::parser::interface; 12 using namespace openpower::vpd::constants; 13 using namespace openpower::vpd::parser::factory; 14 using namespace openpower::vpd::ipz::parser; 15 16 namespace openpower 17 { 18 namespace vpd 19 { 20 namespace manager 21 { 22 namespace editor 23 { 24 25 void EditorImpl::checkPTForRecord(Binary::const_iterator& iterator, 26 Byte ptLength) 27 { 28 // auto iterator = ptRecord.cbegin(); 29 auto end = std::next(iterator, ptLength + 1); 30 31 // Look at each entry in the PT keyword for the record name 32 while (iterator < end) 33 { 34 auto stop = std::next(iterator, lengths::RECORD_NAME); 35 std::string record(iterator, stop); 36 37 if (record == thisRecord.recName) 38 { 39 // Skip record name and record type 40 std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType)); 41 42 // Get record offset 43 thisRecord.recOffset = readUInt16LE(iterator); 44 45 // pass the record offset length to read record length 46 std::advance(iterator, lengths::RECORD_OFFSET); 47 thisRecord.recSize = readUInt16LE(iterator); 48 49 std::advance(iterator, lengths::RECORD_LENGTH); 50 thisRecord.recECCoffset = readUInt16LE(iterator); 51 52 ECCLength len; 53 std::advance(iterator, lengths::RECORD_ECC_OFFSET); 54 len = readUInt16LE(iterator); 55 thisRecord.recECCLength = len; 56 57 // once we find the record we don't need to look further 58 return; 59 } 60 else 61 { 62 // Jump the record 63 std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType) + 64 sizeof(RecordOffset) + 65 sizeof(RecordLength) + 66 sizeof(ECCOffset) + sizeof(ECCLength)); 67 } 68 } 69 // imples the record was not found 70 throw std::runtime_error("Record not found"); 71 } 72 73 void EditorImpl::updateData(const Binary& kwdData) 74 { 75 std::size_t lengthToUpdate = kwdData.size() <= thisRecord.kwdDataLength 76 ? kwdData.size() 77 : thisRecord.kwdDataLength; 78 79 auto iteratorToNewdata = kwdData.cbegin(); 80 auto end = iteratorToNewdata; 81 std::advance(end, lengthToUpdate); 82 83 // update data in file buffer as it will be needed to update ECC 84 // avoiding extra stream operation here 85 auto iteratorToKWdData = vpdFile.begin(); 86 std::advance(iteratorToKWdData, thisRecord.kwDataOffset); 87 std::copy(iteratorToNewdata, end, iteratorToKWdData); 88 89 #ifdef ManagerTest 90 auto startItr = vpdFile.begin(); 91 std::advance(iteratorToKWdData, thisRecord.kwDataOffset); 92 auto endItr = startItr; 93 std::advance(endItr, thisRecord.kwdDataLength); 94 95 Binary updatedData(startItr, endItr); 96 if (updatedData == kwdData) 97 { 98 throw std::runtime_error("Data updated successfully"); 99 } 100 #else 101 102 // update data in EEPROM as well. As we will not write complete file back 103 vpdFileStream.seekp(startOffset + thisRecord.kwDataOffset, std::ios::beg); 104 105 iteratorToNewdata = kwdData.cbegin(); 106 std::copy(iteratorToNewdata, end, 107 std::ostreambuf_iterator<char>(vpdFileStream)); 108 109 // get a hold to new data in case encoding is needed 110 thisRecord.kwdUpdatedData.resize(thisRecord.kwdDataLength); 111 auto itrToKWdData = vpdFile.cbegin(); 112 std::advance(itrToKWdData, thisRecord.kwDataOffset); 113 auto kwdDataEnd = itrToKWdData; 114 std::advance(kwdDataEnd, thisRecord.kwdDataLength); 115 std::copy(itrToKWdData, kwdDataEnd, thisRecord.kwdUpdatedData.begin()); 116 #endif 117 } 118 119 void EditorImpl::checkRecordForKwd() 120 { 121 RecordOffset recOffset = thisRecord.recOffset; 122 123 // Amount to skip for record ID, size, and the RT keyword 124 constexpr auto skipBeg = sizeof(RecordId) + sizeof(RecordSize) + 125 lengths::KW_NAME + sizeof(KwSize); 126 127 auto iterator = vpdFile.cbegin(); 128 std::advance(iterator, recOffset + skipBeg + lengths::RECORD_NAME); 129 130 auto end = iterator; 131 std::advance(end, thisRecord.recSize); 132 std::size_t dataLength = 0; 133 134 while (iterator < end) 135 { 136 // Note keyword name 137 std::string kw(iterator, iterator + lengths::KW_NAME); 138 139 // Check if the Keyword starts with '#' 140 char kwNameStart = *iterator; 141 std::advance(iterator, lengths::KW_NAME); 142 143 // if keyword starts with # 144 if (POUND_KW == kwNameStart) 145 { 146 // Note existing keyword data length 147 dataLength = readUInt16LE(iterator); 148 149 // Jump past 2Byte keyword length + data 150 std::advance(iterator, sizeof(PoundKwSize)); 151 } 152 else 153 { 154 // Note existing keyword data length 155 dataLength = *iterator; 156 157 // Jump past keyword length and data 158 std::advance(iterator, sizeof(KwSize)); 159 } 160 161 if (thisRecord.recKWd == kw) 162 { 163 thisRecord.kwDataOffset = std::distance(vpdFile.cbegin(), iterator); 164 thisRecord.kwdDataLength = dataLength; 165 return; 166 } 167 168 // jump the data of current kwd to point to next kwd name 169 std::advance(iterator, dataLength); 170 } 171 172 throw std::runtime_error("Keyword not found"); 173 } 174 175 void EditorImpl::updateRecordECC() 176 { 177 auto itrToRecordData = vpdFile.cbegin(); 178 std::advance(itrToRecordData, thisRecord.recOffset); 179 180 auto itrToRecordECC = vpdFile.cbegin(); 181 std::advance(itrToRecordECC, thisRecord.recECCoffset); 182 183 auto l_status = vpdecc_create_ecc( 184 const_cast<uint8_t*>(&itrToRecordData[0]), thisRecord.recSize, 185 const_cast<uint8_t*>(&itrToRecordECC[0]), &thisRecord.recECCLength); 186 if (l_status != VPD_ECC_OK) 187 { 188 throw std::runtime_error("Ecc update failed"); 189 } 190 191 auto end = itrToRecordECC; 192 std::advance(end, thisRecord.recECCLength); 193 194 #ifndef ManagerTest 195 vpdFileStream.seekp(startOffset + thisRecord.recECCoffset, std::ios::beg); 196 std::copy(itrToRecordECC, end, 197 std::ostreambuf_iterator<char>(vpdFileStream)); 198 #endif 199 } 200 201 auto EditorImpl::getValue(offsets::Offsets offset) 202 { 203 auto itr = vpdFile.cbegin(); 204 std::advance(itr, offset); 205 LE2ByteData lowByte = *itr; 206 LE2ByteData highByte = *(itr + 1); 207 lowByte |= (highByte << 8); 208 209 return lowByte; 210 } 211 212 void EditorImpl::checkECC(Binary::const_iterator& itrToRecData, 213 Binary::const_iterator& itrToECCData, 214 RecordLength recLength, ECCLength eccLength) 215 { 216 auto l_status = 217 vpdecc_check_data(const_cast<uint8_t*>(&itrToRecData[0]), recLength, 218 const_cast<uint8_t*>(&itrToECCData[0]), eccLength); 219 220 if (l_status != VPD_ECC_OK) 221 { 222 throw std::runtime_error("Ecc check failed for VTOC"); 223 } 224 } 225 226 void EditorImpl::readVTOC() 227 { 228 // read VTOC offset 229 RecordOffset tocOffset = getValue(offsets::VTOC_PTR); 230 231 // read VTOC record length 232 RecordLength tocLength = getValue(offsets::VTOC_REC_LEN); 233 234 // read TOC ecc offset 235 ECCOffset tocECCOffset = getValue(offsets::VTOC_ECC_OFF); 236 237 // read TOC ecc length 238 ECCLength tocECCLength = getValue(offsets::VTOC_ECC_LEN); 239 240 auto itrToRecord = vpdFile.cbegin(); 241 std::advance(itrToRecord, tocOffset); 242 243 auto iteratorToECC = vpdFile.cbegin(); 244 std::advance(iteratorToECC, tocECCOffset); 245 246 // validate ecc for the record 247 checkECC(itrToRecord, iteratorToECC, tocLength, tocECCLength); 248 249 // to get to the record name. 250 std::advance(itrToRecord, sizeof(RecordId) + sizeof(RecordSize) + 251 // Skip past the RT keyword, which contains 252 // the record name. 253 lengths::KW_NAME + sizeof(KwSize)); 254 255 std::string recordName(itrToRecord, itrToRecord + lengths::RECORD_NAME); 256 257 if ("VTOC" != recordName) 258 { 259 throw std::runtime_error("VTOC record not found"); 260 } 261 262 // jump to length of PT kwd 263 std::advance(itrToRecord, lengths::RECORD_NAME + lengths::KW_NAME); 264 265 // Note size of PT 266 Byte ptLen = *itrToRecord; 267 std::advance(itrToRecord, 1); 268 269 checkPTForRecord(itrToRecord, ptLen); 270 } 271 272 template <typename T> 273 void EditorImpl::makeDbusCall(const std::string& object, 274 const std::string& interface, 275 const std::string& property, 276 const std::variant<T>& data) 277 { 278 auto bus = sdbusplus::bus::new_default(); 279 auto properties = 280 bus.new_method_call(INVENTORY_MANAGER_SERVICE, object.c_str(), 281 "org.freedesktop.DBus.Properties", "Set"); 282 properties.append(interface); 283 properties.append(property); 284 properties.append(data); 285 286 auto result = bus.call(properties); 287 288 if (result.is_method_error()) 289 { 290 throw std::runtime_error("bus call failed"); 291 } 292 } 293 294 void EditorImpl::processAndUpdateCI(const std::string& objectPath) 295 { 296 for (auto& commonInterface : jsonFile["commonInterfaces"].items()) 297 { 298 for (auto& ciPropertyList : commonInterface.value().items()) 299 { 300 if (ciPropertyList.value().type() == 301 nlohmann::json::value_t::object) 302 { 303 if ((ciPropertyList.value().value("recordName", "") == 304 thisRecord.recName) && 305 (ciPropertyList.value().value("keywordName", "") == 306 thisRecord.recKWd)) 307 { 308 std::string kwdData(thisRecord.kwdUpdatedData.begin(), 309 thisRecord.kwdUpdatedData.end()); 310 311 makeDbusCall<std::string>((INVENTORY_PATH + objectPath), 312 commonInterface.key(), 313 ciPropertyList.key(), kwdData); 314 } 315 } 316 } 317 } 318 } 319 320 void EditorImpl::processAndUpdateEI(const nlohmann::json& Inventory, 321 const inventory::Path& objPath) 322 { 323 for (const auto& extraInterface : Inventory["extraInterfaces"].items()) 324 { 325 if (extraInterface.value() != NULL) 326 { 327 for (const auto& eiPropertyList : extraInterface.value().items()) 328 { 329 if (eiPropertyList.value().type() == 330 nlohmann::json::value_t::object) 331 { 332 if ((eiPropertyList.value().value("recordName", "") == 333 thisRecord.recName) && 334 ((eiPropertyList.value().value("keywordName", "") == 335 thisRecord.recKWd))) 336 { 337 std::string kwdData(thisRecord.kwdUpdatedData.begin(), 338 thisRecord.kwdUpdatedData.end()); 339 makeDbusCall<std::string>( 340 (INVENTORY_PATH + objPath), extraInterface.key(), 341 eiPropertyList.key(), 342 encodeKeyword(kwdData, eiPropertyList.value().value( 343 "encoding", ""))); 344 } 345 } 346 } 347 } 348 } 349 } 350 351 void EditorImpl::updateCache() 352 { 353 const std::vector<nlohmann::json>& groupEEPROM = 354 jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>(); 355 356 // iterate through all the inventories for this file path 357 for (const auto& singleInventory : groupEEPROM) 358 { 359 // by default inherit property is true 360 bool isInherit = true; 361 bool isInheritEI = true; 362 bool isCpuModuleOnly = false; 363 364 if (singleInventory.find("inherit") != singleInventory.end()) 365 { 366 isInherit = singleInventory["inherit"].get<bool>(); 367 } 368 369 if (singleInventory.find("inheritEI") != singleInventory.end()) 370 { 371 isInheritEI = singleInventory["inheritEI"].get<bool>(); 372 } 373 374 // "type" exists only in CPU module and FRU 375 if (singleInventory.find("type") != singleInventory.end()) 376 { 377 if (singleInventory["type"] == "moduleOnly") 378 { 379 isCpuModuleOnly = true; 380 } 381 } 382 383 if (isInherit) 384 { 385 // update com interface 386 // For CPU- update com interface only when isCI true 387 if ((!isCpuModuleOnly) || (isCpuModuleOnly && isCI)) 388 { 389 makeDbusCall<Binary>( 390 (INVENTORY_PATH + 391 singleInventory["inventoryPath"].get<std::string>()), 392 (IPZ_INTERFACE + (std::string) "." + thisRecord.recName), 393 thisRecord.recKWd, thisRecord.kwdUpdatedData); 394 } 395 396 // process Common interface 397 processAndUpdateCI(singleInventory["inventoryPath"] 398 .get_ref<const nlohmann::json::string_t&>()); 399 } 400 401 if (isInheritEI) 402 { 403 if (isCpuModuleOnly) 404 { 405 makeDbusCall<Binary>( 406 (INVENTORY_PATH + 407 singleInventory["inventoryPath"].get<std::string>()), 408 (IPZ_INTERFACE + (std::string) "." + thisRecord.recName), 409 thisRecord.recKWd, thisRecord.kwdUpdatedData); 410 } 411 412 // process extra interfaces 413 processAndUpdateEI(singleInventory, 414 singleInventory["inventoryPath"] 415 .get_ref<const nlohmann::json::string_t&>()); 416 } 417 } 418 } 419 420 void EditorImpl::expandLocationCode(const std::string& locationCodeType) 421 { 422 std::string propertyFCorTM{}; 423 std::string propertySE{}; 424 425 if (locationCodeType == "fcs") 426 { 427 propertyFCorTM = 428 readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "FC"); 429 propertySE = 430 readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "SE"); 431 } 432 else if (locationCodeType == "mts") 433 { 434 propertyFCorTM = 435 readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "TM"); 436 propertySE = 437 readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "SE"); 438 } 439 440 const nlohmann::json& groupFRUS = 441 jsonFile["frus"].get_ref<const nlohmann::json::object_t&>(); 442 for (const auto& itemFRUS : groupFRUS.items()) 443 { 444 const std::vector<nlohmann::json>& groupEEPROM = 445 itemFRUS.value().get_ref<const nlohmann::json::array_t&>(); 446 for (const auto& itemEEPROM : groupEEPROM) 447 { 448 // check if the given item implements location code interface 449 if (itemEEPROM["extraInterfaces"].find(LOCATION_CODE_INF) != 450 itemEEPROM["extraInterfaces"].end()) 451 { 452 const std::string& unexpandedLocationCode = 453 itemEEPROM["extraInterfaces"][LOCATION_CODE_INF] 454 ["LocationCode"] 455 .get_ref<const nlohmann::json::string_t&>(); 456 std::size_t idx = unexpandedLocationCode.find(locationCodeType); 457 if (idx != std::string::npos) 458 { 459 std::string expandedLoctionCode(unexpandedLocationCode); 460 461 if (locationCodeType == "fcs") 462 { 463 expandedLoctionCode.replace( 464 idx, 3, 465 propertyFCorTM.substr(0, 4) + ".ND0." + propertySE); 466 } 467 else if (locationCodeType == "mts") 468 { 469 std::replace(propertyFCorTM.begin(), 470 propertyFCorTM.end(), '-', '.'); 471 expandedLoctionCode.replace( 472 idx, 3, propertyFCorTM + "." + propertySE); 473 } 474 475 // update the DBUS interface 476 makeDbusCall<std::string>( 477 (INVENTORY_PATH + 478 itemEEPROM["inventoryPath"] 479 .get_ref<const nlohmann::json::string_t&>()), 480 LOCATION_CODE_INF, "LocationCode", expandedLoctionCode); 481 } 482 } 483 } 484 } 485 } 486 487 string EditorImpl::getSysPathForThisFruType(const string& moduleObjPath, 488 const string& fruType) 489 { 490 string fruVpdPath; 491 492 // get all FRUs list 493 for (const auto& eachFru : jsonFile["frus"].items()) 494 { 495 bool moduleObjPathMatched = false; 496 bool expectedFruFound = false; 497 498 for (const auto& eachInventory : eachFru.value()) 499 { 500 const auto& thisObjectPath = eachInventory["inventoryPath"]; 501 502 // "type" exists only in CPU module and FRU 503 if (eachInventory.find("type") != eachInventory.end()) 504 { 505 // If inventory type is fruAndModule then set flag 506 if (eachInventory["type"] == fruType) 507 { 508 expectedFruFound = true; 509 } 510 } 511 512 if (thisObjectPath == moduleObjPath) 513 { 514 moduleObjPathMatched = true; 515 } 516 } 517 518 // If condition satisfies then collect this sys path and exit 519 if (expectedFruFound && moduleObjPathMatched) 520 { 521 fruVpdPath = eachFru.key(); 522 break; 523 } 524 } 525 526 return fruVpdPath; 527 } 528 529 void EditorImpl::getVpdPathForCpu() 530 { 531 isCI = false; 532 // keep a backup In case we need it later 533 inventory::Path vpdFilePathBackup = vpdFilePath; 534 535 // TODO 1:Temp hardcoded list. create it dynamically. 536 std::vector<std::string> commonIntVINIKwds = {"PN", "SN", "DR"}; 537 std::vector<std::string> commonIntVR10Kwds = {"DC"}; 538 std::unordered_map<std::string, std::vector<std::string>> 539 commonIntRecordsList = {{"VINI", commonIntVINIKwds}, 540 {"VR10", commonIntVR10Kwds}}; 541 542 // If requested Record&Kw is one among CI, then update 'FRU' type sys 543 // path, SPI2 544 unordered_map<std::string, vector<string>>::const_iterator isCommonInt = 545 commonIntRecordsList.find(thisRecord.recName); 546 547 if ((isCommonInt != commonIntRecordsList.end()) && 548 (find(isCommonInt->second.begin(), isCommonInt->second.end(), 549 thisRecord.recKWd) != isCommonInt->second.end())) 550 { 551 isCI = true; 552 vpdFilePath = getSysPathForThisFruType(objPath, "fruAndModule"); 553 } 554 else 555 { 556 for (const auto& eachFru : jsonFile["frus"].items()) 557 { 558 for (const auto& eachInventory : eachFru.value()) 559 { 560 if (eachInventory.find("type") != eachInventory.end()) 561 { 562 const auto& thisObjectPath = eachInventory["inventoryPath"]; 563 if ((eachInventory["type"] == "moduleOnly") && 564 (eachInventory.value("inheritEI", true)) && 565 (thisObjectPath == static_cast<string>(objPath))) 566 { 567 vpdFilePath = eachFru.key(); 568 } 569 } 570 } 571 } 572 } 573 // If it is not a CPU fru then go ahead with default vpdFilePath from 574 // fruMap 575 if (vpdFilePath.empty()) 576 { 577 vpdFilePath = vpdFilePathBackup; 578 } 579 } 580 581 void EditorImpl::updateKeyword(const Binary& kwdData, const bool& updCache) 582 { 583 startOffset = 0; 584 #ifndef ManagerTest 585 586 getVpdPathForCpu(); 587 588 // check if offset present? 589 for (const auto& item : jsonFile["frus"][vpdFilePath]) 590 { 591 if (item.find("offset") != item.end()) 592 { 593 startOffset = item["offset"]; 594 } 595 } 596 597 // TODO: Figure out a better way to get max possible VPD size. 598 Binary completeVPDFile; 599 completeVPDFile.resize(65504); 600 vpdFileStream.open(vpdFilePath, 601 std::ios::in | std::ios::out | std::ios::binary); 602 603 vpdFileStream.seekg(startOffset, ios_base::cur); 604 vpdFileStream.read(reinterpret_cast<char*>(&completeVPDFile[0]), 65504); 605 completeVPDFile.resize(vpdFileStream.gcount()); 606 vpdFileStream.clear(std::ios_base::eofbit); 607 608 vpdFile = completeVPDFile; 609 610 #else 611 612 Binary completeVPDFile = vpdFile; 613 614 #endif 615 if (vpdFile.empty()) 616 { 617 throw std::runtime_error("Invalid File"); 618 } 619 auto iterator = vpdFile.cbegin(); 620 std::advance(iterator, IPZ_DATA_START); 621 622 Byte vpdType = *iterator; 623 if (vpdType == KW_VAL_PAIR_START_TAG) 624 { 625 ParserInterface* Iparser = 626 ParserFactory::getParser(std::move(completeVPDFile)); 627 IpzVpdParser* ipzParser = dynamic_cast<IpzVpdParser*>(Iparser); 628 629 try 630 { 631 if (ipzParser == nullptr) 632 { 633 throw std::runtime_error("Invalid cast"); 634 } 635 636 ipzParser->processHeader(); 637 delete ipzParser; 638 ipzParser = nullptr; 639 // ParserFactory::freeParser(Iparser); 640 641 // process VTOC for PTT rkwd 642 readVTOC(); 643 644 // check record for keywrod 645 checkRecordForKwd(); 646 647 // update the data to the file 648 updateData(kwdData); 649 650 // update the ECC data for the record once data has been updated 651 updateRecordECC(); 652 653 #ifndef ManagerTest 654 if (updCache) 655 { 656 // update the cache once data has been updated 657 updateCache(); 658 } 659 #endif 660 } 661 catch (const std::exception& e) 662 { 663 if (ipzParser != nullptr) 664 { 665 delete ipzParser; 666 } 667 throw std::runtime_error(e.what()); 668 } 669 return; 670 } 671 } 672 } // namespace editor 673 } // namespace manager 674 } // namespace vpd 675 } // namespace openpower 676