blob: 13a3ae5c823464db99b55a9e1961a88cb7a07e7b [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 */
Matt Spinlerf9bae182019-10-09 13:37:38 -050016#include "src.hpp"
17
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +080018#include "json_utils.hpp"
19#include "paths.hpp"
20#include "pel_values.hpp"
21
Matt Spinlerf9bae182019-10-09 13:37:38 -050022#include <phosphor-logging/log.hpp>
23
24namespace openpower
25{
26namespace pels
27{
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +080028namespace pv = openpower::pels::pel_values;
29namespace rg = openpower::pels::message;
Matt Spinlerf9bae182019-10-09 13:37:38 -050030using namespace phosphor::logging;
31
Matt Spinler075e5ba2020-02-21 15:46:00 -060032constexpr size_t ccinSize = 4;
33
Matt Spinlerf9bae182019-10-09 13:37:38 -050034void SRC::unflatten(Stream& stream)
35{
36 stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
37 _reserved2B >> _size;
38
39 for (auto& word : _hexData)
40 {
41 stream >> word;
42 }
43
44 _asciiString = std::make_unique<src::AsciiString>(stream);
45
46 if (hasAdditionalSections())
47 {
48 // The callouts section is currently the only extra subsection type
49 _callouts = std::make_unique<src::Callouts>(stream);
50 }
51}
52
Matt Spinler06885452019-11-06 10:35:42 -060053void SRC::flatten(Stream& stream) const
Matt Spinlerf9bae182019-10-09 13:37:38 -050054{
55 stream << _header << _version << _flags << _reserved1B << _wordCount
56 << _reserved2B << _size;
57
58 for (auto& word : _hexData)
59 {
60 stream << word;
61 }
62
63 _asciiString->flatten(stream);
64
65 if (_callouts)
66 {
67 _callouts->flatten(stream);
68 }
69}
70
71SRC::SRC(Stream& pel)
72{
73 try
74 {
75 unflatten(pel);
76 validate();
77 }
78 catch (const std::exception& e)
79 {
80 log<level::ERR>("Cannot unflatten SRC", entry("ERROR=%s", e.what()));
81 _valid = false;
82 }
83}
84
Matt Spinler075e5ba2020-02-21 15:46:00 -060085SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
86 const DataInterfaceBase& dataIface)
Matt Spinlerbd716f02019-10-15 10:54:11 -050087{
88 _header.id = static_cast<uint16_t>(SectionID::primarySRC);
89 _header.version = srcSectionVersion;
90 _header.subType = srcSectionSubtype;
91 _header.componentID = regEntry.componentID;
92
93 _version = srcVersion;
94
95 _flags = 0;
96 if (regEntry.src.powerFault.value_or(false))
97 {
98 _flags |= powerFaultEvent;
99 }
100
101 _reserved1B = 0;
102
103 _wordCount = numSRCHexDataWords + 1;
104
105 _reserved2B = 0;
106
107 // There are multiple fields encoded in the hex data words.
108 std::for_each(_hexData.begin(), _hexData.end(),
109 [](auto& word) { word = 0; });
110 setBMCFormat();
111 setBMCPosition();
Matt Spinler075e5ba2020-02-21 15:46:00 -0600112 setMotherboardCCIN(dataIface);
113
Matt Spinlerbd716f02019-10-15 10:54:11 -0500114 // Partition dump status and partition boot type always 0 for BMC errors.
115 //
116 // TODO: Fill in other fields that aren't available yet.
117
118 // Fill in the last 4 words from the AdditionalData property contents.
119 setUserDefinedHexWords(regEntry, additionalData);
120
121 _asciiString = std::make_unique<src::AsciiString>(regEntry);
122
Matt Spinler03984582020-04-09 13:17:58 -0500123 addCallouts(regEntry, additionalData, dataIface);
Matt Spinlerbd716f02019-10-15 10:54:11 -0500124
125 _size = baseSRCSize;
126 _size += _callouts ? _callouts->flattenedSize() : 0;
127 _header.size = Section::flattenedSize() + _size;
128
129 _valid = true;
130}
131
132void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
133 const AdditionalData& ad)
134{
135 if (!regEntry.src.hexwordADFields)
136 {
137 return;
138 }
139
140 // Save the AdditionalData value corresponding to the
141 // adName key in _hexData[wordNum].
142 for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
143 {
144 // Can only set words 6 - 9
145 if (!isUserDefinedWord(wordNum))
146 {
147 log<level::WARNING>("SRC user data word out of range",
148 entry("WORD_NUM=%d", wordNum),
149 entry("ERROR_NAME=%s", regEntry.name.c_str()));
150 continue;
151 }
152
153 auto value = ad.getValue(adName);
154 if (value)
155 {
156 _hexData[getWordIndexFromWordNum(wordNum)] =
157 std::strtoul(value.value().c_str(), nullptr, 0);
158 }
159 else
160 {
161 log<level::WARNING>("Source for user data SRC word not found",
162 entry("ADDITIONALDATA_KEY=%s", adName.c_str()),
163 entry("ERROR_NAME=%s", regEntry.name.c_str()));
164 }
165 }
166}
167
Matt Spinler075e5ba2020-02-21 15:46:00 -0600168void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
169{
170 uint32_t ccin = 0;
171 auto ccinString = dataIface.getMotherboardCCIN();
172
173 try
174 {
175 if (ccinString.size() == ccinSize)
176 {
177 ccin = std::stoi(ccinString, 0, 16);
178 }
179 }
180 catch (std::exception& e)
181 {
182 log<level::WARNING>("Could not convert motherboard CCIN to a number",
183 entry("CCIN=%s", ccinString.c_str()));
184 return;
185 }
186
187 // Set the first 2 bytes
188 _hexData[1] |= ccin << 16;
189}
190
Matt Spinlerf9bae182019-10-09 13:37:38 -0500191void SRC::validate()
192{
193 bool failed = false;
194
195 if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
196 (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
197 {
198 log<level::ERR>("Invalid SRC section ID",
199 entry("ID=0x%X", header().id));
200 failed = true;
201 }
202
203 // Check the version in the SRC, not in the header
Matt Spinlerbd716f02019-10-15 10:54:11 -0500204 if (_version != srcVersion)
Matt Spinlerf9bae182019-10-09 13:37:38 -0500205 {
Matt Spinlerbd716f02019-10-15 10:54:11 -0500206 log<level::ERR>("Invalid SRC version", entry("VERSION=0x%X", _version));
Matt Spinlerf9bae182019-10-09 13:37:38 -0500207 failed = true;
208 }
209
210 _valid = failed ? false : true;
211}
212
Matt Spinler075e5ba2020-02-21 15:46:00 -0600213bool SRC::isBMCSRC() const
214{
215 auto as = asciiString();
216 if (as.length() >= 2)
217 {
218 uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
219 return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
220 errorType == static_cast<uint8_t>(SRCType::powerError));
221 }
222 return false;
223}
224
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800225std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
226 DetailLevel type,
227 bool toCache) const
228{
229 const std::string jsonIndent(indentLevel, 0x20);
230 std::string errorOut;
Matt Spinler075e5ba2020-02-21 15:46:00 -0600231 if (isBMCSRC())
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800232 {
233 auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
234 rg::LookupType::reasonCode, toCache);
235 if (entry)
236 {
237 errorOut.append(jsonIndent + "\"Error Details\": {\n");
238 auto errorMsg = getErrorMessage(*entry);
239 if (errorMsg)
240 {
241 if (type == DetailLevel::message)
242 {
243 return errorMsg.value();
244 }
245 else
246 {
247 jsonInsert(errorOut, "Message", errorMsg.value(), 2);
248 }
249 }
250 if (entry->src.hexwordADFields)
251 {
252 std::map<size_t, std::string> adFields =
253 entry->src.hexwordADFields.value();
254 for (const auto& hexwordMap : adFields)
255 {
256 jsonInsert(errorOut, hexwordMap.second,
257 getNumberString("0x%X",
258 _hexData[getWordIndexFromWordNum(
259 hexwordMap.first)]),
260 2);
261 }
262 }
263 errorOut.erase(errorOut.size() - 2);
264 errorOut.append("\n");
265 errorOut.append(jsonIndent + "},\n");
266 return errorOut;
267 }
268 }
269 return std::nullopt;
270}
271
272std::optional<std::string>
273 SRC::getErrorMessage(const message::Entry& regEntry) const
274{
275 try
276 {
277 if (regEntry.doc.messageArgSources)
278 {
279 size_t msgLen = regEntry.doc.message.length();
280 char msg[msgLen + 1];
281 strcpy(msg, regEntry.doc.message.c_str());
282 std::vector<uint32_t> argSourceVals;
283 std::string message;
284 const auto& argValues = regEntry.doc.messageArgSources.value();
285 for (size_t i = 0; i < argValues.size(); ++i)
286 {
287 argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
288 argValues[i].back() - '0')]);
289 }
290 const char* msgPointer = msg;
291 while (*msgPointer)
292 {
293 if (*msgPointer == '%')
294 {
295 msgPointer++;
296 size_t wordIndex = *msgPointer - '0';
297 if (isdigit(*msgPointer) && wordIndex >= 1 &&
298 static_cast<uint16_t>(wordIndex) <=
299 argSourceVals.size())
300 {
301 message.append(getNumberString(
302 "0x%X", argSourceVals[wordIndex - 1]));
303 }
304 else
305 {
306 message.append("%" + std::string(1, *msgPointer));
307 }
308 }
309 else
310 {
311 message.push_back(*msgPointer);
312 }
313 msgPointer++;
314 }
315 return message;
316 }
317 else
318 {
319 return regEntry.doc.message;
320 }
321 }
322 catch (const std::exception& e)
323 {
324 log<level::ERR>("Cannot get error message from registry entry",
325 entry("ERROR=%s", e.what()));
326 }
327 return std::nullopt;
328}
329
330std::optional<std::string> SRC::getCallouts() const
331{
332 if (!_callouts)
333 {
334 return std::nullopt;
335 }
336 std::string printOut;
337 const std::string jsonIndent(indentLevel, 0x20);
338 const auto& callout = _callouts->callouts();
339 const auto& compDescrp = pv::failingComponentType;
340 printOut.append(jsonIndent + "\"Callout Section\": {\n");
341 jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
342 printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
343 for (auto& entry : callout)
344 {
345 printOut.append("{\n");
346 if (entry->fruIdentity())
347 {
348 jsonInsert(
349 printOut, "FRU Type",
350 compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
351 jsonInsert(printOut, "Priority",
352 pv::getValue(entry->priority(),
353 pel_values::calloutPriorityValues),
354 3);
355 if (!entry->locationCode().empty())
356 {
357 jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
358 }
359 if (entry->fruIdentity()->getPN().has_value())
360 {
361 jsonInsert(printOut, "Part Number",
362 entry->fruIdentity()->getPN().value(), 3);
363 }
364 if (entry->fruIdentity()->getMaintProc().has_value())
365 {
366 jsonInsert(printOut, "Procedure Number",
367 entry->fruIdentity()->getMaintProc().value(), 3);
368 if (pv::procedureDesc.find(
369 entry->fruIdentity()->getMaintProc().value()) !=
370 pv::procedureDesc.end())
371 {
372 jsonInsert(
373 printOut, "Description",
374 pv::procedureDesc.at(
375 entry->fruIdentity()->getMaintProc().value()),
376 3);
377 }
378 }
379 if (entry->fruIdentity()->getCCIN().has_value())
380 {
381 jsonInsert(printOut, "CCIN",
382 entry->fruIdentity()->getCCIN().value(), 3);
383 }
384 if (entry->fruIdentity()->getSN().has_value())
385 {
386 jsonInsert(printOut, "Serial Number",
387 entry->fruIdentity()->getSN().value(), 3);
388 }
389 }
390 if (entry->pceIdentity())
391 {
392 const auto& pceIdentMtms = entry->pceIdentity()->mtms();
393 if (!pceIdentMtms.machineTypeAndModel().empty())
394 {
395 jsonInsert(printOut, "PCE MTMS",
396 pceIdentMtms.machineTypeAndModel() + "_" +
397 pceIdentMtms.machineSerialNumber(),
398 3);
399 }
400 if (!entry->pceIdentity()->enclosureName().empty())
401 {
402 jsonInsert(printOut, "PCE Name",
403 entry->pceIdentity()->enclosureName(), 3);
404 }
405 }
406 if (entry->mru())
407 {
408 const auto& mruCallouts = entry->mru()->mrus();
409 std::string mruId;
410 for (auto& element : mruCallouts)
411 {
412 if (!mruId.empty())
413 {
414 mruId.append(", " + getNumberString("%08X", element.id));
415 }
416 else
417 {
418 mruId.append(getNumberString("%08X", element.id));
419 }
420 }
421 jsonInsert(printOut, "MRU Id", mruId, 3);
422 }
423 printOut.erase(printOut.size() - 2);
424 printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
425 };
426 printOut.erase(printOut.size() - 2);
427 printOut.append("]\n" + jsonIndent + "}");
428 return printOut;
429}
430
Harisuddin Mohamed Isaa214ed32020-02-28 15:58:23 +0800431std::optional<std::string> SRC::getJSON(message::Registry& registry) const
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800432{
433 std::string ps;
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +0800434 jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
435 jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
436 jsonInsert(ps, pv::createdBy, getNumberString("0x%X", _header.componentID),
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800437 1);
438 jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
Harisuddin Mohamed Isac32e5512020-02-06 18:05:21 +0800439 jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
440 1);
441 jsonInsert(ps, "Virtual Progress SRC",
442 pv::boolString.at(_flags & virtualProgressSRC), 1);
443 jsonInsert(ps, "I5/OS Service Event Bit",
444 pv::boolString.at(_flags & i5OSServiceEventBit), 1);
445 jsonInsert(ps, "Hypervisor Dump Initiated",
446 pv::boolString.at(_flags & hypDumpInit), 1);
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800447 jsonInsert(ps, "Power Control Net Fault",
448 pv::boolString.at(isPowerFaultEvent()), 1);
Matt Spinler075e5ba2020-02-21 15:46:00 -0600449
450 if (isBMCSRC())
451 {
452 std::string ccinString;
453 uint32_t ccin = _hexData[1] >> 16;
454
455 if (ccin)
456 {
457 ccinString = getNumberString("%04X", ccin);
458 }
459 // The PEL spec calls it a backplane, so call it that here.
460 jsonInsert(ps, "Backplane CCIN", ccinString, 1);
461 }
462
Harisuddin Mohamed Isaa214ed32020-02-28 15:58:23 +0800463 auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800464 if (errorDetails)
465 {
466 ps.append(errorDetails.value());
467 }
468 jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
469 1);
470 std::string refcode = asciiString();
Harisuddin Mohamed Isafecaa572020-03-11 16:04:50 +0800471 std::string extRefcode;
472 size_t pos = refcode.find(0x20);
473 if (pos != std::string::npos)
474 {
475 size_t nextPos = refcode.find_first_not_of(0x20, pos);
476 if (nextPos != std::string::npos)
477 {
478 extRefcode = trimEnd(refcode.substr(nextPos));
479 }
480 refcode.erase(pos);
481 }
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800482 jsonInsert(ps, "Reference Code", refcode, 1);
Harisuddin Mohamed Isafecaa572020-03-11 16:04:50 +0800483 if (!extRefcode.empty())
484 {
485 jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
486 }
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800487 for (size_t i = 2; i <= _wordCount; i++)
488 {
489 jsonInsert(
490 ps, "Hex Word " + std::to_string(i),
491 getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]), 1);
492 }
493 auto calloutJson = getCallouts();
494 if (calloutJson)
495 {
496 ps.append(calloutJson.value());
497 }
498 else
499 {
500 ps.erase(ps.size() - 2);
501 }
502 return ps;
503}
504
Matt Spinler03984582020-04-09 13:17:58 -0500505void SRC::addCallouts(const message::Entry& regEntry,
506 const AdditionalData& additionalData,
Matt Spinlered046852020-03-13 13:58:15 -0500507 const DataInterfaceBase& dataIface)
508{
509 auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
510 if (item)
511 {
512 addInventoryCallout(*item, dataIface);
513 }
514
515 // TODO: CALLOUT_DEVICE_PATH
Matt Spinler03984582020-04-09 13:17:58 -0500516
517 if (regEntry.callouts)
518 {
519 addRegistryCallouts(regEntry, additionalData, dataIface);
520 }
Matt Spinlered046852020-03-13 13:58:15 -0500521}
522
523void SRC::addInventoryCallout(const std::string& inventoryPath,
524 const DataInterfaceBase& dataIface)
525{
526 std::string locCode;
527 std::string fn;
528 std::string ccin;
529 std::string sn;
530 std::unique_ptr<src::Callout> callout;
531
Matt Spinlered046852020-03-13 13:58:15 -0500532 try
533 {
Matt Spinler9b90e2a2020-04-14 10:59:04 -0500534 locCode = dataIface.getLocationCode(inventoryPath);
Matt Spinlered046852020-03-13 13:58:15 -0500535
Matt Spinler9b90e2a2020-04-14 10:59:04 -0500536 try
537 {
538 dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
539
540 callout = std::make_unique<src::Callout>(CalloutPriority::high,
541 locCode, fn, ccin, sn);
542 }
543 catch (const SdBusError& e)
544 {
545 std::string msg = "No VPD found for " + inventoryPath;
546 log<level::WARNING>(msg.c_str(), entry("ERROR=%s", e.what()));
547
548 // Just create the callout with empty FRU fields
549 callout = std::make_unique<src::Callout>(CalloutPriority::high,
550 locCode, fn, ccin, sn);
551 }
Matt Spinlered046852020-03-13 13:58:15 -0500552 }
553 catch (const SdBusError& e)
554 {
Matt Spinler9b90e2a2020-04-14 10:59:04 -0500555 std::string msg = "Could not get location code for " + inventoryPath;
556 log<level::WARNING>(msg.c_str(), entry("ERROR=%s", e.what()));
Matt Spinlered046852020-03-13 13:58:15 -0500557
Matt Spinler9b90e2a2020-04-14 10:59:04 -0500558 // If this were to happen, people would have to look in the UserData
559 // section that contains CALLOUT_INVENTORY_PATH to see what failed.
Matt Spinlered046852020-03-13 13:58:15 -0500560 callout = std::make_unique<src::Callout>(CalloutPriority::high,
Matt Spinlera27e2e52020-04-09 11:06:11 -0500561 "no_vpd_for_fru");
Matt Spinlered046852020-03-13 13:58:15 -0500562 }
563
Matt Spinler9b90e2a2020-04-14 10:59:04 -0500564 createCalloutsObject();
Matt Spinlered046852020-03-13 13:58:15 -0500565 _callouts->addCallout(std::move(callout));
Matt Spinler03984582020-04-09 13:17:58 -0500566}
Matt Spinlered046852020-03-13 13:58:15 -0500567
Matt Spinler03984582020-04-09 13:17:58 -0500568void SRC::addRegistryCallouts(const message::Entry& regEntry,
569 const AdditionalData& additionalData,
570 const DataInterfaceBase& dataIface)
571{
572 try
573 {
Matt Spinler6ea4d5f2020-05-20 13:31:07 -0500574 auto systemNames = dataIface.getSystemNames();
Matt Spinler03984582020-04-09 13:17:58 -0500575
576 auto regCallouts = message::Registry::getCallouts(
Matt Spinler6ea4d5f2020-05-20 13:31:07 -0500577 regEntry.callouts.value(), systemNames, additionalData);
Matt Spinler03984582020-04-09 13:17:58 -0500578
579 for (const auto& regCallout : regCallouts)
580 {
581 addRegistryCallout(regCallout, dataIface);
582 }
583 }
584 catch (std::exception& e)
585 {
586 log<level::ERR>("Error parsing PEL message registry callout JSON",
587 entry("ERROR=%s", e.what()));
588 }
589}
590
591void SRC::addRegistryCallout(const message::RegistryCallout& regCallout,
592 const DataInterfaceBase& dataIface)
593{
594 std::unique_ptr<src::Callout> callout;
595
596 // TODO: expand this location code.
597 auto locCode = regCallout.locCode;
598
599 // Via the PEL values table, get the priority enum.
600 // The schema will have validated the priority was a valid value.
601 auto priorityIt =
602 pv::findByName(regCallout.priority, pv::calloutPriorityValues);
603 assert(priorityIt != pv::calloutPriorityValues.end());
604 auto priority =
605 static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
606
607 if (!regCallout.procedure.empty())
608 {
609 // Procedure callout
610 callout =
611 std::make_unique<src::Callout>(priority, regCallout.procedure);
612 }
613 else if (!regCallout.symbolicFRU.empty())
614 {
615 // Symbolic FRU callout
616 callout = std::make_unique<src::Callout>(
617 priority, regCallout.symbolicFRU, locCode, false);
618 }
619 else if (!regCallout.symbolicFRUTrusted.empty())
620 {
621 // Symbolic FRU with trusted location code callout
622
623 // The registry wants it to be trusted, but that requires a valid
624 // location code for it to actually be.
625 callout = std::make_unique<src::Callout>(
626 priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
627 }
628 else
629 {
630 // TODO: HW callouts
631 }
632
633 if (callout)
634 {
635 createCalloutsObject();
636 _callouts->addCallout(std::move(callout));
637 }
638}
Matt Spinlered046852020-03-13 13:58:15 -0500639
Matt Spinlerf9bae182019-10-09 13:37:38 -0500640} // namespace pels
641} // namespace openpower