1 #include "config.h" 2 3 #include "manager.hpp" 4 5 #include "common_utility.hpp" 6 #include "editor_impl.hpp" 7 #include "ibm_vpd_utils.hpp" 8 #include "ipz_parser.hpp" 9 #include "parser_factory.hpp" 10 #include "reader_impl.hpp" 11 #include "vpd_exceptions.hpp" 12 13 #include <filesystem> 14 #include <phosphor-logging/elog-errors.hpp> 15 #include <xyz/openbmc_project/Common/error.hpp> 16 17 using namespace openpower::vpd::constants; 18 using namespace openpower::vpd::inventory; 19 using namespace openpower::vpd::manager::editor; 20 using namespace openpower::vpd::manager::reader; 21 using namespace std; 22 using namespace openpower::vpd::parser; 23 using namespace openpower::vpd::parser::factory; 24 using namespace openpower::vpd::ipz::parser; 25 using namespace openpower::vpd::exceptions; 26 using namespace phosphor::logging; 27 28 namespace openpower 29 { 30 namespace vpd 31 { 32 namespace manager 33 { 34 Manager::Manager(std::shared_ptr<boost::asio::io_context>& ioCon, 35 std::shared_ptr<sdbusplus::asio::dbus_interface>& iFace, 36 std::shared_ptr<sdbusplus::asio::connection>& conn) : 37 ioContext(ioCon), 38 interface(iFace), conn(conn) 39 { 40 interface->register_method( 41 "WriteKeyword", 42 [this](const sdbusplus::message::object_path& path, 43 const std::string& recordName, const std::string& keyword, 44 const Binary& value) { 45 this->writeKeyword(path, recordName, keyword, value); 46 }); 47 48 interface->register_method( 49 "GetFRUsByUnexpandedLocationCode", 50 [this](const std::string& locationCode, 51 const uint16_t nodeNumber) -> inventory::ListOfPaths { 52 return this->getFRUsByUnexpandedLocationCode(locationCode, 53 nodeNumber); 54 }); 55 56 interface->register_method( 57 "GetFRUsByExpandedLocationCode", 58 [this](const std::string& locationCode) -> inventory::ListOfPaths { 59 return this->getFRUsByExpandedLocationCode(locationCode); 60 }); 61 62 interface->register_method( 63 "GetExpandedLocationCode", 64 [this](const std::string& locationCode, 65 const uint16_t nodeNumber) -> std::string { 66 return this->getExpandedLocationCode(locationCode, nodeNumber); 67 }); 68 69 interface->register_method("PerformVPDRecollection", 70 [this]() { this->performVPDRecollection(); }); 71 72 interface->register_method( 73 "deleteFRUVPD", [this](const sdbusplus::message::object_path& path) { 74 this->deleteFRUVPD(path); 75 }); 76 77 interface->register_method( 78 "CollectFRUVPD", [this](const sdbusplus::message::object_path& path) { 79 this->collectFRUVPD(path); 80 }); 81 82 sd_bus_default(&sdBus); 83 initManager(); 84 } 85 86 void Manager::initManager() 87 { 88 try 89 { 90 processJSON(); 91 restoreSystemVpd(); 92 listenHostState(); 93 listenAssetTag(); 94 95 // Create an instance of the BIOS handler 96 biosHandler = std::make_shared<BiosHandler>(conn, *this); 97 98 // instantiate gpioMonitor class 99 gpioMon = std::make_shared<GpioMonitor>(jsonFile, ioContext); 100 } 101 catch (const std::exception& e) 102 { 103 std::cerr << e.what() << "\n"; 104 } 105 } 106 107 /** 108 * @brief An api to get list of blank system VPD properties. 109 * @param[in] vpdMap - IPZ vpd map. 110 * @param[in] objectPath - Object path for the FRU. 111 * @param[out] blankPropertyList - Properties which are blank in System VPD and 112 * needs to be updated as standby. 113 */ 114 static void 115 getListOfBlankSystemVpd(Parsed& vpdMap, const string& objectPath, 116 std::vector<RestoredEeproms>& blankPropertyList) 117 { 118 for (const auto& systemRecKwdPair : svpdKwdMap) 119 { 120 auto it = vpdMap.find(systemRecKwdPair.first); 121 122 // check if record is found in map we got by parser 123 if (it != vpdMap.end()) 124 { 125 const auto& kwdListForRecord = systemRecKwdPair.second; 126 for (const auto& keyword : kwdListForRecord) 127 { 128 DbusPropertyMap& kwdValMap = it->second; 129 auto iterator = kwdValMap.find(keyword); 130 131 if (iterator != kwdValMap.end()) 132 { 133 string& kwdValue = iterator->second; 134 135 // check bus data 136 const string& recordName = systemRecKwdPair.first; 137 const string& busValue = readBusProperty( 138 objectPath, ipzVpdInf + recordName, keyword); 139 140 if (busValue.find_first_not_of(' ') != string::npos) 141 { 142 if (kwdValue.find_first_not_of(' ') == string::npos) 143 { 144 // implies data is blank on EEPROM but not on cache. 145 // So EEPROM vpd update is required. 146 Binary busData(busValue.begin(), busValue.end()); 147 148 blankPropertyList.push_back(std::make_tuple( 149 objectPath, recordName, keyword, busData)); 150 } 151 } 152 } 153 } 154 } 155 } 156 } 157 158 void Manager::restoreSystemVpd() 159 { 160 std::cout << "Attempting system VPD restore" << std::endl; 161 ParserInterface* parser = nullptr; 162 try 163 { 164 auto vpdVector = getVpdDataInVector(jsonFile, systemVpdFilePath); 165 const auto& inventoryPath = 166 jsonFile["frus"][systemVpdFilePath][0]["inventoryPath"] 167 .get_ref<const nlohmann::json::string_t&>(); 168 169 parser = ParserFactory::getParser(vpdVector, (pimPath + inventoryPath)); 170 auto parseResult = parser->parse(); 171 172 if (auto pVal = std::get_if<Store>(&parseResult)) 173 { 174 // map to hold all the keywords whose value is blank and 175 // needs to be updated at standby. 176 std::vector<RestoredEeproms> blankSystemVpdProperties{}; 177 getListOfBlankSystemVpd(pVal->getVpdMap(), SYSTEM_OBJECT, 178 blankSystemVpdProperties); 179 180 // if system VPD restore is required, update the 181 // EEPROM 182 for (const auto& item : blankSystemVpdProperties) 183 { 184 std::cout << "Restoring keyword: " << std::get<2>(item) 185 << std::endl; 186 writeKeyword(std::get<0>(item), std::get<1>(item), 187 std::get<2>(item), std::get<3>(item)); 188 } 189 } 190 else 191 { 192 std::cerr << "Not a valid format to restore system VPD" 193 << std::endl; 194 } 195 } 196 catch (const std::exception& e) 197 { 198 std::cerr << "Failed to restore system VPD due to exception: " 199 << e.what() << std::endl; 200 } 201 // release the parser object 202 ParserFactory::freeParser(parser); 203 } 204 205 void Manager::listenHostState() 206 { 207 static std::shared_ptr<sdbusplus::bus::match_t> hostState = 208 std::make_shared<sdbusplus::bus::match_t>( 209 *conn, 210 sdbusplus::bus::match::rules::propertiesChanged( 211 "/xyz/openbmc_project/state/host0", 212 "xyz.openbmc_project.State.Host"), 213 [this](sdbusplus::message_t& msg) { hostStateCallBack(msg); }); 214 } 215 216 void Manager::checkEssentialFrus() 217 { 218 for (const auto& invPath : essentialFrus) 219 { 220 const auto res = readBusProperty(invPath, invItemIntf, "Present"); 221 222 // implies the essential FRU is missing. Log PEL. 223 if (res == "false") 224 { 225 auto rc = sd_bus_call_method_async( 226 sdBus, NULL, loggerService, loggerObjectPath, 227 loggerCreateInterface, "Create", NULL, NULL, "ssa{ss}", 228 errIntfForEssentialFru, 229 "xyz.openbmc_project.Logging.Entry.Level.Warning", 2, 230 "DESCRIPTION", "Essential fru missing from the system.", 231 "CALLOUT_INVENTORY_PATH", (pimPath + invPath).c_str()); 232 233 if (rc < 0) 234 { 235 log<level::ERR>("Error calling sd_bus_call_method_async", 236 entry("RC=%d", rc), 237 entry("MSG=%s", strerror(-rc))); 238 } 239 } 240 } 241 } 242 243 void Manager::hostStateCallBack(sdbusplus::message_t& msg) 244 { 245 if (msg.is_method_error()) 246 { 247 std::cerr << "Error in reading signal " << std::endl; 248 } 249 250 Path object; 251 PropertyMap propMap; 252 msg.read(object, propMap); 253 const auto itr = propMap.find("CurrentHostState"); 254 if (itr != propMap.end()) 255 { 256 if (auto hostState = std::get_if<std::string>(&(itr->second))) 257 { 258 // implies system is moving from standby to power on state 259 if (*hostState == "xyz.openbmc_project.State.Host.HostState." 260 "TransitioningToRunning") 261 { 262 // detect if essential frus are present in the system. 263 checkEssentialFrus(); 264 265 // check and perfrom recollection for FRUs replaceable at 266 // standby. 267 performVPDRecollection(); 268 return; 269 } 270 } 271 std::cerr << "Failed to read Host state" << std::endl; 272 } 273 } 274 275 void Manager::listenAssetTag() 276 { 277 static std::shared_ptr<sdbusplus::bus::match_t> assetMatcher = 278 std::make_shared<sdbusplus::bus::match_t>( 279 *conn, 280 sdbusplus::bus::match::rules::propertiesChanged( 281 "/xyz/openbmc_project/inventory/system", 282 "xyz.openbmc_project.Inventory.Decorator.AssetTag"), 283 [this](sdbusplus::message_t& msg) { assetTagCallback(msg); }); 284 } 285 286 void Manager::assetTagCallback(sdbusplus::message_t& msg) 287 { 288 if (msg.is_method_error()) 289 { 290 std::cerr << "Error in reading signal " << std::endl; 291 } 292 293 Path object; 294 PropertyMap propMap; 295 msg.read(object, propMap); 296 const auto itr = propMap.find("AssetTag"); 297 if (itr != propMap.end()) 298 { 299 if (auto assetTag = std::get_if<std::string>(&(itr->second))) 300 { 301 // Call Notify to persist the AssetTag 302 inventory::ObjectMap objectMap = { 303 {std::string{"/system"}, 304 {{"xyz.openbmc_project.Inventory.Decorator.AssetTag", 305 {{"AssetTag", *assetTag}}}}}}; 306 307 common::utility::callPIM(std::move(objectMap)); 308 } 309 else 310 { 311 std::cerr << "Failed to read asset tag" << std::endl; 312 } 313 } 314 } 315 316 void Manager::processJSON() 317 { 318 std::ifstream json(INVENTORY_JSON_SYM_LINK, std::ios::binary); 319 320 if (!json) 321 { 322 throw std::runtime_error("json file not found"); 323 } 324 325 jsonFile = nlohmann::json::parse(json); 326 if (jsonFile.find("frus") == jsonFile.end()) 327 { 328 throw std::runtime_error("frus group not found in json"); 329 } 330 331 const nlohmann::json& groupFRUS = 332 jsonFile["frus"].get_ref<const nlohmann::json::object_t&>(); 333 for (const auto& itemFRUS : groupFRUS.items()) 334 { 335 const std::vector<nlohmann::json>& groupEEPROM = 336 itemFRUS.value().get_ref<const nlohmann::json::array_t&>(); 337 for (const auto& itemEEPROM : groupEEPROM) 338 { 339 bool isMotherboard = false; 340 std::string redundantPath; 341 342 if (itemEEPROM["extraInterfaces"].find( 343 "xyz.openbmc_project.Inventory.Item.Board.Motherboard") != 344 itemEEPROM["extraInterfaces"].end()) 345 { 346 isMotherboard = true; 347 } 348 if (itemEEPROM.find("redundantEeprom") != itemEEPROM.end()) 349 { 350 redundantPath = itemEEPROM["redundantEeprom"] 351 .get_ref<const nlohmann::json::string_t&>(); 352 } 353 frus.emplace( 354 itemEEPROM["inventoryPath"] 355 .get_ref<const nlohmann::json::string_t&>(), 356 std::make_tuple(itemFRUS.key(), redundantPath, isMotherboard)); 357 358 if (itemEEPROM["extraInterfaces"].find(IBM_LOCATION_CODE_INF) != 359 itemEEPROM["extraInterfaces"].end()) 360 { 361 fruLocationCode.emplace( 362 itemEEPROM["extraInterfaces"][IBM_LOCATION_CODE_INF] 363 ["LocationCode"] 364 .get_ref<const nlohmann::json::string_t&>(), 365 itemEEPROM["inventoryPath"] 366 .get_ref<const nlohmann::json::string_t&>()); 367 } 368 369 if (itemEEPROM.value("replaceableAtStandby", false)) 370 { 371 replaceableFrus.emplace_back(itemFRUS.key()); 372 } 373 374 if (itemEEPROM.value("essentialFru", false)) 375 { 376 essentialFrus.emplace_back(itemEEPROM["inventoryPath"]); 377 } 378 } 379 } 380 } 381 382 void Manager::writeKeyword(const sdbusplus::message::object_path& path, 383 const std::string& recordName, 384 const std::string& keyword, const Binary& value) 385 { 386 try 387 { 388 std::string objPath{path}; 389 // Strip any inventory prefix in path 390 if (objPath.find(INVENTORY_PATH) == 0) 391 { 392 objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1); 393 } 394 395 if (frus.find(objPath) == frus.end()) 396 { 397 throw std::runtime_error("Inventory path not found"); 398 } 399 400 inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second); 401 402 // instantiate editor class to update the data 403 EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword, objPath); 404 405 uint32_t offset = 0; 406 // Setup offset, if any 407 for (const auto& item : jsonFile["frus"][vpdFilePath]) 408 { 409 if (item.find("offset") != item.end()) 410 { 411 offset = item["offset"]; 412 break; 413 } 414 } 415 416 edit.updateKeyword(value, offset, true); 417 418 // If we have a redundant EEPROM to update, then update just the EEPROM, 419 // not the cache since that is already done when we updated the primary 420 if (!std::get<1>(frus.find(objPath)->second).empty()) 421 { 422 EditorImpl edit(std::get<1>(frus.find(objPath)->second), jsonFile, 423 recordName, keyword, objPath); 424 edit.updateKeyword(value, offset, false); 425 } 426 427 // if it is a motehrboard FRU need to check for location expansion 428 if (std::get<2>(frus.find(objPath)->second)) 429 { 430 if (recordName == "VCEN" && (keyword == "FC" || keyword == "SE")) 431 { 432 edit.expandLocationCode("fcs"); 433 } 434 else if (recordName == "VSYS" && 435 (keyword == "TM" || keyword == "SE")) 436 { 437 edit.expandLocationCode("mts"); 438 } 439 } 440 441 return; 442 } 443 catch (const std::exception& e) 444 { 445 std::cerr << e.what() << std::endl; 446 } 447 } 448 449 ListOfPaths 450 Manager::getFRUsByUnexpandedLocationCode(const LocationCode& locationCode, 451 const NodeNumber nodeNumber) 452 { 453 ReaderImpl read; 454 return read.getFrusAtLocation(locationCode, nodeNumber, fruLocationCode); 455 } 456 457 ListOfPaths 458 Manager::getFRUsByExpandedLocationCode(const LocationCode& locationCode) 459 { 460 ReaderImpl read; 461 return read.getFRUsByExpandedLocationCode(locationCode, fruLocationCode); 462 } 463 464 LocationCode Manager::getExpandedLocationCode(const LocationCode& locationCode, 465 const NodeNumber nodeNumber) 466 { 467 ReaderImpl read; 468 return read.getExpandedLocationCode(locationCode, nodeNumber, 469 fruLocationCode); 470 } 471 472 void Manager::performVPDRecollection() 473 { 474 // get list of FRUs replaceable at standby 475 for (const auto& item : replaceableFrus) 476 { 477 const vector<nlohmann::json>& groupEEPROM = jsonFile["frus"][item]; 478 const nlohmann::json& singleFru = groupEEPROM[0]; 479 480 const string& inventoryPath = 481 singleFru["inventoryPath"] 482 .get_ref<const nlohmann::json::string_t&>(); 483 484 bool prePostActionRequired = false; 485 486 if ((jsonFile["frus"][item].at(0)).find("preAction") != 487 jsonFile["frus"][item].at(0).end()) 488 { 489 try 490 { 491 if (!executePreAction(jsonFile, item)) 492 { 493 // if the FRU has preAction defined then its execution 494 // should pass to ensure bind/unbind of data. 495 // preAction execution failed. should not call 496 // bind/unbind. 497 log<level::ERR>( 498 "Pre-Action execution failed for the FRU", 499 entry("ERROR=%s", 500 ("Inventory path: " + inventoryPath).c_str())); 501 continue; 502 } 503 } 504 catch (const GpioException& e) 505 { 506 log<level::ERR>(e.what()); 507 PelAdditionalData additionalData{}; 508 additionalData.emplace("DESCRIPTION", e.what()); 509 createPEL(additionalData, PelSeverity::WARNING, 510 errIntfForGpioError, sdBus); 511 continue; 512 } 513 prePostActionRequired = true; 514 } 515 516 // unbind, bind the driver to trigger parser. 517 triggerVpdCollection(singleFru, inventoryPath); 518 519 // this check is added to avoid file system expensive call in case not 520 // required. 521 if (prePostActionRequired) 522 { 523 // Check if device showed up (test for file) 524 if (!filesystem::exists(item)) 525 { 526 try 527 { 528 // If not, then take failure postAction 529 executePostFailAction(jsonFile, item); 530 } 531 catch (const GpioException& e) 532 { 533 PelAdditionalData additionalData{}; 534 additionalData.emplace("DESCRIPTION", e.what()); 535 createPEL(additionalData, PelSeverity::WARNING, 536 errIntfForGpioError, sdBus); 537 } 538 } 539 } 540 } 541 } 542 543 void Manager::collectFRUVPD(const sdbusplus::message::object_path& path) 544 { 545 using InvalidArgument = 546 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 547 using Argument = xyz::openbmc_project::Common::InvalidArgument; 548 549 // if path not found in Json. 550 if (frus.find(path) == frus.end()) 551 { 552 elog<InvalidArgument>( 553 Argument::ARGUMENT_NAME("Object Path"), 554 Argument::ARGUMENT_VALUE(std::string(path).c_str())); 555 } 556 557 inventory::Path vpdFilePath = std::get<0>(frus.find(path)->second); 558 559 const std::vector<nlohmann::json>& groupEEPROM = 560 jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>(); 561 562 const nlohmann::json& singleFru = groupEEPROM[0]; 563 564 // check if the device qualifies for CM. 565 if (singleFru.value("concurrentlyMaintainable", false)) 566 { 567 bool prePostActionRequired = false; 568 569 if ((jsonFile["frus"][vpdFilePath].at(0)).find("preAction") != 570 jsonFile["frus"][vpdFilePath].at(0).end()) 571 { 572 if (!executePreAction(jsonFile, vpdFilePath)) 573 { 574 // if the FRU has preAction defined then its execution should 575 // pass to ensure bind/unbind of data. 576 // preAction execution failed. should not call bind/unbind. 577 log<level::ERR>("Pre-Action execution failed for the FRU"); 578 return; 579 } 580 581 prePostActionRequired = true; 582 } 583 584 // unbind, bind the driver to trigger parser. 585 triggerVpdCollection(singleFru, std::string(path)); 586 587 // this check is added to avoid file system expensive call in case not 588 // required. 589 if (prePostActionRequired) 590 { 591 // Check if device showed up (test for file) 592 if (!filesystem::exists(vpdFilePath)) 593 { 594 // If not, then take failure postAction 595 executePostFailAction(jsonFile, vpdFilePath); 596 } 597 } 598 return; 599 } 600 else 601 { 602 elog<InvalidArgument>( 603 Argument::ARGUMENT_NAME("Object Path"), 604 Argument::ARGUMENT_VALUE(std::string(path).c_str())); 605 } 606 } 607 608 void Manager::triggerVpdCollection(const nlohmann::json& singleFru, 609 const std::string& path) 610 { 611 if ((singleFru.find("devAddress") == singleFru.end()) || 612 (singleFru.find("driverType") == singleFru.end()) || 613 (singleFru.find("busType") == singleFru.end())) 614 { 615 // The FRUs is marked for collection but missing mandatory 616 // fields for collection. Log error and return. 617 log<level::ERR>( 618 "Collection Failed as mandatory field missing in Json", 619 entry("ERROR=%s", ("Recollection failed for " + (path)).c_str())); 620 621 return; 622 } 623 624 string deviceAddress = singleFru["devAddress"]; 625 const string& driverType = singleFru["driverType"]; 626 const string& busType = singleFru["busType"]; 627 628 // devTreeStatus flag is present in json as false to mention 629 // that the EEPROM is not mentioned in device tree. If this flag 630 // is absent consider the value to be true, i.e EEPROM is 631 // mentioned in device tree 632 if (!singleFru.value("devTreeStatus", true)) 633 { 634 auto pos = deviceAddress.find('-'); 635 if (pos != string::npos) 636 { 637 string busNum = deviceAddress.substr(0, pos); 638 deviceAddress = "0x" + deviceAddress.substr(pos + 1, string::npos); 639 640 string deleteDevice = "echo" + deviceAddress + " > /sys/bus/" + 641 busType + "/devices/" + busType + "-" + 642 busNum + "/delete_device"; 643 executeCmd(deleteDevice); 644 645 string addDevice = "echo" + driverType + " " + deviceAddress + 646 " > /sys/bus/" + busType + "/devices/" + 647 busType + "-" + busNum + "/new_device"; 648 executeCmd(addDevice); 649 } 650 else 651 { 652 const string& inventoryPath = 653 singleFru["inventoryPath"] 654 .get_ref<const nlohmann::json::string_t&>(); 655 656 log<level::ERR>( 657 "Wrong format of device address in Json", 658 entry("ERROR=%s", 659 ("Recollection failed for " + inventoryPath).c_str())); 660 } 661 } 662 else 663 { 664 executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType, 665 driverType, "/unbind")); 666 executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType, 667 driverType, "/bind")); 668 } 669 } 670 671 void Manager::deleteFRUVPD(const sdbusplus::message::object_path& path) 672 { 673 using InvalidArgument = 674 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 675 using Argument = xyz::openbmc_project::Common::InvalidArgument; 676 677 // if path not found in Json. 678 if (frus.find(path) == frus.end()) 679 { 680 elog<InvalidArgument>( 681 Argument::ARGUMENT_NAME("Object Path"), 682 Argument::ARGUMENT_VALUE(std::string(path).c_str())); 683 } 684 685 // if the FRU is not present then log error 686 if (readBusProperty(path, "xyz.openbmc_project.Inventory.Item", 687 "Present") == "false") 688 { 689 elog<InvalidArgument>( 690 Argument::ARGUMENT_NAME("FRU not preset"), 691 Argument::ARGUMENT_VALUE(std::string(path).c_str())); 692 } 693 else 694 { 695 inventory::ObjectMap objectMap = { 696 {path, 697 {{"xyz.openbmc_project.Inventory.Item", {{"Present", false}}}}}}; 698 699 common::utility::callPIM(move(objectMap)); 700 } 701 } 702 703 } // namespace manager 704 } // namespace vpd 705 } // namespace openpower