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 "registry.hpp" 17 18 #include "pel_types.hpp" 19 #include "pel_values.hpp" 20 21 #include <fstream> 22 #include <phosphor-logging/log.hpp> 23 24 namespace openpower 25 { 26 namespace pels 27 { 28 namespace message 29 { 30 31 namespace pv = pel_values; 32 namespace fs = std::filesystem; 33 using namespace phosphor::logging; 34 35 constexpr auto debugFilePath = "/etc/phosphor-logging/"; 36 37 namespace helper 38 { 39 40 uint8_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 56 uint8_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 71 uint16_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 95 uint8_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 108 uint8_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 122 uint16_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 138 uint8_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 156 std::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 } 199 std::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 232 uint16_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 265 } // namespace helper 266 267 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type, 268 bool toCache) 269 { 270 std::optional<nlohmann::json> registryTmp; 271 auto& registryOpt = (_registry) ? _registry : registryTmp; 272 if (!registryOpt) 273 { 274 registryOpt = readRegistry(_registryFile); 275 if (!registryOpt) 276 { 277 return std::nullopt; 278 } 279 else if (toCache) 280 { 281 // Save message registry in memory for peltool 282 _registry = std::move(registryTmp); 283 } 284 } 285 auto& reg = (_registry) ? _registry : registryTmp; 286 const auto& registry = reg.value(); 287 // Find an entry with this name in the PEL array. 288 auto e = std::find_if( 289 registry["PELs"].begin(), registry["PELs"].end(), 290 [&name, &type](const auto& j) { 291 return ((name == j["Name"] && type == LookupType::name) || 292 (name == j["SRC"]["ReasonCode"] && 293 type == LookupType::reasonCode)); 294 }); 295 296 if (e != registry["PELs"].end()) 297 { 298 // Fill in the Entry structure from the JSON. Most, but not all, fields 299 // are optional. 300 301 try 302 { 303 Entry entry; 304 entry.name = (*e)["Name"]; 305 entry.subsystem = helper::getSubsystem((*e)["Subsystem"]); 306 307 if (e->find("ActionFlags") != e->end()) 308 { 309 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]); 310 } 311 312 if (e->find("MfgActionFlags") != e->end()) 313 { 314 entry.mfgActionFlags = 315 helper::getActionFlags((*e)["MfgActionFlags"]); 316 } 317 318 if (e->find("Severity") != e->end()) 319 { 320 entry.severity = helper::getSeverity((*e)["Severity"]); 321 } 322 323 if (e->find("MfgSeverity") != e->end()) 324 { 325 entry.mfgSeverity = helper::getSeverity((*e)["MfgSeverity"]); 326 } 327 328 if (e->find("EventType") != e->end()) 329 { 330 entry.eventType = helper::getEventType((*e)["EventType"]); 331 } 332 333 if (e->find("EventScope") != e->end()) 334 { 335 entry.eventScope = helper::getEventScope((*e)["EventScope"]); 336 } 337 338 auto& src = (*e)["SRC"]; 339 entry.src.reasonCode = helper::getSRCReasonCode(src, name); 340 341 if (src.find("Type") != src.end()) 342 { 343 entry.src.type = helper::getSRCType(src, name); 344 } 345 else 346 { 347 entry.src.type = static_cast<uint8_t>(SRCType::bmcError); 348 } 349 350 // Now that we know the SRC type and reason code, 351 // we can get the component ID. 352 entry.componentID = helper::getComponentID( 353 entry.src.type, entry.src.reasonCode, *e, name); 354 355 if (src.find("Words6To9") != src.end()) 356 { 357 entry.src.hexwordADFields = 358 helper::getSRCHexwordFields(src, name); 359 } 360 361 if (src.find("SymptomIDFields") != src.end()) 362 { 363 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name); 364 } 365 366 if (src.find("PowerFault") != src.end()) 367 { 368 entry.src.powerFault = src["PowerFault"]; 369 } 370 371 auto& doc = (*e)["Documentation"]; 372 entry.doc.message = doc["Message"]; 373 entry.doc.description = doc["Description"]; 374 if (doc.find("MessageArgSources") != doc.end()) 375 { 376 entry.doc.messageArgSources = doc["MessageArgSources"]; 377 } 378 379 return entry; 380 } 381 catch (std::exception& e) 382 { 383 log<level::ERR>("Found invalid message registry field", 384 entry("ERROR=%s", e.what())); 385 } 386 } 387 388 return std::nullopt; 389 } 390 391 std::optional<nlohmann::json> 392 Registry::readRegistry(const std::filesystem::path& registryFile) 393 { 394 // Look in /etc first in case someone put a test file there 395 fs::path debugFile{fs::path{debugFilePath} / registryFileName}; 396 nlohmann::json registry; 397 std::ifstream file; 398 399 if (fs::exists(debugFile)) 400 { 401 log<level::INFO>("Using debug PEL message registry"); 402 file.open(debugFile); 403 } 404 else 405 { 406 file.open(registryFile); 407 } 408 409 try 410 { 411 registry = nlohmann::json::parse(file); 412 } 413 catch (std::exception& e) 414 { 415 log<level::ERR>("Error parsing message registry JSON", 416 entry("JSON_ERROR=%s", e.what())); 417 return std::nullopt; 418 } 419 return registry; 420 } 421 422 } // namespace message 423 } // namespace pels 424 } // namespace openpower 425