blob: bf583016eaca454d9d84f8e44c6762665bd6337e [file] [log] [blame]
Matt Spinler4a6ea6a2018-03-27 14:25:12 -05001/**
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 */
Matt Spinler66e07072018-09-12 10:36:14 -050016#include "policy_find.hpp"
17
Matt Spinler8e24dbc2018-03-27 14:48:17 -050018#include <phosphor-logging/log.hpp>
Matt Spinlere3be64f2018-03-27 15:04:33 -050019#include <sstream>
Matt Spinler4a6ea6a2018-03-27 14:25:12 -050020
21namespace ibm
22{
23namespace logging
24{
25namespace policy
26{
27
Matt Spinlerc57aa4b2018-09-28 10:32:24 -050028static constexpr auto HOST_EVENT = "org.open_power.Host.Error.Event";
29
Matt Spinler8e24dbc2018-03-27 14:48:17 -050030/**
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 */
Matt Spinler259e7272018-03-29 10:57:17 -050039template <typename T>
Matt Spinler54ea3ad2018-10-23 10:40:09 -050040std::optional<T> getProperty(const DbusPropertyMap& properties,
41 const std::string& name)
Matt Spinler8e24dbc2018-03-27 14:48:17 -050042{
43 auto prop = properties.find(name);
44
45 if (prop != properties.end())
46 {
Patrick Williamsb5af3a32020-05-13 11:11:32 -050047 return std::get<T>(prop->second);
Matt Spinler8e24dbc2018-03-27 14:48:17 -050048 }
49
50 return {};
51}
52
53/**
Matt Spinlere3be64f2018-03-27 15:04:33 -050054 * 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 *
Matt Spinlerc57aa4b2018-09-28 10:32:24 -050062 * @return optional<std::string> - the data value. Will not be empty if found.
Matt Spinlere3be64f2018-03-27 15:04:33 -050063 */
Matt Spinler54ea3ad2018-10-23 10:40:09 -050064std::optional<std::string>
Matt Spinler259e7272018-03-29 10:57:17 -050065 getAdditionalDataItem(const std::vector<std::string>& additionalData,
66 const std::string& name)
Matt Spinlere3be64f2018-03-27 15:04:33 -050067{
Matt Spinlerc57aa4b2018-09-28 10:32:24 -050068 std::string value;
69
Matt Spinlere3be64f2018-03-27 15:04:33 -050070 for (const auto& item : additionalData)
71 {
Matt Spinler259e7272018-03-29 10:57:17 -050072 if (item.find(name + "=") != std::string::npos)
Matt Spinlere3be64f2018-03-27 15:04:33 -050073 {
Matt Spinlerc57aa4b2018-09-28 10:32:24 -050074 value = item.substr(item.find('=') + 1);
75 if (!item.empty())
76 {
77 return value;
78 }
Matt Spinlere3be64f2018-03-27 15:04:33 -050079 }
80 }
81
82 return {};
83}
84
85/**
Matt Spinlerc57aa4b2018-09-28 10:32:24 -050086 * 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 */
Matt Spinler54ea3ad2018-10-23 10:40:09 -0500105std::optional<std::string> getESELSeverity(const std::string& data)
Matt Spinlerc57aa4b2018-09-28 10:32:24 -0500106{
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 */
168std::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/**
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500209 * 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 */
Matt Spinler259e7272018-03-29 10:57:17 -0500223auto getSearchModifier(const DbusPropertyMap& properties)
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500224{
Matt Spinlere3be64f2018-03-27 15:04:33 -0500225 // The modifier may be one of several things within the
226 // AdditionalData property. Try them all until one
227 // is found.
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500228
Matt Spinlere3be64f2018-03-27 15:04:33 -0500229 auto data =
230 getProperty<std::vector<std::string>>(properties, "AdditionalData");
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500231
Matt Spinlere3be64f2018-03-27 15:04:33 -0500232 if (!data)
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500233 {
Matt Spinlere3be64f2018-03-27 15:04:33 -0500234 return std::string{};
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500235 }
236
Matt Spinlere3be64f2018-03-27 15:04:33 -0500237 // 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
Matt Spinler54ea3ad2018-10-23 10:40:09 -0500241 std::optional<std::string> mod;
Matt Spinlere3be64f2018-03-27 15:04:33 -0500242 for (const auto& field : ADFields)
243 {
244 mod = getAdditionalDataItem(*data, field);
Matt Spinlerc57aa4b2018-09-28 10:32:24 -0500245 if (mod)
Matt Spinlere3be64f2018-03-27 15:04:33 -0500246 {
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 {
Matt Spinler259e7272018-03-29 10:57:17 -0500282 std::transform(value.begin(), value.end(), value.begin(),
283 toupper);
Matt Spinlere3be64f2018-03-27 15:04:33 -0500284 return value;
285 }
286 }
Patrick Williamsbf9ea8a2021-10-06 13:10:21 -0500287 catch (const std::exception& e)
Matt Spinlere3be64f2018-03-27 15:04:33 -0500288 {
289 using namespace phosphor::logging;
290 log<level::ERR>("Invalid PROCEDURE value found",
Joseph Reynolds53530262018-05-17 13:45:13 -0500291 entry("PROCEDURE=%s", mod->c_str()));
Matt Spinlere3be64f2018-03-27 15:04:33 -0500292 }
293 }
294
295 return std::string{};
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500296}
297
Matt Spinler259e7272018-03-29 10:57:17 -0500298PolicyProps find(const policy::Table& policy,
299 const DbusPropertyMap& errorLogProperties)
Matt Spinler4a6ea6a2018-03-27 14:25:12 -0500300{
Matt Spinler259e7272018-03-29 10:57:17 -0500301 auto errorMsg = getProperty<std::string>(errorLogProperties,
302 "Message"); // e.g. xyz.X.Error.Y
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500303 if (errorMsg)
304 {
Matt Spinlerc57aa4b2018-09-28 10:32:24 -0500305 FindResult result;
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500306
Matt Spinlerc57aa4b2018-09-28 10:32:24 -0500307 // 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 }
Matt Spinler8e24dbc2018-03-27 14:48:17 -0500323
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 }
Matt Spinler4a6ea6a2018-03-27 14:25:12 -0500334
335 return {policy.defaultEID(), policy.defaultMsg()};
336}
Matt Spinler66e07072018-09-12 10:36:14 -0500337} // namespace policy
338} // namespace logging
339} // namespace ibm