1 /** 2 * Copyright © 2018 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 "policy_find.hpp" 17 18 #include <phosphor-logging/log.hpp> 19 #include <sstream> 20 21 namespace ibm 22 { 23 namespace logging 24 { 25 namespace policy 26 { 27 28 static constexpr auto HOST_EVENT = "org.open_power.Host.Error.Event"; 29 30 /** 31 * Returns a property value from a map of properties. 32 * 33 * @tparam - T the property data type 34 * @param[in] properties - the property map 35 * @param[in] name - the property name 36 * 37 * @return optional<T> - the property value 38 */ 39 template <typename T> 40 std::optional<T> getProperty(const DbusPropertyMap& properties, 41 const std::string& name) 42 { 43 auto prop = properties.find(name); 44 45 if (prop != properties.end()) 46 { 47 return std::get<T>(prop->second); 48 } 49 50 return {}; 51 } 52 53 /** 54 * Finds a value in the AdditionalData property, which is 55 * an array of strings in the form of: 56 * 57 * NAME=VALUE 58 * 59 * @param[in] additionalData - the AdditionalData property contents 60 * @param[in] name - the name of the value to find 61 * 62 * @return optional<std::string> - the data value. Will not be empty if found. 63 */ 64 std::optional<std::string> 65 getAdditionalDataItem(const std::vector<std::string>& additionalData, 66 const std::string& name) 67 { 68 std::string value; 69 70 for (const auto& item : additionalData) 71 { 72 if (item.find(name + "=") != std::string::npos) 73 { 74 value = item.substr(item.find('=') + 1); 75 if (!item.empty()) 76 { 77 return value; 78 } 79 } 80 } 81 82 return {}; 83 } 84 85 /** 86 * Returns a string version of the severity from the PEL 87 * log in the extended SEL data from the host, where a PEL stands 88 * for 'Platform Event Log' and is an IBM standard for error logging 89 * that OpenPower host firmware uses. 90 * 91 * The severity is the 11th byte in the 'User Header' section in a PEL 92 * that starts at byte 48. We only need the first nibble, which signifies 93 * the type - 'Recovered', 'Predictive', 'Critical', etc. 94 * 95 * type value | type | returned severity string 96 * ------------------------------------ 97 * 1 Recovered Informational 98 * 2 Predictive Warning 99 * everything else na Critical 100 * 101 * @param[in] data - the PEL string in the form of "00 11 22 33 4e ff" 102 * 103 * @return optional<std::string> - the severity string as listed above 104 */ 105 std::optional<std::string> getESELSeverity(const std::string& data) 106 { 107 // The User Header section starts at byte 48, and take into account 108 // the input data is a space separated string representation of HEX data. 109 static constexpr auto UH_OFFSET = 48 * 4; 110 111 // The eye catcher is "UH" 112 static constexpr auto UH_EYECATCHER = "55 48"; 113 114 // The severity is the 11th byte in the section, and take into 115 // account a byte is "BB " 116 static constexpr auto UH_SEV_OFFSET = 10 * 3; 117 118 std::string severity = "Critical"; 119 120 // The only values that don't map to "Critical" 121 const std::map<std::string, std::string> sevTypes{{"1", "Informational"}, 122 {"2", "Warning"}}; 123 if (data.size() <= (UH_OFFSET + UH_SEV_OFFSET)) 124 { 125 return {}; 126 } 127 128 // Sanity check that the User Header section is there. 129 auto userHeader = data.substr(UH_OFFSET, 5); 130 if (userHeader.compare(UH_EYECATCHER)) 131 { 132 return {}; 133 } 134 135 // The severity type nibble is a full byte in the string. 136 auto sevType = data.substr(UH_OFFSET + UH_SEV_OFFSET, 1); 137 138 auto sev = sevTypes.find(sevType); 139 if (sev != sevTypes.end()) 140 { 141 severity = sev->second; 142 }; 143 144 return severity; 145 } 146 147 /** 148 * Returns the search modifier to use, but if it isn't found 149 * in the table then code should then call getSearchModifier() 150 * and try again. 151 * 152 * This is to be tolerant of the policy table not having 153 * entries for every device path or FRU callout, and trying 154 * again gives code a chance to find the more generic entries 155 * for those classes of errors rather than not being found 156 * at all. 157 * 158 * e.g. If the device path is missing in the table, then it 159 * can still find the generic "Failed to read from an I2C 160 * device" entry. 161 * 162 * @param[in] message- the error message, like xyz.A.Error.B 163 * @param[in] properties - the property map for the error 164 * 165 * @return string - the search modifier 166 * may be empty if none found 167 */ 168 std::string getSearchModifierFirstTry(const std::string& message, 169 const DbusPropertyMap& properties) 170 { 171 auto data = 172 getProperty<std::vector<std::string>>(properties, "AdditionalData"); 173 174 if (!data) 175 { 176 return std::string{}; 177 } 178 179 // Try the called out device path as the search modifier 180 auto devPath = getAdditionalDataItem(*data, "CALLOUT_DEVICE_PATH"); 181 if (devPath) 182 { 183 return *devPath; 184 } 185 186 // For Host.Error.Event errors, try <callout>||<severity string> 187 // as the search modifier. 188 if (message == HOST_EVENT) 189 { 190 auto callout = getAdditionalDataItem(*data, "CALLOUT_INVENTORY_PATH"); 191 if (callout) 192 { 193 auto selData = getAdditionalDataItem(*data, "ESEL"); 194 if (selData) 195 { 196 auto severity = getESELSeverity(*selData); 197 if (severity) 198 { 199 return *callout + "||" + *severity; 200 } 201 } 202 } 203 } 204 205 return std::string{}; 206 } 207 208 /** 209 * Returns the search modifier to use. 210 * 211 * The modifier is used when the error name itself isn't granular 212 * enough to find a policy table entry. The modifier is determined 213 * using rules provided by the IBM service team. 214 * 215 * Not all errors need a modifier, so this function isn't 216 * guaranteed to find one. 217 * 218 * @param[in] properties - the property map for the error 219 * 220 * @return string - the search modifier 221 * may be empty if none found 222 */ 223 auto getSearchModifier(const DbusPropertyMap& properties) 224 { 225 // The modifier may be one of several things within the 226 // AdditionalData property. Try them all until one 227 // is found. 228 229 auto data = 230 getProperty<std::vector<std::string>>(properties, "AdditionalData"); 231 232 if (!data) 233 { 234 return std::string{}; 235 } 236 237 // AdditionalData fields where the value is the modifier 238 static const std::vector<std::string> ADFields{"CALLOUT_INVENTORY_PATH", 239 "RAIL_NAME", "INPUT_NAME"}; 240 241 std::optional<std::string> mod; 242 for (const auto& field : ADFields) 243 { 244 mod = getAdditionalDataItem(*data, field); 245 if (mod) 246 { 247 return *mod; 248 } 249 } 250 251 // Next are the AdditionalData fields where the value needs 252 // to be massaged to get the modifier. 253 254 // A device path, but we only care about the type 255 mod = getAdditionalDataItem(*data, "CALLOUT_DEVICE_PATH"); 256 if (mod) 257 { 258 // The table only handles I2C and FSI 259 if ((*mod).find("i2c") != std::string::npos) 260 { 261 return std::string{"I2C"}; 262 } 263 else if ((*mod).find("fsi") != std::string::npos) 264 { 265 return std::string{"FSI"}; 266 } 267 } 268 269 // A hostboot procedure ID 270 mod = getAdditionalDataItem(*data, "PROCEDURE"); 271 if (mod) 272 { 273 // Convert decimal (e.g. 109) to hex (e.g. 6D) 274 std::ostringstream stream; 275 try 276 { 277 stream << std::hex << std::stoul((*mod).c_str()); 278 auto value = stream.str(); 279 280 if (!value.empty()) 281 { 282 std::transform(value.begin(), value.end(), value.begin(), 283 toupper); 284 return value; 285 } 286 } 287 catch (const std::exception& e) 288 { 289 using namespace phosphor::logging; 290 log<level::ERR>("Invalid PROCEDURE value found", 291 entry("PROCEDURE=%s", mod->c_str())); 292 } 293 } 294 295 return std::string{}; 296 } 297 298 PolicyProps find(const policy::Table& policy, 299 const DbusPropertyMap& errorLogProperties) 300 { 301 auto errorMsg = getProperty<std::string>(errorLogProperties, 302 "Message"); // e.g. xyz.X.Error.Y 303 if (errorMsg) 304 { 305 FindResult result; 306 307 // Try with the FirstTry modifier first, and then the regular one. 308 309 auto modifier = 310 getSearchModifierFirstTry(*errorMsg, errorLogProperties); 311 312 if (!modifier.empty()) 313 { 314 result = policy.find(*errorMsg, modifier); 315 } 316 317 if (!result) 318 { 319 modifier = getSearchModifier(errorLogProperties); 320 321 result = policy.find(*errorMsg, modifier); 322 } 323 324 if (result) 325 { 326 return {(*result).get().ceid, (*result).get().msg}; 327 } 328 } 329 else 330 { 331 using namespace phosphor::logging; 332 log<level::ERR>("No Message metadata found in an error"); 333 } 334 335 return {policy.defaultEID(), policy.defaultMsg()}; 336 } 337 } // namespace policy 338 } // namespace logging 339 } // namespace ibm 340