xref: /openbmc/openpower-vpd-parser/vpd-manager/manager.cpp (revision 6a1bd39b2aae6e97b6d804a2f71667cd9aee4532)
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