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