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