xref: /openbmc/phosphor-logging/extensions/openpower-pels/src.cpp (revision 8bcadac750d90427bfc4b282be47114c3dc415b1)
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         callout = std::make_unique<src::Callout>(CalloutPriority::high,
882                                                  "no_vpd_for_fru");
883     }
884 
885     createCalloutsObject();
886     _callouts->addCallout(std::move(callout));
887 }
888 
889 std::vector<message::RegistryCallout>
890     SRC::getRegistryCallouts(const message::Entry& regEntry,
891                              const AdditionalData& additionalData,
892                              const DataInterfaceBase& dataIface)
893 {
894     std::vector<message::RegistryCallout> registryCallouts;
895 
896     if (regEntry.callouts)
897     {
898         std::vector<std::string> systemNames;
899 
900         try
901         {
902             systemNames = dataIface.getSystemNames();
903         }
904         catch (const std::exception& e)
905         {
906             // Compatible interface not available yet
907         }
908 
909         try
910         {
911             registryCallouts = message::Registry::getCallouts(
912                 regEntry.callouts.value(), systemNames, additionalData);
913         }
914         catch (const std::exception& e)
915         {
916             addDebugData(fmt::format(
917                 "Error parsing PEL message registry callout JSON: {}",
918                 e.what()));
919         }
920     }
921 
922     return registryCallouts;
923 }
924 
925 void SRC::addRegistryCallouts(
926     const std::vector<message::RegistryCallout>& callouts,
927     const DataInterfaceBase& dataIface,
928     std::optional<std::string> trustedSymbolicFRUInvPath)
929 {
930     try
931     {
932         for (const auto& callout : callouts)
933         {
934             addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
935 
936             // Only the first callout gets the inventory path
937             if (trustedSymbolicFRUInvPath)
938             {
939                 trustedSymbolicFRUInvPath = std::nullopt;
940             }
941         }
942     }
943     catch (std::exception& e)
944     {
945         std::string msg =
946             "Error parsing PEL message registry callout JSON: "s + e.what();
947         addDebugData(msg);
948     }
949 }
950 
951 void SRC::addRegistryCallout(
952     const message::RegistryCallout& regCallout,
953     const DataInterfaceBase& dataIface,
954     const std::optional<std::string>& trustedSymbolicFRUInvPath)
955 {
956     std::unique_ptr<src::Callout> callout;
957     auto locCode = regCallout.locCode;
958 
959     if (!locCode.empty())
960     {
961         try
962         {
963             locCode = dataIface.expandLocationCode(locCode, 0);
964         }
965         catch (const std::exception& e)
966         {
967             auto msg =
968                 "Unable to expand location code " + locCode + ": " + e.what();
969             addDebugData(msg);
970             return;
971         }
972     }
973 
974     // Via the PEL values table, get the priority enum.
975     // The schema will have validated the priority was a valid value.
976     auto priorityIt =
977         pv::findByName(regCallout.priority, pv::calloutPriorityValues);
978     assert(priorityIt != pv::calloutPriorityValues.end());
979     auto priority =
980         static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
981 
982     if (!regCallout.procedure.empty())
983     {
984         // Procedure callout
985         callout =
986             std::make_unique<src::Callout>(priority, regCallout.procedure);
987     }
988     else if (!regCallout.symbolicFRU.empty())
989     {
990         // Symbolic FRU callout
991         callout = std::make_unique<src::Callout>(
992             priority, regCallout.symbolicFRU, locCode, false);
993     }
994     else if (!regCallout.symbolicFRUTrusted.empty())
995     {
996         // Symbolic FRU with trusted location code callout
997 
998         // Use the location code from the inventory path if there is one.
999         if (trustedSymbolicFRUInvPath)
1000         {
1001             try
1002             {
1003                 locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
1004             }
1005             catch (const std::exception& e)
1006             {
1007                 addDebugData(
1008                     fmt::format("Could not get location code for {}: {}",
1009                                 *trustedSymbolicFRUInvPath, e.what()));
1010                 locCode.clear();
1011             }
1012         }
1013 
1014         // The registry wants it to be trusted, but that requires a valid
1015         // location code for it to actually be.
1016         callout = std::make_unique<src::Callout>(
1017             priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
1018     }
1019     else
1020     {
1021         // A hardware callout
1022         std::string inventoryPath;
1023 
1024         try
1025         {
1026             // Get the inventory item from the unexpanded location code
1027             inventoryPath =
1028                 dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
1029         }
1030         catch (const std::exception& e)
1031         {
1032             std::string msg =
1033                 "Unable to get inventory path from location code: " + locCode +
1034                 ": " + e.what();
1035             addDebugData(msg);
1036             return;
1037         }
1038 
1039         addInventoryCallout(inventoryPath, priority, locCode, dataIface);
1040     }
1041 
1042     if (callout)
1043     {
1044         createCalloutsObject();
1045         _callouts->addCallout(std::move(callout));
1046     }
1047 }
1048 
1049 void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
1050                                 const DataInterfaceBase& dataIface)
1051 {
1052     std::vector<device_callouts::Callout> callouts;
1053     auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
1054     auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
1055     auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
1056 
1057     // A device callout contains either:
1058     // * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
1059     // * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
1060     // We don't care about the errno.
1061 
1062     if (devPath)
1063     {
1064         try
1065         {
1066             callouts = device_callouts::getCallouts(*devPath,
1067                                                     dataIface.getSystemNames());
1068         }
1069         catch (const std::exception& e)
1070         {
1071             addDebugData(e.what());
1072             callouts.clear();
1073         }
1074     }
1075     else if (i2cBus && i2cAddr)
1076     {
1077         size_t bus;
1078         uint8_t address;
1079 
1080         try
1081         {
1082             // If /dev/i2c- is prepended, remove it
1083             if (i2cBus->find("/dev/i2c-") != std::string::npos)
1084             {
1085                 *i2cBus = i2cBus->substr(9);
1086             }
1087 
1088             bus = stoul(*i2cBus, nullptr, 0);
1089             address = stoul(*i2cAddr, nullptr, 0);
1090         }
1091         catch (const std::exception& e)
1092         {
1093             std::string msg = "Invalid CALLOUT_IIC_BUS " + *i2cBus +
1094                               " or CALLOUT_IIC_ADDR " + *i2cAddr +
1095                               " in AdditionalData property";
1096             addDebugData(msg);
1097             return;
1098         }
1099 
1100         try
1101         {
1102             callouts = device_callouts::getI2CCallouts(
1103                 bus, address, dataIface.getSystemNames());
1104         }
1105         catch (const std::exception& e)
1106         {
1107             addDebugData(e.what());
1108             callouts.clear();
1109         }
1110     }
1111 
1112     for (const auto& callout : callouts)
1113     {
1114         // The priority shouldn't be invalid, but check just in case.
1115         CalloutPriority priority = CalloutPriority::high;
1116 
1117         if (!callout.priority.empty())
1118         {
1119             auto p = pel_values::findByValue(
1120                 static_cast<uint32_t>(callout.priority[0]),
1121                 pel_values::calloutPriorityValues);
1122 
1123             if (p != pel_values::calloutPriorityValues.end())
1124             {
1125                 priority = static_cast<CalloutPriority>(callout.priority[0]);
1126             }
1127             else
1128             {
1129                 std::string msg =
1130                     "Invalid priority found in dev callout JSON: " +
1131                     callout.priority[0];
1132                 addDebugData(msg);
1133             }
1134         }
1135 
1136         std::optional<std::string> locCode;
1137 
1138         try
1139         {
1140             locCode = dataIface.expandLocationCode(callout.locationCode, 0);
1141         }
1142         catch (const std::exception& e)
1143         {
1144             auto msg = fmt::format("Unable to expand location code {}: {}",
1145                                    callout.locationCode, e.what());
1146             addDebugData(msg);
1147         }
1148 
1149         try
1150         {
1151             auto inventoryPath = dataIface.getInventoryFromLocCode(
1152                 callout.locationCode, 0, false);
1153 
1154             addInventoryCallout(inventoryPath, priority, locCode, dataIface);
1155         }
1156         catch (const std::exception& e)
1157         {
1158             std::string msg =
1159                 "Unable to get inventory path from location code: " +
1160                 callout.locationCode + ": " + e.what();
1161             addDebugData(msg);
1162         }
1163 
1164         // Until the code is there to convert these MRU value strings to
1165         // the official MRU values in the callout objects, just store
1166         // the MRU name in the debug UserData section.
1167         if (!callout.mru.empty())
1168         {
1169             std::string msg = "MRU: " + callout.mru;
1170             addDebugData(msg);
1171         }
1172 
1173         // getCallouts() may have generated some debug data it stored
1174         // in a callout object.  Save it as well.
1175         if (!callout.debug.empty())
1176         {
1177             addDebugData(callout.debug);
1178         }
1179     }
1180 }
1181 
1182 void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
1183                           const DataInterfaceBase& dataIface)
1184 {
1185     if (jsonCallouts.empty())
1186     {
1187         return;
1188     }
1189 
1190     if (!jsonCallouts.is_array())
1191     {
1192         addDebugData("Callout JSON isn't an array");
1193         return;
1194     }
1195 
1196     for (const auto& callout : jsonCallouts)
1197     {
1198         try
1199         {
1200             addJSONCallout(callout, dataIface);
1201         }
1202         catch (const std::exception& e)
1203         {
1204             addDebugData(fmt::format(
1205                 "Failed extracting callout data from JSON: {}", e.what()));
1206         }
1207     }
1208 }
1209 
1210 void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
1211                          const DataInterfaceBase& dataIface)
1212 {
1213     auto priority = getPriorityFromJSON(jsonCallout);
1214     std::string locCode;
1215     std::string unexpandedLocCode;
1216     std::unique_ptr<src::Callout> callout;
1217 
1218     // Expand the location code if it's there
1219     if (jsonCallout.contains("LocationCode"))
1220     {
1221         unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
1222 
1223         try
1224         {
1225             locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
1226         }
1227         catch (const std::exception& e)
1228         {
1229             addDebugData(fmt::format("Unable to expand location code {}: {}",
1230                                      unexpandedLocCode, e.what()));
1231             // Use the value from the JSON so at least there's something
1232             locCode = unexpandedLocCode;
1233         }
1234     }
1235 
1236     // Create either a procedure, symbolic FRU, or normal FRU callout.
1237     if (jsonCallout.contains("Procedure"))
1238     {
1239         auto procedure = jsonCallout.at("Procedure").get<std::string>();
1240 
1241         callout = std::make_unique<src::Callout>(
1242             static_cast<CalloutPriority>(priority), procedure,
1243             src::CalloutValueType::raw);
1244     }
1245     else if (jsonCallout.contains("SymbolicFRU"))
1246     {
1247         auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
1248 
1249         bool trusted = false;
1250         if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
1251         {
1252             trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
1253         }
1254 
1255         callout = std::make_unique<src::Callout>(
1256             static_cast<CalloutPriority>(priority), fru,
1257             src::CalloutValueType::raw, locCode, trusted);
1258     }
1259     else
1260     {
1261         // A hardware FRU
1262         std::string inventoryPath;
1263         std::vector<src::MRU::MRUCallout> mrus;
1264 
1265         if (jsonCallout.contains("InventoryPath"))
1266         {
1267             inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
1268         }
1269         else
1270         {
1271             if (unexpandedLocCode.empty())
1272             {
1273                 throw std::runtime_error{"JSON callout needs either an "
1274                                          "inventory path or location code"};
1275             }
1276 
1277             try
1278             {
1279                 inventoryPath = dataIface.getInventoryFromLocCode(
1280                     unexpandedLocCode, 0, false);
1281             }
1282             catch (const std::exception& e)
1283             {
1284                 throw std::runtime_error{
1285                     fmt::format("Unable to get inventory path from "
1286                                 "location code: {}: {}",
1287                                 unexpandedLocCode, e.what())};
1288             }
1289         }
1290 
1291         if (jsonCallout.contains("MRUs"))
1292         {
1293             mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
1294         }
1295 
1296         // If the location code was also passed in, use that here too
1297         // so addInventoryCallout doesn't have to look it up.
1298         std::optional<std::string> lc;
1299         if (!locCode.empty())
1300         {
1301             lc = locCode;
1302         }
1303 
1304         addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
1305 
1306         if (jsonCallout.contains("Deconfigured"))
1307         {
1308             if (jsonCallout.at("Deconfigured").get<bool>())
1309             {
1310                 setErrorStatusFlag(ErrorStatusFlags::deconfigured);
1311             }
1312         }
1313 
1314         if (jsonCallout.contains("Guarded"))
1315         {
1316             if (jsonCallout.at("Guarded").get<bool>())
1317             {
1318                 setErrorStatusFlag(ErrorStatusFlags::guarded);
1319             }
1320         }
1321     }
1322 
1323     if (callout)
1324     {
1325         createCalloutsObject();
1326         _callouts->addCallout(std::move(callout));
1327     }
1328 }
1329 
1330 CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
1331 {
1332     // Looks like:
1333     // {
1334     //     "Priority": "H"
1335     // }
1336     auto p = json.at("Priority").get<std::string>();
1337     if (p.empty())
1338     {
1339         throw std::runtime_error{"Priority field in callout is empty"};
1340     }
1341 
1342     auto priority = static_cast<CalloutPriority>(p.front());
1343 
1344     // Validate it
1345     auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
1346                                       pv::calloutPriorityValues);
1347     if (priorityIt == pv::calloutPriorityValues.end())
1348     {
1349         throw std::runtime_error{
1350             fmt::format("Invalid priority '{}' found in JSON callout", p)};
1351     }
1352 
1353     return priority;
1354 }
1355 
1356 std::vector<src::MRU::MRUCallout>
1357     SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
1358 {
1359     std::vector<src::MRU::MRUCallout> mrus;
1360 
1361     // Looks like:
1362     // [
1363     //     {
1364     //         "ID": 100,
1365     //         "Priority": "H"
1366     //     }
1367     // ]
1368     if (!mruJSON.is_array())
1369     {
1370         addDebugData("MRU callout JSON is not an array");
1371         return mrus;
1372     }
1373 
1374     for (const auto& mruCallout : mruJSON)
1375     {
1376         try
1377         {
1378             auto priority = getPriorityFromJSON(mruCallout);
1379             auto id = mruCallout.at("ID").get<uint32_t>();
1380 
1381             src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
1382             mrus.push_back(std::move(mru));
1383         }
1384         catch (const std::exception& e)
1385         {
1386             addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
1387                                      mruCallout.dump(), e.what()));
1388         }
1389     }
1390 
1391     return mrus;
1392 }
1393 
1394 } // namespace pels
1395 } // namespace openpower
1396