blob: 830a0138b07e58ca12c4d5bf5f7b8be3272dfbd5 [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 Spinler367144c2019-09-19 15:33:52 -050016#include "registry.hpp"
17
18#include "pel_types.hpp"
19#include "pel_values.hpp"
20
21#include <fstream>
22#include <phosphor-logging/log.hpp>
23
24namespace openpower
25{
26namespace pels
27{
28namespace message
29{
30
31namespace pv = pel_values;
32namespace fs = std::filesystem;
33using namespace phosphor::logging;
34
35constexpr auto debugFilePath = "/etc/phosphor-logging/";
36
37namespace helper
38{
39
40uint8_t getSubsystem(const std::string& subsystemName)
41{
42 // Get the actual value to use in the PEL for the string name
43 auto ss = pv::findByName(subsystemName, pv::subsystemValues);
44 if (ss == pv::subsystemValues.end())
45 {
46 // Schema validation should be catching this.
47 log<level::ERR>("Invalid subsystem name used in message registry",
48 entry("SUBSYSTEM=%s", subsystemName.c_str()));
49
50 throw std::runtime_error("Invalid subsystem used in message registry");
51 }
52
53 return std::get<pv::fieldValuePos>(*ss);
54}
55
56uint8_t getSeverity(const std::string& severityName)
57{
58 auto s = pv::findByName(severityName, pv::severityValues);
59 if (s == pv::severityValues.end())
60 {
61 // Schema validation should be catching this.
62 log<level::ERR>("Invalid severity name used in message registry",
63 entry("SEVERITY=%s", severityName.c_str()));
64
65 throw std::runtime_error("Invalid severity used in message registry");
66 }
67
68 return std::get<pv::fieldValuePos>(*s);
69}
70
71uint16_t getActionFlags(const std::vector<std::string>& flags)
72{
73 uint16_t actionFlags = 0;
74
75 // Make the bitmask based on the array of flag names
76 for (const auto& flag : flags)
77 {
78 auto s = pv::findByName(flag, pv::actionFlagsValues);
79 if (s == pv::actionFlagsValues.end())
80 {
81 // Schema validation should be catching this.
82 log<level::ERR>("Invalid action flag name used in message registry",
83 entry("FLAG=%s", flag.c_str()));
84
85 throw std::runtime_error(
86 "Invalid action flag used in message registry");
87 }
88
89 actionFlags |= std::get<pv::fieldValuePos>(*s);
90 }
91
92 return actionFlags;
93}
94
95uint8_t getEventType(const std::string& eventTypeName)
96{
97 auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
98 if (t == pv::eventTypeValues.end())
99 {
100 log<level::ERR>("Invalid event type used in message registry",
101 entry("EVENT_TYPE=%s", eventTypeName.c_str()));
102
103 throw std::runtime_error("Invalid event type used in message registry");
104 }
105 return std::get<pv::fieldValuePos>(*t);
106}
107
108uint8_t getEventScope(const std::string& eventScopeName)
109{
110 auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
111 if (s == pv::eventScopeValues.end())
112 {
113 log<level::ERR>("Invalid event scope used in registry",
114 entry("EVENT_SCOPE=%s", eventScopeName.c_str()));
115
116 throw std::runtime_error(
117 "Invalid event scope used in message registry");
118 }
119 return std::get<pv::fieldValuePos>(*s);
120}
121
Matt Spinler93e29322019-09-20 11:16:15 -0500122uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
123{
124 std::string rc = src["ReasonCode"];
125 uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
126 if (reasonCode == 0)
127 {
128 log<phosphor::logging::level::ERR>(
129 "Invalid reason code in message registry",
130 entry("ERROR_NAME=%s", name.c_str()),
131 entry("REASON_CODE=%s", rc.c_str()));
132
133 throw std::runtime_error("Invalid reason code in message registry");
134 }
135 return reasonCode;
136}
137
138uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
139{
140 // Looks like: "22"
141 std::string srcType = src["Type"];
142 size_t type = strtoul(srcType.c_str(), nullptr, 16);
143 if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
144 {
145 log<phosphor::logging::level::ERR>(
146 "Invalid SRC Type in message registry",
147 entry("ERROR_NAME=%s", name.c_str()),
148 entry("SRC_TYPE=%s", srcType.c_str()));
149
150 throw std::runtime_error("Invalid SRC Type in message registry");
151 }
152
153 return type;
154}
155
156std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
157 getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
158{
159 std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
160
161 // Build the map of which AdditionalData fields to use for which SRC words
162
163 // Like:
164 // {
165 // "8":
166 // {
167 // "AdditionalDataPropSource": "TEST"
168 // }
169 //
170 // }
171
172 for (const auto& word : src["Words6To9"].items())
173 {
174 std::string num = word.key();
175 size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
176
177 if (wordNum == 0)
178 {
179 log<phosphor::logging::level::ERR>(
180 "Invalid SRC word number in message registry",
181 entry("ERROR_NAME=%s", name.c_str()),
182 entry("SRC_WORD_NUM=%s", num.c_str()));
183
184 throw std::runtime_error("Invalid SRC word in message registry");
185 }
186
187 auto attributes = word.value();
188 std::string adPropName = attributes["AdditionalDataPropSource"];
189 hexwordFields[wordNum] = std::move(adPropName);
190 }
191
192 if (!hexwordFields.empty())
193 {
194 return hexwordFields;
195 }
196
197 return std::nullopt;
198}
199std::optional<std::vector<SRC::WordNum>>
200 getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
201{
202 std::vector<SRC::WordNum> symptomIDFields;
203
204 // Looks like:
205 // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
206
207 for (const std::string& field : src["SymptomIDFields"])
208 {
209 // Just need the last digit off the end, e.g. SRCWord6.
210 // The schema enforces the format of these.
211 auto srcWordNum = field.substr(field.size() - 1);
212 size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
213 if (num == 0)
214 {
215 log<phosphor::logging::level::ERR>(
216 "Invalid symptom ID field in message registry",
217 entry("ERROR_NAME=%s", name.c_str()),
218 entry("FIELD_NAME=%s", srcWordNum.c_str()));
219
220 throw std::runtime_error("Invalid symptom ID in message registry");
221 }
222 symptomIDFields.push_back(num);
223 }
224 if (!symptomIDFields.empty())
225 {
226 return symptomIDFields;
227 }
228
229 return std::nullopt;
230}
231
232uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
233 const nlohmann::json& pelEntry, const std::string& name)
234{
235 uint16_t id = 0;
236
237 // If the ComponentID field is there, use that. Otherwise, if it's a
238 // 0xBD BMC error SRC, use the reasoncode.
239 if (pelEntry.find("ComponentID") != pelEntry.end())
240 {
241 std::string componentID = pelEntry["ComponentID"];
242 id = strtoul(componentID.c_str(), nullptr, 16);
243 }
244 else
245 {
246 // On BMC error SRCs (BD), can just get the component ID from
247 // the first byte of the reason code.
248 if (srcType == static_cast<uint8_t>(SRCType::bmcError))
249 {
250 id = reasonCode & 0xFF00;
251 }
252 else
253 {
254 log<level::ERR>("Missing component ID field in message registry",
255 entry("ERROR_NAME=%s", name.c_str()));
256
257 throw std::runtime_error(
258 "Missing component ID field in message registry");
259 }
260 }
261
262 return id;
263}
264
Matt Spinler6b427cc2020-04-09 09:42:59 -0500265/**
266 * @brief Says if the JSON is the format that contains AdditionalData keys
267 * as in index into them.
268 *
269 * @param[in] json - The highest level callout JSON
270 *
271 * @return bool - If it is the AdditionalData format or not
272 */
273bool calloutUsesAdditionalData(const nlohmann::json& json)
274{
275 return (json.contains("ADName") &&
276 json.contains("CalloutsWithTheirADValues"));
277}
278
279/**
280 * @brief Finds the callouts to use when there is no AdditionalData,
281 * but the system type may be used as a key.
282 *
283 * One entry in the array looks like the following. The System key
284 * is optional and if not present it means that entry applies to
285 * every configuration that doesn't have another entry with a matching
286 * System key.
287 *
288 * {
289 * "System": "system1",
290 * "CalloutList":
291 * [
292 * {
293 * "Priority": "high",
294 * "LocCode": "P1-C1"
295 * },
296 * {
297 * "Priority": "low",
298 * "LocCode": "P1"
299 * }
300 * ]
301 * }
302 */
303const nlohmann::json& findCalloutList(const nlohmann::json& json,
304 const std::string& systemType)
305{
306 const nlohmann::json* callouts = nullptr;
307
308 if (!json.is_array())
309 {
310 throw std::runtime_error{"findCalloutList was not passed a JSON array"};
311 }
312
313 // The entry with the system type match will take precedence over the entry
314 // without any "System" field in it at all, which will match all other
315 // cases.
316 for (const auto& calloutList : json)
317 {
318 if (calloutList.contains("System"))
319 {
320 if (systemType == calloutList["System"].get<std::string>())
321 {
322 callouts = &calloutList["CalloutList"];
323 break;
324 }
325 }
326 else
327 {
328 // Any entry with no System key
329 callouts = &calloutList["CalloutList"];
330 }
331 }
332
333 if (!callouts)
334 {
335 log<level::WARNING>(
336 "No matching system type entry or default system type entry "
337 " for PEL callout list",
338 entry("SYSTEMTYPE=%s", systemType.c_str()));
339
340 throw std::runtime_error{
341 "Could not find a CalloutList JSON for this error and system type"};
342 }
343
344 return *callouts;
345}
346
347/**
348 * @brief Creates a RegistryCallout based on the input JSON.
349 *
350 * The JSON looks like:
351 * {
352 * "Priority": "high",
353 * "LocCode": "E1"
354 * ...
355 * }
356 *
357 * Schema validation enforces what keys are present.
358 *
359 * @param[in] json - The JSON dictionary entry for a callout
360 *
361 * @return RegistryCallout - A filled in RegistryCallout
362 */
363RegistryCallout makeRegistryCallout(const nlohmann::json& json)
364{
365 RegistryCallout callout;
366
367 callout.priority = "high";
368
369 if (json.contains("Priority"))
370 {
371 callout.priority = json["Priority"].get<std::string>();
372 }
373
374 if (json.contains("LocCode"))
375 {
376 callout.locCode = json["LocCode"].get<std::string>();
377 }
378
379 if (json.contains("Procedure"))
380 {
381 callout.procedure = json["Procedure"].get<std::string>();
382 }
383 else if (json.contains("SymbolicFRU"))
384 {
385 callout.symbolicFRU = json["SymbolicFRU"].get<std::string>();
386 }
387 else if (json.contains("SymbolicFRUTrusted"))
388 {
389 callout.symbolicFRUTrusted =
390 json["SymbolicFRUTrusted"].get<std::string>();
391 }
392
393 return callout;
394}
395
396/**
397 * @brief Returns the callouts to use when an AdditionalData key is
398 * required to find the correct entries.
399 *
400 * The System property is used to find which CalloutList to use.
401 * If System is missing, then that CalloutList is valid for
402 * everything.
403 *
404 * The JSON looks like:
405 * [
406 * {
407 * "System": "systemA",
408 * "CalloutList":
409 * [
410 * {
411 * "Priority": "high",
412 * "LocCode": "P1-C5"
413 * }
414 * ]
415 * }
416 * ]
417 *
418 * @param[in] json - The callout JSON
419 * @param[in] systemType - The system type from EntityManager
420 *
421 * @return std::vector<RegistryCallout> - The callouts to use
422 */
423std::vector<RegistryCallout> getCalloutsWithoutAD(const nlohmann::json& json,
424 const std::string& systemType)
425{
426 std::vector<RegistryCallout> calloutEntries;
427
428 // Find the CalloutList to use based on the system type
429 const auto& calloutList = findCalloutList(json, systemType);
430
431 // We finally found the callouts, make the objects.
432 for (const auto& callout : calloutList)
433 {
434 calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
435 }
436
437 return calloutEntries;
438}
439
440/**
441 * @brief Returns the callouts to use when an AdditionalData key is
442 * required to find the correct entries.
443 *
444 * The JSON looks like:
445 * {
446 * "ADName": "PROC_NUM",
447 * "CalloutsWithTheirADValues":
448 * [
449 * {
450 * "ADValue": "0",
451 * "Callouts":
452 * [
453 * {
454 * "CalloutList":
455 * [
456 * {
457 * "Priority": "high",
458 * "LocCode": "P1-C5"
459 * }
460 * ]
461 * }
462 * ]
463 * }
464 * ]
465 * }
466 *
467 * Note that the "Callouts" entry above is the same as the top level
468 * entry used when there is no AdditionalData key.
469 *
470 * @param[in] json - The callout JSON
471 * @param[in] systemType - The system type from EntityManager
472 * @param[in] additionalData - The AdditionalData property
473 *
474 * @return std::vector<RegistryCallout> - The callouts to use
475 */
476std::vector<RegistryCallout>
477 getCalloutsUsingAD(const nlohmann::json& json,
478 const std::string& systemType,
479 const AdditionalData& additionalData)
480{
481 // This indicates which AD field we'll be using
482 auto keyName = json["ADName"].get<std::string>();
483
484 // Get the actual value from the AD data
485 auto adValue = additionalData.getValue(keyName);
486
487 if (!adValue)
488 {
489 // The AdditionalData did not contain the necessary key
490 log<level::WARNING>(
491 "The PEL message registry callouts JSON "
492 "said to use an AdditionalData key that isn't in the "
493 "AdditionalData event log property",
494 entry("ADNAME=%s\n", keyName.c_str()));
495 throw std::runtime_error{
496 "Missing AdditionalData entry for this callout"};
497 }
498
499 const auto& callouts = json["CalloutsWithTheirADValues"];
500
501 // find the entry with that AD value
502 auto it = std::find_if(
503 callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) {
504 return *adValue == j["ADValue"].get<std::string>();
505 });
506
507 if (it == callouts.end())
508 {
509 log<level::WARNING>(
510 "No callout entry found for the AdditionalData value used",
511 entry("AD_VALUE=%s", adValue->c_str()));
512
513 throw std::runtime_error{
514 "No callout entry found for the AdditionalData value used"};
515 }
516
517 // Proceed to find the callouts possibly based on system type.
518 return getCalloutsWithoutAD((*it)["Callouts"], systemType);
519}
520
Matt Spinler367144c2019-09-19 15:33:52 -0500521} // namespace helper
522
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800523std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
524 bool toCache)
Matt Spinler367144c2019-09-19 15:33:52 -0500525{
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800526 std::optional<nlohmann::json> registryTmp;
527 auto& registryOpt = (_registry) ? _registry : registryTmp;
528 if (!registryOpt)
Matt Spinler367144c2019-09-19 15:33:52 -0500529 {
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800530 registryOpt = readRegistry(_registryFile);
531 if (!registryOpt)
532 {
533 return std::nullopt;
534 }
535 else if (toCache)
536 {
537 // Save message registry in memory for peltool
538 _registry = std::move(registryTmp);
539 }
Matt Spinler367144c2019-09-19 15:33:52 -0500540 }
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800541 auto& reg = (_registry) ? _registry : registryTmp;
542 const auto& registry = reg.value();
Matt Spinler367144c2019-09-19 15:33:52 -0500543 // Find an entry with this name in the PEL array.
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800544 auto e = std::find_if(
545 registry["PELs"].begin(), registry["PELs"].end(),
546 [&name, &type](const auto& j) {
547 return ((name == j["Name"] && type == LookupType::name) ||
548 (name == j["SRC"]["ReasonCode"] &&
549 type == LookupType::reasonCode));
550 });
Matt Spinler367144c2019-09-19 15:33:52 -0500551
552 if (e != registry["PELs"].end())
553 {
554 // Fill in the Entry structure from the JSON. Most, but not all, fields
555 // are optional.
556
557 try
558 {
559 Entry entry;
560 entry.name = (*e)["Name"];
561 entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
Matt Spinlere07f9152019-11-01 10:48:36 -0500562
563 if (e->find("ActionFlags") != e->end())
564 {
565 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
566 }
Matt Spinler367144c2019-09-19 15:33:52 -0500567
568 if (e->find("MfgActionFlags") != e->end())
569 {
570 entry.mfgActionFlags =
571 helper::getActionFlags((*e)["MfgActionFlags"]);
572 }
573
574 if (e->find("Severity") != e->end())
575 {
576 entry.severity = helper::getSeverity((*e)["Severity"]);
577 }
578
579 if (e->find("MfgSeverity") != e->end())
580 {
581 entry.mfgSeverity = helper::getSeverity((*e)["MfgSeverity"]);
582 }
583
584 if (e->find("EventType") != e->end())
585 {
586 entry.eventType = helper::getEventType((*e)["EventType"]);
587 }
588
589 if (e->find("EventScope") != e->end())
590 {
591 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
592 }
593
Matt Spinler93e29322019-09-20 11:16:15 -0500594 auto& src = (*e)["SRC"];
595 entry.src.reasonCode = helper::getSRCReasonCode(src, name);
596
597 if (src.find("Type") != src.end())
598 {
599 entry.src.type = helper::getSRCType(src, name);
600 }
601 else
602 {
603 entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
604 }
605
606 // Now that we know the SRC type and reason code,
607 // we can get the component ID.
608 entry.componentID = helper::getComponentID(
609 entry.src.type, entry.src.reasonCode, *e, name);
610
611 if (src.find("Words6To9") != src.end())
612 {
613 entry.src.hexwordADFields =
614 helper::getSRCHexwordFields(src, name);
615 }
616
617 if (src.find("SymptomIDFields") != src.end())
618 {
619 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
620 }
621
622 if (src.find("PowerFault") != src.end())
623 {
624 entry.src.powerFault = src["PowerFault"];
625 }
Matt Spinler367144c2019-09-19 15:33:52 -0500626
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800627 auto& doc = (*e)["Documentation"];
628 entry.doc.message = doc["Message"];
629 entry.doc.description = doc["Description"];
630 if (doc.find("MessageArgSources") != doc.end())
631 {
632 entry.doc.messageArgSources = doc["MessageArgSources"];
633 }
634
Matt Spinlerd8e29002020-04-09 09:11:22 -0500635 // If there are callouts defined, save the JSON for later
636 if (_loadCallouts)
637 {
638 if (e->contains("Callouts"))
639 {
640 entry.callouts = (*e)["Callouts"];
641 }
642 else if (e->contains("CalloutsUsingAD"))
643 {
644 entry.callouts = (*e)["CalloutsUsingAD"];
645 }
646 }
647
Matt Spinler367144c2019-09-19 15:33:52 -0500648 return entry;
649 }
650 catch (std::exception& e)
651 {
652 log<level::ERR>("Found invalid message registry field",
653 entry("ERROR=%s", e.what()));
654 }
655 }
656
657 return std::nullopt;
658}
659
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800660std::optional<nlohmann::json>
661 Registry::readRegistry(const std::filesystem::path& registryFile)
662{
663 // Look in /etc first in case someone put a test file there
664 fs::path debugFile{fs::path{debugFilePath} / registryFileName};
665 nlohmann::json registry;
666 std::ifstream file;
667
668 if (fs::exists(debugFile))
669 {
670 log<level::INFO>("Using debug PEL message registry");
671 file.open(debugFile);
672 }
673 else
674 {
675 file.open(registryFile);
676 }
677
678 try
679 {
680 registry = nlohmann::json::parse(file);
681 }
682 catch (std::exception& e)
683 {
684 log<level::ERR>("Error parsing message registry JSON",
685 entry("JSON_ERROR=%s", e.what()));
686 return std::nullopt;
687 }
688 return registry;
689}
690
Matt Spinler6b427cc2020-04-09 09:42:59 -0500691std::vector<RegistryCallout>
692 Registry::getCallouts(const nlohmann::json& calloutJSON,
693 const std::string& systemType,
694 const AdditionalData& additionalData)
695{
696 // The JSON may either use an AdditionalData key
697 // as an index, or not.
698 if (helper::calloutUsesAdditionalData(calloutJSON))
699 {
700 return helper::getCalloutsUsingAD(calloutJSON, systemType,
701 additionalData);
702 }
703
704 return helper::getCalloutsWithoutAD(calloutJSON, systemType);
705}
706
Matt Spinler367144c2019-09-19 15:33:52 -0500707} // namespace message
708} // namespace pels
709} // namespace openpower