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