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