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