xref: /openbmc/openpower-vpd-parser/vpd-manager/manager.cpp (revision e008432ac875223b916a8c63cc951caec090364e)
1 #include "config.h"
2 
3 #include "manager.hpp"
4 
5 #include "bios_handler.hpp"
6 #include "common_utility.hpp"
7 #include "editor_impl.hpp"
8 #include "gpioMonitor.hpp"
9 #include "ibm_vpd_utils.hpp"
10 #include "ipz_parser.hpp"
11 #include "parser_factory.hpp"
12 #include "reader_impl.hpp"
13 #include "vpd_exceptions.hpp"
14 
15 #include <filesystem>
16 #include <phosphor-logging/elog-errors.hpp>
17 
18 using namespace openpower::vpd::manager;
19 using namespace openpower::vpd::constants;
20 using namespace openpower::vpd::inventory;
21 using namespace openpower::vpd::manager::editor;
22 using namespace openpower::vpd::manager::reader;
23 using namespace std;
24 using namespace openpower::vpd::parser;
25 using namespace openpower::vpd::parser::factory;
26 using namespace openpower::vpd::ipz::parser;
27 using namespace openpower::vpd::exceptions;
28 using namespace phosphor::logging;
29 
30 namespace openpower
31 {
32 namespace vpd
33 {
34 namespace manager
35 {
36 Manager::Manager(sdbusplus::bus_t&& bus, const char* busName,
37                  const char* objPath, const char* /*iFace*/) :
38     ServerObject<ManagerIface>(bus, objPath),
39     _bus(std::move(bus)), _manager(_bus, objPath)
40 {
41     _bus.request_name(busName);
42 }
43 
44 void Manager::run()
45 {
46     try
47     {
48         processJSON();
49         restoreSystemVpd();
50         listenHostState();
51         listenAssetTag();
52 
53         // Create an instance of the BIOS handler
54         BiosHandler biosHandler{_bus, *this};
55 
56         auto event = sdeventplus::Event::get_default();
57         GpioMonitor gpioMon1(jsonFile, event);
58 
59         _bus.attach_event(event.get(), SD_EVENT_PRIORITY_IMPORTANT);
60         cout << "VPD manager event loop started\n";
61         event.loop();
62     }
63     catch (const std::exception& e)
64     {
65         std::cerr << e.what() << "\n";
66     }
67 }
68 
69 /**
70  * @brief An api to get list of blank system VPD properties.
71  * @param[in] vpdMap - IPZ vpd map.
72  * @param[in] objectPath - Object path for the FRU.
73  * @param[out] blankPropertyList - Properties which are blank in System VPD and
74  * needs to be updated as standby.
75  */
76 static void
77     getListOfBlankSystemVpd(Parsed& vpdMap, const string& objectPath,
78                             std::vector<RestoredEeproms>& blankPropertyList)
79 {
80     for (const auto& systemRecKwdPair : svpdKwdMap)
81     {
82         auto it = vpdMap.find(systemRecKwdPair.first);
83 
84         // check if record is found in map we got by parser
85         if (it != vpdMap.end())
86         {
87             const auto& kwdListForRecord = systemRecKwdPair.second;
88             for (const auto& keyword : kwdListForRecord)
89             {
90                 DbusPropertyMap& kwdValMap = it->second;
91                 auto iterator = kwdValMap.find(keyword);
92 
93                 if (iterator != kwdValMap.end())
94                 {
95                     string& kwdValue = iterator->second;
96 
97                     // check bus data
98                     const string& recordName = systemRecKwdPair.first;
99                     const string& busValue = readBusProperty(
100                         objectPath, ipzVpdInf + recordName, keyword);
101 
102                     if (busValue.find_first_not_of(' ') != string::npos)
103                     {
104                         if (kwdValue.find_first_not_of(' ') == string::npos)
105                         {
106                             // implies data is blank on EEPROM but not on cache.
107                             // So EEPROM vpd update is required.
108                             Binary busData(busValue.begin(), busValue.end());
109 
110                             blankPropertyList.push_back(std::make_tuple(
111                                 objectPath, recordName, keyword, busData));
112                         }
113                     }
114                 }
115             }
116         }
117     }
118 }
119 
120 void Manager::restoreSystemVpd()
121 {
122     std::cout << "Attempting system VPD restore" << std::endl;
123     ParserInterface* parser = nullptr;
124     try
125     {
126         auto vpdVector = getVpdDataInVector(jsonFile, systemVpdFilePath);
127         parser = ParserFactory::getParser(vpdVector);
128         auto parseResult = parser->parse();
129 
130         if (auto pVal = std::get_if<Store>(&parseResult))
131         {
132             // map to hold all the keywords whose value is blank and
133             // needs to be updated at standby.
134             std::vector<RestoredEeproms> blankSystemVpdProperties{};
135             getListOfBlankSystemVpd(pVal->getVpdMap(), SYSTEM_OBJECT,
136                                     blankSystemVpdProperties);
137 
138             // if system VPD restore is required, update the
139             // EEPROM
140             for (const auto& item : blankSystemVpdProperties)
141             {
142                 std::cout << "Restoring keyword: " << std::get<2>(item)
143                           << std::endl;
144                 writeKeyword(std::get<0>(item), std::get<1>(item),
145                              std::get<2>(item), std::get<3>(item));
146             }
147         }
148         else
149         {
150             std::cerr << "Not a valid format to restore system VPD"
151                       << std::endl;
152         }
153     }
154     catch (const std::exception& e)
155     {
156         std::cerr << "Failed to restore system VPD due to exception: "
157                   << e.what() << std::endl;
158     }
159     // release the parser object
160     ParserFactory::freeParser(parser);
161 }
162 
163 void Manager::listenHostState()
164 {
165     static std::shared_ptr<sdbusplus::bus::match_t> hostState =
166         std::make_shared<sdbusplus::bus::match_t>(
167             _bus,
168             sdbusplus::bus::match::rules::propertiesChanged(
169                 "/xyz/openbmc_project/state/host0",
170                 "xyz.openbmc_project.State.Host"),
171             [this](sdbusplus::message_t& msg) { hostStateCallBack(msg); });
172 }
173 
174 void Manager::hostStateCallBack(sdbusplus::message_t& msg)
175 {
176     if (msg.is_method_error())
177     {
178         std::cerr << "Error in reading signal " << std::endl;
179     }
180 
181     Path object;
182     PropertyMap propMap;
183     msg.read(object, propMap);
184     const auto itr = propMap.find("CurrentHostState");
185     if (itr != propMap.end())
186     {
187         if (auto hostState = std::get_if<std::string>(&(itr->second)))
188         {
189             // implies system is moving from standby to power on state
190             if (*hostState == "xyz.openbmc_project.State.Host.HostState."
191                               "TransitioningToRunning")
192             {
193                 // check and perfrom recollection for FRUs replaceable at
194                 // standby.
195                 performVPDRecollection();
196                 return;
197             }
198         }
199         std::cerr << "Failed to read Host state" << std::endl;
200     }
201 }
202 
203 void Manager::listenAssetTag()
204 {
205     static std::shared_ptr<sdbusplus::bus::match_t> assetMatcher =
206         std::make_shared<sdbusplus::bus::match_t>(
207             _bus,
208             sdbusplus::bus::match::rules::propertiesChanged(
209                 "/xyz/openbmc_project/inventory/system",
210                 "xyz.openbmc_project.Inventory.Decorator.AssetTag"),
211             [this](sdbusplus::message_t& msg) { assetTagCallback(msg); });
212 }
213 
214 void Manager::assetTagCallback(sdbusplus::message_t& msg)
215 {
216     if (msg.is_method_error())
217     {
218         std::cerr << "Error in reading signal " << std::endl;
219     }
220 
221     Path object;
222     PropertyMap propMap;
223     msg.read(object, propMap);
224     const auto itr = propMap.find("AssetTag");
225     if (itr != propMap.end())
226     {
227         if (auto assetTag = std::get_if<std::string>(&(itr->second)))
228         {
229             // Call Notify to persist the AssetTag
230             inventory::ObjectMap objectMap = {
231                 {std::string{"/system"},
232                  {{"xyz.openbmc_project.Inventory.Decorator.AssetTag",
233                    {{"AssetTag", *assetTag}}}}}};
234 
235             common::utility::callPIM(std::move(objectMap));
236         }
237         else
238         {
239             std::cerr << "Failed to read asset tag" << std::endl;
240         }
241     }
242 }
243 
244 void Manager::processJSON()
245 {
246     std::ifstream json(INVENTORY_JSON_SYM_LINK, std::ios::binary);
247 
248     if (!json)
249     {
250         throw std::runtime_error("json file not found");
251     }
252 
253     jsonFile = nlohmann::json::parse(json);
254     if (jsonFile.find("frus") == jsonFile.end())
255     {
256         throw std::runtime_error("frus group not found in json");
257     }
258 
259     const nlohmann::json& groupFRUS =
260         jsonFile["frus"].get_ref<const nlohmann::json::object_t&>();
261     for (const auto& itemFRUS : groupFRUS.items())
262     {
263         const std::vector<nlohmann::json>& groupEEPROM =
264             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
265         for (const auto& itemEEPROM : groupEEPROM)
266         {
267             bool isMotherboard = false;
268             std::string redundantPath;
269 
270             if (itemEEPROM["extraInterfaces"].find(
271                     "xyz.openbmc_project.Inventory.Item.Board.Motherboard") !=
272                 itemEEPROM["extraInterfaces"].end())
273             {
274                 isMotherboard = true;
275             }
276             if (itemEEPROM.find("redundantEeprom") != itemEEPROM.end())
277             {
278                 redundantPath = itemEEPROM["redundantEeprom"]
279                                     .get_ref<const nlohmann::json::string_t&>();
280             }
281             frus.emplace(
282                 itemEEPROM["inventoryPath"]
283                     .get_ref<const nlohmann::json::string_t&>(),
284                 std::make_tuple(itemFRUS.key(), redundantPath, isMotherboard));
285 
286             if (itemEEPROM["extraInterfaces"].find(IBM_LOCATION_CODE_INF) !=
287                 itemEEPROM["extraInterfaces"].end())
288             {
289                 fruLocationCode.emplace(
290                     itemEEPROM["extraInterfaces"][IBM_LOCATION_CODE_INF]
291                               ["LocationCode"]
292                                   .get_ref<const nlohmann::json::string_t&>(),
293                     itemEEPROM["inventoryPath"]
294                         .get_ref<const nlohmann::json::string_t&>());
295             }
296 
297             if (itemEEPROM.value("replaceableAtStandby", false))
298             {
299                 replaceableFrus.emplace_back(itemFRUS.key());
300             }
301         }
302     }
303 }
304 
305 void Manager::writeKeyword(const sdbusplus::message::object_path path,
306                            const std::string recordName,
307                            const std::string keyword, const Binary value)
308 {
309     try
310     {
311         std::string objPath{path};
312         // Strip any inventory prefix in path
313         if (objPath.find(INVENTORY_PATH) == 0)
314         {
315             objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1);
316         }
317 
318         if (frus.find(objPath) == frus.end())
319         {
320             throw std::runtime_error("Inventory path not found");
321         }
322 
323         inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second);
324 
325         // instantiate editor class to update the data
326         EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword, objPath);
327 
328         uint32_t offset = 0;
329         // Setup offset, if any
330         for (const auto& item : jsonFile["frus"][vpdFilePath])
331         {
332             if (item.find("offset") != item.end())
333             {
334                 offset = item["offset"];
335                 break;
336             }
337         }
338 
339         edit.updateKeyword(value, offset, true);
340 
341         // If we have a redundant EEPROM to update, then update just the EEPROM,
342         // not the cache since that is already done when we updated the primary
343         if (!std::get<1>(frus.find(objPath)->second).empty())
344         {
345             EditorImpl edit(std::get<1>(frus.find(objPath)->second), jsonFile,
346                             recordName, keyword);
347             edit.updateKeyword(value, offset, false);
348         }
349 
350         // if it is a motehrboard FRU need to check for location expansion
351         if (std::get<2>(frus.find(objPath)->second))
352         {
353             if (recordName == "VCEN" && (keyword == "FC" || keyword == "SE"))
354             {
355                 edit.expandLocationCode("fcs");
356             }
357             else if (recordName == "VSYS" &&
358                      (keyword == "TM" || keyword == "SE"))
359             {
360                 edit.expandLocationCode("mts");
361             }
362         }
363 
364         return;
365     }
366     catch (const std::exception& e)
367     {
368         std::cerr << e.what() << std::endl;
369     }
370 }
371 
372 ListOfPaths
373     Manager::getFRUsByUnexpandedLocationCode(const LocationCode locationCode,
374                                              const NodeNumber nodeNumber)
375 {
376     ReaderImpl read;
377     return read.getFrusAtLocation(locationCode, nodeNumber, fruLocationCode);
378 }
379 
380 ListOfPaths
381     Manager::getFRUsByExpandedLocationCode(const LocationCode locationCode)
382 {
383     ReaderImpl read;
384     return read.getFRUsByExpandedLocationCode(locationCode, fruLocationCode);
385 }
386 
387 LocationCode Manager::getExpandedLocationCode(const LocationCode locationCode,
388                                               const NodeNumber nodeNumber)
389 {
390     ReaderImpl read;
391     return read.getExpandedLocationCode(locationCode, nodeNumber,
392                                         fruLocationCode);
393 }
394 
395 void Manager::performVPDRecollection()
396 {
397     // get list of FRUs replaceable at standby
398     for (const auto& item : replaceableFrus)
399     {
400         const vector<nlohmann::json>& groupEEPROM = jsonFile["frus"][item];
401         const nlohmann::json& singleFru = groupEEPROM[0];
402 
403         const string& inventoryPath =
404             singleFru["inventoryPath"]
405                 .get_ref<const nlohmann::json::string_t&>();
406 
407         bool prePostActionRequired = false;
408 
409         if ((jsonFile["frus"][item].at(0)).find("preAction") !=
410             jsonFile["frus"][item].at(0).end())
411         {
412             if (!executePreAction(jsonFile, item))
413             {
414                 // if the FRU has preAction defined then its execution should
415                 // pass to ensure bind/unbind of data.
416                 // preAction execution failed. should not call bind/unbind.
417                 log<level::ERR>(
418                     "Pre-Action execution failed for the FRU",
419                     entry("ERROR=%s",
420                           ("Inventory path: " + inventoryPath).c_str()));
421                 continue;
422             }
423             prePostActionRequired = true;
424         }
425 
426         if ((singleFru.find("devAddress") == singleFru.end()) ||
427             (singleFru.find("driverType") == singleFru.end()) ||
428             (singleFru.find("busType") == singleFru.end()))
429         {
430             // The FRUs is marked for replacement but missing mandatory
431             // fields for recollection. Skip to another replaceable fru.
432             log<level::ERR>(
433                 "Recollection Failed as mandatory field missing in Json",
434                 entry("ERROR=%s",
435                       ("Recollection failed for " + inventoryPath).c_str()));
436             continue;
437         }
438 
439         string str = "echo ";
440         string deviceAddress = singleFru["devAddress"];
441         const string& driverType = singleFru["driverType"];
442         const string& busType = singleFru["busType"];
443 
444         // devTreeStatus flag is present in json as false to mention
445         // that the EEPROM is not mentioned in device tree. If this flag
446         // is absent consider the value to be true, i.e EEPROM is
447         // mentioned in device tree
448         if (!singleFru.value("devTreeStatus", true))
449         {
450             auto pos = deviceAddress.find('-');
451             if (pos != string::npos)
452             {
453                 string busNum = deviceAddress.substr(0, pos);
454                 deviceAddress =
455                     "0x" + deviceAddress.substr(pos + 1, string::npos);
456 
457                 string deleteDevice = str + deviceAddress + " > /sys/bus/" +
458                                       busType + "/devices/" + busType + "-" +
459                                       busNum + "/delete_device";
460                 executeCmd(deleteDevice);
461 
462                 string addDevice = str + driverType + " " + deviceAddress +
463                                    " > /sys/bus/" + busType + "/devices/" +
464                                    busType + "-" + busNum + "/new_device";
465                 executeCmd(addDevice);
466             }
467             else
468             {
469                 log<level::ERR>(
470                     "Wrong format of device address in Json",
471                     entry(
472                         "ERROR=%s",
473                         ("Recollection failed for " + inventoryPath).c_str()));
474                 continue;
475             }
476         }
477         else
478         {
479             executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
480                                                   driverType, "/unbind"));
481             executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
482                                                   driverType, "/bind"));
483         }
484 
485         // this check is added to avoid file system expensive call in case not
486         // required.
487         if (prePostActionRequired)
488         {
489             // Check if device showed up (test for file)
490             if (!filesystem::exists(item))
491             {
492                 // If not, then take failure postAction
493                 executePostFailAction(jsonFile, item);
494             }
495         }
496     }
497 }
498 
499 } // namespace manager
500 } // namespace vpd
501 } // namespace openpower