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