blob: fbedb8bb5a82036fb8ac03691d11835714a573d4 [file] [log] [blame]
Matt Spinler711d51d2019-11-06 09:36:51 -06001/**
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 */
Jayanth Othayothda9b5832021-11-05 04:19:43 -050016#include "config.h"
17
Matt Spinlercb6b0592019-07-16 15:58:51 -050018#include "pel.hpp"
19
20#include "bcd_time.hpp"
Matt Spinler386a61e2020-08-13 15:51:12 -050021#include "extended_user_data.hpp"
Matt Spinlerc63e2e82019-12-02 15:50:12 -060022#include "extended_user_header.hpp"
Matt Spinleraa659472019-10-23 09:26:48 -050023#include "failing_mtms.hpp"
Andrew Geisslerf8e750d2022-01-14 14:56:13 -060024#include "fru_identity.hpp"
Harisuddin Mohamed Isa600d15a2019-12-20 12:42:26 +080025#include "json_utils.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050026#include "log_id.hpp"
Matt Spinlerf1e85e22019-11-01 11:31:31 -050027#include "pel_rules.hpp"
Aatir186ce8c2019-10-20 15:13:39 -050028#include "pel_values.hpp"
Matt Spinler131870c2019-09-25 13:29:04 -050029#include "section_factory.hpp"
Matt Spinlerbd716f02019-10-15 10:54:11 -050030#include "src.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050031#include "stream.hpp"
Matt Spinlerafa857c2019-10-24 13:03:46 -050032#include "user_data_formats.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050033
Jayanth Othayoth92b20662021-11-05 00:09:15 -050034#ifdef PEL_ENABLE_PHAL
Jayanth Othayothda9b5832021-11-05 04:19:43 -050035#include "phal_service_actions.hpp"
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050036#include "sbe_ffdc_handler.hpp"
37#endif
38
Matt Spinler5b289b22020-03-26 14:27:19 -050039#include <sys/stat.h>
40#include <unistd.h>
41
Matt Spinlerfd2da662023-07-07 16:25:14 -050042#include <phosphor-logging/lg2.hpp>
Matt Spinler07eefc52019-09-26 11:18:26 -050043
Jayanth Othayoth1aa90d42023-09-13 04:25:45 -050044#include <format>
Patrick Williams2544b412022-10-04 08:41:06 -050045#include <iostream>
Arya K Padmand8ae6182024-07-19 06:25:10 -050046#include <ranges>
Patrick Williams2544b412022-10-04 08:41:06 -050047
Matt Spinlercb6b0592019-07-16 15:58:51 -050048namespace openpower
49{
50namespace pels
51{
Aatir186ce8c2019-10-20 15:13:39 -050052namespace pv = openpower::pels::pel_values;
Matt Spinlerb8323632019-09-20 15:11:04 -050053
Matt Spinler677381b2020-01-23 10:04:29 -060054constexpr auto unknownValue = "Unknown";
Arya K Padmand8ae6182024-07-19 06:25:10 -050055constexpr auto AdDIMMInfoFetchError = "DIMMs Info Fetch Error";
Matt Spinler677381b2020-01-23 10:04:29 -060056
Matt Spinler4bfc9082020-03-24 15:05:54 -050057PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
Matt Spinlerbd716f02019-10-15 10:54:11 -050058 phosphor::logging::Entry::Level severity,
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050059 const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn,
Matt Spinler9d921092022-12-15 11:54:49 -060060 const DataInterfaceBase& dataIface, const JournalBase& journal)
Matt Spinlerb8323632019-09-20 15:11:04 -050061{
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050062 // No changes in input, for non SBE error related requests
63 PelFFDC ffdcFiles = ffdcFilesIn;
64
Jayanth Othayoth92b20662021-11-05 00:09:15 -050065#ifdef PEL_ENABLE_PHAL
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050066 // Add sbe ffdc processed data into ffdcfiles.
67 namespace sbe = openpower::pels::sbe;
Patrick Williams075c7922024-08-16 15:19:49 -040068 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 });
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050073 // 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 {
Patrick Williams075c7922024-08-16 15:19:49 -040078 sbeFFDCPtr =
79 std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn);
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050080 const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC();
81 ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(),
82 sbeFFDCFiles.end());
Jayanth Othayoth742b00b2022-06-30 05:16:57 -050083
84 // update pel priority for spare clock failures
85 if (auto customSeverity = sbeFFDCPtr->getSeverity())
86 {
87 severity = customSeverity.value();
88 }
Jayanth Othayothe8bdeea2021-06-03 03:01:16 -050089 }
90#endif
91
Arya K Padmand8ae6182024-07-19 06:25:10 -050092 DebugData debugData;
Matt Spinler5a90a952020-08-27 09:39:03 -050093 nlohmann::json callouts;
Matt Spinler85f61a62020-06-03 16:28:55 -050094
Matt Spinler4bfc9082020-03-24 15:05:54 -050095 _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
Matt Spinlerb8323632019-09-20 15:11:04 -050096 timestamp);
Vijay Lobo6b3f3452021-04-15 23:04:42 -050097 _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData,
98 dataIface);
Matt Spinlerb8323632019-09-20 15:11:04 -050099
Matt Spinler5a90a952020-08-27 09:39:03 -0500100 // 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
Patrick Williams075c7922024-08-16 15:19:49 -0400114 auto src =
115 std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface);
Matt Spinler5a90a952020-08-27 09:39:03 -0500116
Arya K Padmand8ae6182024-07-19 06:25:10 -0500117 nlohmann::json adSysInfoData(nlohmann::json::value_t::object);
118 addAdDetailsForDIMMsCallout(src, dataIface, adSysInfoData, debugData);
119
Matt Spinler85f61a62020-06-03 16:28:55 -0500120 if (!src->getDebugData().empty())
121 {
122 // Something didn't go as planned
123 debugData.emplace("SRC", src->getDebugData());
124 }
Matt Spinlerc63e2e82019-12-02 15:50:12 -0600125
Matt Spinler4bfc9082020-03-24 15:05:54 -0500126 auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
Matt Spinlerc63e2e82019-12-02 15:50:12 -0600127
Matt Spinlerbd716f02019-10-15 10:54:11 -0500128 _optionalSections.push_back(std::move(src));
Matt Spinlerc63e2e82019-12-02 15:50:12 -0600129 _optionalSections.push_back(std::move(euh));
Matt Spinlerb8323632019-09-20 15:11:04 -0500130
Matt Spinleraa659472019-10-23 09:26:48 -0500131 auto mtms = std::make_unique<FailingMTMS>(dataIface);
132 _optionalSections.push_back(std::move(mtms));
Matt Spinlerbd716f02019-10-15 10:54:11 -0500133
Arya K Padmand8ae6182024-07-19 06:25:10 -0500134 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface, true,
135 adSysInfoData);
Matt Spinler85f61a62020-06-03 16:28:55 -0500136 addUserDataSection(std::move(ud));
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600137
Sumit Kumar3e274432021-09-14 06:37:56 -0500138 // Check for pel severity of type - 0x51 = critical error, system
139 // termination and update terminate bit in SRC for pels
140 updateTerminateBitInSRCSection();
141
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500142 // Create a UserData section from AdditionalData.
Matt Spinlerafa857c2019-10-24 13:03:46 -0500143 if (!additionalData.empty())
144 {
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600145 ud = util::makeADUserDataSection(additionalData);
Matt Spinler85f61a62020-06-03 16:28:55 -0500146 addUserDataSection(std::move(ud));
Matt Spinlerafa857c2019-10-24 13:03:46 -0500147 }
148
Matt Spinler56ad2a02020-03-26 14:00:52 -0500149 // 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 {
Matt Spinler85f61a62020-06-03 16:28:55 -0500155 // 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
Matt Spinler56ad2a02020-03-26 14:00:52 -0500170 continue;
171 }
172
Matt Spinler85f61a62020-06-03 16:28:55 -0500173 addUserDataSection(std::move(ud));
174 }
Matt Spinler56ad2a02020-03-26 14:00:52 -0500175
Jayanth Othayothda9b5832021-11-05 04:19:43 -0500176#ifdef PEL_ENABLE_PHAL
177 auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
Jayanth Othayoth3ef7b602021-11-09 06:40:38 -0600178 openpower::pels::phal::createServiceActions(callouts, path, dataIface,
179 plid());
Jayanth Othayothda9b5832021-11-05 04:19:43 -0500180#endif
181
Matt Spinler85f61a62020-06-03 16:28:55 -0500182 // 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
Matt Spinler45796e82022-07-01 11:25:27 -0500193 for (const auto& [name, msgs] : debugData)
Matt Spinler85f61a62020-06-03 16:28:55 -0500194 {
Matt Spinler45796e82022-07-01 11:25:27 -0500195 for (const auto& message : msgs)
Matt Spinler85f61a62020-06-03 16:28:55 -0500196 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500197 lg2::info("{NAME}: {MSG}", "NAME", name, "MSG", message);
Matt Spinler56ad2a02020-03-26 14:00:52 -0500198 }
199 }
Matt Spinler56ad2a02020-03-26 14:00:52 -0500200 }
201
Matt Spinler9d921092022-12-15 11:54:49 -0600202 addJournalSections(regEntry, journal);
203
Matt Spinler97d19b42019-10-29 11:34:03 -0500204 _ph->setSectionCount(2 + _optionalSections.size());
Matt Spinlerf1e85e22019-11-01 11:31:31 -0500205
206 checkRulesAndFix();
Matt Spinlerb8323632019-09-20 15:11:04 -0500207}
Matt Spinlercb6b0592019-07-16 15:58:51 -0500208
Patrick Williams2544b412022-10-04 08:41:06 -0500209PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0) {}
Matt Spinlercb6b0592019-07-16 15:58:51 -0500210
Matt Spinler07eefc52019-09-26 11:18:26 -0500211PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
Matt Spinlercb6b0592019-07-16 15:58:51 -0500212{
Matt Spinler07eefc52019-09-26 11:18:26 -0500213 populateFromRawData(data, obmcLogID);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500214}
215
Matt Spinler07eefc52019-09-26 11:18:26 -0500216void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
Matt Spinlercb6b0592019-07-16 15:58:51 -0500217{
Matt Spinler07eefc52019-09-26 11:18:26 -0500218 Stream pelData{data};
Matt Spinlercb6b0592019-07-16 15:58:51 -0500219 _ph = std::make_unique<PrivateHeader>(pelData);
220 if (obmcLogID != 0)
221 {
Matt Spinler97d19b42019-10-29 11:34:03 -0500222 _ph->setOBMCLogID(obmcLogID);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500223 }
224
225 _uh = std::make_unique<UserHeader>(pelData);
Matt Spinler131870c2019-09-25 13:29:04 -0500226
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 }
Matt Spinlercb6b0592019-07-16 15:58:51 -0500233}
234
235bool PEL::valid() const
236{
237 bool valid = _ph->valid();
238
239 if (valid)
240 {
241 valid = _uh->valid();
242 }
243
Matt Spinler131870c2019-09-25 13:29:04 -0500244 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
Matt Spinlercb6b0592019-07-16 15:58:51 -0500253 return valid;
254}
255
256void PEL::setCommitTime()
257{
258 auto now = std::chrono::system_clock::now();
Matt Spinler97d19b42019-10-29 11:34:03 -0500259 _ph->setCommitTimestamp(getBCDTime(now));
Matt Spinlercb6b0592019-07-16 15:58:51 -0500260}
261
262void PEL::assignID()
263{
Matt Spinler97d19b42019-10-29 11:34:03 -0500264 _ph->setID(generatePELID());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500265}
266
Matt Spinler06885452019-11-06 10:35:42 -0600267void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
Matt Spinlercb6b0592019-07-16 15:58:51 -0500268{
269 Stream pelData{pelBuffer};
Matt Spinlerb8323632019-09-20 15:11:04 -0500270
Matt Spinler07eefc52019-09-26 11:18:26 -0500271 if (!valid())
Matt Spinlercb6b0592019-07-16 15:58:51 -0500272 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500273 lg2::warning("Unflattening an invalid PEL");
Matt Spinlercb6b0592019-07-16 15:58:51 -0500274 }
275
Matt Spinler07eefc52019-09-26 11:18:26 -0500276 _ph->flatten(pelData);
Matt Spinlerb8323632019-09-20 15:11:04 -0500277 _uh->flatten(pelData);
Matt Spinler07eefc52019-09-26 11:18:26 -0500278
279 for (auto& section : _optionalSections)
280 {
281 section->flatten(pelData);
282 }
Matt Spinlercb6b0592019-07-16 15:58:51 -0500283}
284
Matt Spinler06885452019-11-06 10:35:42 -0600285std::vector<uint8_t> PEL::data() const
Matt Spinlercb6b0592019-07-16 15:58:51 -0500286{
Matt Spinler07eefc52019-09-26 11:18:26 -0500287 std::vector<uint8_t> pelData;
288 flatten(pelData);
289 return pelData;
Matt Spinlercb6b0592019-07-16 15:58:51 -0500290}
291
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600292size_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
Matt Spinlerbd716f02019-10-15 10:54:11 -0500314std::optional<SRC*> PEL::primarySRC() const
315{
Patrick Williams075c7922024-08-16 15:19:49 -0400316 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 });
Matt Spinlerbd716f02019-10-15 10:54:11 -0500321 if (src != _optionalSections.end())
322 {
323 return static_cast<SRC*>(src->get());
324 }
325
326 return std::nullopt;
327}
328
Matt Spinlerf1e85e22019-11-01 11:31:31 -0500329void PEL::checkRulesAndFix()
330{
Matt Spinler1f93c592020-09-10 10:43:08 -0500331 // 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 {
Patrick Williams075c7922024-08-16 15:19:49 -0400336 auto [actionFlags, eventType] =
337 pel_rules::check(0, _uh->eventType(), _uh->severity());
Matt Spinlerf1e85e22019-11-01 11:31:31 -0500338
Matt Spinler1f93c592020-09-10 10:43:08 -0500339 _uh->setActionFlags(actionFlags);
340 _uh->setEventType(eventType);
341 }
Matt Spinlerf1e85e22019-11-01 11:31:31 -0500342}
343
Patrick Williams075c7922024-08-16 15:19:49 -0400344void 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
Aatir186ce8c2019-10-20 15:13:39 -0500348{
349 char tmpB[5];
Aatir Manzurad0e0472019-10-07 13:18:37 -0500350 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";
Matt Spinleracb7c102020-01-10 13:49:22 -0600357
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
Aatir186ce8c2019-10-20 15:13:39 -0500366 if (section.valid())
367 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800368 std::optional<std::string> json;
369 if (sectionID == "PS" || sectionID == "SS")
370 {
Harisuddin Mohamed Isac8d6cc62020-08-19 22:47:19 +0800371 json = section.getJSON(registry, plugins, creatorID);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800372 }
Matt Spinler386a61e2020-08-13 15:51:12 -0500373 else if ((sectionID == "UD") || (sectionID == "ED"))
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800374 {
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800375 json = section.getJSON(creatorID, plugins);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800376 }
377 else
378 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500379 json = section.getJSON(creatorID);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800380 }
Matt Spinler4220a152020-03-26 10:18:09 -0500381
382 buf += "\"" + sectionName + "\": {\n";
383
Aatir Manzurad0e0472019-10-07 13:18:37 -0500384 if (json)
385 {
Harisuddin Mohamed Isa600d15a2019-12-20 12:42:26 +0800386 buf += *json + "\n},\n";
Aatir Manzurad0e0472019-10-07 13:18:37 -0500387 }
388 else
389 {
Matt Spinler4220a152020-03-26 10:18:09 -0500390 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
Aatir Manzurad0e0472019-10-07 13:18:37 -0500398 std::vector<uint8_t> data;
399 Stream s{data};
400 section.flatten(s);
Matt Spinler4220a152020-03-26 10:18:09 -0500401 std::string dstr =
402 dumpHex(std::data(data) + SectionHeader::flattenedSize(),
Arya K Padman8c8aaa02024-04-28 23:23:45 -0500403 data.size() - SectionHeader::flattenedSize(), 2)
404 .get();
Matt Spinler4220a152020-03-26 10:18:09 -0500405 std::string jsonIndent(indentLevel, 0x20);
406 buf += jsonIndent + "\"Data\": [\n";
407 buf += dstr;
408 buf += jsonIndent + "]\n";
409 buf += "},\n";
Aatir Manzurad0e0472019-10-07 13:18:37 -0500410 }
Aatir186ce8c2019-10-20 15:13:39 -0500411 }
412 else
413 {
Harisuddin Mohamed Isa600d15a2019-12-20 12:42:26 +0800414 buf += "\n\"Invalid Section\": [\n \"invalid\"\n],\n";
Aatir186ce8c2019-10-20 15:13:39 -0500415 }
416}
417
Matt Spinleracb7c102020-01-10 13:49:22 -0600418std::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
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800448void PEL::toJSON(message::Registry& registry,
449 const std::vector<std::string>& plugins) const
Aatir186ce8c2019-10-20 15:13:39 -0500450{
Matt Spinleracb7c102020-01-10 13:49:22 -0600451 auto sections = getPluralSections();
452
Harisuddin Mohamed Isaa214ed32020-02-28 15:58:23 +0800453 std::string buf = "{\n";
Matt Spinlerb832aa52023-03-21 15:32:34 -0500454 printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins,
455 _ph->creatorID());
456 printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins,
457 _ph->creatorID());
Aatir186ce8c2019-10-20 15:13:39 -0500458 for (auto& section : this->optionalSections())
459 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800460 printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
461 _ph->creatorID());
Aatir186ce8c2019-10-20 15:13:39 -0500462 }
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}
Harisuddin Mohamed Isa600d15a2019-12-20 12:42:26 +0800469
Matt Spinler85f61a62020-06-03 16:28:55 -0500470bool 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 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500480 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);
Matt Spinler85f61a62020-06-03 16:28:55 -0500487 return false;
488 }
489 }
490 else
491 {
492 _optionalSections.push_back(std::move(userData));
493 }
494 return true;
495}
496
Matt Spinler5a90a952020-08-27 09:39:03 -0500497nlohmann::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
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600523bool PEL::isHwCalloutPresent() const
Andrew Geissler44fc3162020-07-09 09:21:31 -0500524{
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();
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600539 if ((*fruId).failingComponentType() ==
540 src::FRUIdentity::hardwareFRU)
Andrew Geissler44fc3162020-07-09 09:21:31 -0500541 {
542 calloutPresent = true;
543 break;
544 }
545 }
546 }
547 }
548
549 return calloutPresent;
550}
551
Sumit Kumar3160a542021-04-26 08:07:04 -0500552void 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
Patrick Williams075c7922024-08-16 15:19:49 -0400561 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 });
Sumit Kumar3160a542021-04-26 08:07:04 -0500567
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
George Liu9ac0d9b2022-07-15 10:57:38 +0800584 auto ud = util::makeSysInfoUserDataSection(additionalData,
585 dataIface, false);
Sumit Kumar3160a542021-04-26 08:07:04 -0500586 extUserData->updateDataSection(subType, componentId,
587 ud->data());
588 }
589 }
590 }
591}
592
Matt Spinler8e65f4e2023-05-02 13:40:08 -0500593bool 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
605bool 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
Sumit Kumar3e274432021-09-14 06:37:56 -0500617void 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
Matt Spinler9d921092022-12-15 11:54:49 -0600632void 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 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500660 lg2::error("Failed during journal collection: {ERROR}", "ERROR", e);
Matt Spinler9d921092022-12-15 11:54:49 -0600661 }
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 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500679 lg2::error("Failed during journal collection: {ERROR}", "ERROR",
680 e);
Matt Spinler9d921092022-12-15 11:54:49 -0600681 }
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 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500695 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());
Matt Spinler9d921092022-12-15 11:54:49 -0600699 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.
Matt Spinlerfd2da662023-07-07 16:25:14 -0500721 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());
Matt Spinler9d921092022-12-15 11:54:49 -0600725 ud.reset();
726 continue;
727 }
728 }
729}
730
Arya K Padmand8ae6182024-07-19 06:25:10 -0500731void 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 {
Arya K Padmanced8ed72024-09-02 05:18:07 -0500750 return const_cast<DataInterfaceBase&>(dataIface).isDIMM(locCode);
Arya K Padmand8ae6182024-07-19 06:25:10 -0500751 }
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
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600778namespace util
779{
780
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600781std::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
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600798std::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
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600815 return makeJSONUserDataSection(json);
816}
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600817
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600818void addProcessNameToJSON(nlohmann::json& json,
819 const std::optional<std::string>& pid,
820 const DataInterfaceBase& dataIface)
821{
Matt Spinler677381b2020-01-23 10:04:29 -0600822 std::string name{unknownValue};
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600823
824 try
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600825 {
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600826 if (pid)
827 {
828 auto n = dataIface.getProcessName(*pid);
829 if (n)
830 {
831 name = *n;
832 }
833 }
834 }
Patrick Williams66491c62021-10-06 12:23:37 -0500835 catch (const std::exception& e)
Patrick Williams2544b412022-10-04 08:41:06 -0500836 {}
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600837
Sumit Kumar3160a542021-04-26 08:07:04 -0500838 if (pid)
839 {
840 json["Process Name"] = std::move(name);
841 }
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600842}
843
Matt Spinler677381b2020-01-23 10:04:29 -0600844void addBMCFWVersionIDToJSON(nlohmann::json& json,
845 const DataInterfaceBase& dataIface)
846{
847 auto id = dataIface.getBMCFWVersionID();
848 if (id.empty())
849 {
850 id = unknownValue;
851 }
852
Matt Spinlerc2b8a512021-05-21 12:44:42 -0600853 json["FW Version ID"] = std::move(id);
Matt Spinler677381b2020-01-23 10:04:29 -0600854}
855
Matt Spinler4aa23a12020-02-03 15:05:09 -0600856std::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
Ben Tynere32b7e72021-05-18 12:38:40 -0500867void 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) {
Jayanth Othayoth1aa90d42023-09-13 04:25:45 -0500874 value += std::format("{:02X}", byte);
Ben Tynere32b7e72021-05-18 12:38:40 -0500875 });
876
877 json["System IM"] = value;
878}
879
Matt Spinler4aa23a12020-02-03 15:05:09 -0600880void 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());
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500885 json["BootState"] = lastSegment('.', dataIface.getBootState());
Matt Spinler4aa23a12020-02-03 15:05:09 -0600886}
887
George Liu9ac0d9b2022-07-15 10:57:38 +0800888void 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
Patrick Williams075c7922024-08-16 15:19:49 -0400902std::unique_ptr<UserData> makeSysInfoUserDataSection(
903 const AdditionalData& ad, const DataInterfaceBase& dataIface,
Arya K Padmand8ae6182024-07-19 06:25:10 -0500904 bool addUptime, const nlohmann::json& adSysInfoData)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600905{
906 nlohmann::json json;
907
908 addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
Matt Spinler677381b2020-01-23 10:04:29 -0600909 addBMCFWVersionIDToJSON(json, dataIface);
Ben Tynere32b7e72021-05-18 12:38:40 -0500910 addIMKeyword(json, dataIface);
Matt Spinler4aa23a12020-02-03 15:05:09 -0600911 addStatesToJSON(json, dataIface);
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600912
George Liu9ac0d9b2022-07-15 10:57:38 +0800913 if (addUptime)
914 {
915 addBMCUptime(json, dataIface);
916 }
917
Arya K Padmand8ae6182024-07-19 06:25:10 -0500918 if (!adSysInfoData.empty())
919 {
920 json.update(adSysInfoData);
921 }
922
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600923 return makeJSONUserDataSection(json);
Matt Spinlerc7c3e402020-01-22 15:07:25 -0600924}
925
Matt Spinler5b289b22020-03-26 14:27:19 -0500926std::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;
Matt Spinlerfd2da662023-07-07 16:25:14 -0500936 lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}",
937 "ERRNO", e);
Matt Spinler5b289b22020-03-26 14:27:19 -0500938 return data;
939 }
940
941 if (0 == s.st_size)
942 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500943 lg2::error("FFDC file is empty");
Matt Spinler5b289b22020-03-26 14:27:19 -0500944 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;
Matt Spinlerfd2da662023-07-07 16:25:14 -0500955 lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}",
956 "ERRNO", e);
Matt Spinler5b289b22020-03-26 14:27:19 -0500957 return data;
958 }
959
960 r = read(fd, data.data(), s.st_size);
961 if (r == -1)
962 {
963 auto e = errno;
Matt Spinlerfd2da662023-07-07 16:25:14 -0500964 lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e);
Matt Spinler5b289b22020-03-26 14:27:19 -0500965 }
966 else if (r != s.st_size)
967 {
Matt Spinlerfd2da662023-07-07 16:25:14 -0500968 lg2::warning("Could not read full FFDC file. "
969 "File size = {FSIZE}, Size read = {SIZE_READ}",
970 "FSIZE", s.st_size, "SIZE_READ", r);
Matt Spinler5b289b22020-03-26 14:27:19 -0500971 }
972
973 return data;
974}
975
Patrick Williams075c7922024-08-16 15:19:49 -0400976std::unique_ptr<UserData>
977 makeFFDCuserDataSection(uint16_t componentID, const PelFFDCfile& file)
Matt Spinler56ad2a02020-03-26 14:00:52 -0500978{
Matt Spinler5b289b22020-03-26 14:27:19 -0500979 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);
Matt Spinler56ad2a02020-03-26 14:00:52 -05001036}
1037
Matt Spinler9d921092022-12-15 11:54:49 -06001038std::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
Arya K Padmand8ae6182024-07-19 06:25:10 -05001055void 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
Matt Spinlerc7c3e402020-01-22 15:07:25 -06001069} // namespace util
1070
Matt Spinlercb6b0592019-07-16 15:58:51 -05001071} // namespace pels
1072} // namespace openpower