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