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