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 inventoryPath = _dataIface.getInventoryFromLocCode(locCode, 0,
207                                                                     true);
208             paths.push_back(std::move(inventoryPath));
209         }
210         catch (const std::exception& e)
211         {
212             log<level::ERR>(fmt::format("Could not get inventory path for "
213                                         "location code {} ({}).",
214                                         locCode, e.what())
215                                 .c_str());
216 
217             // Unless we can set the LEDs for all FRUs, we can't turn
218             // on any of them, so clear the list and quit.
219             paths.clear();
220             break;
221         }
222     }
223 
224     return paths;
225 }
226 
227 void LightPath::setNotFunctional(
228     const std::vector<std::string>& inventoryPaths) const
229 {
230     for (const auto& path : inventoryPaths)
231     {
232         try
233         {
234             _dataIface.setFunctional(path, false);
235         }
236         catch (const std::exception& e)
237         {
238             log<level::INFO>(
239                 fmt::format("Could not write Functional property on {} ({})",
240                             path, e.what())
241                     .c_str());
242         }
243     }
244 }
245 
246 void LightPath::createCriticalAssociation(
247     const std::vector<std::string>& inventoryPaths) const
248 {
249     for (const auto& path : inventoryPaths)
250     {
251         try
252         {
253             _dataIface.setCriticalAssociation(path);
254         }
255         catch (const std::exception& e)
256         {
257             log<level::INFO>(
258                 fmt::format(
259                     "Could not set critical association on object path {} ({})",
260                     path, e.what())
261                     .c_str());
262         }
263     }
264 }
265 
266 } // namespace openpower::pels::service_indicators
267