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