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