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