xref: /openbmc/phosphor-logging/extensions/openpower-pels/pel.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3 
4 #include "config.h"
5 
6 #include "pel.hpp"
7 
8 #include "bcd_time.hpp"
9 #include "extended_user_data.hpp"
10 #include "extended_user_header.hpp"
11 #include "failing_mtms.hpp"
12 #include "fru_identity.hpp"
13 #include "json_utils.hpp"
14 #include "log_id.hpp"
15 #include "pel_rules.hpp"
16 #include "pel_values.hpp"
17 #include "section_factory.hpp"
18 #include "src.hpp"
19 #include "stream.hpp"
20 #include "user_data_formats.hpp"
21 
22 #ifdef PEL_ENABLE_PHAL
23 #include "phal_service_actions.hpp"
24 #include "sbe_ffdc_handler.hpp"
25 
26 #include <libguard/guard_interface.hpp>
27 #include <libguard/include/guard_record.hpp>
28 
29 namespace libguard = openpower::guard;
30 #endif
31 
32 #include <sys/stat.h>
33 #include <unistd.h>
34 
35 #include <phosphor-logging/lg2.hpp>
36 
37 #include <format>
38 #include <iostream>
39 #include <ranges>
40 
41 namespace openpower
42 {
43 namespace pels
44 {
45 namespace pv = openpower::pels::pel_values;
46 
47 constexpr auto unknownValue = "Unknown";
48 constexpr auto AdDIMMInfoFetchError = "DIMMs Info Fetch Error";
49 
PEL(const message::Entry & regEntry,uint32_t obmcLogID,uint64_t timestamp,phosphor::logging::Entry::Level severity,const AdditionalData & additionalData,const PelFFDC & ffdcFilesIn,const DataInterfaceBase & dataIface,const JournalBase & journal)50 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
51          phosphor::logging::Entry::Level severity,
52          const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn,
53          const DataInterfaceBase& dataIface, const JournalBase& journal)
54 {
55     // No changes in input, for non SBE error related requests
56     PelFFDC ffdcFiles = ffdcFilesIn;
57 
58 #ifdef PEL_ENABLE_PHAL
59     // Add sbe ffdc processed data into ffdcfiles.
60     namespace sbe = openpower::pels::sbe;
61     auto processReq =
62         std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](const auto& file) {
63             return file.format == UserDataFormat::custom &&
64                    file.subType == sbe::sbeFFDCSubType;
65         });
66     // sbeFFDC can't be destroyed until the end of the PEL constructor
67     // because it needs to keep around the FFDC Files to be used below.
68     std::unique_ptr<sbe::SbeFFDC> sbeFFDCPtr;
69     if (processReq)
70     {
71         sbeFFDCPtr =
72             std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn);
73         const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC();
74         ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(),
75                          sbeFFDCFiles.end());
76 
77         // update pel priority for spare clock failures
78         if (auto customSeverity = sbeFFDCPtr->getSeverity())
79         {
80             severity = customSeverity.value();
81         }
82     }
83 #endif
84 
85     DebugData debugData;
86     nlohmann::json callouts;
87 
88     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
89                                           timestamp);
90     _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData,
91                                        dataIface);
92 
93     // Extract any callouts embedded in an FFDC file.
94     if (!ffdcFiles.empty())
95     {
96         try
97         {
98             callouts = getCalloutJSON(ffdcFiles);
99         }
100         catch (const std::exception& e)
101         {
102             debugData.emplace("FFDC file JSON callouts error",
103                               std::vector<std::string>{e.what()});
104         }
105     }
106 
107     auto src =
108         std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface);
109 
110     nlohmann::json adSysInfoData(nlohmann::json::value_t::object);
111     addAdDetailsForDIMMsCallout(src, dataIface, adSysInfoData, debugData);
112 
113     if (!src->getDebugData().empty())
114     {
115         // Something didn't go as planned
116         debugData.emplace("SRC", src->getDebugData());
117     }
118 
119     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
120 
121     _optionalSections.push_back(std::move(src));
122     _optionalSections.push_back(std::move(euh));
123 
124     auto mtms = std::make_unique<FailingMTMS>(dataIface);
125     _optionalSections.push_back(std::move(mtms));
126 
127     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface, true,
128                                                adSysInfoData);
129     addUserDataSection(std::move(ud));
130 
131     //  Check for pel severity of type - 0x51 = critical error, system
132     //  termination and update terminate bit in SRC for pels
133     updateTerminateBitInSRCSection();
134 
135     // Create a UserData section from AdditionalData.
136     if (!additionalData.empty())
137     {
138         ud = util::makeADUserDataSection(additionalData);
139         addUserDataSection(std::move(ud));
140     }
141 
142     // Add any FFDC files into UserData sections
143     for (const auto& file : ffdcFiles)
144     {
145         ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
146         if (!ud)
147         {
148             // Add this error into the debug data UserData section
149             std::ostringstream msg;
150             msg << "Could not make PEL FFDC UserData section from file"
151                 << std::hex << regEntry.componentID << " " << file.subType
152                 << " " << file.version;
153             if (debugData.count("FFDC File"))
154             {
155                 debugData.at("FFDC File").push_back(msg.str());
156             }
157             else
158             {
159                 debugData.emplace("FFDC File",
160                                   std::vector<std::string>{msg.str()});
161             }
162 
163             continue;
164         }
165 
166         addUserDataSection(std::move(ud));
167     }
168 
169 #ifdef PEL_ENABLE_PHAL
170     auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
171     openpower::pels::phal::createServiceActions(callouts, dataIface, plid());
172 #endif
173 
174     // Store in the PEL any important debug data created while
175     // building the PEL sections.
176     if (!debugData.empty())
177     {
178         nlohmann::json data;
179         data["PEL Internal Debug Data"] = debugData;
180         ud = util::makeJSONUserDataSection(data);
181 
182         addUserDataSection(std::move(ud));
183 
184         // Also put in the journal for debug
185         for (const auto& [name, msgs] : debugData)
186         {
187             for (const auto& message : msgs)
188             {
189                 lg2::info("{NAME}: {MSG}", "NAME", name, "MSG", message);
190             }
191         }
192     }
193 
194     addJournalSections(regEntry, journal);
195 
196     _ph->setSectionCount(2 + _optionalSections.size());
197 
198     checkRulesAndFix();
199 }
200 
PEL(std::vector<uint8_t> & data)201 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0) {}
202 
PEL(std::vector<uint8_t> & data,uint32_t obmcLogID)203 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
204 {
205     populateFromRawData(data, obmcLogID);
206 }
207 
populateFromRawData(std::vector<uint8_t> & data,uint32_t obmcLogID)208 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
209 {
210     Stream pelData{data};
211     _ph = std::make_unique<PrivateHeader>(pelData);
212     if (obmcLogID != 0)
213     {
214         _ph->setOBMCLogID(obmcLogID);
215     }
216 
217     _uh = std::make_unique<UserHeader>(pelData);
218 
219     // Use the section factory to create the rest of the objects
220     for (size_t i = 2; i < _ph->sectionCount(); i++)
221     {
222         auto section = section_factory::create(pelData);
223         _optionalSections.push_back(std::move(section));
224     }
225 }
226 
valid() const227 bool PEL::valid() const
228 {
229     bool valid = _ph->valid();
230 
231     if (valid)
232     {
233         valid = _uh->valid();
234     }
235 
236     if (valid)
237     {
238         if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
239                          [](const auto& section) { return section->valid(); }))
240         {
241             valid = false;
242         }
243     }
244 
245     return valid;
246 }
247 
setCommitTime()248 void PEL::setCommitTime()
249 {
250     auto now = std::chrono::system_clock::now();
251     _ph->setCommitTimestamp(getBCDTime(now));
252 }
253 
assignID()254 void PEL::assignID()
255 {
256     _ph->setID(generatePELID());
257 }
258 
flatten(std::vector<uint8_t> & pelBuffer) const259 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
260 {
261     Stream pelData{pelBuffer};
262 
263     if (!valid())
264     {
265         lg2::warning("Unflattening an invalid PEL");
266     }
267 
268     _ph->flatten(pelData);
269     _uh->flatten(pelData);
270 
271     for (auto& section : _optionalSections)
272     {
273         section->flatten(pelData);
274     }
275 }
276 
data() const277 std::vector<uint8_t> PEL::data() const
278 {
279     std::vector<uint8_t> pelData;
280     flatten(pelData);
281     return pelData;
282 }
283 
size() const284 size_t PEL::size() const
285 {
286     size_t size = 0;
287 
288     if (_ph)
289     {
290         size += _ph->header().size;
291     }
292 
293     if (_uh)
294     {
295         size += _uh->header().size;
296     }
297 
298     for (const auto& section : _optionalSections)
299     {
300         size += section->header().size;
301     }
302 
303     return size;
304 }
305 
primarySRC() const306 std::optional<SRC*> PEL::primarySRC() const
307 {
308     auto src = std::find_if(
309         _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
310             return section->header().id ==
311                    static_cast<uint16_t>(SectionID::primarySRC);
312         });
313     if (src != _optionalSections.end())
314     {
315         return static_cast<SRC*>(src->get());
316     }
317 
318     return std::nullopt;
319 }
320 
checkRulesAndFix()321 void PEL::checkRulesAndFix()
322 {
323     // Only fix if the action flags are at their default value which
324     // means they weren't specified in the registry.  Otherwise
325     // assume the user knows what they are doing.
326     if (_uh->actionFlags() == actionFlagsDefault)
327     {
328         auto [actionFlags, eventType] =
329             pel_rules::check(0, _uh->eventType(), _uh->severity());
330 
331         _uh->setActionFlags(actionFlags);
332         _uh->setEventType(eventType);
333     }
334 }
335 
printSectionInJSON(const Section & section,std::string & buf,std::map<uint16_t,size_t> & pluralSections,message::Registry & registry,const std::vector<std::string> & plugins,uint8_t creatorID) const336 void PEL::printSectionInJSON(
337     const Section& section, std::string& buf,
338     std::map<uint16_t, size_t>& pluralSections, message::Registry& registry,
339     const std::vector<std::string>& plugins, uint8_t creatorID) const
340 {
341     char tmpB[5];
342     uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
343                     static_cast<uint8_t>(section.header().id)};
344     sprintf(tmpB, "%c%c", id[0], id[1]);
345     std::string sectionID(tmpB);
346     std::string sectionName = pv::sectionTitles.count(sectionID)
347                                   ? pv::sectionTitles.at(sectionID)
348                                   : "Unknown Section";
349 
350     // Add a count if there are multiple of this type of section
351     auto count = pluralSections.find(section.header().id);
352     if (count != pluralSections.end())
353     {
354         sectionName += " " + std::to_string(count->second);
355         count->second++;
356     }
357 
358     if (section.valid())
359     {
360         std::optional<std::string> json;
361         if (sectionID == "PS" || sectionID == "SS")
362         {
363             json = section.getJSON(registry, plugins, creatorID);
364         }
365         else if ((sectionID == "UD") || (sectionID == "ED"))
366         {
367             json = section.getJSON(creatorID, plugins);
368         }
369         else
370         {
371             json = section.getJSON(creatorID);
372         }
373 
374         buf += "\"" + sectionName + "\": {\n";
375 
376         if (json)
377         {
378             buf += *json + "\n},\n";
379         }
380         else
381         {
382             jsonInsert(buf, pv::sectionVer,
383                        getNumberString("%d", section.header().version), 1);
384             jsonInsert(buf, pv::subSection,
385                        getNumberString("%d", section.header().subType), 1);
386             jsonInsert(buf, pv::createdBy,
387                        getNumberString("0x%X", section.header().componentID),
388                        1);
389 
390             std::vector<uint8_t> data;
391             Stream s{data};
392             section.flatten(s);
393             std::string dstr =
394                 dumpHex(std::data(data) + SectionHeader::flattenedSize(),
395                         data.size() - SectionHeader::flattenedSize(), 2)
396                     .get();
397             std::string jsonIndent(indentLevel, 0x20);
398             buf += jsonIndent + "\"Data\": [\n";
399             buf += dstr;
400             buf += jsonIndent + "]\n";
401             buf += "},\n";
402         }
403     }
404     else
405     {
406         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
407     }
408 }
409 
getPluralSections() const410 std::map<uint16_t, size_t> PEL::getPluralSections() const
411 {
412     std::map<uint16_t, size_t> sectionCounts;
413 
414     for (const auto& section : optionalSections())
415     {
416         if (sectionCounts.find(section->header().id) == sectionCounts.end())
417         {
418             sectionCounts[section->header().id] = 1;
419         }
420         else
421         {
422             sectionCounts[section->header().id]++;
423         }
424     }
425 
426     std::map<uint16_t, size_t> sections;
427     for (const auto& [id, count] : sectionCounts)
428     {
429         if (count > 1)
430         {
431             // Start with 0 here and printSectionInJSON()
432             // will increment it as it goes.
433             sections.emplace(id, 0);
434         }
435     }
436 
437     return sections;
438 }
439 
toJSON(message::Registry & registry,const std::vector<std::string> & plugins) const440 void PEL::toJSON(message::Registry& registry,
441                  const std::vector<std::string>& plugins) const
442 {
443     auto sections = getPluralSections();
444 
445     std::string buf = "{\n";
446     printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins,
447                        _ph->creatorID());
448     printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins,
449                        _ph->creatorID());
450     for (auto& section : this->optionalSections())
451     {
452         printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
453                            _ph->creatorID());
454     }
455     buf += "}";
456     std::size_t found = buf.rfind(",");
457     if (found != std::string::npos)
458         buf.replace(found, 1, "");
459     std::cout << buf << std::endl;
460 }
461 
addUserDataSection(std::unique_ptr<UserData> userData)462 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
463 {
464     if (size() + userData->header().size > _maxPELSize)
465     {
466         if (userData->shrink(_maxPELSize - size()))
467         {
468             _optionalSections.push_back(std::move(userData));
469         }
470         else
471         {
472             lg2::warning("Could not shrink UserData section. Dropping. "
473                          "Section size = {SSIZE}, Component ID = {COMP_ID}, "
474                          "Subtype = {SUBTYPE}, Version = {VERSION}",
475                          "SSIZE", userData->header().size, "COMP_ID",
476                          userData->header().componentID, "SUBTYPE",
477                          userData->header().subType, "VERSION",
478                          userData->header().version);
479             return false;
480         }
481     }
482     else
483     {
484         _optionalSections.push_back(std::move(userData));
485     }
486     return true;
487 }
488 
getCalloutJSON(const PelFFDC & ffdcFiles)489 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles)
490 {
491     nlohmann::json callouts;
492 
493     for (const auto& file : ffdcFiles)
494     {
495         if ((file.format == UserDataFormat::json) &&
496             (file.subType == jsonCalloutSubtype))
497         {
498             auto data = util::readFD(file.fd);
499             if (data.empty())
500             {
501                 throw std::runtime_error{
502                     "Could not get data from JSON callout file descriptor"};
503             }
504 
505             std::string jsonString{data.begin(), data.begin() + data.size()};
506 
507             callouts = nlohmann::json::parse(jsonString);
508             break;
509         }
510     }
511 
512     return callouts;
513 }
514 
isHwCalloutPresent() const515 bool PEL::isHwCalloutPresent() const
516 {
517     auto pSRC = primarySRC();
518     if (!pSRC)
519     {
520         return false;
521     }
522 
523     bool calloutPresent = false;
524     if ((*pSRC)->callouts())
525     {
526         for (auto& i : (*pSRC)->callouts()->callouts())
527         {
528             if (((*i).fruIdentity()))
529             {
530                 auto& fruId = (*i).fruIdentity();
531                 if ((*fruId).failingComponentType() ==
532                     src::FRUIdentity::hardwareFRU)
533                 {
534                     calloutPresent = true;
535                     break;
536                 }
537             }
538         }
539     }
540 
541     return calloutPresent;
542 }
543 
updateSysInfoInExtendedUserDataSection(const DataInterfaceBase & dataIface)544 void PEL::updateSysInfoInExtendedUserDataSection(
545     const DataInterfaceBase& dataIface)
546 {
547     const AdditionalData additionalData;
548 
549     // Check for PEL from Hostboot
550     if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot))
551     {
552         // Get the ED section from PEL
553         auto op = std::find_if(
554             _optionalSections.begin(), _optionalSections.end(),
555             [](auto& section) {
556                 return section->header().id ==
557                        static_cast<uint16_t>(SectionID::extUserData);
558             });
559 
560         // Check for ED section found and its not the last section of PEL
561         if (op != _optionalSections.end())
562         {
563             // Get the extended user data class mapped to found section
564             auto extUserData = static_cast<ExtendedUserData*>(op->get());
565 
566             // Check for the creator ID is for OpenBMC
567             if (extUserData->creatorID() ==
568                 static_cast<uint8_t>(CreatorID::openBMC))
569             {
570                 // Update subtype and component id
571                 auto subType = static_cast<uint8_t>(UserDataFormat::json);
572                 auto componentId =
573                     static_cast<uint16_t>(ComponentID::phosphorLogging);
574 
575                 // Update system data to ED section
576                 auto ud = util::makeSysInfoUserDataSection(additionalData,
577                                                            dataIface, false);
578                 extUserData->updateDataSection(subType, componentId,
579                                                ud->data());
580             }
581         }
582     }
583 }
584 
getDeconfigFlag() const585 bool PEL::getDeconfigFlag() const
586 {
587     auto creator = static_cast<CreatorID>(_ph->creatorID());
588 
589     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
590     {
591         auto src = primarySRC();
592         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured);
593     }
594     return false;
595 }
596 
getGuardFlag() const597 bool PEL::getGuardFlag() const
598 {
599     auto creator = static_cast<CreatorID>(_ph->creatorID());
600 
601     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
602     {
603         auto src = primarySRC();
604         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::guarded);
605     }
606     return false;
607 }
608 
updateTerminateBitInSRCSection()609 void PEL::updateTerminateBitInSRCSection()
610 {
611     //  Check for pel severity of type - 0x51 = critical error, system
612     //  termination
613     if (_uh->severity() == 0x51)
614     {
615         // Get the primary SRC section
616         auto pSRC = primarySRC();
617         if (pSRC)
618         {
619             (*pSRC)->setTerminateBit();
620         }
621     }
622 }
623 
addJournalSections(const message::Entry & regEntry,const JournalBase & journal)624 void PEL::addJournalSections(const message::Entry& regEntry,
625                              const JournalBase& journal)
626 {
627     if (!regEntry.journalCapture)
628     {
629         return;
630     }
631 
632     // Write all unwritten journal data to disk.
633     journal.sync();
634 
635     const auto& jc = regEntry.journalCapture.value();
636     std::vector<std::vector<std::string>> allMessages;
637 
638     if (std::holds_alternative<size_t>(jc))
639     {
640         // Get the previous numLines journal entries
641         const auto& numLines = std::get<size_t>(jc);
642         try
643         {
644             auto messages = journal.getMessages("", numLines);
645             if (!messages.empty())
646             {
647                 allMessages.push_back(std::move(messages));
648             }
649         }
650         catch (const std::exception& e)
651         {
652             lg2::error("Failed during journal collection: {ERROR}", "ERROR", e);
653         }
654     }
655     else if (std::holds_alternative<message::AppCaptureList>(jc))
656     {
657         // Get journal entries based on the syslog id field.
658         const auto& sections = std::get<message::AppCaptureList>(jc);
659         for (const auto& [syslogID, numLines] : sections)
660         {
661             try
662             {
663                 auto messages = journal.getMessages(syslogID, numLines);
664                 if (!messages.empty())
665                 {
666                     allMessages.push_back(std::move(messages));
667                 }
668             }
669             catch (const std::exception& e)
670             {
671                 lg2::error("Failed during journal collection: {ERROR}", "ERROR",
672                            e);
673             }
674         }
675     }
676 
677     // Create the UserData sections
678     for (const auto& messages : allMessages)
679     {
680         auto buffer = util::flattenLines(messages);
681 
682         // If the buffer is way too big, it can overflow the uint16_t
683         // PEL section size field that is checked below so do a cursory
684         // check here.
685         if (buffer.size() > _maxPELSize)
686         {
687             lg2::warning(
688                 "Journal UserData section does not fit in PEL, dropping. "
689                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
690                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
691             continue;
692         }
693 
694         // Sections must be 4 byte aligned.
695         while (buffer.size() % 4 != 0)
696         {
697             buffer.push_back(0);
698         }
699 
700         auto ud = std::make_unique<UserData>(
701             static_cast<uint16_t>(ComponentID::phosphorLogging),
702             static_cast<uint8_t>(UserDataFormat::text),
703             static_cast<uint8_t>(UserDataFormatVersion::text), buffer);
704 
705         if (size() + ud->header().size <= _maxPELSize)
706         {
707             _optionalSections.push_back(std::move(ud));
708         }
709         else
710         {
711             // Don't attempt to shrink here since we'd be dropping the
712             // most recent journal entries which would be confusing.
713             lg2::warning(
714                 "Journal UserData section does not fit in PEL, dropping. "
715                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
716                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
717             ud.reset();
718             continue;
719         }
720     }
721 }
722 
addAdDetailsForDIMMsCallout(const std::unique_ptr<SRC> & src,const DataInterfaceBase & dataIface,nlohmann::json & adSysInfoData,DebugData & debugData)723 void PEL::addAdDetailsForDIMMsCallout(
724     const std::unique_ptr<SRC>& src, const DataInterfaceBase& dataIface,
725     nlohmann::json& adSysInfoData, DebugData& debugData)
726 {
727     if (!src->callouts())
728     {
729         // No callouts
730         return;
731     }
732 
733     auto isDIMMCallout = [&dataIface](const auto& callout) {
734         auto locCode{callout->locationCode()};
735         if (locCode.empty())
736         {
737             // Not a hardware callout. No action required
738             return false;
739         }
740         else
741         {
742             return const_cast<DataInterfaceBase&>(dataIface).isDIMM(locCode);
743         }
744     };
745     auto addAdDIMMDetails = [&dataIface, &adSysInfoData,
746                              &debugData](const auto& callout) {
747         auto dimmLocCode{callout->locationCode()};
748 
749         auto diPropVal = dataIface.getDIProperty(dimmLocCode);
750         if (!diPropVal.has_value())
751         {
752             std::string errMsg{
753                 std::format("Failed reading DI property from "
754                             "VINI Interface for the LocationCode:[{}]",
755                             dimmLocCode)};
756             debugData[AdDIMMInfoFetchError].emplace_back(errMsg);
757         }
758         else
759         {
760             util::addDIMMInfo(dimmLocCode, diPropVal.value(), adSysInfoData);
761         }
762     };
763 
764     auto DIMMsCallouts = src->callouts()->callouts() |
765                          std::views::filter(isDIMMCallout);
766 
767     std::ranges::for_each(DIMMsCallouts, addAdDIMMDetails);
768 }
769 
770 namespace util
771 {
772 
makeJSONUserDataSection(const nlohmann::json & json)773 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
774 {
775     std::string jsonString;
776     try
777     {
778         jsonString = json.dump();
779     }
780     catch (const std::exception& e)
781     {
782         lg2::error("json.dump() failed with: {ERROR}", "ERROR", e);
783         jsonString = "Invalid JSON passed to makeJSONUserDataSection!";
784     }
785 
786     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
787 
788     // Pad to a 4 byte boundary
789     while ((jsonData.size() % 4) != 0)
790     {
791         jsonData.push_back(0);
792     }
793 
794     return std::make_unique<UserData>(
795         static_cast<uint16_t>(ComponentID::phosphorLogging),
796         static_cast<uint8_t>(UserDataFormat::json),
797         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
798 }
799 
makeADUserDataSection(const AdditionalData & ad)800 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
801 {
802     assert(!ad.empty());
803     nlohmann::json json;
804 
805     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
806     if (ad.getValue("ESEL"))
807     {
808         auto newAD = ad;
809         newAD.remove("ESEL");
810         json = newAD.toJSON();
811     }
812     else
813     {
814         json = ad.toJSON();
815     }
816 
817     return makeJSONUserDataSection(json);
818 }
819 
addProcessNameToJSON(nlohmann::json & json,const std::optional<std::string> & pid,const DataInterfaceBase & dataIface)820 void addProcessNameToJSON(nlohmann::json& json,
821                           const std::optional<std::string>& pid,
822                           const DataInterfaceBase& dataIface)
823 {
824     std::string name{unknownValue};
825 
826     try
827     {
828         if (pid)
829         {
830             auto n = dataIface.getProcessName(*pid);
831             if (n)
832             {
833                 name = *n;
834             }
835         }
836     }
837     catch (const std::exception& e)
838     {}
839 
840     if (pid)
841     {
842         json["Process Name"] = std::move(name);
843     }
844 }
845 
addBMCFWVersionIDToJSON(nlohmann::json & json,const DataInterfaceBase & dataIface)846 void addBMCFWVersionIDToJSON(nlohmann::json& json,
847                              const DataInterfaceBase& dataIface)
848 {
849     auto id = dataIface.getBMCFWVersionID();
850     if (id.empty())
851     {
852         id = unknownValue;
853     }
854 
855     json["FW Version ID"] = std::move(id);
856 }
857 
lastSegment(char separator,std::string data)858 std::string lastSegment(char separator, std::string data)
859 {
860     auto pos = data.find_last_of(separator);
861     if (pos != std::string::npos)
862     {
863         data = data.substr(pos + 1);
864     }
865 
866     return data;
867 }
868 
addIMKeyword(nlohmann::json & json,const DataInterfaceBase & dataIface)869 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface)
870 {
871     auto keyword = dataIface.getSystemIMKeyword();
872 
873     std::string value{};
874 
875     std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) {
876         value += std::format("{:02X}", byte);
877     });
878 
879     json["System IM"] = value;
880 }
881 
addStatesToJSON(nlohmann::json & json,const DataInterfaceBase & dataIface)882 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
883 {
884     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
885     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
886     json["HostState"] = lastSegment('.', dataIface.getHostState());
887     json["BootState"] = lastSegment('.', dataIface.getBootState());
888 }
889 
addBMCUptime(nlohmann::json & json,const DataInterfaceBase & dataIface)890 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface)
891 {
892     auto seconds = dataIface.getUptimeInSeconds();
893     if (seconds)
894     {
895         json["BMCUptime"] = dataIface.getBMCUptime(*seconds);
896     }
897     else
898     {
899         json["BMCUptime"] = "";
900     }
901     json["BMCLoad"] = dataIface.getBMCLoadAvg();
902 }
903 
makeSysInfoUserDataSection(const AdditionalData & ad,const DataInterfaceBase & dataIface,bool addUptime,const nlohmann::json & adSysInfoData)904 std::unique_ptr<UserData> makeSysInfoUserDataSection(
905     const AdditionalData& ad, const DataInterfaceBase& dataIface,
906     bool addUptime, const nlohmann::json& adSysInfoData)
907 {
908     nlohmann::json json;
909 
910     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
911     addBMCFWVersionIDToJSON(json, dataIface);
912     addIMKeyword(json, dataIface);
913     addStatesToJSON(json, dataIface);
914 
915     if (addUptime)
916     {
917         addBMCUptime(json, dataIface);
918     }
919 
920     if (!adSysInfoData.empty())
921     {
922         json.update(adSysInfoData);
923     }
924 
925     return makeJSONUserDataSection(json);
926 }
927 
readFD(int fd)928 std::vector<uint8_t> readFD(int fd)
929 {
930     std::vector<uint8_t> data;
931 
932     // Get the size
933     struct stat s;
934     int r = fstat(fd, &s);
935     if (r != 0)
936     {
937         auto e = errno;
938         lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}",
939                    "ERRNO", e);
940         return data;
941     }
942 
943     if (0 == s.st_size)
944     {
945         lg2::error("FFDC file is empty");
946         return data;
947     }
948 
949     data.resize(s.st_size);
950 
951     // Make sure its at the beginning, as maybe another
952     // extension already used it.
953     r = lseek(fd, 0, SEEK_SET);
954     if (r == -1)
955     {
956         auto e = errno;
957         lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}",
958                    "ERRNO", e);
959         return data;
960     }
961 
962     r = read(fd, data.data(), s.st_size);
963     if (r == -1)
964     {
965         auto e = errno;
966         lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e);
967     }
968     else if (r != s.st_size)
969     {
970         lg2::warning("Could not read full FFDC file. "
971                      "File size = {FSIZE}, Size read = {SIZE_READ}",
972                      "FSIZE", s.st_size, "SIZE_READ", r);
973     }
974 
975     return data;
976 }
977 
makeFFDCuserDataSection(uint16_t componentID,const PelFFDCfile & file)978 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
979                                                   const PelFFDCfile& file)
980 {
981     auto data = readFD(file.fd);
982 
983     if (data.empty())
984     {
985         return std::unique_ptr<UserData>();
986     }
987 
988     // The data needs 4 Byte alignment, and save amount padded for the
989     // CBOR case.
990     uint32_t pad = 0;
991     while (data.size() % 4)
992     {
993         data.push_back(0);
994         pad++;
995     }
996 
997     // For JSON, CBOR, and Text use our component ID, subType, and version,
998     // otherwise use the supplied ones.
999     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
1000     uint8_t subType{};
1001     uint8_t version{};
1002 
1003     switch (file.format)
1004     {
1005         case UserDataFormat::json:
1006             subType = static_cast<uint8_t>(UserDataFormat::json);
1007             version = static_cast<uint8_t>(UserDataFormatVersion::json);
1008             break;
1009         case UserDataFormat::cbor:
1010             subType = static_cast<uint8_t>(UserDataFormat::cbor);
1011             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
1012 
1013             // The CBOR parser will fail on the extra pad bytes since they
1014             // aren't CBOR.  Add the amount we padded to the end and other
1015             // code will remove it all before parsing.
1016             {
1017                 data.resize(data.size() + 4);
1018                 Stream stream{data};
1019                 stream.offset(data.size() - 4);
1020                 stream << pad;
1021             }
1022 
1023             break;
1024         case UserDataFormat::text:
1025             subType = static_cast<uint8_t>(UserDataFormat::text);
1026             version = static_cast<uint8_t>(UserDataFormatVersion::text);
1027             break;
1028         case UserDataFormat::custom:
1029         default:
1030             // Use the passed in values
1031             compID = componentID;
1032             subType = file.subType;
1033             version = file.version;
1034             break;
1035     }
1036 
1037     return std::make_unique<UserData>(compID, subType, version, data);
1038 }
1039 
flattenLines(const std::vector<std::string> & lines)1040 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines)
1041 {
1042     std::vector<uint8_t> out;
1043 
1044     for (const auto& line : lines)
1045     {
1046         out.insert(out.end(), line.begin(), line.end());
1047 
1048         if (out.back() != '\n')
1049         {
1050             out.push_back('\n');
1051         }
1052     }
1053 
1054     return out;
1055 }
1056 
addDIMMInfo(const std::string & locationCode,const std::vector<std::uint8_t> & diPropVal,nlohmann::json & adSysInfoData)1057 void addDIMMInfo(const std::string& locationCode,
1058                  const std::vector<std::uint8_t>& diPropVal,
1059                  nlohmann::json& adSysInfoData)
1060 {
1061     nlohmann::json dimmInfoObj;
1062     dimmInfoObj["Location Code"] = locationCode;
1063     std::ranges::transform(
1064         diPropVal, std::back_inserter(dimmInfoObj["DRAM Manufacturer ID"]),
1065         [](const auto& diPropEachByte) {
1066             return std::format("{:#04x}", diPropEachByte);
1067         });
1068     adSysInfoData["DIMMs Additional Info"] += dimmInfoObj;
1069 }
1070 
1071 } // namespace util
1072 
1073 } // namespace pels
1074 } // namespace openpower
1075