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