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