1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "src.hpp"
17 
18 #include "device_callouts.hpp"
19 #include "json_utils.hpp"
20 #include "paths.hpp"
21 #include "pel_values.hpp"
22 #ifdef PELTOOL
23 #include <Python.h>
24 
25 #include <nlohmann/json.hpp>
26 #include <sstream>
27 #endif
28 #include <fmt/format.h>
29 
30 #include <phosphor-logging/log.hpp>
31 
32 namespace openpower
33 {
34 namespace pels
35 {
36 namespace pv = openpower::pels::pel_values;
37 namespace rg = openpower::pels::message;
38 using namespace phosphor::logging;
39 using namespace std::string_literals;
40 
41 constexpr size_t ccinSize = 4;
42 
43 #ifdef PELTOOL
44 using orderedJSON = nlohmann::ordered_json;
45 
46 void pyDecRef(PyObject* pyObj)
47 {
48     Py_XDECREF(pyObj);
49 }
50 
51 /**
52  * @brief Returns a JSON string to append to SRC section.
53  *
54  * The returning string will contain a JSON object, but without
55  * the outer {}.  If the input JSON isn't a JSON object (dict), then
56  * one will be created with the input added to a 'SRC Details' key.
57  *
58  * @param[in] json - The JSON to convert to a string
59  *
60  * @return std::string - The JSON string
61  */
62 std::string prettyJSON(const orderedJSON& json)
63 {
64     orderedJSON output;
65     if (!json.is_object())
66     {
67         output["SRC Details"] = json;
68     }
69     else
70     {
71         for (const auto& [key, value] : json.items())
72         {
73             output[key] = value;
74         }
75     }
76 
77     // Let nlohmann do the pretty printing.
78     std::stringstream stream;
79     stream << std::setw(4) << output;
80 
81     auto jsonString = stream.str();
82 
83     // Now it looks like:
84     // {
85     //     "Key": "Value",
86     //     ...
87     // }
88 
89     // Replace the { and the following newline, and the } and its
90     // preceeding newline.
91     jsonString.erase(0, 2);
92 
93     auto pos = jsonString.find_last_of('}');
94     jsonString.erase(pos - 1);
95 
96     return jsonString;
97 }
98 
99 /**
100  * @brief Call Python modules to parse the data into a JSON string
101  *
102  * The module to call is based on the Creator Subsystem ID under the namespace
103  * "srcparsers". For example: "srcparsers.xsrc.xsrc" where "x" is the Creator
104  * Subsystem ID in ASCII lowercase.
105  *
106  * All modules must provide the following:
107  * Function: parseSRCToJson
108  * Argument list:
109  *    1. (str) ASCII string (Hex Word 1)
110  *    2. (str) Hex Word 2
111  *    3. (str) Hex Word 3
112  *    4. (str) Hex Word 4
113  *    5. (str) Hex Word 5
114  *    6. (str) Hex Word 6
115  *    7. (str) Hex Word 7
116  *    8. (str) Hex Word 8
117  *    9. (str) Hex Word 9
118  *-Return data:
119  *    1. (str) JSON string
120  *
121  * @param[in] hexwords - Vector of strings of Hexwords 1-9
122  * @param[in] creatorID - The creatorID from the Private Header section
123  * @return std::optional<std::string> - The JSON string if it could be created,
124  *                                      else std::nullopt
125  */
126 std::optional<std::string> getPythonJSON(std::vector<std::string>& hexwords,
127                                          uint8_t creatorID)
128 {
129     PyObject *pName, *pModule, *pDict, *pFunc, *pArgs, *pResult, *pBytes,
130         *eType, *eValue, *eTraceback;
131     std::string pErrStr;
132     std::string module = getNumberString("%c", tolower(creatorID)) + "src";
133     pName = PyUnicode_FromString(
134         std::string("srcparsers." + module + "." + module).c_str());
135     std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
136     pModule = PyImport_Import(pName);
137     std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(pModule, &pyDecRef);
138     if (pModule == NULL)
139     {
140         pErrStr = "No error string found";
141         PyErr_Fetch(&eType, &eValue, &eTraceback);
142         if (eValue)
143         {
144             PyObject* pStr = PyObject_Str(eValue);
145             if (pStr)
146             {
147                 pErrStr = PyUnicode_AsUTF8(pStr);
148             }
149             Py_XDECREF(pStr);
150         }
151     }
152     else
153     {
154         pDict = PyModule_GetDict(pModule);
155         pFunc = PyDict_GetItemString(pDict, "parseSRCToJson");
156         if (PyCallable_Check(pFunc))
157         {
158             pArgs = PyTuple_New(9);
159             std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(pArgs,
160                                                                   &pyDecRef);
161             for (size_t i = 0; i < 9; i++)
162             {
163                 if (i < hexwords.size())
164                 {
165                     auto arg = hexwords[i];
166                     PyTuple_SetItem(pArgs, i,
167                                     Py_BuildValue("s#", arg.c_str(), 8));
168                 }
169                 else
170                 {
171                     PyTuple_SetItem(pArgs, i, Py_BuildValue("s", "00000000"));
172                 }
173             }
174             pResult = PyObject_CallObject(pFunc, pArgs);
175             std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(pResult,
176                                                                   &pyDecRef);
177             if (pResult)
178             {
179                 pBytes = PyUnicode_AsEncodedString(pResult, "utf-8", "~E~");
180                 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
181                     pBytes, &pyDecRef);
182                 const char* output = PyBytes_AS_STRING(pBytes);
183                 try
184                 {
185                     orderedJSON json = nlohmann::json::parse(output);
186                     return prettyJSON(json);
187                 }
188                 catch (std::exception& e)
189                 {
190                     log<level::ERR>("Bad JSON from parser",
191                                     entry("ERROR=%s", e.what()),
192                                     entry("SRC=%s", hexwords.front().c_str()),
193                                     entry("PARSER_MODULE=%s", module.c_str()));
194                     return std::nullopt;
195                 }
196             }
197             else
198             {
199                 pErrStr = "No error string found";
200                 PyErr_Fetch(&eType, &eValue, &eTraceback);
201                 if (eValue)
202                 {
203                     PyObject* pStr = PyObject_Str(eValue);
204                     if (pStr)
205                     {
206                         pErrStr = PyUnicode_AsUTF8(pStr);
207                     }
208                     Py_XDECREF(pStr);
209                 }
210             }
211         }
212     }
213     if (!pErrStr.empty())
214     {
215         log<level::ERR>("Python exception thrown by parser",
216                         entry("ERROR=%s", pErrStr.c_str()),
217                         entry("SRC=%s", hexwords.front().c_str()),
218                         entry("PARSER_MODULE=%s", module.c_str()));
219     }
220     Py_XDECREF(eType);
221     Py_XDECREF(eValue);
222     Py_XDECREF(eTraceback);
223     return std::nullopt;
224 }
225 #endif
226 
227 void SRC::unflatten(Stream& stream)
228 {
229     stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
230         _reserved2B >> _size;
231 
232     for (auto& word : _hexData)
233     {
234         stream >> word;
235     }
236 
237     _asciiString = std::make_unique<src::AsciiString>(stream);
238 
239     if (hasAdditionalSections())
240     {
241         // The callouts section is currently the only extra subsection type
242         _callouts = std::make_unique<src::Callouts>(stream);
243     }
244 }
245 
246 void SRC::flatten(Stream& stream) const
247 {
248     stream << _header << _version << _flags << _reserved1B << _wordCount
249            << _reserved2B << _size;
250 
251     for (auto& word : _hexData)
252     {
253         stream << word;
254     }
255 
256     _asciiString->flatten(stream);
257 
258     if (_callouts)
259     {
260         _callouts->flatten(stream);
261     }
262 }
263 
264 SRC::SRC(Stream& pel)
265 {
266     try
267     {
268         unflatten(pel);
269         validate();
270     }
271     catch (const std::exception& e)
272     {
273         log<level::ERR>("Cannot unflatten SRC", entry("ERROR=%s", e.what()));
274         _valid = false;
275     }
276 }
277 
278 SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
279          const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface)
280 {
281     _header.id = static_cast<uint16_t>(SectionID::primarySRC);
282     _header.version = srcSectionVersion;
283     _header.subType = srcSectionSubtype;
284     _header.componentID = regEntry.componentID;
285 
286     _version = srcVersion;
287 
288     _flags = 0;
289 
290     auto item = additionalData.getValue("POWER_THERMAL_CRITICAL_FAULT");
291     if ((regEntry.src.powerFault.value_or(false)) ||
292         (item.value_or("") == "TRUE"))
293     {
294         _flags |= powerFaultEvent;
295     }
296 
297     _reserved1B = 0;
298 
299     _wordCount = numSRCHexDataWords + 1;
300 
301     _reserved2B = 0;
302 
303     // There are multiple fields encoded in the hex data words.
304     std::for_each(_hexData.begin(), _hexData.end(),
305                   [](auto& word) { word = 0; });
306 
307     // Hex Word 2 Nibbles:
308     //   MIGVEPFF
309     //   M: Partition dump status = 0
310     //   I: System boot state = TODO
311     //   G: Partition Boot type = 0
312     //   V: BMC dump status = TODO
313     //   E: Platform boot mode = 0 (side = temporary, speed = fast)
314     //   P: Platform dump status = TODO
315     //  FF: SRC format, set below
316 
317     setBMCFormat();
318     setBMCPosition();
319     setMotherboardCCIN(dataIface);
320 
321     // Fill in the last 4 words from the AdditionalData property contents.
322     setUserDefinedHexWords(regEntry, additionalData);
323 
324     _asciiString = std::make_unique<src::AsciiString>(regEntry);
325 
326     addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
327 
328     _size = baseSRCSize;
329     _size += _callouts ? _callouts->flattenedSize() : 0;
330     _header.size = Section::flattenedSize() + _size;
331 
332     _valid = true;
333 }
334 
335 void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
336                                  const AdditionalData& ad)
337 {
338     if (!regEntry.src.hexwordADFields)
339     {
340         return;
341     }
342 
343     // Save the AdditionalData value corresponding to the first element of
344     // adName tuple into _hexData[wordNum].
345     for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
346     {
347         // Can only set words 6 - 9
348         if (!isUserDefinedWord(wordNum))
349         {
350             std::string msg =
351                 "SRC user data word out of range: " + std::to_string(wordNum);
352             addDebugData(msg);
353             continue;
354         }
355 
356         auto value = ad.getValue(std::get<0>(adName));
357         if (value)
358         {
359             _hexData[getWordIndexFromWordNum(wordNum)] =
360                 std::strtoul(value.value().c_str(), nullptr, 0);
361         }
362         else
363         {
364             std::string msg = "Source for user data SRC word not found: " +
365                               std::get<0>(adName);
366             addDebugData(msg);
367         }
368     }
369 }
370 
371 void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
372 {
373     uint32_t ccin = 0;
374     auto ccinString = dataIface.getMotherboardCCIN();
375 
376     try
377     {
378         if (ccinString.size() == ccinSize)
379         {
380             ccin = std::stoi(ccinString, 0, 16);
381         }
382     }
383     catch (std::exception& e)
384     {
385         log<level::WARNING>("Could not convert motherboard CCIN to a number",
386                             entry("CCIN=%s", ccinString.c_str()));
387         return;
388     }
389 
390     // Set the first 2 bytes
391     _hexData[1] |= ccin << 16;
392 }
393 
394 void SRC::validate()
395 {
396     bool failed = false;
397 
398     if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
399         (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
400     {
401         log<level::ERR>("Invalid SRC section ID",
402                         entry("ID=0x%X", header().id));
403         failed = true;
404     }
405 
406     // Check the version in the SRC, not in the header
407     if (_version != srcVersion)
408     {
409         log<level::ERR>("Invalid SRC version", entry("VERSION=0x%X", _version));
410         failed = true;
411     }
412 
413     _valid = failed ? false : true;
414 }
415 
416 bool SRC::isBMCSRC() const
417 {
418     auto as = asciiString();
419     if (as.length() >= 2)
420     {
421         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
422         return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
423                 errorType == static_cast<uint8_t>(SRCType::powerError));
424     }
425     return false;
426 }
427 
428 std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
429                                                 DetailLevel type,
430                                                 bool toCache) const
431 {
432     const std::string jsonIndent(indentLevel, 0x20);
433     std::string errorOut;
434     if (isBMCSRC())
435     {
436         auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
437                                      rg::LookupType::reasonCode, toCache);
438         if (entry)
439         {
440             errorOut.append(jsonIndent + "\"Error Details\": {\n");
441             auto errorMsg = getErrorMessage(*entry);
442             if (errorMsg)
443             {
444                 if (type == DetailLevel::message)
445                 {
446                     return errorMsg.value();
447                 }
448                 else
449                 {
450                     jsonInsert(errorOut, "Message", errorMsg.value(), 2);
451                 }
452             }
453             if (entry->src.hexwordADFields)
454             {
455                 std::map<size_t, std::tuple<std::string, std::string>>
456                     adFields = entry->src.hexwordADFields.value();
457                 for (const auto& hexwordMap : adFields)
458                 {
459                     std::vector<std::string> valueDescr;
460                     valueDescr.push_back(getNumberString(
461                         "0x%X",
462                         _hexData[getWordIndexFromWordNum(hexwordMap.first)]));
463                     valueDescr.push_back(std::get<1>(hexwordMap.second));
464                     jsonInsertArray(errorOut, std::get<0>(hexwordMap.second),
465                                     valueDescr, 2);
466                 }
467             }
468             errorOut.erase(errorOut.size() - 2);
469             errorOut.append("\n");
470             errorOut.append(jsonIndent + "},\n");
471             return errorOut;
472         }
473     }
474     return std::nullopt;
475 }
476 
477 std::optional<std::string>
478     SRC::getErrorMessage(const message::Entry& regEntry) const
479 {
480     try
481     {
482         if (regEntry.doc.messageArgSources)
483         {
484             size_t msgLen = regEntry.doc.message.length();
485             char msg[msgLen + 1];
486             strcpy(msg, regEntry.doc.message.c_str());
487             std::vector<uint32_t> argSourceVals;
488             std::string message;
489             const auto& argValues = regEntry.doc.messageArgSources.value();
490             for (size_t i = 0; i < argValues.size(); ++i)
491             {
492                 argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
493                     argValues[i].back() - '0')]);
494             }
495             const char* msgPointer = msg;
496             while (*msgPointer)
497             {
498                 if (*msgPointer == '%')
499                 {
500                     msgPointer++;
501                     size_t wordIndex = *msgPointer - '0';
502                     if (isdigit(*msgPointer) && wordIndex >= 1 &&
503                         static_cast<uint16_t>(wordIndex) <=
504                             argSourceVals.size())
505                     {
506                         message.append(getNumberString(
507                             "0x%X", argSourceVals[wordIndex - 1]));
508                     }
509                     else
510                     {
511                         message.append("%" + std::string(1, *msgPointer));
512                     }
513                 }
514                 else
515                 {
516                     message.push_back(*msgPointer);
517                 }
518                 msgPointer++;
519             }
520             return message;
521         }
522         else
523         {
524             return regEntry.doc.message;
525         }
526     }
527     catch (const std::exception& e)
528     {
529         log<level::ERR>("Cannot get error message from registry entry",
530                         entry("ERROR=%s", e.what()));
531     }
532     return std::nullopt;
533 }
534 
535 std::optional<std::string> SRC::getCallouts() const
536 {
537     if (!_callouts)
538     {
539         return std::nullopt;
540     }
541     std::string printOut;
542     const std::string jsonIndent(indentLevel, 0x20);
543     const auto& callout = _callouts->callouts();
544     const auto& compDescrp = pv::failingComponentType;
545     printOut.append(jsonIndent + "\"Callout Section\": {\n");
546     jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
547     printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
548     for (auto& entry : callout)
549     {
550         printOut.append("{\n");
551         if (entry->fruIdentity())
552         {
553             jsonInsert(
554                 printOut, "FRU Type",
555                 compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
556             jsonInsert(printOut, "Priority",
557                        pv::getValue(entry->priority(),
558                                     pel_values::calloutPriorityValues),
559                        3);
560             if (!entry->locationCode().empty())
561             {
562                 jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
563             }
564             if (entry->fruIdentity()->getPN().has_value())
565             {
566                 jsonInsert(printOut, "Part Number",
567                            entry->fruIdentity()->getPN().value(), 3);
568             }
569             if (entry->fruIdentity()->getMaintProc().has_value())
570             {
571                 jsonInsert(printOut, "Procedure",
572                            entry->fruIdentity()->getMaintProc().value(), 3);
573                 if (pv::procedureDesc.find(
574                         entry->fruIdentity()->getMaintProc().value()) !=
575                     pv::procedureDesc.end())
576                 {
577                     jsonInsert(
578                         printOut, "Description",
579                         pv::procedureDesc.at(
580                             entry->fruIdentity()->getMaintProc().value()),
581                         3);
582                 }
583             }
584             if (entry->fruIdentity()->getCCIN().has_value())
585             {
586                 jsonInsert(printOut, "CCIN",
587                            entry->fruIdentity()->getCCIN().value(), 3);
588             }
589             if (entry->fruIdentity()->getSN().has_value())
590             {
591                 jsonInsert(printOut, "Serial Number",
592                            entry->fruIdentity()->getSN().value(), 3);
593             }
594         }
595         if (entry->pceIdentity())
596         {
597             const auto& pceIdentMtms = entry->pceIdentity()->mtms();
598             if (!pceIdentMtms.machineTypeAndModel().empty())
599             {
600                 jsonInsert(printOut, "PCE MTMS",
601                            pceIdentMtms.machineTypeAndModel() + "_" +
602                                pceIdentMtms.machineSerialNumber(),
603                            3);
604             }
605             if (!entry->pceIdentity()->enclosureName().empty())
606             {
607                 jsonInsert(printOut, "PCE Name",
608                            entry->pceIdentity()->enclosureName(), 3);
609             }
610         }
611         if (entry->mru())
612         {
613             const auto& mruCallouts = entry->mru()->mrus();
614             std::string mruId;
615             for (auto& element : mruCallouts)
616             {
617                 if (!mruId.empty())
618                 {
619                     mruId.append(", " + getNumberString("%08X", element.id));
620                 }
621                 else
622                 {
623                     mruId.append(getNumberString("%08X", element.id));
624                 }
625             }
626             jsonInsert(printOut, "MRU Id", mruId, 3);
627         }
628         printOut.erase(printOut.size() - 2);
629         printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
630     };
631     printOut.erase(printOut.size() - 2);
632     printOut.append("]\n" + jsonIndent + "}");
633     return printOut;
634 }
635 
636 std::optional<std::string> SRC::getJSON(message::Registry& registry,
637                                         const std::vector<std::string>& plugins,
638                                         uint8_t creatorID) const
639 {
640     std::string ps;
641     std::vector<std::string> hexwords;
642     jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
643     jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
644     jsonInsert(ps, pv::createdBy, getNumberString("0x%X", _header.componentID),
645                1);
646     jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
647     jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
648                1);
649     jsonInsert(ps, "Virtual Progress SRC",
650                pv::boolString.at(_flags & virtualProgressSRC), 1);
651     jsonInsert(ps, "I5/OS Service Event Bit",
652                pv::boolString.at(_flags & i5OSServiceEventBit), 1);
653     jsonInsert(ps, "Hypervisor Dump Initiated",
654                pv::boolString.at(_flags & hypDumpInit), 1);
655     jsonInsert(ps, "Power Control Net Fault",
656                pv::boolString.at(isPowerFaultEvent()), 1);
657 
658     if (isBMCSRC())
659     {
660         std::string ccinString;
661         uint32_t ccin = _hexData[1] >> 16;
662 
663         if (ccin)
664         {
665             ccinString = getNumberString("%04X", ccin);
666         }
667         // The PEL spec calls it a backplane, so call it that here.
668         jsonInsert(ps, "Backplane CCIN", ccinString, 1);
669 
670         jsonInsert(ps, "Deconfigured",
671                    pv::boolString.at(
672                        _hexData[3] &
673                        static_cast<uint32_t>(ErrorStatusFlags::deconfigured)),
674                    1);
675 
676         jsonInsert(
677             ps, "Guarded",
678             pv::boolString.at(_hexData[3] &
679                               static_cast<uint32_t>(ErrorStatusFlags::guarded)),
680             1);
681     }
682 
683     auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
684     if (errorDetails)
685     {
686         ps.append(errorDetails.value());
687     }
688     jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
689                1);
690     std::string refcode = asciiString();
691     hexwords.push_back(refcode);
692     std::string extRefcode;
693     size_t pos = refcode.find(0x20);
694     if (pos != std::string::npos)
695     {
696         size_t nextPos = refcode.find_first_not_of(0x20, pos);
697         if (nextPos != std::string::npos)
698         {
699             extRefcode = trimEnd(refcode.substr(nextPos));
700         }
701         refcode.erase(pos);
702     }
703     jsonInsert(ps, "Reference Code", refcode, 1);
704     if (!extRefcode.empty())
705     {
706         jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
707     }
708     for (size_t i = 2; i <= _wordCount; i++)
709     {
710         std::string tmpWord =
711             getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]);
712         jsonInsert(ps, "Hex Word " + std::to_string(i), tmpWord, 1);
713         hexwords.push_back(tmpWord);
714     }
715     auto calloutJson = getCallouts();
716     if (calloutJson)
717     {
718         ps.append(calloutJson.value());
719         ps.append(",\n");
720     }
721     std::string subsystem = getNumberString("%c", tolower(creatorID));
722     bool srcDetailExists = false;
723 #ifdef PELTOOL
724     if (std::find(plugins.begin(), plugins.end(), subsystem + "src") !=
725         plugins.end())
726     {
727         auto pyJson = getPythonJSON(hexwords, creatorID);
728         if (pyJson)
729         {
730             ps.append(pyJson.value());
731             srcDetailExists = true;
732         }
733     }
734 #endif
735     if (!srcDetailExists)
736     {
737         ps.erase(ps.size() - 2);
738     }
739     return ps;
740 }
741 
742 void SRC::addCallouts(const message::Entry& regEntry,
743                       const AdditionalData& additionalData,
744                       const nlohmann::json& jsonCallouts,
745                       const DataInterfaceBase& dataIface)
746 {
747     auto registryCallouts =
748         getRegistryCallouts(regEntry, additionalData, dataIface);
749 
750     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
751     auto priority = additionalData.getValue("CALLOUT_PRIORITY");
752 
753     std::optional<CalloutPriority> calloutPriority;
754 
755     // Only  H, M or L priority values.
756     if (priority && !(*priority).empty())
757     {
758         uint8_t p = (*priority)[0];
759         if (p == 'H' || p == 'M' || p == 'L')
760         {
761             calloutPriority = static_cast<CalloutPriority>(p);
762         }
763     }
764     // If the first registry callout says to use the passed in inventory
765     // path to get the location code for a symbolic FRU callout with a
766     // trusted location code, then do not add the inventory path as a
767     // normal FRU callout.
768     bool useInvForSymbolicFRULocCode =
769         !registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
770         !registryCallouts[0].symbolicFRUTrusted.empty();
771 
772     if (item && !useInvForSymbolicFRULocCode)
773     {
774         addInventoryCallout(*item, calloutPriority, std::nullopt, dataIface);
775     }
776 
777     addDevicePathCallouts(additionalData, dataIface);
778 
779     addRegistryCallouts(registryCallouts, dataIface,
780                         (useInvForSymbolicFRULocCode) ? item : std::nullopt);
781 
782     if (!jsonCallouts.empty())
783     {
784         addJSONCallouts(jsonCallouts, dataIface);
785     }
786 }
787 
788 void SRC::addInventoryCallout(const std::string& inventoryPath,
789                               const std::optional<CalloutPriority>& priority,
790                               const std::optional<std::string>& locationCode,
791                               const DataInterfaceBase& dataIface,
792                               const std::vector<src::MRU::MRUCallout>& mrus)
793 {
794     std::string locCode;
795     std::string fn;
796     std::string ccin;
797     std::string sn;
798     std::unique_ptr<src::Callout> callout;
799 
800     try
801     {
802         // Use the passed in location code if there otherwise look it up
803         if (locationCode)
804         {
805             locCode = *locationCode;
806         }
807         else
808         {
809             locCode = dataIface.getLocationCode(inventoryPath);
810         }
811 
812         try
813         {
814             dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
815 
816             CalloutPriority p =
817                 priority ? priority.value() : CalloutPriority::high;
818 
819             callout =
820                 std::make_unique<src::Callout>(p, locCode, fn, ccin, sn, mrus);
821         }
822         catch (const SdBusError& e)
823         {
824             std::string msg =
825                 "No VPD found for " + inventoryPath + ": " + e.what();
826             addDebugData(msg);
827 
828             // Just create the callout with empty FRU fields
829             callout = std::make_unique<src::Callout>(
830                 CalloutPriority::high, locCode, fn, ccin, sn, mrus);
831         }
832     }
833     catch (const SdBusError& e)
834     {
835         std::string msg = "Could not get location code for " + inventoryPath +
836                           ": " + e.what();
837         addDebugData(msg);
838 
839         callout = std::make_unique<src::Callout>(CalloutPriority::high,
840                                                  "no_vpd_for_fru");
841     }
842 
843     createCalloutsObject();
844     _callouts->addCallout(std::move(callout));
845 }
846 
847 std::vector<message::RegistryCallout>
848     SRC::getRegistryCallouts(const message::Entry& regEntry,
849                              const AdditionalData& additionalData,
850                              const DataInterfaceBase& dataIface)
851 {
852     std::vector<message::RegistryCallout> registryCallouts;
853 
854     if (regEntry.callouts)
855     {
856         std::vector<std::string> systemNames;
857 
858         try
859         {
860             systemNames = dataIface.getSystemNames();
861         }
862         catch (const std::exception& e)
863         {
864             // Compatible interface not available yet
865         }
866 
867         try
868         {
869             registryCallouts = message::Registry::getCallouts(
870                 regEntry.callouts.value(), systemNames, additionalData);
871         }
872         catch (const std::exception& e)
873         {
874             addDebugData(fmt::format(
875                 "Error parsing PEL message registry callout JSON: {}",
876                 e.what()));
877         }
878     }
879 
880     return registryCallouts;
881 }
882 
883 void SRC::addRegistryCallouts(
884     const std::vector<message::RegistryCallout>& callouts,
885     const DataInterfaceBase& dataIface,
886     std::optional<std::string> trustedSymbolicFRUInvPath)
887 {
888     try
889     {
890         for (const auto& callout : callouts)
891         {
892             addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
893 
894             // Only the first callout gets the inventory path
895             if (trustedSymbolicFRUInvPath)
896             {
897                 trustedSymbolicFRUInvPath = std::nullopt;
898             }
899         }
900     }
901     catch (std::exception& e)
902     {
903         std::string msg =
904             "Error parsing PEL message registry callout JSON: "s + e.what();
905         addDebugData(msg);
906     }
907 }
908 
909 void SRC::addRegistryCallout(
910     const message::RegistryCallout& regCallout,
911     const DataInterfaceBase& dataIface,
912     const std::optional<std::string>& trustedSymbolicFRUInvPath)
913 {
914     std::unique_ptr<src::Callout> callout;
915     auto locCode = regCallout.locCode;
916 
917     if (!locCode.empty())
918     {
919         try
920         {
921             locCode = dataIface.expandLocationCode(locCode, 0);
922         }
923         catch (const std::exception& e)
924         {
925             auto msg =
926                 "Unable to expand location code " + locCode + ": " + e.what();
927             addDebugData(msg);
928             return;
929         }
930     }
931 
932     // Via the PEL values table, get the priority enum.
933     // The schema will have validated the priority was a valid value.
934     auto priorityIt =
935         pv::findByName(regCallout.priority, pv::calloutPriorityValues);
936     assert(priorityIt != pv::calloutPriorityValues.end());
937     auto priority =
938         static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
939 
940     if (!regCallout.procedure.empty())
941     {
942         // Procedure callout
943         callout =
944             std::make_unique<src::Callout>(priority, regCallout.procedure);
945     }
946     else if (!regCallout.symbolicFRU.empty())
947     {
948         // Symbolic FRU callout
949         callout = std::make_unique<src::Callout>(
950             priority, regCallout.symbolicFRU, locCode, false);
951     }
952     else if (!regCallout.symbolicFRUTrusted.empty())
953     {
954         // Symbolic FRU with trusted location code callout
955 
956         // Use the location code from the inventory path if there is one.
957         if (trustedSymbolicFRUInvPath)
958         {
959             try
960             {
961                 locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
962             }
963             catch (const std::exception& e)
964             {
965                 addDebugData(
966                     fmt::format("Could not get location code for {}: {}",
967                                 *trustedSymbolicFRUInvPath, e.what()));
968                 locCode.clear();
969             }
970         }
971 
972         // The registry wants it to be trusted, but that requires a valid
973         // location code for it to actually be.
974         callout = std::make_unique<src::Callout>(
975             priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
976     }
977     else
978     {
979         // A hardware callout
980         std::string inventoryPath;
981 
982         try
983         {
984             // Get the inventory item from the unexpanded location code
985             inventoryPath =
986                 dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
987         }
988         catch (const std::exception& e)
989         {
990             std::string msg =
991                 "Unable to get inventory path from location code: " + locCode +
992                 ": " + e.what();
993             addDebugData(msg);
994             return;
995         }
996 
997         addInventoryCallout(inventoryPath, priority, locCode, dataIface);
998     }
999 
1000     if (callout)
1001     {
1002         createCalloutsObject();
1003         _callouts->addCallout(std::move(callout));
1004     }
1005 }
1006 
1007 void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
1008                                 const DataInterfaceBase& dataIface)
1009 {
1010     std::vector<device_callouts::Callout> callouts;
1011     auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
1012     auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
1013     auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
1014 
1015     // A device callout contains either:
1016     // * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
1017     // * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
1018     // We don't care about the errno.
1019 
1020     if (devPath)
1021     {
1022         try
1023         {
1024             callouts = device_callouts::getCallouts(*devPath,
1025                                                     dataIface.getSystemNames());
1026         }
1027         catch (const std::exception& e)
1028         {
1029             addDebugData(e.what());
1030             callouts.clear();
1031         }
1032     }
1033     else if (i2cBus && i2cAddr)
1034     {
1035         size_t bus;
1036         uint8_t address;
1037 
1038         try
1039         {
1040             // If /dev/i2c- is prepended, remove it
1041             if (i2cBus->find("/dev/i2c-") != std::string::npos)
1042             {
1043                 *i2cBus = i2cBus->substr(9);
1044             }
1045 
1046             bus = stoul(*i2cBus, nullptr, 0);
1047             address = stoul(*i2cAddr, nullptr, 0);
1048         }
1049         catch (const std::exception& e)
1050         {
1051             std::string msg = "Invalid CALLOUT_IIC_BUS " + *i2cBus +
1052                               " or CALLOUT_IIC_ADDR " + *i2cAddr +
1053                               " in AdditionalData property";
1054             addDebugData(msg);
1055             return;
1056         }
1057 
1058         try
1059         {
1060             callouts = device_callouts::getI2CCallouts(
1061                 bus, address, dataIface.getSystemNames());
1062         }
1063         catch (const std::exception& e)
1064         {
1065             addDebugData(e.what());
1066             callouts.clear();
1067         }
1068     }
1069 
1070     for (const auto& callout : callouts)
1071     {
1072         // The priority shouldn't be invalid, but check just in case.
1073         CalloutPriority priority = CalloutPriority::high;
1074 
1075         if (!callout.priority.empty())
1076         {
1077             auto p = pel_values::findByValue(
1078                 static_cast<uint32_t>(callout.priority[0]),
1079                 pel_values::calloutPriorityValues);
1080 
1081             if (p != pel_values::calloutPriorityValues.end())
1082             {
1083                 priority = static_cast<CalloutPriority>(callout.priority[0]);
1084             }
1085             else
1086             {
1087                 std::string msg =
1088                     "Invalid priority found in dev callout JSON: " +
1089                     callout.priority[0];
1090                 addDebugData(msg);
1091             }
1092         }
1093 
1094         try
1095         {
1096             auto inventoryPath = dataIface.getInventoryFromLocCode(
1097                 callout.locationCode, 0, false);
1098 
1099             addInventoryCallout(inventoryPath, priority, std::nullopt,
1100                                 dataIface);
1101         }
1102         catch (const std::exception& e)
1103         {
1104             std::string msg =
1105                 "Unable to get inventory path from location code: " +
1106                 callout.locationCode + ": " + e.what();
1107             addDebugData(msg);
1108         }
1109 
1110         // Until the code is there to convert these MRU value strings to
1111         // the official MRU values in the callout objects, just store
1112         // the MRU name in the debug UserData section.
1113         if (!callout.mru.empty())
1114         {
1115             std::string msg = "MRU: " + callout.mru;
1116             addDebugData(msg);
1117         }
1118 
1119         // getCallouts() may have generated some debug data it stored
1120         // in a callout object.  Save it as well.
1121         if (!callout.debug.empty())
1122         {
1123             addDebugData(callout.debug);
1124         }
1125     }
1126 }
1127 
1128 void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
1129                           const DataInterfaceBase& dataIface)
1130 {
1131     if (jsonCallouts.empty())
1132     {
1133         return;
1134     }
1135 
1136     if (!jsonCallouts.is_array())
1137     {
1138         addDebugData("Callout JSON isn't an array");
1139         return;
1140     }
1141 
1142     for (const auto& callout : jsonCallouts)
1143     {
1144         try
1145         {
1146             addJSONCallout(callout, dataIface);
1147         }
1148         catch (const std::exception& e)
1149         {
1150             addDebugData(fmt::format(
1151                 "Failed extracting callout data from JSON: {}", e.what()));
1152         }
1153     }
1154 }
1155 
1156 void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
1157                          const DataInterfaceBase& dataIface)
1158 {
1159     auto priority = getPriorityFromJSON(jsonCallout);
1160     std::string locCode;
1161     std::string unexpandedLocCode;
1162     std::unique_ptr<src::Callout> callout;
1163 
1164     // Expand the location code if it's there
1165     if (jsonCallout.contains("LocationCode"))
1166     {
1167         unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
1168 
1169         try
1170         {
1171             locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
1172         }
1173         catch (const std::exception& e)
1174         {
1175             addDebugData(fmt::format("Unable to expand location code {}: {}",
1176                                      unexpandedLocCode, e.what()));
1177             // Use the value from the JSON so at least there's something
1178             locCode = unexpandedLocCode;
1179         }
1180     }
1181 
1182     // Create either a procedure, symbolic FRU, or normal FRU callout.
1183     if (jsonCallout.contains("Procedure"))
1184     {
1185         auto procedure = jsonCallout.at("Procedure").get<std::string>();
1186 
1187         callout = std::make_unique<src::Callout>(
1188             static_cast<CalloutPriority>(priority), procedure,
1189             src::CalloutValueType::raw);
1190     }
1191     else if (jsonCallout.contains("SymbolicFRU"))
1192     {
1193         auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
1194 
1195         bool trusted = false;
1196         if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
1197         {
1198             trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
1199         }
1200 
1201         callout = std::make_unique<src::Callout>(
1202             static_cast<CalloutPriority>(priority), fru,
1203             src::CalloutValueType::raw, locCode, trusted);
1204     }
1205     else
1206     {
1207         // A hardware FRU
1208         std::string inventoryPath;
1209         std::vector<src::MRU::MRUCallout> mrus;
1210 
1211         if (jsonCallout.contains("InventoryPath"))
1212         {
1213             inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
1214         }
1215         else
1216         {
1217             if (unexpandedLocCode.empty())
1218             {
1219                 throw std::runtime_error{"JSON callout needs either an "
1220                                          "inventory path or location code"};
1221             }
1222 
1223             try
1224             {
1225                 inventoryPath = dataIface.getInventoryFromLocCode(
1226                     unexpandedLocCode, 0, false);
1227             }
1228             catch (const std::exception& e)
1229             {
1230                 throw std::runtime_error{
1231                     fmt::format("Unable to get inventory path from "
1232                                 "location code: {}: {}",
1233                                 unexpandedLocCode, e.what())};
1234             }
1235         }
1236 
1237         if (jsonCallout.contains("MRUs"))
1238         {
1239             mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
1240         }
1241 
1242         // If the location code was also passed in, use that here too
1243         // so addInventoryCallout doesn't have to look it up.
1244         std::optional<std::string> lc;
1245         if (!locCode.empty())
1246         {
1247             lc = locCode;
1248         }
1249 
1250         addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
1251 
1252         if (jsonCallout.contains("Deconfigured"))
1253         {
1254             if (jsonCallout.at("Deconfigured").get<bool>())
1255             {
1256                 setErrorStatusFlag(ErrorStatusFlags::deconfigured);
1257             }
1258         }
1259 
1260         if (jsonCallout.contains("Guarded"))
1261         {
1262             if (jsonCallout.at("Guarded").get<bool>())
1263             {
1264                 setErrorStatusFlag(ErrorStatusFlags::guarded);
1265             }
1266         }
1267     }
1268 
1269     if (callout)
1270     {
1271         createCalloutsObject();
1272         _callouts->addCallout(std::move(callout));
1273     }
1274 }
1275 
1276 CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
1277 {
1278     // Looks like:
1279     // {
1280     //     "Priority": "H"
1281     // }
1282     auto p = json.at("Priority").get<std::string>();
1283     if (p.empty())
1284     {
1285         throw std::runtime_error{"Priority field in callout is empty"};
1286     }
1287 
1288     auto priority = static_cast<CalloutPriority>(p.front());
1289 
1290     // Validate it
1291     auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
1292                                       pv::calloutPriorityValues);
1293     if (priorityIt == pv::calloutPriorityValues.end())
1294     {
1295         throw std::runtime_error{
1296             fmt::format("Invalid priority '{}' found in JSON callout", p)};
1297     }
1298 
1299     return priority;
1300 }
1301 
1302 std::vector<src::MRU::MRUCallout>
1303     SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
1304 {
1305     std::vector<src::MRU::MRUCallout> mrus;
1306 
1307     // Looks like:
1308     // [
1309     //     {
1310     //         "ID": 100,
1311     //         "Priority": "H"
1312     //     }
1313     // ]
1314     if (!mruJSON.is_array())
1315     {
1316         addDebugData("MRU callout JSON is not an array");
1317         return mrus;
1318     }
1319 
1320     for (const auto& mruCallout : mruJSON)
1321     {
1322         try
1323         {
1324             auto priority = getPriorityFromJSON(mruCallout);
1325             auto id = mruCallout.at("ID").get<uint32_t>();
1326 
1327             src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
1328             mrus.push_back(std::move(mru));
1329         }
1330         catch (const std::exception& e)
1331         {
1332             addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
1333                                      mruCallout.dump(), e.what()));
1334         }
1335     }
1336 
1337     return mrus;
1338 }
1339 
1340 } // namespace pels
1341 } // namespace openpower
1342