1 /**
2  * Copyright © 2020 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 "service_indicators.hpp"
17 
18 #include <fmt/format.h>
19 
20 #include <phosphor-logging/log.hpp>
21 
22 #include <bitset>
23 
24 namespace openpower::pels::service_indicators
25 {
26 
27 using namespace phosphor::logging;
28 
29 static constexpr auto platformSaiLedGroup =
30     "/xyz/openbmc_project/led/groups/platform_system_attention_indicator";
31 
32 std::unique_ptr<Policy> getPolicy(const DataInterfaceBase& dataIface)
33 {
34     // At the moment there is just one type of policy.
35     return std::make_unique<LightPath>(dataIface);
36 }
37 
38 bool LightPath::ignore(const PEL& pel) const
39 {
40     auto creator = pel.privateHeader().creatorID();
41 
42     // Don't ignore serviceable BMC or hostboot errors
43     if ((static_cast<CreatorID>(creator) == CreatorID::openBMC) ||
44         (static_cast<CreatorID>(creator) == CreatorID::hostboot))
45     {
46         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
47         if (actionFlags.test(serviceActionFlagBit))
48         {
49             return false;
50         }
51     }
52 
53     return true;
54 }
55 
56 void LightPath::activate(const PEL& pel)
57 {
58     if (ignore(pel))
59     {
60         return;
61     }
62 
63     // Now that we've gotten this far, we'll need to turn on
64     // the system attention indicator if we don't find other
65     // indicators to turn on.
66     bool sai = true;
67     auto src = pel.primarySRC();
68     const auto& calloutsObj = (*src)->callouts();
69 
70     if (calloutsObj && !calloutsObj->callouts().empty())
71     {
72         const auto& callouts = calloutsObj->callouts();
73 
74         // From the callouts, find the location codes whose
75         // LEDs need to be turned on.
76         auto locCodes = getLocationCodes(callouts);
77         if (!locCodes.empty())
78         {
79             // Find the inventory paths for those location codes.
80             auto paths = getInventoryPaths(locCodes);
81             if (!paths.empty())
82             {
83                 setNotFunctional(paths);
84                 createCriticalAssociation(paths);
85                 sai = false;
86             }
87         }
88     }
89 
90     if (sai)
91     {
92         try
93         {
94             _dataIface.assertLEDGroup(platformSaiLedGroup, true);
95         }
96         catch (const std::exception& e)
97         {
98             log<level::ERR>(
99                 fmt::format("Failed to assert platform SAI LED group: {}",
100                             e.what())
101                     .c_str());
102         }
103     }
104 }
105 
106 std::vector<std::string> LightPath::getLocationCodes(
107     const std::vector<std::unique_ptr<src::Callout>>& callouts) const
108 {
109     std::vector<std::string> locCodes;
110     bool firstCallout = true;
111     uint8_t firstCalloutPriority;
112 
113     // Collect location codes for the first group of callouts,
114     // where a group can be:
115     //  * a single medium priority callout
116     //  * one or more high priority callouts
117     //  * one or more medium group a priority callouts
118     //
119     // All callouts in the group must be hardware callouts.
120 
121     for (const auto& callout : callouts)
122     {
123         if (firstCallout)
124         {
125             firstCallout = false;
126 
127             firstCalloutPriority = callout->priority();
128 
129             // If the first callout is High, Medium, or Medium
130             // group A, and is a hardware callout, then we
131             // want it.
132             if (isRequiredPriority(firstCalloutPriority) &&
133                 isHardwareCallout(*callout))
134             {
135                 locCodes.push_back(callout->locationCode());
136             }
137             else
138             {
139                 break;
140             }
141 
142             // By definition a medium priority callout can't be part
143             // of a group, so no need to look for more.
144             if (static_cast<CalloutPriority>(firstCalloutPriority) ==
145                 CalloutPriority::medium)
146             {
147                 break;
148             }
149         }
150         else
151         {
152             // Only continue while the callouts are the same
153             // priority as the first callout.
154             if (callout->priority() != firstCalloutPriority)
155             {
156                 break;
157             }
158 
159             // If any callout in the group isn't a hardware callout,
160             // then don't light up any LEDs at all.
161             if (!isHardwareCallout(*callout))
162             {
163                 locCodes.clear();
164                 break;
165             }
166 
167             locCodes.push_back(callout->locationCode());
168         }
169     }
170 
171     return locCodes;
172 }
173 
174 bool LightPath::isRequiredPriority(uint8_t priority) const
175 {
176     auto calloutPriority = static_cast<CalloutPriority>(priority);
177     return (calloutPriority == CalloutPriority::high) ||
178            (calloutPriority == CalloutPriority::medium) ||
179            (calloutPriority == CalloutPriority::mediumGroupA);
180 }
181 
182 bool LightPath::isHardwareCallout(const src::Callout& callout) const
183 {
184     const auto& fruIdentity = callout.fruIdentity();
185     if (fruIdentity)
186     {
187         return (callout.locationCodeSize() != 0) &&
188                ((fruIdentity->failingComponentType() ==
189                  src::FRUIdentity::hardwareFRU) ||
190                 (fruIdentity->failingComponentType() ==
191                  src::FRUIdentity::symbolicFRUTrustedLocCode));
192     }
193 
194     return false;
195 }
196 
197 std::vector<std::string> LightPath::getInventoryPaths(
198     const std::vector<std::string>& locationCodes) const
199 {
200     std::vector<std::string> paths;
201 
202     for (const auto& locCode : locationCodes)
203     {
204         try
205         {
206             auto inventoryPaths = _dataIface.getInventoryFromLocCode(locCode, 0,
207                                                                      true);
208             for (const auto& path : inventoryPaths)
209             {
210                 if (std::find(paths.begin(), paths.end(), path) == paths.end())
211                 {
212                     paths.push_back(path);
213                 }
214             }
215         }
216         catch (const std::exception& e)
217         {
218             log<level::ERR>(fmt::format("Could not get inventory path for "
219                                         "location code {} ({}).",
220                                         locCode, e.what())
221                                 .c_str());
222 
223             // Unless we can set the LEDs for all FRUs, we can't turn
224             // on any of them, so clear the list and quit.
225             paths.clear();
226             break;
227         }
228     }
229 
230     return paths;
231 }
232 
233 void LightPath::setNotFunctional(
234     const std::vector<std::string>& inventoryPaths) const
235 {
236     for (const auto& path : inventoryPaths)
237     {
238         try
239         {
240             _dataIface.setFunctional(path, false);
241         }
242         catch (const std::exception& e)
243         {
244             log<level::INFO>(
245                 fmt::format("Could not write Functional property on {} ({})",
246                             path, e.what())
247                     .c_str());
248         }
249     }
250 }
251 
252 void LightPath::createCriticalAssociation(
253     const std::vector<std::string>& inventoryPaths) const
254 {
255     for (const auto& path : inventoryPaths)
256     {
257         try
258         {
259             _dataIface.setCriticalAssociation(path);
260         }
261         catch (const std::exception& e)
262         {
263             log<level::INFO>(
264                 fmt::format(
265                     "Could not set critical association on object path {} ({})",
266                     path, e.what())
267                     .c_str());
268         }
269     }
270 }
271 
272 } // namespace openpower::pels::service_indicators
273