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