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