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